blob: beed980fa962389de033d06178b46f553d9aa86c [file] [log] [blame]
Chip Boling8e042f62019-02-12 16:14:34 -06001#
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#
16import structlog
17import ast
18from pon_port import PonPort
19from uni_port import UniPort
20from heartbeat import HeartBeat
21from omci.omci import OMCI
22from onu_traffic_descriptor import OnuTrafficDescriptor
23from onu_tcont import OnuTCont
24from onu_gem_port import OnuGemPort
25
26from pyvoltha.adapters.extensions.alarms.adapter_alarms import AdapterAlarms
27from pyvoltha.adapters.extensions.kpi.onu.onu_pm_metrics import OnuPmMetrics
28from pyvoltha.adapters.extensions.kpi.onu.onu_omci_pm import OnuOmciPmMetrics
29
30from twisted.internet import reactor
31from twisted.internet.defer import DeferredQueue, inlineCallbacks
32from twisted.internet.defer import returnValue
33
34from pyvoltha.common.utils.registry import registry
35from pyvoltha.protos import third_party
36from pyvoltha.protos.common_pb2 import OperStatus, ConnectStatus
37from pyvoltha.common.tech_profile.tech_profile import TechProfile
38from pyvoltha.adapters.common.kvstore.consul_client import Consul
39from pyvoltha.adapters.common.kvstore.etcd_client import EtcdClient
40
41import adtran_olt.resources.adtranolt_platform as platform
42from adapters.adtran_common.flow.flow_entry import FlowEntry
43from omci.adtn_install_flow import AdtnInstallFlowTask
44from omci.adtn_remove_flow import AdtnRemoveFlowTask
45from omci.adtn_tp_service_specific_task import AdtnTpServiceSpecificTask
46from pyvoltha.common.tech_profile.tech_profile import DEFAULT_TECH_PROFILE_TABLE_ID
47
48_ = third_party
49_MAXIMUM_PORT = 17 # Only one PON and UNI port at this time
50_ONU_REBOOT_MIN = 90 # IBONT 602 takes about 3 minutes
51_ONU_REBOOT_RETRY = 10
52_STARTUP_RETRY_WAIT = 20
53
54
55class AdtranOnuHandler(object):
56 def __init__(self, adapter, device_id):
57 self.adapter = adapter
58 self.adapter_agent = adapter.adapter_agent
59 self.device_id = device_id
60 self.log = structlog.get_logger(device_id=device_id)
61 self.logical_device_id = None
62 self.proxy_address = None
63 self._enabled = False
64 self.pm_metrics = None
65 self.alarms = None
66
67 self._openomci = OMCI(self, adapter.omci_agent)
68 self._in_sync_subscription = None
69
70 self._pon_port_number = 1
71
72 self._unis = dict() # Port # -> UniPort
73 self._pon = PonPort.create(self, self._pon_port_number)
74 self._heartbeat = HeartBeat.create(self, device_id)
75 self._deferred = None
76
77 # Flow entries
78 self._flows = dict()
79
80 # OMCI resources # TODO: Some of these could be dynamically chosen
81 self.vlan_tcis_1 = 0x900
82 self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1
83 self.gal_enet_profile_entity_id = 0
84
85 # Technology profile related values
86 self.incoming_messages = DeferredQueue()
87 self.event_messages = DeferredQueue()
88 self._tp_service_specific_task = dict()
89 self._tech_profile_download_done = dict()
90
91 # Initialize KV store client
92 self.args = registry('main').get_args()
93 if self.args.backend == 'etcd':
94 host, port = self.args.etcd.split(':', 1)
95 self.kv_client = EtcdClient(host, port,
96 TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX)
97 elif self.args.backend == 'consul':
98 host, port = self.args.consul.split(':', 1)
99 self.kv_client = Consul(host, port,
100 TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX)
101 else:
102 self.log.error('Invalid-backend')
103 raise Exception("Invalid-backend-for-kv-store")
104
105 # Handle received ONU event messages
106 reactor.callLater(0, self.handle_onu_events)
107
108 def __str__(self):
109 return "AdtranOnuHandler: {}".format(self.device_id)
110
111 def _cancel_deferred(self):
112 d, self._deferred = self._deferred, None
113 try:
114 if d is not None and not d.called:
115 d.cancel()
116 except:
117 pass
118
119 @property
120 def enabled(self):
121 return self._enabled
122
123 @enabled.setter
124 def enabled(self, value):
125 assert isinstance(value, bool), 'enabled is a boolean'
126 if self._enabled != value:
127 self._enabled = value
128 if self._enabled:
129 self.start()
130 else:
131 self.stop()
132
133 @property
134 def openomci(self):
135 return self._openomci
136
137 @property
138 def heartbeat(self):
139 return self._heartbeat
140
141 @property
142 def uni_ports(self):
143 return self._unis.values()
144
145 def uni_port(self, port_no_or_name):
146 if isinstance(port_no_or_name, (str, unicode)):
147 return next((uni for uni in self.uni_ports
148 if uni.name == port_no_or_name), None)
149
150 assert isinstance(port_no_or_name, int), 'Invalid parameter type'
151 return self._unis.get(port_no_or_name)
152
153 def pon_port(self, port_no=None):
154 return self._pon if port_no is None or port_no == self._pon.port_number else None
155
156 @property
157 def pon_ports(self):
158 return [self._pon]
159
160 def start(self):
161 assert self._enabled, 'Start should only be called if enabled'
162 self._cancel_deferred()
163
164 # Register for adapter messages
165 self.adapter_agent.register_for_inter_adapter_messages()
166
167 # OpenOMCI Startup
168 self._subscribe_to_events()
169 self._openomci.enabled = True
170
171 # Port startup
172 if self._pon is not None:
173 self._pon.enabled = True
174
175 for port in self.uni_ports:
176 port.enabled = True
177
178 # Heartbeat
179 self._heartbeat.enabled = True
180
181 def stop(self):
182 assert not self._enabled, 'Stop should only be called if disabled'
183 self._cancel_deferred()
184
185 # Drop registration for adapter messages
186 self.adapter_agent.unregister_for_inter_adapter_messages()
187
188 # Heartbeat
189 self._heartbeat.enabled = False
190
191 # OMCI Communications
192 self._unsubscribe_to_events()
193
194 # Port shutdown
195 for port in self.uni_ports:
196 port.enabled = False
197
198 if self._pon is not None:
199 self._pon.enabled = False
200 self._openomci.enabled = False
201
202 def receive_message(self, msg):
203 if self.enabled:
204 # TODO: Have OpenOMCI actually receive the messages
205 self.openomci.receive_message(msg)
206
207 def activate(self, device):
208 self.log.info('activating')
209
210 try:
211 # first we verify that we got parent reference and proxy info
212 assert device.parent_id, 'Invalid Parent ID'
213 assert device.proxy_address.device_id, 'Invalid Device ID'
214
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500215 # Cache our proxy address
Chip Boling8e042f62019-02-12 16:14:34 -0600216 self.proxy_address = device.proxy_address
Chip Boling8e042f62019-02-12 16:14:34 -0600217
218 # initialize device info
219 device.root = False
220 device.vendor = 'Adtran Inc.'
221 device.model = 'n/a'
222 device.hardware_version = 'n/a'
223 device.firmware_version = 'n/a'
224 device.reason = ''
225 device.connect_status = ConnectStatus.UNKNOWN
226
227 # Register physical ports. Should have at least one of each
228 self.adapter_agent.add_port(device.id, self._pon.get_port())
229
230 def xpon_not_found():
231 self.enabled = True
232
233 # Schedule xPON 'not found' startup for 10 seconds from now. We will
234 # easily get a vONT-ANI create within that time if xPON is being used
235 # as this is how we are initially launched and activated in the first
236 # place if xPON is in use.
237 reactor.callLater(10, xpon_not_found) # TODO: Clean up old xPON delay
238
239 # reference of uni_port is required when re-enabling the device if
240 # it was disabled previously
241 # Need to query ONU for number of supported uni ports
242 # For now, temporarily set number of ports to 1 - port #2
243 parent_device = self.adapter_agent.get_device(device.parent_id)
244
245 self.logical_device_id = parent_device.parent_id
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500246 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600247
248 ############################################################################
249 # Setup PM configuration for this device
250 # Pass in ONU specific options
251 kwargs = {
252 OnuPmMetrics.DEFAULT_FREQUENCY_KEY: OnuPmMetrics.DEFAULT_ONU_COLLECTION_FREQUENCY,
253 'heartbeat': self.heartbeat,
254 OnuOmciPmMetrics.OMCI_DEV_KEY: self.openomci.onu_omci_device
255 }
256 self.pm_metrics = OnuPmMetrics(self.adapter_agent, self.device_id,
257 self.logical_device_id, grouped=True,
258 freq_override=False, **kwargs)
259 pm_config = self.pm_metrics.make_proto()
260 self.openomci.set_pm_config(self.pm_metrics.omci_pm.openomci_interval_pm)
261 self.log.info("initial-pm-config", pm_config=pm_config)
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500262 yield self.adapter_agent.device_pm_config_update(pm_config, init=True)
Chip Boling8e042f62019-02-12 16:14:34 -0600263
264 ############################################################################
265 # Setup Alarm handler
266 self.alarms = AdapterAlarms(self.adapter_agent, device.id, self.logical_device_id)
267 self.openomci.onu_omci_device.alarm_synchronizer.set_alarm_params(mgr=self.alarms,
268 ani_ports=[self._pon])
269 ############################################################################
270 # Start collecting stats from the device after a brief pause
271 reactor.callLater(30, self.pm_metrics.start_collector)
272
273 except Exception as e:
274 self.log.exception('activate-failure', e=e)
275 device.reason = 'Failed to activate: {}'.format(e.message)
276 device.connect_status = ConnectStatus.UNREACHABLE
277 device.oper_status = OperStatus.FAILED
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500278 self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600279
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500280 @inlineCallbacks
Chip Boling8e042f62019-02-12 16:14:34 -0600281 def reconcile(self, device):
282 self.log.info('reconciling-ONU-device-starts')
283
284 # first we verify that we got parent reference and proxy info
285 assert device.parent_id
286 assert device.proxy_address.device_id
287 # assert device.proxy_address.channel_id
288 self._cancel_deferred()
289
290 # register for proxied messages right away
291 self.proxy_address = device.proxy_address
292 self.adapter_agent.register_for_proxied_messages(device.proxy_address)
293
294 # Register for adapter messages
295 self.adapter_agent.register_for_inter_adapter_messages()
296
Chip Boling8e042f62019-02-12 16:14:34 -0600297 # TODO: Verify that the uni, pon and logical ports exists
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500298 self.enabled = True
Chip Boling8e042f62019-02-12 16:14:34 -0600299
300 # Mark the device as REACHABLE and ACTIVE
301 device = self.adapter_agent.get_device(device.id)
302 device.connect_status = ConnectStatus.REACHABLE
303 device.oper_status = OperStatus.ACTIVE
304 device.reason = ''
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500305 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600306
307 self.log.info('reconciling-ONU-device-ends')
308
309 @inlineCallbacks
310 def handle_onu_events(self):
311 # TODO: Add 'shutdown' message to exit loop
312 event_msg = yield self.event_messages.get()
313 try:
314 if event_msg['event'] == 'download_tech_profile':
315 tp_path = event_msg['event_data']
316 uni_id = event_msg['uni_id']
317 self.load_and_configure_tech_profile(uni_id, tp_path)
318
319 except Exception as e:
320 self.log.error("exception-handling-onu-event", e=e)
321
322 # Handle next event
323 reactor.callLater(0, self.handle_onu_events)
324
325 def _tp_path_to_tp_id(self, tp_path):
326 parts = tp_path.split('/')
327 if len(parts) > 2:
328 try:
329 return int(tp_path[1])
330 except ValueError:
331 return DEFAULT_TECH_PROFILE_TABLE_ID
332
333 def _create_tcont(self, uni_id, us_scheduler, tech_profile_id):
334 """
335 Decode Upstream Scheduler and create appropriate TCONT structures
336
337 :param uni_id: (int) UNI ID on the PON
338 :param us_scheduler: (Scheduler) Upstream Scheduler with TCONT information
339 :param tech_profile_id: (int) Tech Profile ID
340
341 :return (OnuTCont) Created TCONT
342 """
343 self.log.debug('create-tcont', us_scheduler=us_scheduler, profile_id=tech_profile_id)
344
345 q_sched_policy = {
346 'strictpriority': 1, # Per TCONT (ME #262) values
347 'wrr': 2
348 }.get(us_scheduler.get('q_sched_policy', 'none').lower(), 0)
349
350 tcont_data = {
351 'tech-profile-id': tech_profile_id,
352 'uni-id': uni_id,
353 'alloc-id': us_scheduler['alloc_id'],
354 'q-sched-policy': q_sched_policy
355 }
356 # TODO: Support TD if shaping on ONU is to be performed
357 td = OnuTrafficDescriptor(0, 0, 0)
358 tcont = OnuTCont.create(self, tcont_data, td)
359 self._pon.add_tcont(tcont)
360 return tcont
361
362 # Called when there is an olt up indication, providing the gem port id chosen by the olt handler
363 def _create_gemports(self, upstream_ports, downstream_ports, tcont, uni_id, tech_profile_id):
364 """
365 Create GEM Ports for a specifc tech profile
366
367 The routine will attempt to combine upstream and downstream GEM Ports into bidirectional
368 ports where possible
369
370 :param upstream_ports: (list of IGemPortAttribute) Upstream GEM Port attributes
371 :param downstream_ports: (list of IGemPortAttribute) Downstream GEM Port attributes
372 :param tcont: (OnuTCont) Associated TCONT
373 :param uni_id: (int) UNI Instance ID
374 :param tech_profile_id: (int) Tech Profile ID
375 """
376 self.log.debug('create-gemports', upstream=upstream_ports,
377 downstream_ports=downstream_ports,
378 tcont=tcont, tech_id=tech_profile_id)
379 # Convert GEM Port lists to dicts with GEM ID as they key
380 upstream = {gem['gemport_id']: gem for gem in upstream_ports}
381 downstream = {gem['gemport_id']: gem for gem in downstream_ports}
382
383 upstream_ids = set(upstream.keys())
384 downstream_ids = set(downstream.keys())
385 bidirectional_ids = upstream_ids & downstream_ids
386
387 gem_port_types = { # Keys are the 'direction' attribute value, value is list of GEM attributes
388 OnuGemPort.UPSTREAM: [upstream[gid] for gid in upstream_ids - bidirectional_ids],
389 OnuGemPort.DOWNSTREAM: [downstream[gid] for gid in downstream_ids - bidirectional_ids],
390 OnuGemPort.BIDIRECTIONAL: [upstream[gid] for gid in bidirectional_ids]
391 }
392 for direction, gem_list in gem_port_types.items():
393 for gem in gem_list:
394 gem_data = {
395 'gemport-id': gem['gemport_id'],
396 'direction': direction,
397 'encryption': gem['aes_encryption'].lower() == 'true',
398 'discard-policy': gem['discard_policy'],
399 'max-q-size': gem['max_q_size'],
400 'pbit-map': gem['pbit_map'],
401 'priority-q': gem['priority_q'],
402 'scheduling-policy': gem['scheduling_policy'],
403 'weight': gem['weight'],
404 'uni-id': uni_id,
405 'discard-config': {
406 'max-probability': gem['discard_config']['max_probability'],
407 'max-threshold': gem['discard_config']['max_threshold'],
408 'min-threshold': gem['discard_config']['min_threshold'],
409 },
410 }
411 gem_port = OnuGemPort.create(self, gem_data, tcont.alloc_id,
412 tech_profile_id, uni_id,
413 self._pon.next_gem_entity_id)
414 self._pon.add_gem_port(gem_port)
415
416 def _do_tech_profile_configuration(self, uni_id, tp, tech_profile_id):
417 us_scheduler = tp['us_scheduler']
418 tcont = self._create_tcont(uni_id, us_scheduler, tech_profile_id)
419
420 upstream = tp['upstream_gem_port_attribute_list']
421 downstream = tp['downstream_gem_port_attribute_list']
422 self._create_gemports(upstream, downstream, tcont, uni_id, tech_profile_id)
423
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500424 @inlineCallbacks
Chip Boling8e042f62019-02-12 16:14:34 -0600425 def load_and_configure_tech_profile(self, uni_id, tp_path):
426 self.log.debug("loading-tech-profile-configuration", uni_id=uni_id, tp_path=tp_path)
427
428 if uni_id not in self._tp_service_specific_task:
429 self._tp_service_specific_task[uni_id] = dict()
430
431 if uni_id not in self._tech_profile_download_done:
432 self._tech_profile_download_done[uni_id] = dict()
433
434 if tp_path not in self._tech_profile_download_done[uni_id]:
435 self._tech_profile_download_done[uni_id][tp_path] = False
436
437 if not self._tech_profile_download_done[uni_id][tp_path]:
438 try:
439 if tp_path in self._tp_service_specific_task[uni_id]:
440 self.log.info("tech-profile-config-already-in-progress",
441 tp_path=tp_path)
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500442 returnValue('already-in-progress')
Chip Boling8e042f62019-02-12 16:14:34 -0600443
444 tp = self.kv_client[tp_path]
445 tp = ast.literal_eval(tp)
446 self.log.debug("tp-instance", tp=tp)
447
448 tech_profile_id = self._tp_path_to_tp_id(tp_path)
449 self._do_tech_profile_configuration(uni_id, tp, tech_profile_id)
450
451 def success(_results):
452 self.log.info("tech-profile-config-done-successfully")
453 device = self.adapter_agent.get_device(self.device_id)
454 device.reason = ''
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500455 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600456
457 if tp_path in self._tp_service_specific_task[uni_id]:
458 del self._tp_service_specific_task[uni_id][tp_path]
459
460 self._tech_profile_download_done[uni_id][tp_path] = True
461
462 def failure(_reason):
463 self.log.warn('tech-profile-config-failure-retrying', reason=_reason)
464 device = self.adapter_agent.get_device(self.device_id)
465 device.reason = 'Tech Profile config failed-retrying'
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500466 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600467
468 if tp_path in self._tp_service_specific_task[uni_id]:
469 del self._tp_service_specific_task[uni_id][tp_path]
470
471 self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT,
472 self.load_and_configure_tech_profile,
473 uni_id, tp_path)
474
475 self.log.info('downloading-tech-profile-configuration')
476 tp_task = AdtnTpServiceSpecificTask(self.openomci.omci_agent, self, uni_id)
477
478 self._tp_service_specific_task[uni_id][tp_path] = tp_task
479 self._deferred = self.openomci.onu_omci_device.task_runner.queue_task(tp_task)
480 self._deferred.addCallbacks(success, failure)
481
482 except Exception as e:
483 self.log.exception("error-loading-tech-profile", e=e)
484 else:
485 self.log.info("tech-profile-config-already-done")
486
487 def update_pm_config(self, _device, pm_config):
488 # TODO: This has not been tested
489 self.log.info('update_pm_config', pm_config=pm_config)
490 self.pm_metrics.update(pm_config)
491
492 @inlineCallbacks
493 def update_flow_table(self, flows):
494 if len(flows) == 0:
495 returnValue('nop') # TODO: Do we need to delete all flows if empty?
496
497 self.log.debug('bulk-flow-update', flows=flows)
498 valid_flows = set()
499
500 for flow in flows:
501 # Decode it
502 flow_entry = FlowEntry.create(flow, self)
503
504 # Already handled?
505 if flow_entry.flow_id in self._flows:
506 valid_flows.add(flow_entry.flow_id)
507
508 if flow_entry is None or flow_entry.flow_direction not in \
509 FlowEntry.upstream_flow_types | FlowEntry.downstream_flow_types:
510 continue
511
512 is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types
513
514 # Ignore untagged upstream etherType flows. These are trapped at the
515 # OLT and the default flows during initial OMCI service download will
516 # send them to the Default VLAN (4091) port for us
517 if is_upstream and flow_entry.vlan_vid is None and flow_entry.etype is not None:
518 continue
519
520 # Also ignore upstream untagged/priority tag that sets priority tag
521 # since that is already installed and any user-data flows for upstream
522 # priority tag data will be at a higher level. Also should ignore the
523 # corresponding priority-tagged to priority-tagged flow as well.
524 if (flow_entry.vlan_vid == 0 and flow_entry.set_vlan_vid == 0) or \
525 (flow_entry.vlan_vid is None and flow_entry.set_vlan_vid == 0
526 and not is_upstream):
527 continue
528
529 # Add it to hardware
530 try:
531 def failed(_reason, fid):
532 del self._flows[fid]
533
534 task = AdtnInstallFlowTask(self.openomci.omci_agent, self, flow_entry)
535 d = self.openomci.onu_omci_device.task_runner.queue_task(task)
536 d.addErrback(failed, flow_entry.flow_id)
537
538 valid_flows.add(flow_entry.flow_id)
539 self._flows[flow_entry.flow_id] = flow_entry
540
541 except Exception as e:
542 self.log.exception('flow-add', e=e, flow=flow_entry)
543
544 # Now check for flows that were missing in the bulk update
545 deleted_flows = set(self._flows.keys()) - valid_flows
546
547 for flow_id in deleted_flows:
548 try:
549 del_flow = self._flows[flow_id]
550
551 task = AdtnRemoveFlowTask(self.openomci.omci_agent, self, del_flow)
552 self.openomci.onu_omci_device.task_runner.queue_task(task)
553 # TODO: Change to success/failure callback checks later
554 # d.addCallback(success, flow_entry.flow_id)
555 del self._flows[flow_id]
556
557 except Exception as e:
558 self.log.exception('flow-remove', e=e, flow=self._flows[flow_id])
559
560
561 def remove_from_flow_table(self, _flows):
562 """
563 Remove flows from the device
564
565 :param _flows: (list) Flows
566 """
567 raise NotImplementedError
568
569 def add_to_flow_table(self, _flows):
570 """
571 Remove flows from the device
572
573 :param _flows: (list) Flows
574 """
575 raise NotImplementedError
576
577 @inlineCallbacks
578 def reboot(self):
579 self.log.info('rebooting', device_id=self.device_id)
580 self._cancel_deferred()
581
582 reregister = False
583 try:
584 # Drop registration for adapter messages
585 reregister = True
586 self.adapter_agent.unregister_for_inter_adapter_messages()
587
588 except KeyError:
589 reregister = False
590
591 # Update the operational status to ACTIVATING and connect status to
592 # UNREACHABLE
593 device = self.adapter_agent.get_device(self.device_id)
594
595 previous_oper_status = device.oper_status
596 previous_conn_status = device.connect_status
597
598 device.oper_status = OperStatus.ACTIVATING
599 device.connect_status = ConnectStatus.UNREACHABLE
600 device.reason = 'Attempting reboot'
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500601 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600602
603 # TODO: send alert and clear alert after the reboot
604 try:
605 ######################################################
606 # MIB Reset
607 yield self.openomci.onu_omci_device.reboot(timeout=1)
608
609 except Exception as e:
610 self.log.exception('send-reboot', e=e)
611 raise
612
613 # Reboot in progress. A reboot may take up to 3 min 30 seconds
614 # Go ahead and pause less than that and start to look
615 # for it being alive
616 device.reason = 'reboot in progress'
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500617 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600618
619 # Disable OpenOMCI
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500620 self.openomci.enabled = False
Chip Boling8e042f62019-02-12 16:14:34 -0600621 self._deferred = reactor.callLater(_ONU_REBOOT_MIN,
622 self._finish_reboot,
623 previous_oper_status,
624 previous_conn_status,
625 reregister)
626
627 @inlineCallbacks
628 def _finish_reboot(self, previous_oper_status, previous_conn_status,
629 reregister):
630 # Restart OpenOMCI
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500631 self.openomci.enabled = True
Chip Boling8e042f62019-02-12 16:14:34 -0600632
633 device = self.adapter_agent.get_device(self.device_id)
634 device.oper_status = previous_oper_status
635 device.connect_status = previous_conn_status
636 device.reason = ''
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500637 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600638
639 if reregister:
640 self.adapter_agent.register_for_inter_adapter_messages()
641
642 self.log.info('reboot-complete', device_id=self.device_id)
643
644 def self_test_device(self, device):
645 """
646 This is called to Self a device based on a NBI call.
647 :param device: A Voltha.Device object.
648 :return: Will return result of self test
649 """
650 from pyvoltha.protos.voltha_pb2 import SelfTestResponse
651 self.log.info('self-test-device', device=device.id)
652 # TODO: Support self test?
653 return SelfTestResponse(result=SelfTestResponse.NOT_SUPPORTED)
654
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500655 @inlineCallbacks
Chip Boling8e042f62019-02-12 16:14:34 -0600656 def disable(self):
657 self.log.info('disabling', device_id=self.device_id)
658 try:
659 # Get the latest device reference
660 device = self.adapter_agent.get_device(self.device_id)
661
662 # Disable all ports on that device
663 self.adapter_agent.disable_all_ports(self.device_id)
664
665 # Update the device operational status to UNKNOWN
666 device.oper_status = OperStatus.UNKNOWN
667 device.connect_status = ConnectStatus.UNREACHABLE
668 device.reason = 'Disabled'
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500669 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600670
671 # Remove the uni logical port from the OLT, if still present
672 parent_device = self.adapter_agent.get_device(device.parent_id)
673 assert parent_device
674
675 for uni in self.uni_ports:
676 # port_id = 'uni-{}'.format(uni.port_number)
677 port_id = uni.port_id_name()
678 try:
679 logical_device_id = parent_device.parent_id
680 assert logical_device_id
681 port = self.adapter_agent.get_logical_port(logical_device_id,port_id)
682 self.adapter_agent.delete_logical_port(logical_device_id, port)
683 except KeyError:
684 self.log.info('logical-port-not-found', device_id=self.device_id,
685 portid=port_id)
686
687 # Remove pon port from parent and disable
688 if self._pon is not None:
689 self.adapter_agent.delete_port_reference_from_parent(self.device_id,
690 self._pon.get_port())
691 self._pon.enabled = False
692
693 # Unregister for proxied message
694 self.adapter_agent.unregister_for_proxied_messages(device.proxy_address)
695
696 except Exception as _e:
697 pass # This is expected if OLT has deleted the ONU device handler
698
699 # And disable OMCI as well
700 self.enabled = False
701 self.log.info('disabled')
702
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500703 @inlineCallbacks
Chip Boling8e042f62019-02-12 16:14:34 -0600704 def reenable(self):
705 self.log.info('re-enabling', device_id=self.device_id)
706 try:
707 # Get the latest device reference
708 device = self.adapter_agent.get_device(self.device_id)
709 self._cancel_deferred()
710
711 # First we verify that we got parent reference and proxy info
712 assert device.parent_id
713 assert device.proxy_address.device_id
714 # assert device.proxy_address.channel_id
715
716 # Re-register for proxied messages right away
717 self.proxy_address = device.proxy_address
718 self.adapter_agent.register_for_proxied_messages(
719 device.proxy_address)
720
721 # Re-enable the ports on that device
722 self.adapter_agent.enable_all_ports(self.device_id)
723
724 # Add the pon port reference to the parent
725 if self._pon is not None:
726 self._pon.enabled = True
727 self.adapter_agent.add_port_reference_to_parent(device.id,
728 self._pon.get_port())
729 # Update the connect status to REACHABLE
730 device.connect_status = ConnectStatus.REACHABLE
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500731 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600732
733 # re-add uni port to logical device
734 parent_device = self.adapter_agent.get_device(device.parent_id)
735 self.logical_device_id = parent_device.parent_id
736 assert self.logical_device_id, 'Invalid logical device ID'
737
738 # reestablish logical ports for each UNI
739 multi_uni = len(self.uni_ports) > 1
740 for uni in self.uni_ports:
741 self.adapter_agent.add_port(device.id, uni.get_port())
742 uni.add_logical_port(uni.logical_port_number, multi_uni)
743
744 device = self.adapter_agent.get_device(device.id)
745 device.oper_status = OperStatus.ACTIVE
746 device.connect_status = ConnectStatus.REACHABLE
747 device.reason = ''
748
749 self.enabled = True
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500750 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600751
752 self.log.info('re-enabled')
753
754 except Exception, e:
755 self.log.exception('error-re-enabling', e=e)
756
757 def delete(self):
758 self.log.info('deleting', device_id=self.device_id)
759
760 try:
761 for uni in self._unis.values():
762 uni.stop()
763 uni.delete()
764
765 self._pon.stop()
766 self._pon.delete()
767
768 except Exception as _e:
769 pass # Expected if the OLT deleted us from the device handler
770
771 # OpenOMCI cleanup
772 omci, self._openomci = self._openomci, None
773 omci.delete()
774
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500775 @inlineCallbacks
Chip Boling8e042f62019-02-12 16:14:34 -0600776 def add_uni_ports(self):
777 """ Called after in-sync achieved and not in xPON mode"""
778 # TODO: We have to methods adding UNI ports. Go to one
779 # TODO: Should this be moved to the omci.py module for this ONU?
780
781 # This is only for working WITHOUT xPON
782 pptp_entities = self.openomci.onu_omci_device.configuration.pptp_entities
783 device = self.adapter_agent.get_device(self.device_id)
784
785 multi_uni = len(pptp_entities) > 1
786 uni_id = 0
787
788 for entity_id, pptp in pptp_entities.items():
789 intf_id = self.proxy_address.channel_id
790 onu_id = self.proxy_address.onu_id
791 uni_no = platform.mk_uni_port_num(intf_id, onu_id, uni_id=uni_id)
792 uni_name = "uni-{}".format(uni_no)
793 mac_bridge_port_num = uni_id + 1
794
795 uni_port = UniPort.create(self, uni_name, uni_no, uni_name)
796 uni_port.entity_id = entity_id
797 uni_port.enabled = True
798 uni_port.mac_bridge_port_num = mac_bridge_port_num
799 uni_port.add_logical_port(uni_port.port_number, multi_uni)
800 self.log.debug("created-uni-port", uni=uni_port)
801
802 self.adapter_agent.add_port(device.id, uni_port.get_port())
803 parent_device = self.adapter_agent.get_device(device.parent_id)
804
805 parent_adapter_agent = registry('adapter_loader').get_agent(parent_device.adapter)
806 if parent_adapter_agent is None:
807 self.log.error('olt-adapter-agent-could-not-be-retrieved')
808
809 parent_adapter_agent.add_port(device.parent_id, uni_port.get_port())
810
811 self._unis[uni_port.port_number] = uni_port
812 self.openomci.onu_omci_device.alarm_synchronizer.set_alarm_params(onu_id=self.proxy_address.onu_id,
813 uni_ports=self._unis.values())
814 # TODO: this should be in the PonPort class
815 pon_port = self._pon.get_port()
816 self.adapter_agent.delete_port_reference_from_parent(self.device_id,
817 pon_port)
818 # Find index where this ONU peer is (should almost always be zero)
819 d = [i for i, e in enumerate(pon_port.peers) if
820 e.port_no == intf_id and e.device_id == device.parent_id]
821
822 if len(d) > 0:
823 pon_port.peers[d[0]].port_no = uni_port.port_number
824 self.adapter_agent.add_port_reference_to_parent(self.device_id,
825 pon_port)
Chip Bolingd2d7a4d2019-03-14 14:34:56 -0500826 yield self.adapter_agent.device_update(device)
Chip Boling8e042f62019-02-12 16:14:34 -0600827 uni_port.enabled = True
828 uni_id += 1
829
830 def rx_inter_adapter_message(self, msg):
831 raise NotImplemented('Not currently supported')
832
833 def _subscribe_to_events(self):
834 from pyvoltha.adapters.extensions.omci.onu_device_entry import OnuDeviceEvents, \
835 OnuDeviceEntry
836
837 # OMCI MIB Database sync status
838 bus = self.openomci.onu_omci_device.event_bus
839 topic = OnuDeviceEntry.event_bus_topic(self.device_id,
840 OnuDeviceEvents.MibDatabaseSyncEvent)
841 self._in_sync_subscription = bus.subscribe(topic, self.in_sync_handler)
842
843 def _unsubscribe_to_events(self):
844 insync, self._in_sync_subscription = self._in_sync_subscription, None
845
846 if insync is not None:
847 bus = self.openomci.onu_omci_device.event_bus
848 bus.unsubscribe(insync)
849
850 def in_sync_handler(self, _topic, msg):
851 # Create UNI Ports on first In-Sync event
852 if self._in_sync_subscription is not None:
853 try:
854 from pyvoltha.adapters.extensions.omci.onu_device_entry import IN_SYNC_KEY
855
856 if msg[IN_SYNC_KEY]:
857 # Do not proceed if we have not got our vENET information yet.
858
859 if len(self.uni_ports) == 0:
860 # Drop subscription....
861 insync, self._in_sync_subscription = self._in_sync_subscription, None
862
863 if insync is not None:
864 bus = self.openomci.onu_omci_device.event_bus
865 bus.unsubscribe(insync)
866
867 # Set up UNI Ports. The UNI ports are currently created when the xPON
868 # vENET information is created. Once xPON is removed, we need to create
869 # them from the information provided from the MIB upload UNI-G and other
870 # UNI related MEs.
871 self.add_uni_ports()
872
873 except Exception as e:
874 self.log.exception('in-sync', e=e)