blob: be57fac4f33a17095052958ebf696c1880957f02 [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
Sergio Slobodrian98eff412017-03-15 14:46:30 -040022import arrow
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080023import grpc
Stephane Barbarie4475a252017-03-31 13:49:20 -040024import json
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080025import structlog
26from scapy.layers.l2 import Ether, Dot1Q
Stephane Barbarie4475a252017-03-31 13:49:20 -040027from scapy.layers.inet import Raw
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080028from twisted.internet import reactor
Khen Nursimulud068d812017-03-06 11:44:18 -050029from twisted.internet.defer import inlineCallbacks
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080030from zope.interface import implementer
31
32from common.frameio.frameio import BpfProgramFilter, hexify
Khen Nursimulud068d812017-03-06 11:44:18 -050033from common.utils.asleep import asleep
Sergio Slobodrian98eff412017-03-15 14:46:30 -040034from twisted.internet.task import LoopingCall
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080035from voltha.adapters.interface import IAdapterInterface
36from voltha.core.logical_device_agent import mac_str_to_tuple
37from voltha.protos import third_party
38from voltha.protos import ponsim_pb2
39from voltha.protos.adapter_pb2 import Adapter
40from voltha.protos.adapter_pb2 import AdapterConfig
41from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
42 AdminState
Sergio Slobodrian98eff412017-03-15 14:46:30 -040043from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port, Device, \
Stephane Barbarie5253c652017-03-22 16:29:46 -040044 PmConfig, PmConfigs
Sergio Slobodrian98eff412017-03-15 14:46:30 -040045from voltha.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080046from voltha.protos.health_pb2 import HealthStatus
47from google.protobuf.empty_pb2 import Empty
48
49from voltha.protos.logical_device_pb2 import LogicalPort, LogicalDevice
Stephane Barbarie5253c652017-03-22 16:29:46 -040050from voltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, \
51 OFPPF_1GB_FD, \
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080052 OFPC_GROUP_STATS, OFPC_PORT_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS, \
53 ofp_switch_features, ofp_desc
54from voltha.protos.openflow_13_pb2 import ofp_port
55from voltha.protos.ponsim_pb2 import FlowTable
56from voltha.registry import registry
57
58_ = third_party
59log = structlog.get_logger()
60
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080061PACKET_IN_VLAN = 4000
62is_inband_frame = BpfProgramFilter('(ether[14:2] & 0xfff) = 0x{:03x}'.format(
63 PACKET_IN_VLAN))
64
Stephane Barbarie5253c652017-03-22 16:29:46 -040065
Sergio Slobodrian98eff412017-03-15 14:46:30 -040066class AdapterPmMetrics:
Stephane Barbarie5253c652017-03-22 16:29:46 -040067 def __init__(self, device):
Sergio Slobodrianec6e3912017-04-02 11:46:55 -040068 self.pm_names = {'tx_64_pkts', 'tx_65_127_pkts', 'tx_128_255_pkts',
69 'tx_256_511_pkts', 'tx_512_1023_pkts',
70 'tx_1024_1518_pkts', 'tx_1519_9k_pkts',
71 'rx_64_pkts', 'rx_65_127_pkts',
72 'rx_128_255_pkts', 'rx_256_511_pkts',
73 'rx_512_1023_pkts', 'rx_1024_1518_pkts',
74 'rx_1519_9k_pkts'}
Sergio Slobodrian98eff412017-03-15 14:46:30 -040075 self.device = device
76 self.id = device.id
77 self.name = 'ponsim_olt'
Stephane Barbarie5253c652017-03-22 16:29:46 -040078 # self.id = "abc"
Sergio Slobodrian98eff412017-03-15 14:46:30 -040079 self.default_freq = 150
80 self.grouped = False
81 self.freq_override = False
82 self.pon_metrics_config = dict()
83 self.nni_metrics_config = dict()
84 self.lc = None
85 for m in self.pm_names:
86 self.pon_metrics_config[m] = PmConfig(name=m,
87 type=PmConfig.COUNTER,
88 enabled=True)
89 self.nni_metrics_config[m] = PmConfig(name=m,
90 type=PmConfig.COUNTER,
91 enabled=True)
92
93 def update(self, pm_config):
94 if self.default_freq != pm_config.default_freq:
95 # Update the callback to the new frequency.
96 self.default_freq = pm_config.default_freq
97 self.lc.stop()
Stephane Barbarie5253c652017-03-22 16:29:46 -040098 self.lc.start(interval=self.default_freq / 10)
Sergio Slobodrian98eff412017-03-15 14:46:30 -040099 for m in pm_config.metrics:
100 self.pon_metrics_config[m.name].enabled = m.enabled
101 self.nni_metrics_config[m.name].enabled = m.enabled
102
103 def make_proto(self):
104 pm_config = PmConfigs(
105 id=self.id,
106 default_freq=self.default_freq,
Stephane Barbarie5253c652017-03-22 16:29:46 -0400107 grouped=False,
108 freq_override=False)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400109 for m in sorted(self.pon_metrics_config):
Stephane Barbarie5253c652017-03-22 16:29:46 -0400110 pm = self.pon_metrics_config[m] # Either will do they're the same
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400111 pm_config.metrics.extend([PmConfig(name=pm.name,
112 type=pm.type,
113 enabled=pm.enabled)])
114 return pm_config
115
116 def collect_port_metrics(self, channel):
117 rtrn_port_metrics = dict()
118 stub = ponsim_pb2.PonSimStub(channel)
119 stats = stub.GetStats(Empty())
120 rtrn_port_metrics['pon'] = self.extract_pon_metrics(stats)
121 rtrn_port_metrics['nni'] = self.extract_nni_metrics(stats)
122 return rtrn_port_metrics
123
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400124 def extract_pon_metrics(self, stats):
125 rtrn_pon_metrics = dict()
126 for m in stats.metrics:
127 if m.port_name == "pon":
128 for p in m.packets:
129 if self.pon_metrics_config[p.name].enabled:
130 rtrn_pon_metrics[p.name] = p.value
131 return rtrn_pon_metrics
132
133 def extract_nni_metrics(self, stats):
134 rtrn_pon_metrics = dict()
135 for m in stats.metrics:
136 if m.port_name == "nni":
137 for p in m.packets:
138 if self.pon_metrics_config[p.name].enabled:
139 rtrn_pon_metrics[p.name] = p.value
140 return rtrn_pon_metrics
141
142 def start_collector(self, callback):
143 log.info("starting-pm-collection", device_name=self.name,
144 device_id=self.device.id)
145 prefix = 'voltha.{}.{}'.format(self.name, self.device.id)
146 self.lc = LoopingCall(callback, self.device.id, prefix)
Stephane Barbarie5253c652017-03-22 16:29:46 -0400147 self.lc.start(interval=self.default_freq / 10)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400148
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800149
Stephane Barbarie4475a252017-03-31 13:49:20 -0400150class AdapterAlarms:
151 def __init__(self, adapter, device):
152 self.adapter = adapter
153 self.device = device
154 self.lc = None
155
156 def send_alarm(self, context_data, alarm_data):
157 try:
158 current_context = {}
159 for key, value in context_data.__dict__.items():
160 current_context[key] = str(value)
161
162 alarm_event = self.adapter.adapter_agent.create_alarm(
163 resource_id=self.device.id,
khenaidoo032d3302017-06-09 14:50:04 -0400164 description="{}.{} - {}".format(self.adapter.name,
165 self.device.id,
166 alarm_data[
167 'description']) if 'description' in alarm_data else None,
Stephane Barbarie4475a252017-03-31 13:49:20 -0400168 type=alarm_data['type'] if 'type' in alarm_data else None,
khenaidoo032d3302017-06-09 14:50:04 -0400169 category=alarm_data[
170 'category'] if 'category' in alarm_data else None,
171 severity=alarm_data[
172 'severity'] if 'severity' in alarm_data else None,
Stephane Barbarie4475a252017-03-31 13:49:20 -0400173 state=alarm_data['state'] if 'state' in alarm_data else None,
174 raised_ts=alarm_data['ts'] if 'ts' in alarm_data else 0,
175 context=current_context
176 )
177
khenaidoo032d3302017-06-09 14:50:04 -0400178 self.adapter.adapter_agent.submit_alarm(self.device.id,
179 alarm_event)
Stephane Barbarie4475a252017-03-31 13:49:20 -0400180
181 except Exception as e:
182 log.exception('failed-to-send-alarm', e=e)
183
184
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800185@implementer(IAdapterInterface)
186class PonSimOltAdapter(object):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800187 name = 'ponsim_olt'
188
189 supported_device_types = [
190 DeviceType(
191 id=name,
192 adapter=name,
193 accepts_bulk_flow_update=True
194 )
195 ]
196
197 def __init__(self, adapter_agent, config):
198 self.adapter_agent = adapter_agent
199 self.config = config
200 self.descriptor = Adapter(
201 id=self.name,
202 vendor='Voltha project',
203 version='0.4',
204 config=AdapterConfig(log_level=LogLevel.INFO)
205 )
206 self.devices_handlers = dict() # device_id -> PonSimOltHandler()
207 self.logical_device_id_to_root_device_id = dict()
208
209 def start(self):
210 log.debug('starting')
211 log.info('started')
212
213 def stop(self):
Khen Nursimulud068d812017-03-06 11:44:18 -0500214 """
215 This method is called when this device instance is no longer
216 required, which means there is a request to remove this device.
217 :return:
218 """
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800219 log.debug('stopping')
220 log.info('stopped')
221
222 def adapter_descriptor(self):
223 return self.descriptor
224
225 def device_types(self):
226 return DeviceTypes(items=self.supported_device_types)
227
228 def health(self):
229 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
230
231 def change_master_state(self, master):
232 raise NotImplementedError()
233
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400234 def update_pm_config(self, device, pm_config):
Stephane Barbarie5253c652017-03-22 16:29:46 -0400235 log.info("adapter-update-pm-config", device=device,
236 pm_config=pm_config)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400237 handler = self.devices_handlers[device.id]
238 handler.update_pm_config(device, pm_config)
Sergio Slobodrianec864c62017-03-09 11:41:43 -0500239
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800240 def adopt_device(self, device):
241 self.devices_handlers[device.id] = PonSimOltHandler(self, device.id)
242 reactor.callLater(0, self.devices_handlers[device.id].activate, device)
243 return device
244
khenaidoo032d3302017-06-09 14:50:04 -0400245 def reconcile_device(self, device):
246 try:
247 self.devices_handlers[device.id] = PonSimOltHandler(self,
248 device.id)
249 # Work only required for devices that are in ENABLED state
250 if device.admin_state == AdminState.ENABLED:
251 reactor.callLater(0,
252 self.devices_handlers[device.id].reconcile,
253 device)
254 else:
255 # Invoke the children reconciliation which would setup the
256 # basic children data structures
257 self.adapter_agent.reconcile_child_devices(device.id)
258 return device
259 except Exception, e:
260 log.exception('Exception', e=e)
261
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800262 def abandon_device(self, device):
263 raise NotImplementedError()
264
Khen Nursimulud068d812017-03-06 11:44:18 -0500265 def disable_device(self, device):
266 log.info('disable-device', device_id=device.id)
267 reactor.callLater(0, self.devices_handlers[device.id].disable)
268 return device
269
270 def reenable_device(self, device):
271 log.info('reenable-device', device_id=device.id)
272 reactor.callLater(0, self.devices_handlers[device.id].reenable)
273 return device
274
275 def reboot_device(self, device):
276 log.info('reboot-device', device_id=device.id)
277 reactor.callLater(0, self.devices_handlers[device.id].reboot)
278 return device
279
280 def delete_device(self, device):
281 log.info('delete-device', device_id=device.id)
khenaidoo032d3302017-06-09 14:50:04 -0400282 # TODO: Update the logical device mapping
Khen Nursimulud068d812017-03-06 11:44:18 -0500283 reactor.callLater(0, self.devices_handlers[device.id].delete)
284 return device
285
286 def get_device_details(self, device):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800287 raise NotImplementedError()
288
289 def update_flows_bulk(self, device, flows, groups):
290 log.info('bulk-flow-update', device_id=device.id,
Stephane Barbarie5253c652017-03-22 16:29:46 -0400291 flows=flows, groups=groups)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800292 assert len(groups.items) == 0
293 handler = self.devices_handlers[device.id]
294 return handler.update_flow_table(flows.items)
295
296 def update_flows_incrementally(self, device, flow_changes, group_changes):
297 raise NotImplementedError()
298
299 def send_proxied_message(self, proxy_address, msg):
300 log.info('send-proxied-message', proxy_address=proxy_address, msg=msg)
301 handler = self.devices_handlers[proxy_address.device_id]
302 handler.send_proxied_message(proxy_address, msg)
303
304 def receive_proxied_message(self, proxy_address, msg):
305 raise NotImplementedError()
306
307 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800308 def ldi_to_di(ldi):
309 di = self.logical_device_id_to_root_device_id.get(ldi)
310 if di is None:
311 logical_device = self.adapter_agent.get_logical_device(ldi)
312 di = logical_device.root_device_id
313 self.logical_device_id_to_root_device_id[ldi] = di
314 return di
315
316 device_id = ldi_to_di(logical_device_id)
317 handler = self.devices_handlers[device_id]
318 handler.packet_out(egress_port_no, msg)
319
Peter Shafik9107f2e2017-05-02 15:54:39 -0400320 def receive_inter_adapter_message(self, msg):
321 raise NotImplementedError()
322
Stephane Barbarie980a0912017-05-11 11:27:06 -0400323 def suppress_alarm(self, filter):
324 raise NotImplementedError()
325
326 def unsuppress_alarm(self, filter):
327 raise NotImplementedError()
328
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800329
330class PonSimOltHandler(object):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800331 def __init__(self, adapter, device_id):
332 self.adapter = adapter
333 self.adapter_agent = adapter.adapter_agent
334 self.device_id = device_id
335 self.log = structlog.get_logger(device_id=device_id)
336 self.channel = None
337 self.io_port = None
338 self.logical_device_id = None
Khen Nursimulud068d812017-03-06 11:44:18 -0500339 self.nni_port = None
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400340 self.ofp_port_no = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800341 self.interface = registry('main').get_args().interface
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400342 self.pm_metrics = None
Stephane Barbarie4475a252017-03-31 13:49:20 -0400343 self.alarms = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800344
345 def __del__(self):
346 if self.io_port is not None:
Zsolt Haraszti3e6f0892017-01-19 11:51:40 -0800347 registry('frameio').close_port(self.io_port)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800348
349 def get_channel(self):
350 if self.channel is None:
351 device = self.adapter_agent.get_device(self.device_id)
352 self.channel = grpc.insecure_channel(device.host_and_port)
353 return self.channel
354
khenaidoo032d3302017-06-09 14:50:04 -0400355 def _get_nni_port(self):
356 ports = self.adapter_agent.get_ports(self.device_id, Port.ETHERNET_NNI)
357 if ports:
358 # For now, we use on one NNI port
359 return ports[0]
360
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800361 def activate(self, device):
362 self.log.info('activating')
363
364 if not device.host_and_port:
365 device.oper_status = OperStatus.FAILED
366 device.reason = 'No host_and_port field provided'
367 self.adapter_agent.update_device(device)
368 return
369
370 stub = ponsim_pb2.PonSimStub(self.get_channel())
371 info = stub.GetDeviceInfo(Empty())
372 log.info('got-info', info=info)
373
374 device.root = True
375 device.vendor = 'ponsim'
Stephane Barbarie5253c652017-03-22 16:29:46 -0400376 device.model = 'n/a'
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800377 device.serial_number = device.host_and_port
378 device.connect_status = ConnectStatus.REACHABLE
379 self.adapter_agent.update_device(device)
380
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400381 # Now set the initial PM configuration for this device
Stephane Barbarie5253c652017-03-22 16:29:46 -0400382 self.pm_metrics = AdapterPmMetrics(device)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400383 pm_config = self.pm_metrics.make_proto()
384 log.info("initial-pm-config", pm_config=pm_config)
Stephane Barbarie5253c652017-03-22 16:29:46 -0400385 self.adapter_agent.update_device_pm_config(pm_config, init=True)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400386
Stephane Barbarie4475a252017-03-31 13:49:20 -0400387 # Setup alarm handler
388 self.alarms = AdapterAlarms(self.adapter, device)
389
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800390 nni_port = Port(
391 port_no=2,
392 label='NNI facing Ethernet port',
393 type=Port.ETHERNET_NNI,
394 admin_state=AdminState.ENABLED,
395 oper_status=OperStatus.ACTIVE
396 )
Khen Nursimulud068d812017-03-06 11:44:18 -0500397 self.nni_port = nni_port
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800398 self.adapter_agent.add_port(device.id, nni_port)
399 self.adapter_agent.add_port(device.id, Port(
400 port_no=1,
401 label='PON port',
402 type=Port.PON_OLT,
403 admin_state=AdminState.ENABLED,
404 oper_status=OperStatus.ACTIVE
405 ))
406
407 ld = LogicalDevice(
408 # not setting id and datapth_id will let the adapter agent pick id
409 desc=ofp_desc(
410 mfr_desc='cord porject',
411 hw_desc='simualted pon',
412 sw_desc='simualted pon',
413 serial_num=uuid4().hex,
414 dp_desc='n/a'
415 ),
416 switch_features=ofp_switch_features(
417 n_buffers=256, # TODO fake for now
418 n_tables=2, # TODO ditto
419 capabilities=( # TODO and ditto
420 OFPC_FLOW_STATS
421 | OFPC_TABLE_STATS
422 | OFPC_PORT_STATS
423 | OFPC_GROUP_STATS
424 )
425 ),
426 root_device_id=device.id
427 )
428 ld_initialized = self.adapter_agent.create_logical_device(ld)
429 cap = OFPPF_1GB_FD | OFPPF_FIBER
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400430 self.ofp_port_no = info.nni_port
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800431 self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
432 id='nni',
433 ofp_port=ofp_port(
434 port_no=info.nni_port,
Stephane Barbarie5253c652017-03-22 16:29:46 -0400435 hw_addr=mac_str_to_tuple(
436 '00:00:00:00:00:%02x' % info.nni_port),
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800437 name='nni',
438 config=0,
439 state=OFPPS_LIVE,
440 curr=cap,
441 advertised=cap,
442 peer=cap,
443 curr_speed=OFPPF_1GB_FD,
444 max_speed=OFPPF_1GB_FD
445 ),
446 device_id=device.id,
447 device_port_no=nni_port.port_no,
448 root_port=True
449 ))
450
451 device = self.adapter_agent.get_device(device.id)
452 device.parent_id = ld_initialized.id
453 device.oper_status = OperStatus.ACTIVE
454 self.adapter_agent.update_device(device)
455 self.logical_device_id = ld_initialized.id
456
457 # register ONUS per uni port
458 for port_no in info.uni_ports:
459 vlan_id = port_no
460 self.adapter_agent.child_device_detected(
461 parent_device_id=device.id,
462 parent_port_no=1,
463 child_device_type='ponsim_onu',
464 proxy_address=Device.ProxyAddress(
465 device_id=device.id,
466 channel_id=vlan_id
467 ),
468 vlan=vlan_id
469 )
470
471 # finally, open the frameio port to receive in-band packet_in messages
472 self.log.info('registering-frameio')
Zsolt Haraszti3e6f0892017-01-19 11:51:40 -0800473 self.io_port = registry('frameio').open_port(
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800474 self.interface, self.rcv_io, is_inband_frame)
475 self.log.info('registered-frameio')
476
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400477 # Start collecting stats from the device after a brief pause
478 self.start_kpi_collection(device.id)
479
khenaidoo032d3302017-06-09 14:50:04 -0400480 def reconcile(self, device):
481 self.log.info('reconciling-OLT-device-starts')
482
483 if not device.host_and_port:
484 device.oper_status = OperStatus.FAILED
485 device.reason = 'No host_and_port field provided'
486 self.adapter_agent.update_device(device)
487 return
488
489 try:
490 stub = ponsim_pb2.PonSimStub(self.get_channel())
491 info = stub.GetDeviceInfo(Empty())
492 log.info('got-info', info=info)
493 # TODO: Verify we are connected to the same device we are
494 # reconciling - not much data in ponsim to differentiate at the
495 # time
496 device.oper_status = OperStatus.ACTIVE
497 self.adapter_agent.update_device(device)
498 self.ofp_port_no = info.nni_port
499 self.nni_port = self._get_nni_port()
500 except Exception, e:
501 log.exception('device-unreachable', e=e)
502 device.connect_status = ConnectStatus.UNREACHABLE
503 device.oper_status = OperStatus.UNKNOWN
504 self.adapter_agent.update_device(device)
505 return
506
507 # Now set the initial PM configuration for this device
508 self.pm_metrics = AdapterPmMetrics(device)
509 pm_config = self.pm_metrics.make_proto()
510 log.info("initial-pm-config", pm_config=pm_config)
511 self.adapter_agent.update_device_pm_config(pm_config, init=True)
512
513 # Setup alarm handler
514 self.alarms = AdapterAlarms(self.adapter, device)
515
516 # TODO: Is there anything required to verify nni and PON ports
517
518 # Set the logical device id
519 device = self.adapter_agent.get_device(device.id)
520 if device.parent_id:
521 self.logical_device_id = device.parent_id
522 self.adapter_agent.reconcile_logical_device(device.parent_id)
523 else:
524 self.log.info('no-logical-device-set')
525
526 # Reconcile child devices
527 self.adapter_agent.reconcile_child_devices(device.id)
528
529 # finally, open the frameio port to receive in-band packet_in messages
530 self.io_port = registry('frameio').open_port(
531 self.interface, self.rcv_io, is_inband_frame)
532
533 # Start collecting stats from the device after a brief pause
534 self.start_kpi_collection(device.id)
535
536 self.log.info('reconciling-OLT-device-ends')
537
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800538 def rcv_io(self, port, frame):
khenaidoo032d3302017-06-09 14:50:04 -0400539 self.log.info('received', iface_name=port.iface_name,
Stephane Barbarie5253c652017-03-22 16:29:46 -0400540 frame_len=len(frame))
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800541 pkt = Ether(frame)
542 if pkt.haslayer(Dot1Q):
543 outer_shim = pkt.getlayer(Dot1Q)
544 if isinstance(outer_shim.payload, Dot1Q):
545 inner_shim = outer_shim.payload
546 cvid = inner_shim.vlan
547 logical_port = cvid
548 popped_frame = (
549 Ether(src=pkt.src, dst=pkt.dst, type=inner_shim.type) /
550 inner_shim.payload
551 )
552 kw = dict(
553 logical_device_id=self.logical_device_id,
554 logical_port_no=logical_port,
555 )
556 self.log.info('sending-packet-in', **kw)
557 self.adapter_agent.send_packet_in(
558 packet=str(popped_frame), **kw)
Stephane Barbarie4475a252017-03-31 13:49:20 -0400559 elif pkt.haslayer(Raw):
560 raw_data = json.loads(pkt.getlayer(Raw).load)
561 self.alarms.send_alarm(self, raw_data)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800562
563 def update_flow_table(self, flows):
564 stub = ponsim_pb2.PonSimStub(self.get_channel())
565 self.log.info('pushing-olt-flow-table')
566 stub.UpdateFlowTable(FlowTable(
567 port=0,
568 flows=flows
569 ))
570 self.log.info('success')
571
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400572 def update_pm_config(self, device, pm_config):
Stephane Barbarie5253c652017-03-22 16:29:46 -0400573 log.info("handler-update-pm-config", device=device,
574 pm_config=pm_config)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400575 self.pm_metrics.update(pm_config)
576
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800577 def send_proxied_message(self, proxy_address, msg):
578 self.log.info('sending-proxied-message')
579 if isinstance(msg, FlowTable):
580 stub = ponsim_pb2.PonSimStub(self.get_channel())
581 self.log.info('pushing-onu-flow-table', port=msg.port)
582 res = stub.UpdateFlowTable(msg)
583 self.adapter_agent.receive_proxied_message(proxy_address, res)
584
585 def packet_out(self, egress_port, msg):
586 self.log.info('sending-packet-out', egress_port=egress_port,
587 msg=hexify(msg))
588 pkt = Ether(msg)
589 out_pkt = (
590 Ether(src=pkt.src, dst=pkt.dst) /
591 Dot1Q(vlan=4000) /
592 Dot1Q(vlan=egress_port, type=pkt.type) /
593 pkt.payload
594 )
595 self.io_port.send(str(out_pkt))
Khen Nursimulud068d812017-03-06 11:44:18 -0500596
597 @inlineCallbacks
598 def reboot(self):
599 self.log.info('rebooting', device_id=self.device_id)
600
601 # Update the operational status to ACTIVATING and connect status to
602 # UNREACHABLE
603 device = self.adapter_agent.get_device(self.device_id)
604 previous_oper_status = device.oper_status
605 previous_conn_status = device.connect_status
606 device.oper_status = OperStatus.ACTIVATING
607 device.connect_status = ConnectStatus.UNREACHABLE
608 self.adapter_agent.update_device(device)
609
khenaidoo71d0a6c2017-03-22 21:46:04 -0400610 # Update the child devices connect state to UNREACHABLE
khenaidoo2d7af132017-03-23 15:45:51 -0400611 self.adapter_agent.update_child_devices_state(self.device_id,
Stephane Barbarie4475a252017-03-31 13:49:20 -0400612 connect_status=ConnectStatus.UNREACHABLE)
khenaidoo71d0a6c2017-03-22 21:46:04 -0400613
Khen Nursimulud068d812017-03-06 11:44:18 -0500614 # Sleep 10 secs, simulating a reboot
Stephane Barbarie5253c652017-03-22 16:29:46 -0400615 # TODO: send alert and clear alert after the reboot
Khen Nursimulud068d812017-03-06 11:44:18 -0500616 yield asleep(10)
617
618 # Change the operational status back to its previous state. With a
619 # real OLT the operational state should be the state the device is
620 # after a reboot.
621 # Get the latest device reference
622 device = self.adapter_agent.get_device(self.device_id)
623 device.oper_status = previous_oper_status
624 device.connect_status = previous_conn_status
625 self.adapter_agent.update_device(device)
khenaidoo71d0a6c2017-03-22 21:46:04 -0400626
627 # Update the child devices connect state to REACHABLE
khenaidoo2d7af132017-03-23 15:45:51 -0400628 self.adapter_agent.update_child_devices_state(self.device_id,
Stephane Barbarie4475a252017-03-31 13:49:20 -0400629 connect_status=ConnectStatus.REACHABLE)
khenaidoo71d0a6c2017-03-22 21:46:04 -0400630
Khen Nursimulud068d812017-03-06 11:44:18 -0500631 self.log.info('rebooted', device_id=self.device_id)
632
633 def disable(self):
634 self.log.info('disabling', device_id=self.device_id)
635
636 # Get the latest device reference
637 device = self.adapter_agent.get_device(self.device_id)
638
639 # Update the operational status to UNKNOWN
640 device.oper_status = OperStatus.UNKNOWN
641 device.connect_status = ConnectStatus.UNREACHABLE
642 self.adapter_agent.update_device(device)
643
644 # Remove the logical device
645 logical_device = self.adapter_agent.get_logical_device(
Stephane Barbarie5253c652017-03-22 16:29:46 -0400646 self.logical_device_id)
Khen Nursimulud068d812017-03-06 11:44:18 -0500647 self.adapter_agent.delete_logical_device(logical_device)
648
649 # Disable all child devices first
khenaidoo2d7af132017-03-23 15:45:51 -0400650 self.adapter_agent.update_child_devices_state(self.device_id,
Stephane Barbarie4475a252017-03-31 13:49:20 -0400651 admin_state=AdminState.DISABLED)
Khen Nursimulud068d812017-03-06 11:44:18 -0500652
Khen Nursimulud068d812017-03-06 11:44:18 -0500653 # Remove the peer references from this device
654 self.adapter_agent.delete_all_peer_references(self.device_id)
655
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400656 # Set all ports to disabled
657 self.adapter_agent.disable_all_ports(self.device_id)
658
Khen Nursimulud068d812017-03-06 11:44:18 -0500659 # close the frameio port
660 registry('frameio').close_port(self.io_port)
661
khenaidoo032d3302017-06-09 14:50:04 -0400662 # Update the logice device mapping
663 if self.logical_device_id in \
664 self.adapter.logical_device_id_to_root_device_id:
665 del self.adapter.logical_device_id_to_root_device_id[
666 self.logical_device_id]
667
Khen Nursimulud068d812017-03-06 11:44:18 -0500668 # TODO:
669 # 1) Remove all flows from the device
670 # 2) Remove the device from ponsim
671
672 self.log.info('disabled', device_id=device.id)
673
Khen Nursimulud068d812017-03-06 11:44:18 -0500674 def reenable(self):
675 self.log.info('re-enabling', device_id=self.device_id)
676
677 # Get the latest device reference
678 device = self.adapter_agent.get_device(self.device_id)
679
khenaidoo032d3302017-06-09 14:50:04 -0400680 # Set the ofp_port_no and nni_port in case we bypassed the reconcile
681 # process if the device was in DISABLED state on voltha restart
682 if not self.ofp_port_no and not self.nni_port:
683 stub = ponsim_pb2.PonSimStub(self.get_channel())
684 info = stub.GetDeviceInfo(Empty())
685 log.info('got-info', info=info)
686 self.ofp_port_no = info.nni_port
687 self.nni_port = self._get_nni_port()
688
Khen Nursimulud068d812017-03-06 11:44:18 -0500689 # Update the connect status to REACHABLE
690 device.connect_status = ConnectStatus.REACHABLE
691 self.adapter_agent.update_device(device)
692
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400693 # Set all ports to enabled
694 self.adapter_agent.enable_all_ports(self.device_id)
695
Khen Nursimulud068d812017-03-06 11:44:18 -0500696 ld = LogicalDevice(
697 # not setting id and datapth_id will let the adapter agent pick id
698 desc=ofp_desc(
699 mfr_desc='cord porject',
700 hw_desc='simulated pon',
701 sw_desc='simulated pon',
702 serial_num=uuid4().hex,
703 dp_desc='n/a'
704 ),
705 switch_features=ofp_switch_features(
706 n_buffers=256, # TODO fake for now
707 n_tables=2, # TODO ditto
708 capabilities=( # TODO and ditto
709 OFPC_FLOW_STATS
710 | OFPC_TABLE_STATS
711 | OFPC_PORT_STATS
712 | OFPC_GROUP_STATS
713 )
714 ),
715 root_device_id=device.id
716 )
717 ld_initialized = self.adapter_agent.create_logical_device(ld)
718 cap = OFPPF_1GB_FD | OFPPF_FIBER
719 self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
720 id='nni',
721 ofp_port=ofp_port(
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400722 port_no=self.ofp_port_no,
Khen Nursimulud068d812017-03-06 11:44:18 -0500723 hw_addr=mac_str_to_tuple(
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400724 '00:00:00:00:00:%02x' % self.ofp_port_no),
Khen Nursimulud068d812017-03-06 11:44:18 -0500725 name='nni',
726 config=0,
727 state=OFPPS_LIVE,
728 curr=cap,
729 advertised=cap,
730 peer=cap,
731 curr_speed=OFPPF_1GB_FD,
732 max_speed=OFPPF_1GB_FD
733 ),
734 device_id=device.id,
735 device_port_no=self.nni_port.port_no,
736 root_port=True
737 ))
738
739 device = self.adapter_agent.get_device(device.id)
740 device.parent_id = ld_initialized.id
741 device.oper_status = OperStatus.ACTIVE
742 self.adapter_agent.update_device(device)
743 self.logical_device_id = ld_initialized.id
744
745 # Reenable all child devices
khenaidoo2d7af132017-03-23 15:45:51 -0400746 self.adapter_agent.update_child_devices_state(device.id,
Stephane Barbarie4475a252017-03-31 13:49:20 -0400747 admin_state=AdminState.ENABLED)
Khen Nursimulud068d812017-03-06 11:44:18 -0500748
Khen Nursimulud068d812017-03-06 11:44:18 -0500749 # finally, open the frameio port to receive in-band packet_in messages
Khen Nursimulud068d812017-03-06 11:44:18 -0500750 self.io_port = registry('frameio').open_port(
751 self.interface, self.rcv_io, is_inband_frame)
Khen Nursimulud068d812017-03-06 11:44:18 -0500752
753 self.log.info('re-enabled', device_id=device.id)
754
Khen Nursimulud068d812017-03-06 11:44:18 -0500755 def delete(self):
756 self.log.info('deleting', device_id=self.device_id)
757
758 # Remove all child devices
759 self.adapter_agent.delete_all_child_devices(self.device_id)
760
761 # TODO:
762 # 1) Remove all flows from the device
763 # 2) Remove the device from ponsim
764
765 self.log.info('deleted', device_id=self.device_id)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400766
767 def start_kpi_collection(self, device_id):
768
769 def _collect(device_id, prefix):
770
771 try:
772 # Step 1: gather metrics from device
773 port_metrics = \
Stephane Barbarie5253c652017-03-22 16:29:46 -0400774 self.pm_metrics.collect_port_metrics(self.get_channel())
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400775
776 # Step 2: prepare the KpiEvent for submission
777 # we can time-stamp them here (or could use time derived from OLT
778 ts = arrow.utcnow().timestamp
779 kpi_event = KpiEvent(
780 type=KpiEventType.slice,
781 ts=ts,
782 prefixes={
783 # OLT NNI port
784 prefix + '.nni': MetricValuePairs(
785 metrics=port_metrics['nni']),
786 # OLT PON port
787 prefix + '.pon': MetricValuePairs(
788 metrics=port_metrics['pon'])
789 }
790 )
791
792 # Step 3: submit
793 self.adapter_agent.submit_kpis(kpi_event)
794
795 except Exception as e:
796 log.exception('failed-to-submit-kpis', e=e)
Stephane Barbarie5253c652017-03-22 16:29:46 -0400797
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400798 self.pm_metrics.start_collector(_collect)