blob: 8abe3a34c3288dc6df629be320994382ac91bf65 [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
Khen Nursimulud068d812017-03-06 11:44:18 -050096 def disable_device(self, device):
97 raise NotImplementedError()
98
99 def reenable_device(self, device):
100 raise NotImplementedError()
101
102 def reboot_device(self, device):
103 raise NotImplementedError()
104
105 def delete_device(self, device):
106 raise NotImplementedError()
107
108 def get_device_details(self, device):
Zsolt Haraszti66862032016-11-28 14:28:39 -0800109 raise NotImplementedError()
110
Sergio Slobodrianec864c62017-03-09 11:41:43 -0500111 def update_pm_config(self, device, pm_configs):
112 raise NotImplementedError()
113
Zsolt Haraszti66862032016-11-28 14:28:39 -0800114 @inlineCallbacks
115 def _simulate_device_activation(self, device):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800116
Zsolt Haraszti66862032016-11-28 14:28:39 -0800117 # first we verify that we got parent reference and proxy info
118 assert device.parent_id
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800119 assert device.proxy_address.device_id
120 assert device.proxy_address.channel_id
Zsolt Haraszti66862032016-11-28 14:28:39 -0800121
122 # we pretend that we were able to contact the device and obtain
123 # additional information about it
124 device.vendor = 'simulated onu adapter'
125 device.model = 'n/a'
126 device.hardware_version = 'n/a'
127 device.firmware_version = 'n/a'
128 device.software_version = '1.0'
129 device.serial_number = uuid4().hex
130 device.connect_status = ConnectStatus.REACHABLE
131 self.adapter_agent.update_device(device)
132
133 # then shortly after we create some ports for the device
134 yield asleep(0.05)
135 uni_port = Port(
136 port_no=2,
137 label='UNI facing Ethernet port',
138 type=Port.ETHERNET_UNI,
139 admin_state=AdminState.ENABLED,
140 oper_status=OperStatus.ACTIVE
141 )
142 self.adapter_agent.add_port(device.id, uni_port)
143 self.adapter_agent.add_port(device.id, Port(
144 port_no=1,
145 label='PON port',
146 type=Port.PON_ONU,
147 admin_state=AdminState.ENABLED,
148 oper_status=OperStatus.ACTIVE,
149 peers=[
150 Port.PeerPort(
151 device_id=device.parent_id,
152 port_no=device.parent_port_no
153 )
154 ]
155 ))
156
157 # TODO adding vports to the logical device shall be done by agent?
158 # then we create the logical device port that corresponds to the UNI
159 # port of the device
160 yield asleep(0.05)
161
162 # obtain logical device id
163 parent_device = self.adapter_agent.get_device(device.parent_id)
164 logical_device_id = parent_device.parent_id
165 assert logical_device_id
166
167 # we are going to use the proxy_address.channel_id as unique number
168 # and name for the virtual ports, as this is guaranteed to be unique
169 # in the context of the OLT port, so it is also unique in the context
170 # of the logical device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800171 port_no = device.proxy_address.channel_id
Zsolt Haraszti66862032016-11-28 14:28:39 -0800172 cap = OFPPF_1GB_FD | OFPPF_FIBER
173 self.adapter_agent.add_logical_port(logical_device_id, LogicalPort(
174 id=str(port_no),
175 ofp_port=ofp_port(
176 port_no=port_no,
177 hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % port_no),
178 name='uni-{}'.format(port_no),
179 config=0,
180 state=OFPPS_LIVE,
181 curr=cap,
182 advertised=cap,
183 peer=cap,
184 curr_speed=OFPPF_1GB_FD,
185 max_speed=OFPPF_1GB_FD
186 ),
187 device_id=device.id,
188 device_port_no=uni_port.port_no
189 ))
190
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800191 # simulate a proxied message sending and receving a reply
192 reply = yield self._simulate_message_exchange(device)
193
194 # and finally update to "ACTIVE"
Zsolt Haraszti66862032016-11-28 14:28:39 -0800195 device = self.adapter_agent.get_device(device.id)
196 device.oper_status = OperStatus.ACTIVE
197 self.adapter_agent.update_device(device)
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800198
199 def update_flows_bulk(self, device, flows, groups):
200 log.debug('bulk-flow-update', device_id=device.id,
201 flows=flows, groups=groups)
202
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800203 # sample code that analyzes the incoming flow table
204 assert len(groups.items) == 0, "Cannot yet deal with groups"
205
206 for flow in flows.items:
207 in_port = get_in_port(flow)
208 assert in_port is not None
209
210 if in_port == 2:
211
212 # Downstream rule
213
214 for field in get_ofb_fields(flow):
215 if field.type == ETH_TYPE:
216 _type = field.eth_type
217 pass # construct ether type based condition here
218
219 elif field.type == IP_PROTO:
220 _proto = field.ip_proto
221 pass # construct ip_proto based condition here
222
223 elif field.type == IN_PORT:
224 _port = field.port
225 pass # construct in_port based condition here
226
227 elif field.type == VLAN_VID:
228 _vlan_vid = field.vlan_vid
229 pass # construct VLAN ID based filter condition here
230
231 elif field.type == VLAN_PCP:
232 _vlan_pcp = field.vlan_pcp
233 pass # construct VLAN PCP based filter condition here
234
235 # TODO
236 else:
237 raise NotImplementedError('field.type={}'.format(
238 field.type))
239
240 for action in get_actions(flow):
241
242 if action.type == OUTPUT:
243 pass # construct packet emit rule here
244
245 elif action.type == PUSH_VLAN:
246 if action.push.ethertype != 0x8100:
247 log.error('unhandled-ether-type',
248 ethertype=action.push.ethertype)
249 pass # construct vlan push command here
250
251 elif action.type == POP_VLAN:
252 pass # construct vlan pop command here
253
254 elif action.type == SET_FIELD:
255 assert (action.set_field.field.oxm_class ==
256 ofp.OFPXMC_OPENFLOW_BASIC)
257 field = action.set_field.field.ofb_field
258 if field.type == VLAN_VID:
259 pass # construct vlan_id set command here
260 else:
261 log.error('unsupported-action-set-field-type',
262 field_type=field.type)
263
264 else:
265 log.error('unsupported-action-type',
266 action_type=action.type)
267
268 # final assembly of low level device flow rule and pushing it
269 # down to device
270 pass
271
272 elif in_port == 1:
273
274 # Upstream rule
275
276 for field in get_ofb_fields(flow):
277 if field.type == ETH_TYPE:
278 _type = field.eth_type
279 pass # construct ether type based condition here
280
281 elif field.type == IP_PROTO:
282 _proto = field.ip_proto
283 pass # construct ip_proto based condition here
284
285 elif field.type == IN_PORT:
286 _port = field.port
287 pass # construct in_port based condition here
288
289 elif field.type == VLAN_VID:
290 _vlan_vid = field.vlan_vid
291 pass # construct VLAN ID based filter condition here
292
293 elif field.type == VLAN_PCP:
294 _vlan_pcp = field.vlan_pcp
295 pass # construct VLAN PCP based filter condition here
296
297 elif field.type == IPV4_DST:
298 _ipv4_dst = field.ipv4_dst
299 pass # construct IPv4 DST address based condition
300
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800301 elif field.type == UDP_SRC:
302 _udp_src = field.udp_src
303 pass # construct UDP SRC based filter here
304
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800305 elif field.type == UDP_DST:
306 _udp_dst = field.udp_dst
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800307 pass # construct UDP DST based filter here
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800308
309 # TODO
310 else:
311 raise NotImplementedError('field.type={}'.format(
312 field.type))
313
314 for action in get_actions(flow):
315
316 if action.type == OUTPUT:
317 pass # construct packet emit rule here
318
319 elif action.type == PUSH_VLAN:
320 if action.push.ethertype != 0x8100:
321 log.error('unhandled-ether-type',
322 ethertype=action.push.ethertype)
323 pass # construct vlan push command here
324
325 elif action.type == SET_FIELD:
326 assert (action.set_field.field.oxm_class ==
327 ofp.OFPXMC_OPENFLOW_BASIC)
328 field = action.set_field.field.ofb_field
329 if field.type == VLAN_VID:
330 pass # construct vlan_id set command here
331 else:
332 log.error('unsupported-action-set-field-type',
333 field_type=field.type)
334
335 else:
336 log.error('unsupported-action-type',
337 action_type=action.type)
338
339 # final assembly of low level device flow rule and pushing it
340 # down to device
341 pass
342
343 else:
344 raise Exception('Port should be 1 or 2 by our convention')
345
346
Zsolt Harasztic5c5d102016-12-07 21:12:27 -0800347 def update_flows_incrementally(self, device, flow_changes, group_changes):
348 raise NotImplementedError()
349
350 def send_proxied_message(self, proxy_address, msg):
351 raise NotImplementedError()
352
353 def receive_proxied_message(self, proxy_address, msg):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800354 # just place incoming message to a list
355 self.incoming_messages.put((proxy_address, msg))
356
357 @inlineCallbacks
358 def _simulate_message_exchange(self, device):
359
360 # register for receiving async messages
361 self.adapter_agent.register_for_proxied_messages(device.proxy_address)
362
363 # reset incoming message queue
364 while self.incoming_messages.pending:
365 _ = yield self.incoming_messages.get()
366
367 # construct message
368 msg = 'test message'
369
370 # send message
371 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
372
373 # wait till we detect incoming message
374 yield self.incoming_messages.get()
375
376 # by returning we allow the device to be shown as active, which
377 # indirectly verified that message passing works
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800378
379 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
380 log.info('packet-out', logical_device_id=logical_device_id,
381 egress_port_no=egress_port_no, msg_len=len(msg))
Peter Shafik9107f2e2017-05-02 15:54:39 -0400382
383 def receive_inter_adapter_message(self, msg):
384 raise NotImplementedError()