blob: 5e096b411ff26c7c9507ed9beab280b22a398fe1 [file] [log] [blame]
khenaidoob9203542018-09-17 22:56:37 -04001#
2# Copyright 2017 the original author or authors.
3#
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 adapter.
19"""
20from uuid import uuid4
21
22import arrow
23import adapters.common.openflow.utils as fd
24import grpc
25import structlog
26from scapy.layers.l2 import Ether, Dot1Q
27from twisted.internet import reactor
28from twisted.internet.defer import inlineCallbacks
29from grpc._channel import _Rendezvous
30
31from adapters.common.frameio.frameio import BpfProgramFilter, hexify
32from adapters.common.utils.asleep import asleep
33from twisted.internet.task import LoopingCall
34from adapters.iadapter import OltAdapter
35from adapters.protos import third_party
36from adapters.protos import openflow_13_pb2 as ofp
37from adapters.protos import ponsim_pb2
38from adapters.protos.common_pb2 import OperStatus, ConnectStatus, AdminState
39from adapters.protos.device_pb2 import Port, PmConfig, PmConfigs
40from adapters.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
41from google.protobuf.empty_pb2 import Empty
42from adapters.protos.logical_device_pb2 import LogicalPort, LogicalDevice
43from adapters.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, \
44 OFPPF_1GB_FD, \
45 OFPC_GROUP_STATS, OFPC_PORT_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS, \
46 ofp_switch_features, ofp_desc
47from adapters.protos.openflow_13_pb2 import ofp_port
48from adapters.protos.ponsim_pb2 import FlowTable, PonSimFrame
49from adapters.protos.core_adapter_pb2 import SwitchCapability, PortCapability
50from adapters.common.utils.registry import registry
51
52_ = third_party
53log = structlog.get_logger()
54
55PACKET_IN_VLAN = 4000
56is_inband_frame = BpfProgramFilter('(ether[14:2] & 0xfff) = 0x{:03x}'.format(
57 PACKET_IN_VLAN))
58
59def mac_str_to_tuple(mac):
60 return tuple(int(d, 16) for d in mac.split(':'))
61
62class AdapterPmMetrics:
63 def __init__(self, device):
64 self.pm_names = {'tx_64_pkts', 'tx_65_127_pkts', 'tx_128_255_pkts',
65 'tx_256_511_pkts', 'tx_512_1023_pkts',
66 'tx_1024_1518_pkts', 'tx_1519_9k_pkts',
67 'rx_64_pkts', 'rx_65_127_pkts',
68 'rx_128_255_pkts', 'rx_256_511_pkts',
69 'rx_512_1023_pkts', 'rx_1024_1518_pkts',
70 'rx_1519_9k_pkts'}
71 self.device = device
72 self.id = device.id
73 self.name = 'ponsim_olt'
74 self.default_freq = 150
75 self.grouped = False
76 self.freq_override = False
77 self.pon_metrics_config = dict()
78 self.nni_metrics_config = dict()
79 self.lc = None
80 for m in self.pm_names:
81 self.pon_metrics_config[m] = PmConfig(name=m,
82 type=PmConfig.COUNTER,
83 enabled=True)
84 self.nni_metrics_config[m] = PmConfig(name=m,
85 type=PmConfig.COUNTER,
86 enabled=True)
87
88 def update(self, pm_config):
89 if self.default_freq != pm_config.default_freq:
90 # Update the callback to the new frequency.
91 self.default_freq = pm_config.default_freq
92 self.lc.stop()
93 self.lc.start(interval=self.default_freq / 10)
94 for m in pm_config.metrics:
95 self.pon_metrics_config[m.name].enabled = m.enabled
96 self.nni_metrics_config[m.name].enabled = m.enabled
97
98 def make_proto(self):
99 pm_config = PmConfigs(
100 id=self.id,
101 default_freq=self.default_freq,
102 grouped=False,
103 freq_override=False)
104 for m in sorted(self.pon_metrics_config):
105 pm = self.pon_metrics_config[m] # Either will do they're the same
106 pm_config.metrics.extend([PmConfig(name=pm.name,
107 type=pm.type,
108 enabled=pm.enabled)])
109 return pm_config
110
111 def collect_port_metrics(self, channel):
112 rtrn_port_metrics = dict()
113 stub = ponsim_pb2.PonSimStub(channel)
114 stats = stub.GetStats(Empty())
115 rtrn_port_metrics['pon'] = self.extract_pon_metrics(stats)
116 rtrn_port_metrics['nni'] = self.extract_nni_metrics(stats)
117 return rtrn_port_metrics
118
119 def extract_pon_metrics(self, stats):
120 rtrn_pon_metrics = dict()
121 for m in stats.metrics:
122 if m.port_name == "pon":
123 for p in m.packets:
124 if self.pon_metrics_config[p.name].enabled:
125 rtrn_pon_metrics[p.name] = p.value
126 return rtrn_pon_metrics
127
128 def extract_nni_metrics(self, stats):
129 rtrn_pon_metrics = dict()
130 for m in stats.metrics:
131 if m.port_name == "nni":
132 for p in m.packets:
133 if self.pon_metrics_config[p.name].enabled:
134 rtrn_pon_metrics[p.name] = p.value
135 return rtrn_pon_metrics
136
137 def start_collector(self, callback):
138 log.info("starting-pm-collection", device_name=self.name,
139 device_id=self.device.id)
140 prefix = 'voltha.{}.{}'.format(self.name, self.device.id)
141 self.lc = LoopingCall(callback, self.device.id, prefix)
142 self.lc.start(interval=self.default_freq / 10)
143
144 def stop_collector(self):
145 log.info("stopping-pm-collection", device_name=self.name,
146 device_id=self.device.id)
147 self.lc.stop()
148
149
150class 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,
164 description="{}.{} - {}".format(self.adapter.name,
165 self.device.id,
166 alarm_data[
167 'description']) if 'description' in alarm_data else None,
168 type=alarm_data['type'] if 'type' in alarm_data else None,
169 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,
173 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
178 self.adapter.adapter_agent.submit_alarm(self.device.id,
179 alarm_event)
180
181 except Exception as e:
182 log.exception('failed-to-send-alarm', e=e)
183
184
185class PonSimOltAdapter(OltAdapter):
186 def __init__(self, adapter_agent, config):
187 super(PonSimOltAdapter, self).__init__(adapter_agent=adapter_agent,
188 config=config,
189 device_handler_class=PonSimOltHandler,
190 name='ponsim_olt',
191 vendor='Voltha project',
192 version='0.4',
193 device_type='ponsim_olt',
194 accepts_bulk_flow_update=True,
195 accepts_add_remove_flow_updates=False)
196
197 def update_pm_config(self, device, pm_config):
198 log.info("adapter-update-pm-config", device=device,
199 pm_config=pm_config)
200 handler = self.devices_handlers[device.id]
201 handler.update_pm_config(device, pm_config)
202
203
204
205class PonSimOltHandler(object):
206 def __init__(self, adapter, device_id):
207 self.adapter = adapter
208 self.adapter_agent = adapter.adapter_agent
209 self.device_id = device_id
210 self.log = structlog.get_logger(device_id=device_id)
211 self.channel = None
212 self.io_port = None
213 self.logical_device_id = None
214 self.nni_port = None
215 self.ofp_port_no = None
216 self.interface = registry('main').get_args().interface
217 self.pm_metrics = None
218 self.alarms = None
219 self.frames = None
220
221 @inlineCallbacks
222 def get_channel(self):
223 if self.channel is None:
224 try:
225 device = yield self.adapter_agent.get_device(self.device_id)
226 self.log.info('device-info', device=device, host_port=device.host_and_port)
227 self.channel = grpc.insecure_channel(device.host_and_port)
228 except Exception as e:
229 log.exception("ponsim-connection-failure", e=e)
230
231 # returnValue(self.channel)
232
233 def close_channel(self):
234 if self.channel is None:
235 self.log.info('grpc-channel-already-closed')
236 return
237 else:
238 if self.frames is not None:
239 self.frames.cancel()
240 self.frames = None
241 self.log.info('cancelled-grpc-frame-stream')
242
243 self.channel.unsubscribe(lambda *args: None)
244 self.channel = None
245
246 self.log.info('grpc-channel-closed')
247
248 def _get_nni_port(self):
249 ports = self.adapter_agent.get_ports(self.device_id, Port.ETHERNET_NNI)
250 if ports:
251 # For now, we use on one NNI port
252 return ports[0]
253
254 @inlineCallbacks
255 def activate(self, device):
256 try:
257 self.log.info('activating')
258
259 if not device.host_and_port:
260 device.oper_status = OperStatus.FAILED
261 device.reason = 'No host_and_port field provided'
262 self.adapter_agent.device_update(device)
263 return
264
265 yield self.get_channel()
266 stub = ponsim_pb2.PonSimStub(self.channel)
267 info = stub.GetDeviceInfo(Empty())
268 log.info('got-info', info=info, device_id=device.id)
269 self.ofp_port_no = info.nni_port
270
271 device.root = True
272 device.vendor = 'ponsim'
273 device.model = 'n/a'
274 device.serial_number = device.host_and_port
275 device.connect_status = ConnectStatus.REACHABLE
276 yield self.adapter_agent.device_update(device)
277
278 # Now set the initial PM configuration for this device
279 self.pm_metrics = AdapterPmMetrics(device)
280 pm_config = self.pm_metrics.make_proto()
281 log.info("initial-pm-config", pm_config=pm_config)
282 self.adapter_agent.device_pm_config_update(pm_config, init=True)
283
284 # Setup alarm handler
285 self.alarms = AdapterAlarms(self.adapter, device)
286
287 nni_port = Port(
288 port_no=info.nni_port,
289 label='NNI facing Ethernet port',
290 type=Port.ETHERNET_NNI,
291 admin_state=AdminState.ENABLED,
292 oper_status=OperStatus.ACTIVE
293 )
294 self.nni_port = nni_port
295 yield self.adapter_agent.port_created(device.id, nni_port)
296 yield self.adapter_agent.port_created(device.id, Port(
297 port_no=1,
298 label='PON port',
299 type=Port.PON_OLT,
300 admin_state=AdminState.ENABLED,
301 oper_status=OperStatus.ACTIVE
302 ))
303 yield self.adapter_agent.device_state_update(device.id, oper_status=OperStatus.ACTIVE)
304
305 # register ONUS
306 self.log.info('onu-found', onus=info.onus, len=len(info.onus))
307 for onu in info.onus:
308 vlan_id = onu.uni_port
309 yield self.adapter_agent.child_device_detected(
310 parent_device_id=device.id,
311 parent_port_no=1,
312 child_device_type='ponsim_onu',
313 channel_id=vlan_id,
314 )
315
316 self.log.info('starting-frame-grpc-stream')
317 reactor.callInThread(self.rcv_grpc)
318 self.log.info('started-frame-grpc-stream')
319
320 # TODO
321 # Start collecting stats from the device after a brief pause
322 # self.start_kpi_collection(device.id)
323 except Exception as e:
324 log.exception("Exception-activating", e=e)
325
326
327 def get_ofp_device_info(self, device):
328 return SwitchCapability(
329 desc=ofp_desc(
330 hw_desc='ponsim pon',
331 sw_desc='ponsim pon',
332 serial_num=device.serial_number,
333 dp_desc='n/a'
334 ),
335 switch_features=ofp_switch_features(
336 n_buffers=256, # TODO fake for now
337 n_tables=2, # TODO ditto
338 capabilities=( # TODO and ditto
339 OFPC_FLOW_STATS
340 | OFPC_TABLE_STATS
341 | OFPC_PORT_STATS
342 | OFPC_GROUP_STATS
343 )
344 )
345 )
346
347 def get_ofp_port_info(self, device, port_no):
348 # Since the adapter created the device port then it has the reference of the port to
349 # return the capability. TODO: Do a lookup on the NNI port number and return the
350 # appropriate attributes
351 self.log.info('get_ofp_port_info', port_no=port_no, info=self.ofp_port_no, device_id=device.id)
352 cap = OFPPF_1GB_FD | OFPPF_FIBER
353 return PortCapability(
354 port = LogicalPort (
355 id='nni',
356 ofp_port=ofp_port(
357 port_no=port_no,
358 hw_addr=mac_str_to_tuple(
359 '00:00:00:00:00:%02x' % self.ofp_port_no),
360 name='nni',
361 config=0,
362 state=OFPPS_LIVE,
363 curr=cap,
364 advertised=cap,
365 peer=cap,
366 curr_speed=OFPPF_1GB_FD,
367 max_speed=OFPPF_1GB_FD
368 )
369 )
370 )
371
372 def reconcile(self, device):
373 self.log.info('reconciling-OLT-device-starts')
374
375 if not device.host_and_port:
376 device.oper_status = OperStatus.FAILED
377 device.reason = 'No host_and_port field provided'
378 self.adapter_agent.device_update(device)
379 return
380
381 try:
382 stub = ponsim_pb2.PonSimStub(self.get_channel())
383 info = stub.GetDeviceInfo(Empty())
384 log.info('got-info', info=info)
385 # TODO: Verify we are connected to the same device we are
386 # reconciling - not much data in ponsim to differentiate at the
387 # time
388 device.oper_status = OperStatus.ACTIVE
389 self.adapter_agent.device_update(device)
390 self.ofp_port_no = info.nni_port
391 self.nni_port = self._get_nni_port()
392 except Exception, e:
393 log.exception('device-unreachable', e=e)
394 device.connect_status = ConnectStatus.UNREACHABLE
395 device.oper_status = OperStatus.UNKNOWN
396 self.adapter_agent.device_update(device)
397 return
398
399 # Now set the initial PM configuration for this device
400 self.pm_metrics = AdapterPmMetrics(device)
401 pm_config = self.pm_metrics.make_proto()
402 log.info("initial-pm-config", pm_config=pm_config)
403 self.adapter_agent.device_update_pm_config(pm_config, init=True)
404
405 # Setup alarm handler
406 self.alarms = AdapterAlarms(self.adapter, device)
407
408 # TODO: Is there anything required to verify nni and PON ports
409
410 # Set the logical device id
411 device = self.adapter_agent.get_device(device.id)
412 if device.parent_id:
413 self.logical_device_id = device.parent_id
414 self.adapter_agent.reconcile_logical_device(device.parent_id)
415 else:
416 self.log.info('no-logical-device-set')
417
418 # Reconcile child devices
419 self.adapter_agent.reconcile_child_devices(device.id)
420
421 reactor.callInThread(self.rcv_grpc)
422
423 # Start collecting stats from the device after a brief pause
424 self.start_kpi_collection(device.id)
425
426 self.log.info('reconciling-OLT-device-ends')
427
428 @inlineCallbacks
429 def rcv_grpc(self):
430 """
431 This call establishes a GRPC stream to receive frames.
432 """
433 yield self.get_channel()
434 stub = ponsim_pb2.PonSimStub(self.channel)
435 # stub = ponsim_pb2.PonSimStub(self.get_channel())
436
437 # Attempt to establish a grpc stream with the remote ponsim service
438 self.frames = stub.ReceiveFrames(Empty())
439
440 self.log.info('start-receiving-grpc-frames')
441
442 try:
443 for frame in self.frames:
444 self.log.info('received-grpc-frame',
445 frame_len=len(frame.payload))
446 self._rcv_frame(frame.payload)
447
448 except _Rendezvous, e:
449 log.warn('grpc-connection-lost', message=e.message)
450
451 self.log.info('stopped-receiving-grpc-frames')
452
453
454 # VOLTHA's flow decomposition removes the information about which flows
455 # are trap flows where traffic should be forwarded to the controller.
456 # We'll go through the flows and change the output port of flows that we
457 # know to be trap flows to the OF CONTROLLER port.
458 def update_flow_table(self, flows):
459 stub = ponsim_pb2.PonSimStub(self.get_channel())
460 self.log.info('pushing-olt-flow-table')
461 for flow in flows:
462 classifier_info = {}
463 for field in fd.get_ofb_fields(flow):
464 if field.type == fd.ETH_TYPE:
465 classifier_info['eth_type'] = field.eth_type
466 self.log.debug('field-type-eth-type',
467 eth_type=classifier_info['eth_type'])
468 elif field.type == fd.IP_PROTO:
469 classifier_info['ip_proto'] = field.ip_proto
470 self.log.debug('field-type-ip-proto',
471 ip_proto=classifier_info['ip_proto'])
472 if ('ip_proto' in classifier_info and (
473 classifier_info['ip_proto'] == 17 or
474 classifier_info['ip_proto'] == 2)) or (
475 'eth_type' in classifier_info and
476 classifier_info['eth_type'] == 0x888e):
477 for action in fd.get_actions(flow):
478 if action.type == ofp.OFPAT_OUTPUT:
479 action.output.port = ofp.OFPP_CONTROLLER
480 self.log.info('out_port', out_port=fd.get_out_port(flow))
481
482 stub.UpdateFlowTable(FlowTable(
483 port=0,
484 flows=flows
485 ))
486 self.log.info('success')
487
488 def remove_from_flow_table(self, flows):
489 self.log.debug('remove-from-flow-table', flows=flows)
490 # TODO: Update PONSIM code to accept incremental flow changes
491 # Once completed, the accepts_add_remove_flow_updates for this
492 # device type can be set to True
493
494 def add_to_flow_table(self, flows):
495 self.log.debug('add-to-flow-table', flows=flows)
496 # TODO: Update PONSIM code to accept incremental flow changes
497 # Once completed, the accepts_add_remove_flow_updates for this
498 # device type can be set to True
499
500 def update_pm_config(self, device, pm_config):
501 log.info("handler-update-pm-config", device=device,
502 pm_config=pm_config)
503 self.pm_metrics.update(pm_config)
504
505 def send_proxied_message(self, proxy_address, msg):
506 self.log.info('sending-proxied-message')
507 if isinstance(msg, FlowTable):
508 stub = ponsim_pb2.PonSimStub(self.get_channel())
509 self.log.info('pushing-onu-flow-table', port=msg.port)
510 res = stub.UpdateFlowTable(msg)
511 self.adapter_agent.receive_proxied_message(proxy_address, res)
512
513 def packet_out(self, egress_port, msg):
514 self.log.info('sending-packet-out', egress_port=egress_port,
515 msg=hexify(msg))
516 pkt = Ether(msg)
517 out_pkt = pkt
518 if egress_port != self.nni_port.port_no:
519 # don't do the vlan manipulation for the NNI port, vlans are already correct
520 out_pkt = (
521 Ether(src=pkt.src, dst=pkt.dst) /
522 Dot1Q(vlan=egress_port, type=pkt.type) /
523 pkt.payload
524 )
525
526 # TODO need better way of mapping logical ports to PON ports
527 out_port = self.nni_port.port_no if egress_port == self.nni_port.port_no else 1
528
529 # send over grpc stream
530 stub = ponsim_pb2.PonSimStub(self.get_channel())
531 frame = PonSimFrame(id=self.device_id, payload=str(out_pkt), out_port=out_port)
532 stub.SendFrame(frame)
533
534
535 @inlineCallbacks
536 def reboot(self):
537 self.log.info('rebooting', device_id=self.device_id)
538
539 # Update the operational status to ACTIVATING and connect status to
540 # UNREACHABLE
541 device = self.adapter_agent.get_device(self.device_id)
542 previous_oper_status = device.oper_status
543 previous_conn_status = device.connect_status
544 device.oper_status = OperStatus.ACTIVATING
545 device.connect_status = ConnectStatus.UNREACHABLE
546 self.adapter_agent.device_update(device)
547
548 # Update the child devices connect state to UNREACHABLE
549 self.adapter_agent.update_child_devices_state(self.device_id,
550 connect_status=ConnectStatus.UNREACHABLE)
551
552 # Sleep 10 secs, simulating a reboot
553 # TODO: send alert and clear alert after the reboot
554 yield asleep(10)
555
556 # Change the operational status back to its previous state. With a
557 # real OLT the operational state should be the state the device is
558 # after a reboot.
559 # Get the latest device reference
560 device = self.adapter_agent.get_device(self.device_id)
561 device.oper_status = previous_oper_status
562 device.connect_status = previous_conn_status
563 self.adapter_agent.device_update(device)
564
565 # Update the child devices connect state to REACHABLE
566 self.adapter_agent.update_child_devices_state(self.device_id,
567 connect_status=ConnectStatus.REACHABLE)
568
569 self.log.info('rebooted', device_id=self.device_id)
570
571 def self_test_device(self, device):
572 """
573 This is called to Self a device based on a NBI call.
574 :param device: A Voltha.Device object.
575 :return: Will return result of self test
576 """
577 log.info('self-test-device', device=device.id)
578 raise NotImplementedError()
579
580 def disable(self):
581 self.log.info('disabling', device_id=self.device_id)
582
583 self.stop_kpi_collection()
584
585 # Get the latest device reference
586 device = self.adapter_agent.get_device(self.device_id)
587
588 # Update the operational status to UNKNOWN
589 device.oper_status = OperStatus.UNKNOWN
590 device.connect_status = ConnectStatus.UNREACHABLE
591 self.adapter_agent.device_update(device)
592
593 # Remove the logical device
594 logical_device = self.adapter_agent.get_logical_device(
595 self.logical_device_id)
596 self.adapter_agent.delete_logical_device(logical_device)
597
598 # Disable all child devices first
599 self.adapter_agent.update_child_devices_state(self.device_id,
600 admin_state=AdminState.DISABLED)
601
602 # Remove the peer references from this device
603 self.adapter_agent.delete_all_peer_references(self.device_id)
604
605 # Set all ports to disabled
606 self.adapter_agent.disable_all_ports(self.device_id)
607
608 self.close_channel()
609 self.log.info('disabled-grpc-channel')
610
611 # Update the logice device mapping
612 if self.logical_device_id in \
613 self.adapter.logical_device_id_to_root_device_id:
614 del self.adapter.logical_device_id_to_root_device_id[
615 self.logical_device_id]
616
617 # TODO:
618 # 1) Remove all flows from the device
619 # 2) Remove the device from ponsim
620
621 self.log.info('disabled', device_id=device.id)
622
623 def reenable(self):
624 self.log.info('re-enabling', device_id=self.device_id)
625
626 # Get the latest device reference
627 device = self.adapter_agent.get_device(self.device_id)
628
629 # Set the ofp_port_no and nni_port in case we bypassed the reconcile
630 # process if the device was in DISABLED state on voltha restart
631 if not self.ofp_port_no and not self.nni_port:
632 stub = ponsim_pb2.PonSimStub(self.get_channel())
633 info = stub.GetDeviceInfo(Empty())
634 log.info('got-info', info=info)
635 self.ofp_port_no = info.nni_port
636 self.nni_port = self._get_nni_port()
637
638 # Update the connect status to REACHABLE
639 device.connect_status = ConnectStatus.REACHABLE
640 self.adapter_agent.device_update(device)
641
642 # Set all ports to enabled
643 self.adapter_agent.enable_all_ports(self.device_id)
644
645 ld = LogicalDevice(
646 # not setting id and datapth_id will let the adapter agent pick id
647 desc=ofp_desc(
648 hw_desc='simulated pon',
649 sw_desc='simulated pon',
650 serial_num=uuid4().hex,
651 dp_desc='n/a'
652 ),
653 switch_features=ofp_switch_features(
654 n_buffers=256, # TODO fake for now
655 n_tables=2, # TODO ditto
656 capabilities=( # TODO and ditto
657 OFPC_FLOW_STATS
658 | OFPC_TABLE_STATS
659 | OFPC_PORT_STATS
660 | OFPC_GROUP_STATS
661 )
662 ),
663 root_device_id=device.id
664 )
665 mac_address = "AA:BB:CC:DD:EE:FF"
666 ld_initialized = self.adapter_agent.create_logical_device(ld,
667 dpid=mac_address)
668 cap = OFPPF_1GB_FD | OFPPF_FIBER
669 self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
670 id='nni',
671 ofp_port=ofp_port(
672 port_no=self.ofp_port_no,
673 hw_addr=mac_str_to_tuple(
674 '00:00:00:00:00:%02x' % self.ofp_port_no),
675 name='nni',
676 config=0,
677 state=OFPPS_LIVE,
678 curr=cap,
679 advertised=cap,
680 peer=cap,
681 curr_speed=OFPPF_1GB_FD,
682 max_speed=OFPPF_1GB_FD
683 ),
684 device_id=device.id,
685 device_port_no=self.nni_port.port_no,
686 root_port=True
687 ))
688
689 device = self.adapter_agent.get_device(device.id)
690 device.parent_id = ld_initialized.id
691 device.oper_status = OperStatus.ACTIVE
692 self.adapter_agent.device_update(device)
693 self.logical_device_id = ld_initialized.id
694
695 # Reenable all child devices
696 self.adapter_agent.update_child_devices_state(device.id,
697 admin_state=AdminState.ENABLED)
698
699 # establish frame grpc-stream
700 reactor.callInThread(self.rcv_grpc)
701
702 self.start_kpi_collection(device.id)
703
704 self.log.info('re-enabled', device_id=device.id)
705
706 def delete(self):
707 self.log.info('deleting', device_id=self.device_id)
708
709 # Remove all child devices
710 self.adapter_agent.delete_all_child_devices(self.device_id)
711
712 self.close_channel()
713 self.log.info('disabled-grpc-channel')
714
715 # TODO:
716 # 1) Remove all flows from the device
717 # 2) Remove the device from ponsim
718
719 self.log.info('deleted', device_id=self.device_id)
720
721 def start_kpi_collection(self, device_id):
722
723 def _collect(device_id, prefix):
724
725 try:
726 # Step 1: gather metrics from device
727 port_metrics = \
728 self.pm_metrics.collect_port_metrics(self.get_channel())
729
730 # Step 2: prepare the KpiEvent for submission
731 # we can time-stamp them here (or could use time derived from OLT
732 ts = arrow.utcnow().timestamp
733 kpi_event = KpiEvent(
734 type=KpiEventType.slice,
735 ts=ts,
736 prefixes={
737 # OLT NNI port
738 prefix + '.nni': MetricValuePairs(
739 metrics=port_metrics['nni']),
740 # OLT PON port
741 prefix + '.pon': MetricValuePairs(
742 metrics=port_metrics['pon'])
743 }
744 )
745
746 # Step 3: submit
747 self.adapter_agent.submit_kpis(kpi_event)
748
749 except Exception as e:
750 log.exception('failed-to-submit-kpis', e=e)
751
752 self.pm_metrics.start_collector(_collect)
753
754 def stop_kpi_collection(self):
755 self.pm_metrics.stop_collector()