blob: 2b0ea9f5864b6a540aa3a6d7ceb4e16dc60a3c64 [file] [log] [blame]
Zsolt Haraszti66862032016-11-28 14:28:39 -08001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Haraszti66862032016-11-28 14:28:39 -08003#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Mock device adapter for testing.
19"""
20from uuid import uuid4
21
22import structlog
23from twisted.internet import reactor
Zsolt Haraszti89a27302016-12-08 16:53:06 -080024from twisted.internet.defer import inlineCallbacks, DeferredQueue
Zsolt Haraszti66862032016-11-28 14:28:39 -080025from zope.interface import implementer
26
27from common.utils.asleep import asleep
28from voltha.adapters.interface import IAdapterInterface
Zsolt Haraszti85f12852016-12-24 08:30:58 -080029from voltha.core.flow_decomposer import *
Zsolt Haraszti66862032016-11-28 14:28:39 -080030from voltha.core.logical_device_agent import mac_str_to_tuple
31from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
32from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port
33from voltha.protos.health_pb2 import HealthStatus
34from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
35 AdminState
36from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
37from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_1GB_FD, \
38 OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
39 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
40
41log = structlog.get_logger()
42
43
44@implementer(IAdapterInterface)
45class SimulatedOnuAdapter(object):
46
47 name = 'simulated_onu'
48
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080049 supported_device_types = [
50 DeviceType(
51 id='simulated_onu',
52 adapter=name,
53 accepts_bulk_flow_update=True
54 )
55 ]
56
Zsolt Haraszti66862032016-11-28 14:28:39 -080057 def __init__(self, adapter_agent, config):
58 self.adapter_agent = adapter_agent
59 self.config = config
60 self.descriptor = Adapter(
61 id=self.name,
62 vendor='Voltha project',
63 version='0.1',
64 config=AdapterConfig(log_level=LogLevel.INFO)
65 )
Zsolt Haraszti89a27302016-12-08 16:53:06 -080066 self.incoming_messages = DeferredQueue()
Zsolt Haraszti66862032016-11-28 14:28:39 -080067
68 def start(self):
69 log.debug('starting')
70 log.info('started')
71
72 def stop(self):
73 log.debug('stopping')
74 log.info('stopped')
75
76 def adapter_descriptor(self):
77 return self.descriptor
78
79 def device_types(self):
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080080 return DeviceTypes(items=self.supported_device_types)
Zsolt Haraszti66862032016-11-28 14:28:39 -080081
82 def health(self):
83 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
84
85 def change_master_state(self, master):
86 raise NotImplementedError()
87
88 def adopt_device(self, device):
89 # We kick of a simulated activation scenario
90 reactor.callLater(0.2, self._simulate_device_activation, device)
91 return device
92
93 def abandon_device(self, device):
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080094 raise NotImplementedError()
95
Zsolt Haraszti66862032016-11-28 14:28:39 -080096 def deactivate_device(self, device):
97 raise NotImplementedError()
98
99 @inlineCallbacks
100 def _simulate_device_activation(self, device):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800101
Zsolt Haraszti66862032016-11-28 14:28:39 -0800102 # first we verify that we got parent reference and proxy info
103 assert device.parent_id
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800104 assert device.proxy_address.device_id
105 assert device.proxy_address.channel_id
Zsolt Haraszti66862032016-11-28 14:28:39 -0800106
107 # we pretend that we were able to contact the device and obtain
108 # additional information about it
109 device.vendor = 'simulated onu adapter'
110 device.model = 'n/a'
111 device.hardware_version = 'n/a'
112 device.firmware_version = 'n/a'
113 device.software_version = '1.0'
114 device.serial_number = uuid4().hex
115 device.connect_status = ConnectStatus.REACHABLE
116 self.adapter_agent.update_device(device)
117
118 # then shortly after we create some ports for the device
119 yield asleep(0.05)
120 uni_port = Port(
121 port_no=2,
122 label='UNI facing Ethernet port',
123 type=Port.ETHERNET_UNI,
124 admin_state=AdminState.ENABLED,
125 oper_status=OperStatus.ACTIVE
126 )
127 self.adapter_agent.add_port(device.id, uni_port)
128 self.adapter_agent.add_port(device.id, Port(
129 port_no=1,
130 label='PON port',
131 type=Port.PON_ONU,
132 admin_state=AdminState.ENABLED,
133 oper_status=OperStatus.ACTIVE,
134 peers=[
135 Port.PeerPort(
136 device_id=device.parent_id,
137 port_no=device.parent_port_no
138 )
139 ]
140 ))
141
142 # TODO adding vports to the logical device shall be done by agent?
143 # then we create the logical device port that corresponds to the UNI
144 # port of the device
145 yield asleep(0.05)
146
147 # obtain logical device id
148 parent_device = self.adapter_agent.get_device(device.parent_id)
149 logical_device_id = parent_device.parent_id
150 assert logical_device_id
151
152 # we are going to use the proxy_address.channel_id as unique number
153 # and name for the virtual ports, as this is guaranteed to be unique
154 # in the context of the OLT port, so it is also unique in the context
155 # of the logical device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800156 port_no = device.proxy_address.channel_id
Zsolt Haraszti66862032016-11-28 14:28:39 -0800157 cap = OFPPF_1GB_FD | OFPPF_FIBER
158 self.adapter_agent.add_logical_port(logical_device_id, LogicalPort(
159 id=str(port_no),
160 ofp_port=ofp_port(
161 port_no=port_no,
162 hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % port_no),
163 name='uni-{}'.format(port_no),
164 config=0,
165 state=OFPPS_LIVE,
166 curr=cap,
167 advertised=cap,
168 peer=cap,
169 curr_speed=OFPPF_1GB_FD,
170 max_speed=OFPPF_1GB_FD
171 ),
172 device_id=device.id,
173 device_port_no=uni_port.port_no
174 ))
175
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800176 # simulate a proxied message sending and receving a reply
177 reply = yield self._simulate_message_exchange(device)
178
179 # and finally update to "ACTIVE"
Zsolt Haraszti66862032016-11-28 14:28:39 -0800180 device = self.adapter_agent.get_device(device.id)
181 device.oper_status = OperStatus.ACTIVE
182 self.adapter_agent.update_device(device)
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800183
184 def update_flows_bulk(self, device, flows, groups):
185 log.debug('bulk-flow-update', device_id=device.id,
186 flows=flows, groups=groups)
187
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800188 # sample code that analyzes the incoming flow table
189 assert len(groups.items) == 0, "Cannot yet deal with groups"
190
191 for flow in flows.items:
192 in_port = get_in_port(flow)
193 assert in_port is not None
194
195 if in_port == 2:
196
197 # Downstream rule
198
199 for field in get_ofb_fields(flow):
200 if field.type == ETH_TYPE:
201 _type = field.eth_type
202 pass # construct ether type based condition here
203
204 elif field.type == IP_PROTO:
205 _proto = field.ip_proto
206 pass # construct ip_proto based condition here
207
208 elif field.type == IN_PORT:
209 _port = field.port
210 pass # construct in_port based condition here
211
212 elif field.type == VLAN_VID:
213 _vlan_vid = field.vlan_vid
214 pass # construct VLAN ID based filter condition here
215
216 elif field.type == VLAN_PCP:
217 _vlan_pcp = field.vlan_pcp
218 pass # construct VLAN PCP based filter condition here
219
220 # TODO
221 else:
222 raise NotImplementedError('field.type={}'.format(
223 field.type))
224
225 for action in get_actions(flow):
226
227 if action.type == OUTPUT:
228 pass # construct packet emit rule here
229
230 elif action.type == PUSH_VLAN:
231 if action.push.ethertype != 0x8100:
232 log.error('unhandled-ether-type',
233 ethertype=action.push.ethertype)
234 pass # construct vlan push command here
235
236 elif action.type == POP_VLAN:
237 pass # construct vlan pop command here
238
239 elif action.type == SET_FIELD:
240 assert (action.set_field.field.oxm_class ==
241 ofp.OFPXMC_OPENFLOW_BASIC)
242 field = action.set_field.field.ofb_field
243 if field.type == VLAN_VID:
244 pass # construct vlan_id set command here
245 else:
246 log.error('unsupported-action-set-field-type',
247 field_type=field.type)
248
249 else:
250 log.error('unsupported-action-type',
251 action_type=action.type)
252
253 # final assembly of low level device flow rule and pushing it
254 # down to device
255 pass
256
257 elif in_port == 1:
258
259 # Upstream rule
260
261 for field in get_ofb_fields(flow):
262 if field.type == ETH_TYPE:
263 _type = field.eth_type
264 pass # construct ether type based condition here
265
266 elif field.type == IP_PROTO:
267 _proto = field.ip_proto
268 pass # construct ip_proto based condition here
269
270 elif field.type == IN_PORT:
271 _port = field.port
272 pass # construct in_port based condition here
273
274 elif field.type == VLAN_VID:
275 _vlan_vid = field.vlan_vid
276 pass # construct VLAN ID based filter condition here
277
278 elif field.type == VLAN_PCP:
279 _vlan_pcp = field.vlan_pcp
280 pass # construct VLAN PCP based filter condition here
281
282 elif field.type == IPV4_DST:
283 _ipv4_dst = field.ipv4_dst
284 pass # construct IPv4 DST address based condition
285
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800286 elif field.type == UDP_SRC:
287 _udp_src = field.udp_src
288 pass # construct UDP SRC based filter here
289
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800290 elif field.type == UDP_DST:
291 _udp_dst = field.udp_dst
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800292 pass # construct UDP DST based filter here
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800293
294 # TODO
295 else:
296 raise NotImplementedError('field.type={}'.format(
297 field.type))
298
299 for action in get_actions(flow):
300
301 if action.type == OUTPUT:
302 pass # construct packet emit rule here
303
304 elif action.type == PUSH_VLAN:
305 if action.push.ethertype != 0x8100:
306 log.error('unhandled-ether-type',
307 ethertype=action.push.ethertype)
308 pass # construct vlan push command here
309
310 elif action.type == SET_FIELD:
311 assert (action.set_field.field.oxm_class ==
312 ofp.OFPXMC_OPENFLOW_BASIC)
313 field = action.set_field.field.ofb_field
314 if field.type == VLAN_VID:
315 pass # construct vlan_id set command here
316 else:
317 log.error('unsupported-action-set-field-type',
318 field_type=field.type)
319
320 else:
321 log.error('unsupported-action-type',
322 action_type=action.type)
323
324 # final assembly of low level device flow rule and pushing it
325 # down to device
326 pass
327
328 else:
329 raise Exception('Port should be 1 or 2 by our convention')
330
331
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800332 def update_flows_incrementally(self, device, flow_changes, group_changes):
333 raise NotImplementedError()
334
335 def send_proxied_message(self, proxy_address, msg):
336 raise NotImplementedError()
337
338 def receive_proxied_message(self, proxy_address, msg):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800339 # just place incoming message to a list
340 self.incoming_messages.put((proxy_address, msg))
341
342 @inlineCallbacks
343 def _simulate_message_exchange(self, device):
344
345 # register for receiving async messages
346 self.adapter_agent.register_for_proxied_messages(device.proxy_address)
347
348 # reset incoming message queue
349 while self.incoming_messages.pending:
350 _ = yield self.incoming_messages.get()
351
352 # construct message
353 msg = 'test message'
354
355 # send message
356 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
357
358 # wait till we detect incoming message
359 yield self.incoming_messages.get()
360
361 # by returning we allow the device to be shown as active, which
362 # indirectly verified that message passing works
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800363
364 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
365 log.info('packet-out', logical_device_id=logical_device_id,
366 egress_port_no=egress_port_no, msg_len=len(msg))