blob: 95bafaaabbd3a621a7156bd34dded0c43bff8b5d [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
khenaidoo92e62c52018-10-03 14:02:54 -040028from twisted.internet.defer import inlineCallbacks, returnValue
khenaidoob9203542018-09-17 22:56:37 -040029from 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
khenaidoo92e62c52018-10-03 14:02:54 -0400248 @inlineCallbacks
khenaidoob9203542018-09-17 22:56:37 -0400249 def _get_nni_port(self):
khenaidoo92e62c52018-10-03 14:02:54 -0400250 ports = yield self.adapter_agent.get_ports(self.device_id, Port.ETHERNET_NNI)
251 returnValue(ports)
khenaidoob9203542018-09-17 22:56:37 -0400252
253 @inlineCallbacks
254 def activate(self, device):
255 try:
256 self.log.info('activating')
257
258 if not device.host_and_port:
259 device.oper_status = OperStatus.FAILED
260 device.reason = 'No host_and_port field provided'
261 self.adapter_agent.device_update(device)
262 return
263
264 yield self.get_channel()
265 stub = ponsim_pb2.PonSimStub(self.channel)
266 info = stub.GetDeviceInfo(Empty())
267 log.info('got-info', info=info, device_id=device.id)
268 self.ofp_port_no = info.nni_port
269
270 device.root = True
271 device.vendor = 'ponsim'
272 device.model = 'n/a'
273 device.serial_number = device.host_and_port
khenaidoo92e62c52018-10-03 14:02:54 -0400274 device.mac_address = "AA:BB:CC:DD:EE:FF"
275 # device.connect_status = ConnectStatus.REACHABLE
khenaidoob9203542018-09-17 22:56:37 -0400276 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,
khenaidoo92e62c52018-10-03 14:02:54 -0400291 # admin_state=AdminState.ENABLED,
khenaidoob9203542018-09-17 22:56:37 -0400292 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,
khenaidoo92e62c52018-10-03 14:02:54 -0400300 # admin_state=AdminState.ENABLED,
khenaidoob9203542018-09-17 22:56:37 -0400301 oper_status=OperStatus.ACTIVE
302 ))
khenaidoo92e62c52018-10-03 14:02:54 -0400303
304 yield self.adapter_agent.device_state_update(device.id, connect_status=ConnectStatus.REACHABLE, oper_status=OperStatus.ACTIVE)
khenaidoob9203542018-09-17 22:56:37 -0400305
306 # register ONUS
307 self.log.info('onu-found', onus=info.onus, len=len(info.onus))
308 for onu in info.onus:
309 vlan_id = onu.uni_port
310 yield self.adapter_agent.child_device_detected(
311 parent_device_id=device.id,
312 parent_port_no=1,
313 child_device_type='ponsim_onu',
314 channel_id=vlan_id,
315 )
316
317 self.log.info('starting-frame-grpc-stream')
318 reactor.callInThread(self.rcv_grpc)
319 self.log.info('started-frame-grpc-stream')
320
321 # TODO
322 # Start collecting stats from the device after a brief pause
khenaidoo92e62c52018-10-03 14:02:54 -0400323 self.start_kpi_collection(device.id)
khenaidoob9203542018-09-17 22:56:37 -0400324 except Exception as e:
325 log.exception("Exception-activating", e=e)
326
327
328 def get_ofp_device_info(self, device):
329 return SwitchCapability(
330 desc=ofp_desc(
331 hw_desc='ponsim pon',
332 sw_desc='ponsim pon',
333 serial_num=device.serial_number,
334 dp_desc='n/a'
335 ),
336 switch_features=ofp_switch_features(
337 n_buffers=256, # TODO fake for now
338 n_tables=2, # TODO ditto
339 capabilities=( # TODO and ditto
340 OFPC_FLOW_STATS
341 | OFPC_TABLE_STATS
342 | OFPC_PORT_STATS
343 | OFPC_GROUP_STATS
344 )
345 )
346 )
347
348 def get_ofp_port_info(self, device, port_no):
349 # Since the adapter created the device port then it has the reference of the port to
350 # return the capability. TODO: Do a lookup on the NNI port number and return the
351 # appropriate attributes
352 self.log.info('get_ofp_port_info', port_no=port_no, info=self.ofp_port_no, device_id=device.id)
353 cap = OFPPF_1GB_FD | OFPPF_FIBER
354 return PortCapability(
355 port = LogicalPort (
356 id='nni',
357 ofp_port=ofp_port(
358 port_no=port_no,
359 hw_addr=mac_str_to_tuple(
360 '00:00:00:00:00:%02x' % self.ofp_port_no),
361 name='nni',
362 config=0,
363 state=OFPPS_LIVE,
364 curr=cap,
365 advertised=cap,
366 peer=cap,
367 curr_speed=OFPPF_1GB_FD,
368 max_speed=OFPPF_1GB_FD
369 )
370 )
371 )
372
373 def reconcile(self, device):
374 self.log.info('reconciling-OLT-device-starts')
375
376 if not device.host_and_port:
377 device.oper_status = OperStatus.FAILED
378 device.reason = 'No host_and_port field provided'
379 self.adapter_agent.device_update(device)
380 return
381
382 try:
383 stub = ponsim_pb2.PonSimStub(self.get_channel())
384 info = stub.GetDeviceInfo(Empty())
385 log.info('got-info', info=info)
386 # TODO: Verify we are connected to the same device we are
387 # reconciling - not much data in ponsim to differentiate at the
388 # time
389 device.oper_status = OperStatus.ACTIVE
390 self.adapter_agent.device_update(device)
391 self.ofp_port_no = info.nni_port
392 self.nni_port = self._get_nni_port()
393 except Exception, e:
394 log.exception('device-unreachable', e=e)
395 device.connect_status = ConnectStatus.UNREACHABLE
396 device.oper_status = OperStatus.UNKNOWN
397 self.adapter_agent.device_update(device)
398 return
399
400 # Now set the initial PM configuration for this device
401 self.pm_metrics = AdapterPmMetrics(device)
402 pm_config = self.pm_metrics.make_proto()
403 log.info("initial-pm-config", pm_config=pm_config)
404 self.adapter_agent.device_update_pm_config(pm_config, init=True)
405
406 # Setup alarm handler
407 self.alarms = AdapterAlarms(self.adapter, device)
408
409 # TODO: Is there anything required to verify nni and PON ports
410
411 # Set the logical device id
412 device = self.adapter_agent.get_device(device.id)
413 if device.parent_id:
414 self.logical_device_id = device.parent_id
415 self.adapter_agent.reconcile_logical_device(device.parent_id)
416 else:
417 self.log.info('no-logical-device-set')
418
419 # Reconcile child devices
420 self.adapter_agent.reconcile_child_devices(device.id)
421
422 reactor.callInThread(self.rcv_grpc)
423
424 # Start collecting stats from the device after a brief pause
425 self.start_kpi_collection(device.id)
426
427 self.log.info('reconciling-OLT-device-ends')
428
429 @inlineCallbacks
430 def rcv_grpc(self):
431 """
432 This call establishes a GRPC stream to receive frames.
433 """
434 yield self.get_channel()
435 stub = ponsim_pb2.PonSimStub(self.channel)
436 # stub = ponsim_pb2.PonSimStub(self.get_channel())
437
438 # Attempt to establish a grpc stream with the remote ponsim service
439 self.frames = stub.ReceiveFrames(Empty())
440
441 self.log.info('start-receiving-grpc-frames')
442
443 try:
444 for frame in self.frames:
445 self.log.info('received-grpc-frame',
446 frame_len=len(frame.payload))
447 self._rcv_frame(frame.payload)
448
449 except _Rendezvous, e:
450 log.warn('grpc-connection-lost', message=e.message)
451
452 self.log.info('stopped-receiving-grpc-frames')
453
454
455 # VOLTHA's flow decomposition removes the information about which flows
456 # are trap flows where traffic should be forwarded to the controller.
457 # We'll go through the flows and change the output port of flows that we
458 # know to be trap flows to the OF CONTROLLER port.
459 def update_flow_table(self, flows):
460 stub = ponsim_pb2.PonSimStub(self.get_channel())
461 self.log.info('pushing-olt-flow-table')
462 for flow in flows:
463 classifier_info = {}
464 for field in fd.get_ofb_fields(flow):
465 if field.type == fd.ETH_TYPE:
466 classifier_info['eth_type'] = field.eth_type
467 self.log.debug('field-type-eth-type',
468 eth_type=classifier_info['eth_type'])
469 elif field.type == fd.IP_PROTO:
470 classifier_info['ip_proto'] = field.ip_proto
471 self.log.debug('field-type-ip-proto',
472 ip_proto=classifier_info['ip_proto'])
473 if ('ip_proto' in classifier_info and (
474 classifier_info['ip_proto'] == 17 or
475 classifier_info['ip_proto'] == 2)) or (
476 'eth_type' in classifier_info and
477 classifier_info['eth_type'] == 0x888e):
478 for action in fd.get_actions(flow):
479 if action.type == ofp.OFPAT_OUTPUT:
480 action.output.port = ofp.OFPP_CONTROLLER
481 self.log.info('out_port', out_port=fd.get_out_port(flow))
482
483 stub.UpdateFlowTable(FlowTable(
484 port=0,
485 flows=flows
486 ))
487 self.log.info('success')
488
489 def remove_from_flow_table(self, flows):
490 self.log.debug('remove-from-flow-table', flows=flows)
491 # TODO: Update PONSIM code to accept incremental flow changes
492 # Once completed, the accepts_add_remove_flow_updates for this
493 # device type can be set to True
494
495 def add_to_flow_table(self, flows):
496 self.log.debug('add-to-flow-table', flows=flows)
497 # TODO: Update PONSIM code to accept incremental flow changes
498 # Once completed, the accepts_add_remove_flow_updates for this
499 # device type can be set to True
500
501 def update_pm_config(self, device, pm_config):
502 log.info("handler-update-pm-config", device=device,
503 pm_config=pm_config)
504 self.pm_metrics.update(pm_config)
505
506 def send_proxied_message(self, proxy_address, msg):
507 self.log.info('sending-proxied-message')
508 if isinstance(msg, FlowTable):
509 stub = ponsim_pb2.PonSimStub(self.get_channel())
510 self.log.info('pushing-onu-flow-table', port=msg.port)
511 res = stub.UpdateFlowTable(msg)
512 self.adapter_agent.receive_proxied_message(proxy_address, res)
513
514 def packet_out(self, egress_port, msg):
515 self.log.info('sending-packet-out', egress_port=egress_port,
516 msg=hexify(msg))
517 pkt = Ether(msg)
518 out_pkt = pkt
519 if egress_port != self.nni_port.port_no:
520 # don't do the vlan manipulation for the NNI port, vlans are already correct
521 out_pkt = (
522 Ether(src=pkt.src, dst=pkt.dst) /
523 Dot1Q(vlan=egress_port, type=pkt.type) /
524 pkt.payload
525 )
526
527 # TODO need better way of mapping logical ports to PON ports
528 out_port = self.nni_port.port_no if egress_port == self.nni_port.port_no else 1
529
530 # send over grpc stream
531 stub = ponsim_pb2.PonSimStub(self.get_channel())
532 frame = PonSimFrame(id=self.device_id, payload=str(out_pkt), out_port=out_port)
533 stub.SendFrame(frame)
534
535
536 @inlineCallbacks
537 def reboot(self):
538 self.log.info('rebooting', device_id=self.device_id)
539
540 # Update the operational status to ACTIVATING and connect status to
541 # UNREACHABLE
542 device = self.adapter_agent.get_device(self.device_id)
543 previous_oper_status = device.oper_status
544 previous_conn_status = device.connect_status
545 device.oper_status = OperStatus.ACTIVATING
546 device.connect_status = ConnectStatus.UNREACHABLE
547 self.adapter_agent.device_update(device)
548
549 # Update the child devices connect state to UNREACHABLE
550 self.adapter_agent.update_child_devices_state(self.device_id,
551 connect_status=ConnectStatus.UNREACHABLE)
552
553 # Sleep 10 secs, simulating a reboot
554 # TODO: send alert and clear alert after the reboot
555 yield asleep(10)
556
557 # Change the operational status back to its previous state. With a
558 # real OLT the operational state should be the state the device is
559 # after a reboot.
560 # Get the latest device reference
561 device = self.adapter_agent.get_device(self.device_id)
562 device.oper_status = previous_oper_status
563 device.connect_status = previous_conn_status
564 self.adapter_agent.device_update(device)
565
566 # Update the child devices connect state to REACHABLE
567 self.adapter_agent.update_child_devices_state(self.device_id,
568 connect_status=ConnectStatus.REACHABLE)
569
570 self.log.info('rebooted', device_id=self.device_id)
571
572 def self_test_device(self, device):
573 """
574 This is called to Self a device based on a NBI call.
575 :param device: A Voltha.Device object.
576 :return: Will return result of self test
577 """
578 log.info('self-test-device', device=device.id)
579 raise NotImplementedError()
580
khenaidoo92e62c52018-10-03 14:02:54 -0400581
582 @inlineCallbacks
khenaidoob9203542018-09-17 22:56:37 -0400583 def disable(self):
584 self.log.info('disabling', device_id=self.device_id)
585
586 self.stop_kpi_collection()
587
khenaidoo92e62c52018-10-03 14:02:54 -0400588 # Update the operational status to UNKNOWN and connection status to UNREACHABLE
589 yield self.adapter_agent.device_state_update(self.device_id, oper_status=OperStatus.UNKNOWN, connect_status=ConnectStatus.UNREACHABLE)
khenaidoob9203542018-09-17 22:56:37 -0400590
591 self.close_channel()
592 self.log.info('disabled-grpc-channel')
593
khenaidoob9203542018-09-17 22:56:37 -0400594 # TODO:
595 # 1) Remove all flows from the device
596 # 2) Remove the device from ponsim
597
khenaidoo92e62c52018-10-03 14:02:54 -0400598 self.log.info('disabled', device_id=self.device_id)
khenaidoob9203542018-09-17 22:56:37 -0400599
khenaidoo92e62c52018-10-03 14:02:54 -0400600 @inlineCallbacks
khenaidoob9203542018-09-17 22:56:37 -0400601 def reenable(self):
602 self.log.info('re-enabling', device_id=self.device_id)
603
khenaidoob9203542018-09-17 22:56:37 -0400604 # Set the ofp_port_no and nni_port in case we bypassed the reconcile
605 # process if the device was in DISABLED state on voltha restart
606 if not self.ofp_port_no and not self.nni_port:
khenaidoo92e62c52018-10-03 14:02:54 -0400607 yield self.get_channel()
608 stub = ponsim_pb2.PonSimStub(self.channel)
khenaidoob9203542018-09-17 22:56:37 -0400609 info = stub.GetDeviceInfo(Empty())
610 log.info('got-info', info=info)
611 self.ofp_port_no = info.nni_port
khenaidoo92e62c52018-10-03 14:02:54 -0400612 ports = yield self._get_nni_port()
613 # For ponsim, we are using only 1 NNI port
614 if ports.items:
615 self.nni_port = ports.items[0]
khenaidoob9203542018-09-17 22:56:37 -0400616
khenaidoo92e62c52018-10-03 14:02:54 -0400617 # Update the state of the NNI port
618 yield self.adapter_agent.port_state_update(self.device_id,
619 port_type=Port.ETHERNET_NNI,
620 port_no=self.ofp_port_no,
621 oper_status=OperStatus.ACTIVE)
khenaidoob9203542018-09-17 22:56:37 -0400622
khenaidoo92e62c52018-10-03 14:02:54 -0400623 # Update the state of the PON port
624 yield self.adapter_agent.port_state_update(self.device_id,
625 port_type=Port.PON_OLT,
626 port_no=1,
627 oper_status=OperStatus.ACTIVE)
khenaidoob9203542018-09-17 22:56:37 -0400628
khenaidoo92e62c52018-10-03 14:02:54 -0400629 # Set the operational state of the device to ACTIVE and connect status to REACHABLE
630 yield self.adapter_agent.device_state_update(self.device_id,
631 connect_status=ConnectStatus.REACHABLE,
632 oper_status=OperStatus.ACTIVE)
khenaidoob9203542018-09-17 22:56:37 -0400633
khenaidoo92e62c52018-10-03 14:02:54 -0400634 # TODO: establish frame grpc-stream
635 # yield reactor.callInThread(self.rcv_grpc)
khenaidoob9203542018-09-17 22:56:37 -0400636
khenaidoo92e62c52018-10-03 14:02:54 -0400637 self.start_kpi_collection(self.device_id)
khenaidoob9203542018-09-17 22:56:37 -0400638
khenaidoo92e62c52018-10-03 14:02:54 -0400639 self.log.info('re-enabled', device_id=self.device_id)
khenaidoob9203542018-09-17 22:56:37 -0400640
641 def delete(self):
642 self.log.info('deleting', device_id=self.device_id)
643
khenaidoob9203542018-09-17 22:56:37 -0400644 self.close_channel()
645 self.log.info('disabled-grpc-channel')
646
647 # TODO:
648 # 1) Remove all flows from the device
649 # 2) Remove the device from ponsim
650
651 self.log.info('deleted', device_id=self.device_id)
652
653 def start_kpi_collection(self, device_id):
654
655 def _collect(device_id, prefix):
656
657 try:
658 # Step 1: gather metrics from device
659 port_metrics = \
khenaidoo92e62c52018-10-03 14:02:54 -0400660 self.pm_metrics.collect_port_metrics(self.channel)
khenaidoob9203542018-09-17 22:56:37 -0400661
662 # Step 2: prepare the KpiEvent for submission
663 # we can time-stamp them here (or could use time derived from OLT
664 ts = arrow.utcnow().timestamp
665 kpi_event = KpiEvent(
666 type=KpiEventType.slice,
667 ts=ts,
668 prefixes={
669 # OLT NNI port
670 prefix + '.nni': MetricValuePairs(
671 metrics=port_metrics['nni']),
672 # OLT PON port
673 prefix + '.pon': MetricValuePairs(
674 metrics=port_metrics['pon'])
675 }
676 )
677
678 # Step 3: submit
679 self.adapter_agent.submit_kpis(kpi_event)
680
681 except Exception as e:
682 log.exception('failed-to-submit-kpis', e=e)
683
684 self.pm_metrics.start_collector(_collect)
685
686 def stop_kpi_collection(self):
687 self.pm_metrics.stop_collector()