blob: 3fed5c462681ef0d4ef55f8978dd2407eb715ae9 [file] [log] [blame]
Zsolt Haraszti656ecc62016-12-28 15:08:23 -08001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Haraszti656ecc62016-12-28 15:08:23 -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"""
18Fully simulated OLT/ONU adapter.
19"""
20from uuid import uuid4
21
22import grpc
23import structlog
24from scapy.layers.l2 import Ether, Dot1Q
25from twisted.internet import reactor
Khen Nursimulud068d812017-03-06 11:44:18 -050026from twisted.internet.defer import inlineCallbacks
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080027from zope.interface import implementer
28
29from common.frameio.frameio import BpfProgramFilter, hexify
Khen Nursimulud068d812017-03-06 11:44:18 -050030from common.utils.asleep import asleep
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080031from voltha.adapters.interface import IAdapterInterface
32from voltha.core.logical_device_agent import mac_str_to_tuple
33from voltha.protos import third_party
34from voltha.protos import ponsim_pb2
35from voltha.protos.adapter_pb2 import Adapter
36from voltha.protos.adapter_pb2 import AdapterConfig
37from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
38 AdminState
39from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port, Device
40from voltha.protos.health_pb2 import HealthStatus
41from google.protobuf.empty_pb2 import Empty
42
43from voltha.protos.logical_device_pb2 import LogicalPort, LogicalDevice
44from voltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, OFPPF_1GB_FD, \
45 OFPC_GROUP_STATS, OFPC_PORT_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS, \
46 ofp_switch_features, ofp_desc
47from voltha.protos.openflow_13_pb2 import ofp_port
48from voltha.protos.ponsim_pb2 import FlowTable
49from voltha.registry import registry
50
51_ = third_party
52log = structlog.get_logger()
53
54
55PACKET_IN_VLAN = 4000
56is_inband_frame = BpfProgramFilter('(ether[14:2] & 0xfff) = 0x{:03x}'.format(
57 PACKET_IN_VLAN))
58
59
60@implementer(IAdapterInterface)
61class PonSimOltAdapter(object):
62
63 name = 'ponsim_olt'
64
65 supported_device_types = [
66 DeviceType(
67 id=name,
68 adapter=name,
69 accepts_bulk_flow_update=True
70 )
71 ]
72
73 def __init__(self, adapter_agent, config):
74 self.adapter_agent = adapter_agent
75 self.config = config
76 self.descriptor = Adapter(
77 id=self.name,
78 vendor='Voltha project',
79 version='0.4',
80 config=AdapterConfig(log_level=LogLevel.INFO)
81 )
82 self.devices_handlers = dict() # device_id -> PonSimOltHandler()
83 self.logical_device_id_to_root_device_id = dict()
84
85 def start(self):
86 log.debug('starting')
87 log.info('started')
88
89 def stop(self):
Khen Nursimulud068d812017-03-06 11:44:18 -050090 """
91 This method is called when this device instance is no longer
92 required, which means there is a request to remove this device.
93 :return:
94 """
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080095 log.debug('stopping')
96 log.info('stopped')
97
98 def adapter_descriptor(self):
99 return self.descriptor
100
101 def device_types(self):
102 return DeviceTypes(items=self.supported_device_types)
103
104 def health(self):
105 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
106
107 def change_master_state(self, master):
108 raise NotImplementedError()
109
Sergio Slobodrianec864c62017-03-09 11:41:43 -0500110 def update_pm_config(self, device, pm_configs):
111 raise NotImplementedError()
112
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800113 def adopt_device(self, device):
114 self.devices_handlers[device.id] = PonSimOltHandler(self, device.id)
115 reactor.callLater(0, self.devices_handlers[device.id].activate, device)
116 return device
117
118 def abandon_device(self, device):
119 raise NotImplementedError()
120
Khen Nursimulud068d812017-03-06 11:44:18 -0500121 def disable_device(self, device):
122 log.info('disable-device', device_id=device.id)
123 reactor.callLater(0, self.devices_handlers[device.id].disable)
124 return device
125
126 def reenable_device(self, device):
127 log.info('reenable-device', device_id=device.id)
128 reactor.callLater(0, self.devices_handlers[device.id].reenable)
129 return device
130
131 def reboot_device(self, device):
132 log.info('reboot-device', device_id=device.id)
133 reactor.callLater(0, self.devices_handlers[device.id].reboot)
134 return device
135
136 def delete_device(self, device):
137 log.info('delete-device', device_id=device.id)
138 reactor.callLater(0, self.devices_handlers[device.id].delete)
139 return device
140
141 def get_device_details(self, device):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800142 raise NotImplementedError()
143
144 def update_flows_bulk(self, device, flows, groups):
145 log.info('bulk-flow-update', device_id=device.id,
146 flows=flows, groups=groups)
147 assert len(groups.items) == 0
148 handler = self.devices_handlers[device.id]
149 return handler.update_flow_table(flows.items)
150
151 def update_flows_incrementally(self, device, flow_changes, group_changes):
152 raise NotImplementedError()
153
154 def send_proxied_message(self, proxy_address, msg):
155 log.info('send-proxied-message', proxy_address=proxy_address, msg=msg)
156 handler = self.devices_handlers[proxy_address.device_id]
157 handler.send_proxied_message(proxy_address, msg)
158
159 def receive_proxied_message(self, proxy_address, msg):
160 raise NotImplementedError()
161
162 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
163
164 def ldi_to_di(ldi):
165 di = self.logical_device_id_to_root_device_id.get(ldi)
166 if di is None:
167 logical_device = self.adapter_agent.get_logical_device(ldi)
168 di = logical_device.root_device_id
169 self.logical_device_id_to_root_device_id[ldi] = di
170 return di
171
172 device_id = ldi_to_di(logical_device_id)
173 handler = self.devices_handlers[device_id]
174 handler.packet_out(egress_port_no, msg)
175
176
177class PonSimOltHandler(object):
178
179 def __init__(self, adapter, device_id):
180 self.adapter = adapter
181 self.adapter_agent = adapter.adapter_agent
182 self.device_id = device_id
183 self.log = structlog.get_logger(device_id=device_id)
184 self.channel = None
185 self.io_port = None
186 self.logical_device_id = None
Khen Nursimulud068d812017-03-06 11:44:18 -0500187 self.nni_port = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800188 self.interface = registry('main').get_args().interface
189
190 def __del__(self):
191 if self.io_port is not None:
Zsolt Haraszti3e6f0892017-01-19 11:51:40 -0800192 registry('frameio').close_port(self.io_port)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800193
194 def get_channel(self):
195 if self.channel is None:
196 device = self.adapter_agent.get_device(self.device_id)
197 self.channel = grpc.insecure_channel(device.host_and_port)
198 return self.channel
199
200 def activate(self, device):
201 self.log.info('activating')
202
203 if not device.host_and_port:
204 device.oper_status = OperStatus.FAILED
205 device.reason = 'No host_and_port field provided'
206 self.adapter_agent.update_device(device)
207 return
208
209 stub = ponsim_pb2.PonSimStub(self.get_channel())
210 info = stub.GetDeviceInfo(Empty())
211 log.info('got-info', info=info)
212
213 device.root = True
214 device.vendor = 'ponsim'
215 device.model ='n/a'
216 device.serial_number = device.host_and_port
217 device.connect_status = ConnectStatus.REACHABLE
218 self.adapter_agent.update_device(device)
219
220 nni_port = Port(
221 port_no=2,
222 label='NNI facing Ethernet port',
223 type=Port.ETHERNET_NNI,
224 admin_state=AdminState.ENABLED,
225 oper_status=OperStatus.ACTIVE
226 )
Khen Nursimulud068d812017-03-06 11:44:18 -0500227 self.nni_port = nni_port
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800228 self.adapter_agent.add_port(device.id, nni_port)
229 self.adapter_agent.add_port(device.id, Port(
230 port_no=1,
231 label='PON port',
232 type=Port.PON_OLT,
233 admin_state=AdminState.ENABLED,
234 oper_status=OperStatus.ACTIVE
235 ))
236
237 ld = LogicalDevice(
238 # not setting id and datapth_id will let the adapter agent pick id
239 desc=ofp_desc(
240 mfr_desc='cord porject',
241 hw_desc='simualted pon',
242 sw_desc='simualted pon',
243 serial_num=uuid4().hex,
244 dp_desc='n/a'
245 ),
246 switch_features=ofp_switch_features(
247 n_buffers=256, # TODO fake for now
248 n_tables=2, # TODO ditto
249 capabilities=( # TODO and ditto
250 OFPC_FLOW_STATS
251 | OFPC_TABLE_STATS
252 | OFPC_PORT_STATS
253 | OFPC_GROUP_STATS
254 )
255 ),
256 root_device_id=device.id
257 )
258 ld_initialized = self.adapter_agent.create_logical_device(ld)
259 cap = OFPPF_1GB_FD | OFPPF_FIBER
260 self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
261 id='nni',
262 ofp_port=ofp_port(
263 port_no=info.nni_port,
264 hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % info.nni_port),
265 name='nni',
266 config=0,
267 state=OFPPS_LIVE,
268 curr=cap,
269 advertised=cap,
270 peer=cap,
271 curr_speed=OFPPF_1GB_FD,
272 max_speed=OFPPF_1GB_FD
273 ),
274 device_id=device.id,
275 device_port_no=nni_port.port_no,
276 root_port=True
277 ))
278
279 device = self.adapter_agent.get_device(device.id)
280 device.parent_id = ld_initialized.id
281 device.oper_status = OperStatus.ACTIVE
282 self.adapter_agent.update_device(device)
283 self.logical_device_id = ld_initialized.id
284
285 # register ONUS per uni port
286 for port_no in info.uni_ports:
287 vlan_id = port_no
288 self.adapter_agent.child_device_detected(
289 parent_device_id=device.id,
290 parent_port_no=1,
291 child_device_type='ponsim_onu',
292 proxy_address=Device.ProxyAddress(
293 device_id=device.id,
294 channel_id=vlan_id
295 ),
296 vlan=vlan_id
297 )
298
299 # finally, open the frameio port to receive in-band packet_in messages
300 self.log.info('registering-frameio')
Zsolt Haraszti3e6f0892017-01-19 11:51:40 -0800301 self.io_port = registry('frameio').open_port(
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800302 self.interface, self.rcv_io, is_inband_frame)
303 self.log.info('registered-frameio')
304
305 def rcv_io(self, port, frame):
306 self.log.info('reveived', iface_name=port.iface_name,
307 frame_len=len(frame))
308 pkt = Ether(frame)
309 if pkt.haslayer(Dot1Q):
310 outer_shim = pkt.getlayer(Dot1Q)
311 if isinstance(outer_shim.payload, Dot1Q):
312 inner_shim = outer_shim.payload
313 cvid = inner_shim.vlan
314 logical_port = cvid
315 popped_frame = (
316 Ether(src=pkt.src, dst=pkt.dst, type=inner_shim.type) /
317 inner_shim.payload
318 )
319 kw = dict(
320 logical_device_id=self.logical_device_id,
321 logical_port_no=logical_port,
322 )
323 self.log.info('sending-packet-in', **kw)
324 self.adapter_agent.send_packet_in(
325 packet=str(popped_frame), **kw)
326
Khen Nursimulud068d812017-03-06 11:44:18 -0500327
328
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800329 def update_flow_table(self, flows):
330 stub = ponsim_pb2.PonSimStub(self.get_channel())
331 self.log.info('pushing-olt-flow-table')
332 stub.UpdateFlowTable(FlowTable(
333 port=0,
334 flows=flows
335 ))
336 self.log.info('success')
337
338 def send_proxied_message(self, proxy_address, msg):
339 self.log.info('sending-proxied-message')
340 if isinstance(msg, FlowTable):
341 stub = ponsim_pb2.PonSimStub(self.get_channel())
342 self.log.info('pushing-onu-flow-table', port=msg.port)
343 res = stub.UpdateFlowTable(msg)
344 self.adapter_agent.receive_proxied_message(proxy_address, res)
345
346 def packet_out(self, egress_port, msg):
347 self.log.info('sending-packet-out', egress_port=egress_port,
348 msg=hexify(msg))
349 pkt = Ether(msg)
350 out_pkt = (
351 Ether(src=pkt.src, dst=pkt.dst) /
352 Dot1Q(vlan=4000) /
353 Dot1Q(vlan=egress_port, type=pkt.type) /
354 pkt.payload
355 )
356 self.io_port.send(str(out_pkt))
Khen Nursimulud068d812017-03-06 11:44:18 -0500357
358 @inlineCallbacks
359 def reboot(self):
360 self.log.info('rebooting', device_id=self.device_id)
361
362 # Update the operational status to ACTIVATING and connect status to
363 # UNREACHABLE
364 device = self.adapter_agent.get_device(self.device_id)
365 previous_oper_status = device.oper_status
366 previous_conn_status = device.connect_status
367 device.oper_status = OperStatus.ACTIVATING
368 device.connect_status = ConnectStatus.UNREACHABLE
369 self.adapter_agent.update_device(device)
370
371 # Sleep 10 secs, simulating a reboot
372 #TODO: send alert and clear alert after the reboot
373 yield asleep(10)
374
375 # Change the operational status back to its previous state. With a
376 # real OLT the operational state should be the state the device is
377 # after a reboot.
378 # Get the latest device reference
379 device = self.adapter_agent.get_device(self.device_id)
380 device.oper_status = previous_oper_status
381 device.connect_status = previous_conn_status
382 self.adapter_agent.update_device(device)
383 self.log.info('rebooted', device_id=self.device_id)
384
385 def disable(self):
386 self.log.info('disabling', device_id=self.device_id)
387
388 # Get the latest device reference
389 device = self.adapter_agent.get_device(self.device_id)
390
391 # Update the operational status to UNKNOWN
392 device.oper_status = OperStatus.UNKNOWN
393 device.connect_status = ConnectStatus.UNREACHABLE
394 self.adapter_agent.update_device(device)
395
396 # Remove the logical device
397 logical_device = self.adapter_agent.get_logical_device(
398 self.logical_device_id)
399 self.adapter_agent.delete_logical_device(logical_device)
400
401 # Disable all child devices first
402 self.adapter_agent.disable_all_child_devices(self.device_id)
403
404 # # Remove all child devices
405 # self.adapter_agent.remove_all_child_devices(self.device_id)
406
407 # Remove the peer references from this device
408 self.adapter_agent.delete_all_peer_references(self.device_id)
409
410 # close the frameio port
411 registry('frameio').close_port(self.io_port)
412
413 # TODO:
414 # 1) Remove all flows from the device
415 # 2) Remove the device from ponsim
416
417 self.log.info('disabled', device_id=device.id)
418
419
420 def reenable(self):
421 self.log.info('re-enabling', device_id=self.device_id)
422
423 # Get the latest device reference
424 device = self.adapter_agent.get_device(self.device_id)
425
426 # Update the connect status to REACHABLE
427 device.connect_status = ConnectStatus.REACHABLE
428 self.adapter_agent.update_device(device)
429
430 ld = LogicalDevice(
431 # not setting id and datapth_id will let the adapter agent pick id
432 desc=ofp_desc(
433 mfr_desc='cord porject',
434 hw_desc='simulated pon',
435 sw_desc='simulated pon',
436 serial_num=uuid4().hex,
437 dp_desc='n/a'
438 ),
439 switch_features=ofp_switch_features(
440 n_buffers=256, # TODO fake for now
441 n_tables=2, # TODO ditto
442 capabilities=( # TODO and ditto
443 OFPC_FLOW_STATS
444 | OFPC_TABLE_STATS
445 | OFPC_PORT_STATS
446 | OFPC_GROUP_STATS
447 )
448 ),
449 root_device_id=device.id
450 )
451 ld_initialized = self.adapter_agent.create_logical_device(ld)
452 cap = OFPPF_1GB_FD | OFPPF_FIBER
453 self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
454 id='nni',
455 ofp_port=ofp_port(
456 # port_no=info.nni_port,
457 # hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % info.nni_port),
458 port_no=self.nni_port.port_no,
459 hw_addr=mac_str_to_tuple(
460 '00:00:00:00:00:%02x' % self.nni_port.port_no),
461 name='nni',
462 config=0,
463 state=OFPPS_LIVE,
464 curr=cap,
465 advertised=cap,
466 peer=cap,
467 curr_speed=OFPPF_1GB_FD,
468 max_speed=OFPPF_1GB_FD
469 ),
470 device_id=device.id,
471 device_port_no=self.nni_port.port_no,
472 root_port=True
473 ))
474
475 device = self.adapter_agent.get_device(device.id)
476 device.parent_id = ld_initialized.id
477 device.oper_status = OperStatus.ACTIVE
478 self.adapter_agent.update_device(device)
479 self.logical_device_id = ld_initialized.id
480
481 # Reenable all child devices
482 self.adapter_agent.reenable_all_child_devices(device.id)
483
484
485 # finally, open the frameio port to receive in-band packet_in messages
486 self.log.info('registering-frameio')
487 self.io_port = registry('frameio').open_port(
488 self.interface, self.rcv_io, is_inband_frame)
489 self.log.info('registered-frameio')
490
491 self.log.info('re-enabled', device_id=device.id)
492
493
494 def delete(self):
495 self.log.info('deleting', device_id=self.device_id)
496
497 # Remove all child devices
498 self.adapter_agent.delete_all_child_devices(self.device_id)
499
500 # TODO:
501 # 1) Remove all flows from the device
502 # 2) Remove the device from ponsim
503
504 self.log.info('deleted', device_id=self.device_id)