blob: 70ec5649a171a7b244d79cd6af6d2343359586e2 [file] [log] [blame]
Chip Bolingf5af85d2019-02-12 15:36:17 -06001# Copyright 2017-present Adtran, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import json
16import random
17import arrow
18
19import structlog
20from adapters.adtran_common.port import AdtnPort
21from twisted.internet import reactor, defer
22from twisted.internet.defer import inlineCallbacks, returnValue
23from adtran_olt_handler import AdtranOltHandler
24from adapters.adtran_common.net.adtran_rest import RestInvalidResponseCode
25from codec.olt_config import OltConfig
26from onu import Onu
27from pyvoltha.adapters.extensions.alarms.onu.onu_los_alarm import OnuLosAlarm
28from pyvoltha.adapters.extensions.alarms.onu.onu_discovery_alarm import OnuDiscoveryAlarm
29from pyvoltha.protos.common_pb2 import AdminState
30from pyvoltha.protos.device_pb2 import Port
31import resources.adtranolt_platform as platform
32
33
34class PonPort(AdtnPort):
35 """
36 GPON Port
37 """
38 MAX_ONUS_SUPPORTED = 128
39 MAX_DEPLOYMENT_RANGE = 25000 # Meters (OLT-PB maximum)
40
41 _MCAST_ONU_ID = 253
42 _MCAST_ALLOC_BASE = 0x500
43
44 # AutoActivate should be used if xPON configuration is not supported
45 _SUPPORTED_ACTIVATION_METHODS = ['autodiscovery', 'autoactivate']
46 _SUPPORTED_AUTHENTICATION_METHODS = ['serial-number']
47
48 def __init__(self, parent, **kwargs):
49 super(PonPort, self).__init__(parent, **kwargs)
50 assert 'pon-id' in kwargs, 'PON ID not found'
51
52 self._parent = parent
53 self._pon_id = kwargs['pon-id']
54 self.log = structlog.get_logger(device_id=parent.device_id, pon_id=self._pon_id)
55 self._port_no = kwargs['port_no']
56 self._physical_port_name = 'xpon 0/{}'.format(self._pon_id+1)
57 self._label = 'pon-{}'.format(self._pon_id)
58
59 self._in_sync = False
60 self._expedite_sync = False
61 self._expedite_count = 0
62
63 self._discovery_tick = 20.0
64 self._no_onu_discover_tick = self._discovery_tick / 2
65 self._discovered_onus = [] # List of serial numbers
66 self._discovery_deferred = None # Specifically for ONU discovery
67
68 self._onus = {} # serial_number-base64 -> ONU
69 self._onu_by_id = {} # onu-id -> ONU
70 self._mcast_gem_ports = {} # VLAN -> GemPort
71
72 self._active_los_alarms = set() # ONU-ID
73
74 # xPON configuration
75 self._activation_method = 'autoactivate'
76
77 self._downstream_fec_enable = True
78 self._upstream_fec_enable = True
79 self._deployment_range = 25000
80 self._authentication_method = 'serial-number'
81 self._mcast_aes = False
82
83 # Statistics
84 self.tx_bip_errors = 0
85
86 def __str__(self):
87 return "PonPort-{}: Admin: {}, Oper: {}, OLT: {}".format(self._label,
88 self._admin_state,
89 self._oper_status,
90 self.olt)
91
92 def get_port(self):
93 """
94 Get the VOLTHA PORT object for this port
95 :return: VOLTHA Port object
96 """
97 if self._port is None:
98 self._port = Port(port_no=self._port_no,
99 label=self._label,
100 type=Port.PON_OLT,
101 admin_state=self._admin_state,
102 oper_status=self._oper_status)
103
104 return self._port
105
106 @property
107 def pon_id(self):
108 return self._pon_id
109
110 @property
111 def onus(self):
112 """
113 Get a set of all ONUs. While the set is immutable, do not use this method
114 to get a collection that you will iterate through that my yield the CPU
115 such as inline callback. ONUs may be deleted at any time and they will
116 set some references to other objects to NULL during the 'delete' call.
117 Instead, get a list of ONU-IDs and iterate on these and call the 'onu'
118 method below (which will return 'None' if the ONU has been deleted.
119
120 :return: (frozenset) collection of ONU objects on this PON
121 """
122 return frozenset(self._onus.values())
123
124 @property
125 def onu_ids(self):
126 return frozenset(self._onu_by_id.keys())
127
128 def onu(self, onu_id):
129 return self._onu_by_id.get(onu_id)
130
131 @property
132 def in_service_onus(self):
133 return len({onu.onu_id for onu in self.onus
134 if onu.onu_id not in self._active_los_alarms})
135
136 @property
137 def closest_onu_distance(self):
138 distance = -1
139 for onu in self.onus:
140 if onu.fiber_length < distance or distance == -1:
141 distance = onu.fiber_length
142 return distance
143
144 @property
145 def downstream_fec_enable(self):
146 return self._downstream_fec_enable
147
148 @downstream_fec_enable.setter
149 def downstream_fec_enable(self, value):
150 assert isinstance(value, bool), 'downstream FEC enabled is a boolean'
151
152 if self._downstream_fec_enable != value:
153 self._downstream_fec_enable = value
154 if self.state == AdtnPort.State.RUNNING:
155 self.deferred = self._set_pon_config("downstream-fec-enable", value)
156
157 @property
158 def upstream_fec_enable(self):
159 return self._upstream_fec_enable
160
161 @upstream_fec_enable.setter
162 def upstream_fec_enable(self, value):
163 assert isinstance(value, bool), 'upstream FEC enabled is a boolean'
164 if self._upstream_fec_enable != value:
165 self._upstream_fec_enable = value
166 if self.state == AdtnPort.State.RUNNING:
167 self.deferred = self._set_pon_config("upstream-fec-enable", value)
168
169 @property
170 def any_upstream_fec_enabled(self):
171 for onu in self.onus:
172 if onu.upstream_fec_enable and onu.enabled:
173 return True
174 return False
175
176 @property
177 def mcast_aes(self):
178 return self._mcast_aes
179
180 @mcast_aes.setter
181 def mcast_aes(self, value):
182 assert isinstance(value, bool), 'MCAST AES is a boolean'
183 if self._mcast_aes != value:
184 self._mcast_aes = value
185 if self.state == AdtnPort.State.RUNNING:
186 pass # TODO
187
188 @property
189 def deployment_range(self):
190 """Maximum deployment range (in meters)"""
191 return self._deployment_range
192
193 @deployment_range.setter
194 def deployment_range(self, value):
195 """Maximum deployment range (in meters)"""
196 if not 0 <= value <= PonPort.MAX_DEPLOYMENT_RANGE:
197 raise ValueError('Deployment range should be 0..{} meters'.
198 format(PonPort.MAX_DEPLOYMENT_RANGE))
199 if self._deployment_range != value:
200 self._deployment_range = value
201 if self.state == AdtnPort.State.RUNNING:
202 self.deferred = self._set_pon_config("deployment-range", value)
203
204 @property
205 def discovery_tick(self):
206 return self._discovery_tick * 10
207
208 @discovery_tick.setter
209 def discovery_tick(self, value):
210 if value < 0:
211 raise ValueError("Polling interval must be >= 0")
212
213 if self.discovery_tick != value:
214 self._discovery_tick = value / 10
215
216 try:
217 if self._discovery_deferred is not None and \
218 not self._discovery_deferred.called:
219 self._discovery_deferred.cancel()
220 except:
221 pass
222 self._discovery_deferred = None
223
224 if self._discovery_tick > 0:
225 self._discovery_deferred = reactor.callLater(self._discovery_tick,
226 self._discover_onus)
227
228 @property
229 def activation_method(self):
230 return self._activation_method
231
232 @activation_method.setter
233 def activation_method(self, value):
234 value = value.lower()
235 if value not in PonPort._SUPPORTED_ACTIVATION_METHODS:
236 raise ValueError('Invalid ONU activation method')
237
238 self._activation_method = value
239
240 @property
241 def authentication_method(self):
242 return self._authentication_method
243
244 @authentication_method.setter
245 def authentication_method(self, value):
246 value = value.lower()
247 if value not in PonPort._SUPPORTED_AUTHENTICATION_METHODS:
248 raise ValueError('Invalid ONU authentication method')
249 self._authentication_method = value
250
251 def cancel_deferred(self):
252 super(PonPort, self).cancel_deferred()
253
254 d, self._discovery_deferred = self._discovery_deferred, None
255
256 try:
257 if d is not None and not d.called:
258 d.cancel()
259 except Exception as e:
260 pass
261
262 def _update_adapter_agent(self):
263 """
264 Update the port status and state in the core
265 """
266 self.log.debug('update-adapter-agent', admin_state=self._admin_state,
267 oper_status=self._oper_status)
268
269 # because the core does not provide methods for updating admin
270 # and oper status per port, we need to copy any existing port
271 # info so that we don't wipe out the peers
272 if self._port is not None:
273 agent_ports = self.adapter_agent.get_ports(self.olt.device_id, Port.PON_OLT)
274
275 agent_port = next((ap for ap in agent_ports if ap.port_no == self._port_no), None)
276
277 # copy current Port info
278 if agent_port is not None:
279 self._port = agent_port
280
281 # set new states
282 self._port.admin_state = self._admin_state
283 self._port.oper_status = self._oper_status
284
285 # adapter_agent add_port also does an update of existing port
286 self.adapter_agent.add_port(self.olt.device_id, self.get_port())
287
288 @inlineCallbacks
289 def finish_startup(self):
290 """
291 Do all startup offline since REST may fail
292 """
293 if self.state != AdtnPort.State.INITIAL:
294 returnValue('Done')
295
296 self.log.debug('final-startup')
297 results = None
298
299 try:
300 self.deferred = self._get_pon_config()
301 results = yield self.deferred
302
303 except Exception as e:
304 self.log.exception('initial-GET', e=e)
305 self.deferred = reactor.callLater(5, self.finish_startup)
306 returnValue(self.deferred)
307
308 # Load config from hardware
309
310 enabled = results.get('enabled', False)
311 downstream_fec_enable = results.get('downstream-fec-enable', False)
312 upstream_fec_enable = results.get('upstream-fec-enable', False)
313 deployment_range = results.get('deployment-range', 25000)
314 self._in_sync = True
315
316 if enabled != self._enabled:
317 try:
318 self.deferred = self._set_pon_config("enabled", True)
319 yield self.deferred
320
321 except Exception as e:
322 self.log.exception('final-startup-enable', e=e)
323 self.deferred = reactor.callLater(3, self.finish_startup)
324 returnValue(self.deferred)
325
326 if downstream_fec_enable != self._downstream_fec_enable:
327 try:
328 self.deferred = self._set_pon_config("downstream-fec-enable",
329 self._downstream_fec_enable)
330 yield self.deferred
331
332 except Exception as e:
333 self.log.warning('final-startup-downstream-FEC', e=e)
334 self._in_sync = False
335 # Non-fatal. May have failed due to no SFQ in slot
336
337 if upstream_fec_enable != self._upstream_fec_enable:
338 try:
339 self.deferred = self._set_pon_config("upstream-fec-enable",
340 self._upstream_fec_enable)
341 yield self.deferred
342
343 except Exception as e:
344 self.log.warning('final-startup-upstream-FEC', e=e)
345 self._in_sync = False
346 # Non-fatal. May have failed due to no SFQ in slot
347
348 if deployment_range != self._deployment_range:
349 try:
350 self.deferred = self._set_pon_config("deployment-range",
351 self._deployment_range)
352 yield self.deferred
353
354 except Exception as e:
355 self.log.warning('final-startup-deployment-range', e=e)
356 self._in_sync = False
357 # Non-fatal. May have failed due to no SFQ in slot
358
359 if len(self._onus) > 0:
360 dl = []
361 for onu_id in self.onu_ids:
362 onu = self.onu(onu_id)
363 if onu is not None:
364 dl.append(onu.restart())
365 yield defer.gatherResults(dl, consumeErrors=True)
366
367 # Begin to ONU discovery and hardware sync
368
369 self._discovery_deferred = reactor.callLater(5, self._discover_onus)
370
371 # If here, initial settings were successfully written to hardware
372
373 super(PonPort, self).finish_startup()
374 returnValue('Enabled')
375
376 @inlineCallbacks
377 def finish_stop(self):
378 # Remove all existing ONUs. They will need to be re-discovered
379 dl = []
380 onu_ids = frozenset(self._onu_by_id.keys())
381 for onu_id in onu_ids:
382 try:
383 dl.append(self.delete_onu(onu_id))
384
385 except Exception as e:
386 self.log.exception('onu-cleanup', onu_id=onu_id, e=e)
387
388 dl.append(self._set_pon_config("enabled", False))
389 results = yield defer.gatherResults(dl, consumeErrors=True)
390 returnValue(results)
391
392 @inlineCallbacks
393 def reset(self):
394 """
395 Set the PON Port to a known good state on initial port startup. Actual
396 PON 'Start' is done elsewhere
397 """
398 initial_port_state = AdminState.ENABLED
399 self.log.info('reset', initial_state=initial_port_state)
400
401 try:
402 self.deferred = self._get_pon_config()
403 results = yield self.deferred
404 enabled = results.get('enabled', False)
405
406 except Exception as e:
407 self.log.exception('get-config', e=e)
408 enabled = False
409
410 enable = initial_port_state == AdminState.ENABLED
411
412 if enable != enabled:
413 try:
414 self.deferred = yield self._set_pon_config("enabled", enable)
415 except Exception as e:
416 self.log.exception('reset-enabled', e=e, enabled=enabled)
417
418 # TODO: Move to 'set_pon_config' method and also make sure GRPC/Port is ok
419 self._admin_state = AdminState.ENABLED if enable else AdminState.DISABLED
420
421 try:
422 # Walk the provisioned ONU list and disable any existing ONUs
423 results = yield self._get_onu_config()
424
425 if isinstance(results, list) and len(results) > 0:
426 onu_configs = OltConfig.Pon.Onu.decode(results)
427 dl = []
428 for onu_id in onu_configs.iterkeys():
429 dl.append(self.delete_onu(onu_id))
430
431 try:
432 if len(dl) > 0:
433 yield defer.gatherResults(dl, consumeErrors=True)
434
435 except Exception as e:
436 self.log.exception('rest-ONU-delete', e=e)
437 pass # Non-fatal
438
439 except Exception as e:
440 self.log.exception('onu-delete', e=e)
441
442 returnValue('Reset complete')
443
444 def gem_ids(self, logical_port, flow_vlan, multicast_gems=False):
445 """
446 Get all GEM Port IDs used on a given PON
447
448 :param logical_port: (int) Logical port number of ONU. None if for all ONUs
449 on PON, if Multicast, VID for Multicast, or None for all
450 Multicast GEMPorts
451 :param flow_vlan: (int) If not None, this is the ingress tag (c-tag)
452 :param multicast_gems: (boolean) Select from available Multicast GEM Ports
453 :return: (dict) data_gem -> key -> onu-id, value -> tuple(sorted list of GEM Port IDs, onu_vid)
454 mcast_gem-> key -> mcast-vid, value -> GEM Port IDs
455 """
456 gem_ids = {}
457
458 if multicast_gems:
459 # Multicast GEMs belong to the PON, but we may need to register them on
460 # all ONUs. TODO: Rework when BBF MCAST is addressed in VOLTHA v2.O+
461 for vlan, gem_port in self._mcast_gem_ports.iteritems():
462 if logical_port is None or (logical_port == vlan and logical_port in self.olt.multicast_vlans):
463 gem_ids[vlan] = ([gem_port.gem_id], None)
464 else:
465 raise NotImplemented('TODO: This is deprecated')
466 # for onu_id, onu in self._onu_by_id.iteritems():
467 # if logical_port is None or logical_port == onu.logical_port:
468 # gem_ids[onu_id] = (onu.gem_ids(), flow_vlan)
469 return gem_ids
470
471 def _get_pon_config(self):
472 uri = AdtranOltHandler.GPON_PON_CONFIG_URI.format(self._pon_id)
473 name = 'pon-get-config-{}'.format(self._pon_id)
474 return self._parent.rest_client.request('GET', uri, name=name)
475
476 def _get_onu_config(self, onu_id=None):
477 if onu_id is None:
478 uri = AdtranOltHandler.GPON_ONU_CONFIG_LIST_URI.format(self._pon_id)
479 else:
480 uri = AdtranOltHandler.GPON_ONU_CONFIG_URI.format(self._pon_id, onu_id)
481
482 name = 'pon-get-onu_config-{}-{}'.format(self._pon_id, onu_id)
483 return self._parent.rest_client.request('GET', uri, name=name)
484
485 def _set_pon_config(self, leaf, value):
486 data = json.dumps({leaf: value})
487 uri = AdtranOltHandler.GPON_PON_CONFIG_URI.format(self._pon_id)
488 name = 'pon-set-config-{}-{}-{}'.format(self._pon_id, leaf, str(value))
489 # If no optics on PON, then PON config fails with status 400, suppress this
490 suppress_error = len(self.onu_ids) == 0
491 return self._parent.rest_client.request('PATCH', uri, data=data, name=name,
492 suppress_error=suppress_error)
493
494 def _discover_onus(self):
495 self.log.debug('discovery', state=self._admin_state, in_sync=self._in_sync)
496 if self._admin_state == AdminState.ENABLED:
497 if self._in_sync:
498 data = json.dumps({'pon-id': self._pon_id})
499 uri = AdtranOltHandler.GPON_PON_DISCOVER_ONU
500 name = 'pon-discover-onu-{}'.format(self._pon_id)
501
502 self._discovery_deferred = self._parent.rest_client.request('POST', uri, data, name=name)
503 self._discovery_deferred.addBoth(self._onu_discovery_init_complete)
504 else:
505 self.discovery_deferred = reactor.callLater(0,
506 self._onu_discovery_init_complete,
507 None)
508
509 def _onu_discovery_init_complete(self, _result):
510 """
511 This method is called after the REST POST to request ONU discovery is
512 completed. The results (body) of the post is always empty / 204 NO CONTENT
513 """
514 delay = self._no_onu_discover_tick if len(self._onus) == 0 else self._discovery_tick
515 delay += random.uniform(-delay / 10, delay / 10)
516 self._discovery_deferred = reactor.callLater(delay, self._discover_onus)
517
518 def sync_hardware(self):
519 if self.state == AdtnPort.State.RUNNING or self.state == AdtnPort.State.STOPPED:
520 def read_config(results):
521 self.log.debug('read-config', results=results)
522 config = OltConfig.Pon.decode([results])
523 assert self.pon_id in config, 'sync-pon-not-found-{}'.format(self.pon_id)
524 config = config[self.pon_id]
525 self._in_sync = True
526
527 dl = []
528
529 if self.enabled != config.enabled:
530 self._in_sync = False
531 self._expedite_sync = True
532 dl.append(self._set_pon_config("enabled", self.enabled))
533
534 elif self.state == AdtnPort.State.RUNNING:
535 if self.deployment_range != config.deployment_range:
536 self._in_sync = False
537 self._expedite_sync = True
538 dl.append(self._set_pon_config("deployment-range",
539 self.deployment_range))
540
541 # A little side note: FEC enable/disable cannot be changed and
542 # will remain in the previous status until an optical module
543 # is plugged in.
544 if self.downstream_fec_enable != config.downstream_fec_enable:
545 self._in_sync = False
546 dl.append(self._set_pon_config("downstream-fec-enable",
547 self.downstream_fec_enable))
548
549 if self.upstream_fec_enable != config.upstream_fec_enable:
550 self._in_sync = False
551 self._expedite_sync = True
552 dl.append(self._set_pon_config("upstream-fec-enable",
553 self.upstream_fec_enable))
554 defer.gatherResults(dl, consumeErrors=True)
555 return config.onus
556
557 def sync_onus(hw_onus):
558 if self.state == AdtnPort.State.RUNNING:
559 self.log.debug('sync-pon-onu-results', config=hw_onus)
560
561 # ONU's have their own sync task, extra (should be deleted) are
562 # handled here.
563 hw_onu_ids = frozenset(hw_onus.keys())
564 my_onu_ids = frozenset(self._onu_by_id.keys())
565
566 extra_onus = hw_onu_ids - my_onu_ids
567 dl = [self.delete_onu(onu_id, hw_only=True) for onu_id in extra_onus]
568
569 if self.activation_method == "autoactivate":
570 # Autoactivation of ONUs requires missing ONU detection. If
571 # not found, create them here but let the TCont/GEM-Port restore
572 # be handle by ONU H/w sync logic.
573 for onu in [self._onu_by_id[onu_id] for onu_id in my_onu_ids - hw_onu_ids
574 if self._onu_by_id.get(onu_id) is not None]:
575 dl.append(onu.create(reflow=True))
576
577 return defer.gatherResults(dl, consumeErrors=True)
578
579 def failure(reason, what):
580 self.log.error('hardware-sync-{}-failed'.format(what), reason=reason)
581 self._in_sync = False
582 self._expedite_sync = False
583
584 def reschedule(_):
585 # Speed up sequential resync a limited number of times if out of sync.
586
587 delay = self.sync_tick
588
589 if self._expedite_sync:
590 self._expedite_count += 1
591 if self._expedite_count < 5:
592 delay = 1
593 else:
594 self._expedite_count = 0
595
596 delay += random.uniform(-delay / 10, delay / 10)
597 self.sync_deferred = reactor.callLater(delay, self.sync_hardware)
598
599 self.sync_deferred = self._get_pon_config()
600 self.sync_deferred.addCallbacks(read_config, failure, errbackArgs=['get-config'])
601 self.sync_deferred.addCallbacks(sync_onus, failure, errbackArgs=['pon-sync'])
602 self.sync_deferred.addBoth(reschedule)
603
604 def process_status_poll(self, status):
605 """
606 Process PON status poll request
607
608 :param status: (OltState.Pon object) results from RESTCONF GET
609 """
610 self.log.debug('process-status-poll', status=status)
611
612 if self._admin_state != AdminState.ENABLED:
613 return
614
615 # Process LOS list
616 self._process_los_alarms(frozenset(status.ont_los))
617
618 # Get new/missing from the discovered ONU leaf. Stale ONUs from previous
619 # configs are now cleaned up during h/w re-sync/reflow.
620 new, rediscovered_onus = self._process_status_onu_discovered_list(status.discovered_onu)
621
622 # Process newly discovered ONU list and rediscovered ONUs
623 for serial_number in new | rediscovered_onus:
624 reactor.callLater(0, self.add_onu, serial_number, status)
625
626 # PON Statistics
627 timestamp = arrow.utcnow().float_timestamp
628 self._process_statistics(status, timestamp)
629
630 # Process ONU info. Note that newly added ONUs will not be processed
631 # until the next pass
632 self._update_onu_status(status.onus, timestamp)
633
634 # Process GEM Port information
635 self._update_gem_status(status.gems, timestamp)
636
637 def _process_statistics(self, status, timestamp):
638 self.timestamp = timestamp
639 self.rx_packets = status.rx_packets
640 self.rx_bytes = status.rx_bytes
641 self.tx_packets = status.tx_packets
642 self.tx_bytes = status.tx_bytes
643 self.tx_bip_errors = status.tx_bip_errors
644
645 def _update_onu_status(self, onus, timestamp):
646 """
647 Process ONU status for this PON
648 :param onus: (dict) onu_id: ONU State
649 """
650 for onu_id, onu_status in onus.iteritems():
651 if onu_id in self._onu_by_id:
652 onu = self._onu_by_id[onu_id]
653 onu.timestamp = timestamp
654 onu.rssi = onu_status.rssi
655 onu.equalization_delay = onu_status.equalization_delay
656 onu.equalization_delay = onu_status.equalization_delay
657 onu.fiber_length = onu_status.fiber_length
658 onu.password = onu_status.reported_password
659
660 def _update_gem_status(self, gems, timestamp):
661 for gem_id, gem_status in gems.iteritems():
662 onu = self._onu_by_id.get(gem_status.onu_id)
663 if onu is not None:
664 gem_port = onu.gem_port(gem_status.gem_id)
665 if gem_port is not None:
666 gem_port.timestamp = timestamp
667 gem_port.rx_packets = gem_status.rx_packets
668 gem_port.rx_bytes = gem_status.rx_bytes
669 gem_port.tx_packets = gem_status.tx_packets
670 gem_port.tx_bytes = gem_status.tx_bytes
671
672 def _process_los_alarms(self, ont_los):
673 """
674 Walk current LOS and set/clear LOS as appropriate
675 :param ont_los: (frozenset) ONU IDs of ONUs in LOS alarm state
676 """
677 cleared_alarms = self._active_los_alarms - ont_los
678 new_alarms = ont_los - self._active_los_alarms
679
680 if len(cleared_alarms) > 0 or len(new_alarms) > 0:
681 self.log.info('onu-los', cleared=cleared_alarms, new=new_alarms)
682
683 for onu_id in cleared_alarms:
684 self._active_los_alarms.remove(onu_id)
685 OnuLosAlarm(self.olt.alarms, onu_id, self.port_no).clear_alarm()
686
687 for onu_id in new_alarms:
688 self._active_los_alarms.add(onu_id)
689 OnuLosAlarm(self.olt.alarms, onu_id, self.port_no).raise_alarm()
690 reactor.callLater(0, self.delete_onu, onu_id)
691
692 def _process_status_onu_discovered_list(self, discovered_onus):
693 """
694 Look for new ONUs
695
696 :param discovered_onus: (frozenset) Set of ONUs currently discovered
697 """
698 self.log.debug('discovered-ONUs', list=discovered_onus)
699
700 # Only request discovery if activation is auto-discovery or auto-activate
701 continue_discovery = ['autodiscovery', 'autoactivate']
702
703 if self._activation_method not in continue_discovery:
704 return set(), set()
705
706 my_onus = frozenset(self._onus.keys())
707
708 new_onus = discovered_onus - my_onus
709 rediscovered_onus = my_onus & discovered_onus
710
711 return new_onus, rediscovered_onus
712
713 def _get_onu_info(self, serial_number):
714 """
715 Parse through available xPON information for ONU configuration settings
716
717 :param serial_number: (string) Decoded (not base64) serial number string
718 :return: (dict) onu config data or None on lookup failure
719 """
720 try:
721 if self.activation_method == "autodiscovery":
722 # if self.authentication_method == 'serial-number':
723 raise NotImplemented('autodiscovery: Not supported at this time')
724
725 elif self.activation_method == "autoactivate":
726 onu_id = self.get_next_onu_id
727 enabled = True
728 upstream_fec_enabled = True
729
730 else:
731 self.log.error('unsupported-activation-method', method=self.activation_method)
732 return None
733
734 onu_info = {
735 'device-id': self.olt.device_id,
736 'serial-number': serial_number,
737 'pon': self,
738 'onu-id': onu_id,
739 'enabled': enabled,
740 'upstream-fec': upstream_fec_enabled,
741 'password': Onu.DEFAULT_PASSWORD,
742 }
743 pon_id = self.olt.pon_id_to_port_number(self._pon_id)
744
745 # TODO: Currently only one UNI port and it is hardcoded to port 0
746 onu_info['uni-ports'] = [platform.mk_uni_port_num(pon_id, onu_id)]
747
748 # return onu_info
749 return onu_info
750
751 except Exception as e:
752 self.log.exception('get-onu-info-tech-profiles', e=e)
753 return None
754
755 @inlineCallbacks
756 def add_onu(self, serial_number_64, status):
757 """
758 Add an ONU to the PON
759
760 :param serial_number_64: (str) base-64 encoded serial number
761 :param status: (dict) OLT PON status. Used to detect if ONU is already provisioned
762 """
763 serial_number = Onu.serial_number_to_string(serial_number_64)
764 self.log.info('add-onu', serial_number=serial_number,
765 serial_number_64=serial_number_64, status=status)
766
767 # It takes a little while for a new ONU to be removed from the discovery
768 # list. Return early here so extra ONU IDs are not allocated
769 if serial_number_64 in self._onus:
770 returnValue('wait-for-fpga')
771
772 if serial_number_64 in status.onus:
773 # Handles fast entry into this task before FPGA can clear results of ONU delete
774 returnValue('sticky-onu')
775
776 # At our limit? TODO: Retrieve from device resource manager if available
777 if len(self._onus) >= self.MAX_ONUS_SUPPORTED:
778 self.log.warning('max-onus-provisioned', count=len(self._onus))
779 returnValue('max-onus-reached')
780
781 onu_info = self._get_onu_info(serial_number)
782 onu_id = onu_info['onu-id']
783
784 if onu_id is None:
785 self.log.warning('no-onu-ids-available', serial_number=serial_number,
786 serial_number_64=serial_number_64)
787 returnValue('no-ids-available')
788
789 # TODO: Is the best before or after creation in parent device?
790 alarm = OnuDiscoveryAlarm(self.olt.alarms, self.pon_id, serial_number)
791 reactor.callLater(0, alarm.raise_alarm)
792
793 # Have the core create the ONU device
794 self._parent.add_onu_device(self.pon_id, onu_id, serial_number)
795
796 try:
797 onu = Onu(onu_info)
798 self._onus[serial_number_64] = onu
799 self._onu_by_id[onu.onu_id] = onu
800
801 # Add Multicast to PON on a per-ONU basis
802 #
803 # for id_or_vid, gem_port in gem_ports.iteritems():
804 # try:
805 # if gem_port.multicast:
806 # self.log.debug('id-or-vid', id_or_vid=id_or_vid)
807 # vid = self.olt.multicast_vlans[0] if len(self.olt.multicast_vlans) else None
808 # if vid is not None:
809 # self.add_mcast_gem_port(gem_port, vid)
810 #
811 # except Exception as e:
812 # self.log.exception('id-or-vid', e=e)
813
814 _results = yield onu.create()
815
816 except Exception as e:
817 self.log.warning('add-onu', serial_number=serial_number_64, e=e)
818 # allowable exception. H/w re-sync will recover/fix any issues
819
820 @property
821 def get_next_onu_id(self):
822 return self._parent.resource_mgr.get_onu_id(self._pon_id)
823
824 def release_onu_id(self, onu_id):
825 self._parent.resource_mgr.free_onu_id(self._pon_id, onu_id)
826
827 @inlineCallbacks
828 def _remove_from_hardware(self, onu_id):
829 uri = AdtranOltHandler.GPON_ONU_CONFIG_URI.format(self._pon_id, onu_id)
830 name = 'pon-delete-onu-{}-{}'.format(self._pon_id, onu_id)
831
832 try:
833 yield self._parent.rest_client.request('DELETE', uri, name=name)
834
835 except RestInvalidResponseCode as e:
836 if e.code != 404:
837 self.log.exception('onu-delete', e=e)
838
839 except Exception as e:
840 self.log.exception('onu-hw-delete', onu_id=onu_id, e=e)
841
842 @inlineCallbacks
843 def delete_onu(self, onu_id, hw_only=False):
844 onu = self._onu_by_id.get(onu_id)
845
846 # Remove from any local dictionary
847 if onu_id in self._onu_by_id:
848 del self._onu_by_id[onu_id]
849
850 if onu is not None:
851 if onu.serial_number_64 in self._onus:
852 del self._onus[onu.serial_number_64]
853 try:
854 proxy_address = onu.proxy_address
855 onu.delete() # Remove from hardware
856
857 # And removal from VOLTHA adapter agent
858 if not hw_only:
859 self._parent.delete_child_device(proxy_address)
860
861 except Exception as e:
862 self.log.exception('onu-delete', serial_number=onu.serial_number, e=e)
863 else:
864 try:
865 yield self._remove_from_hardware(onu_id)
866
867 except Exception as e:
868 self.log.debug('onu-remove', serial_number=onu.serial_number, e=e)
869
870 # Remove from LOS list if needed TODO: Should a 'clear' alarm be sent as well ?
871 if onu is not None and onu.id in self._active_los_alarms:
872 self._active_los_alarms.remove(onu.id)
873
874 def add_mcast_gem_port(self, mcast_gem, vlan):
875 """
876 Add any new Multicast GEM Ports to the PON
877 :param mcast_gem: (GemPort)
878 """
879 if vlan in self._mcast_gem_ports:
880 return
881
882 assert len(self._mcast_gem_ports) == 0, 'Only 1 MCAST GEMPort until BBF Support'
883 assert 1 <= vlan <= 4095, 'Invalid Multicast VLAN ID'
884 assert len(self.olt.multicast_vlans) == 1, 'Only support 1 MCAST VLAN until BBF Support'
885
886 self._mcast_gem_ports[vlan] = mcast_gem