blob: c7b7d64d2d526e3eec4158d4dd5f15c7f9115bb0 [file] [log] [blame]
Chip Boling32aab302019-01-23 10:50:18 -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
17from datetime import datetime
18from transitions import Machine
19from twisted.internet import reactor
20from voltha.extensions.omci.omci_defs import ReasonCodes, EntityOperations
21from voltha.extensions.omci.omci_cc import OmciCCRxEvents, OMCI_CC, RX_RESPONSE_KEY
22from voltha.extensions.omci.omci_messages import OmciGetAllAlarmsResponse
23from voltha.extensions.omci.omci_frame import OmciFrame
24from voltha.extensions.omci.database.alarm_db_ext import AlarmDbExternal
25from voltha.extensions.omci.database.mib_db_api import ATTRIBUTES_KEY
26from voltha.extensions.omci.omci_entities import CircuitPack, PptpEthernetUni, OntG, AniG
27
28from common.event_bus import EventBusClient
29from voltha.protos.omci_alarm_db_pb2 import AlarmOpenOmciEventType
30
31RxEvent = OmciCCRxEvents
32RC = ReasonCodes
33OP = EntityOperations
34
35
36class AlarmSynchronizer(object):
37 """
38 OpenOMCI Alarm Synchronizer state machine
39 """
40 DEFAULT_STATES = ['disabled', 'starting', 'auditing', 'in_sync']
41
42 DEFAULT_TRANSITIONS = [
43 {'trigger': 'start', 'source': 'disabled', 'dest': 'starting'},
44
45 {'trigger': 'audit_alarm', 'source': 'starting', 'dest': 'auditing'},
46 {'trigger': 'sync_alarm', 'source': 'starting', 'dest': 'in_sync'},
47
48 {'trigger': 'success', 'source': 'auditing', 'dest': 'in_sync'},
49 {'trigger': 'audit_alarm', 'source': 'auditing', 'dest': 'auditing'},
50 {'trigger': 'failure', 'source': 'auditing', 'dest': 'auditing'},
51
52 {'trigger': 'audit_alarm', 'source': 'in_sync', 'dest': 'auditing'},
53
54 # Do wildcard 'stop' trigger last so it covers all previous states
55 {'trigger': 'stop', 'source': '*', 'dest': 'disabled'},
56 ]
57 DEFAULT_TIMEOUT_RETRY = 15 # Seconds to delay after task failure/timeout
58 DEFAULT_AUDIT_DELAY = 180 # Periodic tick to audit the ONU's alarm table
59
60 def __init__(self, agent, device_id, alarm_sync_tasks, db,
61 advertise_events=False,
62 states=DEFAULT_STATES,
63 transitions=DEFAULT_TRANSITIONS,
64 initial_state='disabled',
65 timeout_delay=DEFAULT_TIMEOUT_RETRY,
66 audit_delay=DEFAULT_AUDIT_DELAY):
67 """
68 Class initialization
69
70 :param agent: (OpenOmciAgent) Agent
71 :param device_id: (str) ONU Device ID
72 :param db: (MibDbApi) MIB/Alarm Database
73 :param advertise_events: (bool) Advertise events on OpenOMCI Event Bus
74 :param alarm_sync_tasks: (dict) Tasks to run
75 :param states: (list) List of valid states
76 :param transitions: (dict) Dictionary of triggers and state changes
77 :param initial_state: (str) Initial state machine state
78 :param timeout_delay: (int/float) Number of seconds after a timeout to attempt
79 a retry (goes back to starting state)
80 :param audit_delay: (int) Seconds between Alarm audits while in sync. Set to
81 zero to disable audit. An operator can request
82 an audit manually by calling 'self.audit_alarm'
83 """
84
85 self.log = structlog.get_logger(device_id=device_id)
86
87 self._agent = agent
88 self._device_id = device_id
89 self._device = None
90 self._database = db
91 self._timeout_delay = timeout_delay
92 self._audit_delay = audit_delay
93 self._resync_task = alarm_sync_tasks['alarm-resync']
94 self._advertise_events = advertise_events
95 self._alarm_manager = None
96 self._onu_id = None
97 self._uni_ports = list()
98 self._ani_ports = list()
99
100 self._deferred = None
101 self._current_task = None
102 self._task_deferred = None
103 self._last_alarm_sequence_value = 0
104 self._device_in_db = False
105
106 self._event_bus = EventBusClient()
107 self._omci_cc_subscriptions = { # RxEvent.enum -> Subscription Object
108 RxEvent.Get_ALARM_Get: None,
109 RxEvent.Alarm_Notification: None
110 }
111 self._omci_cc_sub_mapping = {
112 RxEvent.Get_ALARM_Get: self.on_alarm_update_response,
113 RxEvent.Alarm_Notification: self.on_alarm_notification
114 }
115
116 # Statistics and attributes
117 # TODO: add any others if it will support problem diagnosis
118
119 # Set up state machine to manage states
120 self.machine = Machine(model=self, states=states,
121 transitions=transitions,
122 initial=initial_state,
123 queued=True,
124 name='{}-{}'.format(self.__class__.__name__,
125 device_id))
126
127 def _cancel_deferred(self):
128 d1, self._deferred = self._deferred, None
129 d2, self._task_deferred = self._task_deferred, None
130
131 for d in [d1, d1]:
132 try:
133 if d is not None and not d.called:
134 d.cancel()
135 except:
136 pass
137
138 def __str__(self):
139 return 'Alarm Synchronizer: Device ID: {}, State:{}'.format(self._device_id, self.state)
140
141 def delete(self):
142 """
143 Cleanup any state information
144 """
145 self.stop()
146 db, self._database = self._database, None
147
148 if db is not None:
149 db.remove(self._device_id)
150
151 @property
152 def device_id(self):
153 return self._device_id
154
155 @property
156 def last_alarm_sequence(self):
157 return self._last_alarm_sequence_value
158
159 def reset_alarm_sequence(self):
160 if self._last_alarm_sequence_value != 0:
161 self._last_alarm_sequence_value = 0
162
163 def increment_alarm_sequence(self):
164 self._last_alarm_sequence_value += 1
165 if self._last_alarm_sequence_value > 255:
166 self._last_alarm_sequence_value = 1
167
168 @property
169 def advertise_events(self):
170 return self._advertise_events
171
172 @advertise_events.setter
173 def advertise_events(self, value):
174 if not isinstance(value, bool):
175 raise TypeError('Advertise event is a boolean')
176 self._advertise_events = value
177
178 def advertise(self, event, info):
179 """Advertise an event on the OpenOMCI event bus"""
180 if self._advertise_events:
181 self._agent.advertise(event,
182 {
183 'state-machine': self.machine.name,
184 'info': info,
185 'time': str(datetime.utcnow())
186 })
187
188 def set_alarm_params(self, mgr=None, onu_id=None, uni_ports=None, ani_ports=None):
189 if mgr is not None:
190 self._alarm_manager = mgr
191
192 if onu_id is not None:
193 self._onu_id = onu_id
194
195 if uni_ports is not None:
196 assert isinstance(uni_ports, list)
197 self._uni_ports = uni_ports
198
199 if ani_ports is not None:
200 assert isinstance(ani_ports, list)
201 self._ani_ports = ani_ports
202
203 def on_enter_disabled(self):
204 """
205 State machine is being stopped
206 """
207 self.advertise(AlarmOpenOmciEventType.state_change, self.state)
208
209 self._cancel_deferred()
210
211 task, self._current_task = self._current_task, None
212 if task is not None:
213 task.stop()
214
215 # Drop Response and Autonomous notification subscriptions
216 for event, sub in self._omci_cc_subscriptions.iteritems():
217 if sub is not None:
218 self._omci_cc_subscriptions[event] = None
219 self._device.omci_cc.event_bus.unsubscribe(sub)
220
221 def _seed_database(self):
222 if not self._device_in_db:
223 try:
224 try:
225 self._database.start()
226 self._database.add(self._device_id)
227 self.log.debug('seed-db-does-not-exist', device_id=self._device_id)
228
229 except KeyError:
230 # Device already is in database
231 self.log.debug('seed-db-exist', device_id=self._device_id)
232
233 self._device_in_db = True
234
235 except Exception as e:
236 self.log.exception('seed-database-failure', e=e)
237
238 def on_enter_starting(self):
239 """
240 Determine ONU status and start Alarm Synchronization tasks
241 """
242 self._device = self._agent.get_device(self._device_id)
243 self.advertise(AlarmOpenOmciEventType.state_change, self.state)
244
245 # Make sure root of external Alarm Database exists
246 self._seed_database()
247
248 # Set up Response and Autonomous notification subscriptions
249 try:
250 for event, sub in self._omci_cc_sub_mapping.iteritems():
251 if self._omci_cc_subscriptions[event] is None:
252 self._omci_cc_subscriptions[event] = \
253 self._device.omci_cc.event_bus.subscribe(
254 topic=OMCI_CC.event_bus_topic(self._device_id, event),
255 callback=sub)
256
257 except Exception as e:
258 self.log.exception('omci-cc-subscription-setup', e=e)
259
260 # Schedule first audit if enabled
261 if self._audit_delay > 0:
262 # Note using the shorter timeout delay here since this is the first
263 # audit after startup
264 self._deferred = reactor.callLater(self._timeout_delay, self.audit_alarm)
265 else:
266 self._deferred = reactor.callLater(0, self.sync_alarm)
267
268 def on_enter_in_sync(self):
269 """
270 Schedule a tick to occur to in the future to request an audit
271 """
272 self.advertise(AlarmOpenOmciEventType.state_change, self.state)
273
274 if self._audit_delay > 0:
275 # Note using the shorter timeout delay here since this is the first
276 # audit after startup
277 self._deferred = reactor.callLater(self._audit_delay, self.audit_alarm)
278
279 def on_enter_auditing(self):
280 """
281 Begin full Alarm data sync, Comparing the all alarms
282 """
283 self.advertise(AlarmOpenOmciEventType.state_change, self.state)
284
285 def success(results):
286 self.log.debug('alarm-diff-success')
287 self._current_task = None
288
289 # Any differences found between ONU and OpenOMCI Alarm tables?
290 if results is None:
291 self._device.alarm_db_in_sync = True
292 self._deferred = reactor.callLater(0, self.success)
293 else:
294 # Reconcile the alarm table and re-run audit
295 self.reconcile_alarm_table(results)
296 self._deferred = reactor.callLater(5, self.audit_alarm)
297
298 def failure(reason):
299 self.log.info('alarm-update-failure', reason=reason)
300 self._current_task = None
301 self._deferred = reactor.callLater(self._timeout_delay, self.failure)
302
303 self._current_task = self._resync_task(self._agent, self._device_id)
304 self._task_deferred = self._device.task_runner.queue_task(self._current_task)
305 self._task_deferred.addCallbacks(success, failure)
306
307 def reconcile_alarm_table(self, results):
308 self.log.debug('alarm-reconcile', state=self.state, results=results)
309
310 onu_only = results['onu-only']
311 olt_only = results['olt-only']
312 attr_diffs = results['attr-diffs']
313 onu_db = results['onu-db']
314 olt_db = results['olt-db']
315
316 if any(item is not None for item in (onu_only, olt_only, attr_diffs)):
317 self._device.alarm_db_in_sync = False
318
319 # Compare the differences. During upload, if there are no alarms at all,
320 # then the ONU alarm table retrieved may be empty (instead of MEs with all
321 # bits cleared) depending upon the ONU's OMCI Stack.
322
323 if onu_only is not None:
324 self.process_onu_only_diffs(onu_only, onu_db)
325
326 if olt_only is not None:
327 self.process_olt_only_diffs(olt_only)
328
329 if attr_diffs is not None:
330 self.process_attr_diffs(attr_diffs, olt_db, onu_db)
331
332 def process_onu_only_diffs(self, onu_only, onu_db):
333 """
334 ONU only alarms will typically occur when doing the first audit as our
335 database is clear and we are seeding the alarm table. Save the entries
336 and if any are set, we need to raise that alarm.
337
338 :param onu_only: (list) Tuples with [0]=class ID, [1]=entity ID
339 :param onu_db: (dict) ONU Alarm database from the alarm audit upload
340 """
341 for cid_eid in onu_only:
342 class_id = cid_eid[0]
343 entity_id = cid_eid[1]
344 try:
345 bitmap = onu_db[class_id][entity_id][ATTRIBUTES_KEY][AlarmDbExternal.ALARM_BITMAP_KEY]
346 self.process_alarm_data(class_id, entity_id, bitmap, -1)
347
348 except KeyError as e:
349 self.log.error('alarm-not-found', class_id=class_id, entity_id=entity_id, e=e)
350
351 def process_olt_only_diffs(self, olt_only):
352 """
353 OLT only alarms may occur if the alarm(s) are no longer active on the ONU
354 and the notification was missed. Process this by sending a cleared bitmap
355 for any alarm in the OLT database only
356
357 :param olt_only: (list) Tuples with [0]=class ID, [1]=entity ID
358 """
359 for cid_eid in olt_only:
360 # First process the alarm clearing
361 self.process_alarm_data(cid_eid[0], cid_eid[1], 0, -1)
362 # Now remove from alarm DB so we match the ONU alarm table
363 self._database.delete(self._device_id, cid_eid[0], cid_eid[1])
364
365 def process_attr_diffs(self, attr_diffs, onu_db):
366 """
367 Mismatch in alarm settings. Note that the attribute should always be the
368 alarm bitmap attribute (long). For differences, the ONU is always right
369
370 :param attr_diffs: (list(int,int,str)) [0]=class ID, [1]=entity ID, [1]=attr
371 :param olt_db: (dict) OLT Alarm database snapshot from the alarm audit
372 :param onu_db: (dict) ONU Alarm database from the alarm audit upload
373 """
374 for cid_eid_attr in attr_diffs:
375 class_id = cid_eid_attr[0]
376 entity_id = cid_eid_attr[1]
377
378 try:
379 assert AlarmDbExternal.ALARM_BITMAP_KEY == cid_eid_attr[2]
380 bitmap = onu_db[class_id][entity_id][ATTRIBUTES_KEY][AlarmDbExternal.ALARM_BITMAP_KEY]
381 self.process_alarm_data(class_id, entity_id, bitmap, -1)
382
383 except KeyError as e:
384 self.log.error('alarm-not-found', class_id=class_id, entity_id=entity_id, e=e)
385
386 def on_alarm_update_response(self, _topic, msg):
387 """
388 Process a Get All Alarms response
389
390 :param _topic: (str) OMCI-RX topic
391 :param msg: (dict) Dictionary with 'rx-response' and 'tx-request' (if any)
392 """
393 self.log.debug('on-alarm-update-response', state=self.state, msg=msg)
394
395 if self._omci_cc_subscriptions[RxEvent.Get_ALARM_Get]:
396 if self.state == 'disabled':
397 self.log.error('rx-in-invalid-state', state=self.state)
398 return
399
400 try:
401 response = msg.get(RX_RESPONSE_KEY)
402
403 if isinstance(response, OmciFrame) and \
404 isinstance(response.fields.get('omci_message'), OmciGetAllAlarmsResponse):
405 # ONU will reset its last alarm sequence number to 0 on receipt of the
406 # Get All Alarms request
407 self.log.debug('received-alarm-response')
408 self.reset_alarm_sequence()
409
410 except Exception as e:
411 self.log.exception('upload-alarm-failure', e=e)
412
413 def on_alarm_notification(self, _topic, msg):
414 """
415 Process an alarm Notification
416
417 :param _topic: (str) OMCI-RX topic
418 :param msg: (dict) Dictionary with keys:
419 TX_REQUEST_KEY -> None (this is an autonomous msg)
420 RX_RESPONSE_KEY -> OmciMessage (Alarm notification frame)
421 """
422 self.log.debug('on-alarm-update-response', state=self.state, msg=msg)
423
424 alarm_msg = msg.get(RX_RESPONSE_KEY)
425 if alarm_msg is not None:
426 omci_msg = alarm_msg.fields['omci_message'].fields
427 class_id = omci_msg['entity_class']
428 seq_no = omci_msg['alarm_sequence_number']
429
430 # Validate that this ME supports alarm notifications
431 if class_id not in self._device.me_map or \
432 OP.AlarmNotification not in self._device.me_map[class_id].notifications or \
433 len(self._device.me_map[class_id].alarms) == 0:
434 self.log.warn('invalid-alarm-notification', class_id=class_id)
435 return
436
437 self.process_alarm_data(class_id,
438 omci_msg['entity_id'],
439 omci_msg['alarm_bit_map'],
440 seq_no)
441
442 def process_alarm_data(self, class_id, entity_id, bitmap, msg_seq_no):
443 """
444 Process new alarm data
445
446 :param class_id: (int) Class ID of alarm
447 :param entity_id: (int) Entity ID of alarm
448 :param bitmap: (long) Alarm bitmap value
449 :param msg_seq_no: (int) Alarm sequence number. -1 if generated during an audit
450 """
451 if msg_seq_no > 0:
452 # increment alarm number & compare to alarm # in message
453 # Signal early audit if no match and audits are enabled
454 self.increment_alarm_sequence()
455
456 if self.last_alarm_sequence != msg_seq_no and self._audit_delay > 0:
457 self._deferred = reactor.callLater(0, self.audit_alarm)
458
459 key = AlarmDbExternal.ALARM_BITMAP_KEY
460 prev_entry = self._database.query(self._device_id, class_id, entity_id)
461 try:
462 # Need to access the bit map structure which is nested in dict attributes
463 prev_bitmap = 0 if len(prev_entry) == 0 else long(prev_entry['attributes'][key])
464 except Exception as e:
465 self.log.exception('alarm-prev-entry-collection-failure', class_id=class_id,
466 device_id=self._device_id, entity_id=entity_id, value=bitmap, e=e)
467 # Save current entry before going on
468 try:
469 self._database.set(self._device_id, class_id, entity_id, {key: bitmap})
470
471 except Exception as e:
472 self.log.exception('alarm-save-failure', class_id=class_id,
473 device_id=self._device_id, entity_id=entity_id, value=bitmap, e=e)
474
475 if self._alarm_manager is not None:
476 # Generate a set of alarm number that are raised in current and previous
477 previously_raised = {alarm_no for alarm_no in xrange(224)
478 if prev_bitmap & (1L << (223-alarm_no)) != 0L}
479
480 currently_raised = {alarm_no for alarm_no in xrange(224)
481 if bitmap & (1L << (223-alarm_no)) != 0L}
482
483 newly_cleared = previously_raised - currently_raised
484 newly_raised = currently_raised - previously_raised
485
486 # Generate the set/clear alarms now
487 for alarm_number in newly_cleared:
488 reactor.callLater(0, self.clear_alarm, class_id, entity_id, alarm_number)
489
490 for alarm_number in newly_raised:
491 reactor.callLater(0, self.raise_alarm, class_id, entity_id, alarm_number)
492
493 def get_alarm_description(self, class_id, alarm_number):
494 """
495 Get the alarm description, both as a printable-string and also a CamelCase value
496 """
497 if alarm_number in self._device.me_map[class_id].alarms:
498 description = self._device.me_map[class_id].alarms[alarm_number]
499 elif alarm_number <= 207:
500 description = 'Reserved alarm {}'.format(alarm_number)
501 else:
502 description = 'Vendor specific alarm {}'.format(alarm_number)
503
504 # For CamelCase, replace hyphens with spaces before camel casing the string
505 return description, description.replace('-', ' ').title().replace(' ', '')
506
507 def raise_alarm(self, class_id, entity_id, alarm_number):
508 """
509 Raise an alarm on the ONU
510
511 :param class_id: (int) Class ID of the Alarm ME
512 :param entity_id: (int) Entity ID of the Alarm
513 :param alarm_number: (int) Alarm number (bit) that is alarmed
514 """
515 description, name = self.get_alarm_description(class_id, alarm_number)
516
517 self.log.warn('alarm-set', class_id=class_id, entity_id=entity_id,
518 alarm_number=alarm_number, name=name, description=description)
519
520 if self._alarm_manager is not None:
521 alarm = self.omci_alarm_to_onu_alarm(class_id, entity_id, alarm_number)
522 if alarm is not None:
523 alarm.raise_alarm()
524
525 def clear_alarm(self, class_id, entity_id, alarm_number):
526 """
527 Lower/clear an alarm on the ONU
528
529 :param class_id: (int) Class ID of the Alarm ME
530 :param entity_id: (int) Entity ID of the Alarm
531 :param alarm_number: (int) Alarm number (bit) that is alarmed
532 """
533 description, name = self.get_alarm_description(class_id, alarm_number)
534
535 self.log.info('alarm-cleared', class_id=class_id, entity_id=entity_id,
536 alarm_number=alarm_number, name=name, description=description)
537
538 if self._alarm_manager is not None:
539 alarm = self.omci_alarm_to_onu_alarm(class_id, entity_id, alarm_number)
540 if alarm is not None:
541 alarm.clear_alarm()
542
543 def query_mib(self, class_id=None, instance_id=None):
544 """
545 Get Alarm database information.
546
547 This method can be used to request information from the database to the detailed
548 level requested
549
550 :param class_id: (int) Managed Entity class ID
551 :param instance_id: (int) Managed Entity instance
552
553 :return: (dict) The value(s) requested. If class/inst/attribute is
554 not found, an empty dictionary is returned
555 :raises DatabaseStateError: If the database is not enabled or does not exist
556 """
557 from voltha.extensions.omci.database.mib_db_api import DatabaseStateError
558
559 self.log.debug('query', class_id=class_id, instance_id=instance_id)
560 if self._database is None:
561 raise DatabaseStateError('Database does not yet exist')
562
563 return self._database.query(self._device_id, class_id=class_id, instance_id=instance_id)
564
565 def omci_alarm_to_onu_alarm(self, class_id, entity_id, alarm_number):
566 """
567 Map an OMCI Alarm Notification alarm to the proper ONU Alarm Library alarm
568
569 :param class_id: (int) ME Class ID
570 :param entity_id: (int) ME Class instance ID
571 :param alarm_number: (int) Alarm Number
572 :return: (AlarmBase) Alarm library alarm or None if not supported/found
573 """
574 from voltha.extensions.alarms.onu.onu_dying_gasp_alarm import OnuDyingGaspAlarm
575 from voltha.extensions.alarms.onu.onu_los_alarm import OnuLosAlarm
576 from voltha.extensions.alarms.onu.onu_equipment_alarm import OnuEquipmentAlarm
577 from voltha.extensions.alarms.onu.onu_selftest_failure_alarm import OnuSelfTestFailureAlarm
578 from voltha.extensions.alarms.onu.onu_laser_eol_alarm import OnuLaserEolAlarm
579 from voltha.extensions.alarms.onu.onu_laser_bias_current_alarm import OnuLaserBiasAlarm
580 from voltha.extensions.alarms.onu.onu_temp_yellow_alarm import OnuTempYellowAlarm
581 from voltha.extensions.alarms.onu.onu_temp_red_alarm import OnuTempRedAlarm
582 from voltha.extensions.alarms.onu.onu_voltage_yellow_alarm import OnuVoltageYellowAlarm
583 from voltha.extensions.alarms.onu.onu_voltage_red_alarm import OnuVoltageRedAlarm
584 from voltha.extensions.alarms.onu.onu_low_rx_optical_power_alarm import OnuLowRxOpticalAlarm
585 from voltha.extensions.alarms.onu.onu_high_rx_optical_power_alarm import OnuHighRxOpticalAlarm
586 from voltha.extensions.alarms.onu.onu_low_tx_optical_power_alarm import OnuLowTxOpticalAlarm
587 from voltha.extensions.alarms.onu.onu_high_tx_optical_power_alarm import OnuHighTxOpticalAlarm
588
589 mgr = self._alarm_manager
590 if class_id in (CircuitPack.class_id, PptpEthernetUni.class_id):
591 intf_id = self.select_uni_port(class_id, entity_id)
592
593 elif class_id in (AniG.class_id, OntG.class_id):
594 intf_id = self.select_ani_port(class_id, entity_id)
595
596 else:
597 self.log.error('unsupported-class-id', class_id=class_id, alarm_number=alarm_number)
598 return
599
600 alarm_map = {
601 (CircuitPack.class_id, 0): OnuEquipmentAlarm,
602 (CircuitPack.class_id, 2): OnuSelfTestFailureAlarm,
603 (CircuitPack.class_id, 3): OnuLaserEolAlarm,
604 (CircuitPack.class_id, 4): OnuTempYellowAlarm,
605 (CircuitPack.class_id, 5): OnuTempRedAlarm,
606
607 (PptpEthernetUni.class_id, 0): OnuLosAlarm,
608
609 (OntG.class_id, 0): OnuEquipmentAlarm,
610 (OntG.class_id, 6): OnuSelfTestFailureAlarm,
611 (OntG.class_id, 7): OnuDyingGaspAlarm,
612 (OntG.class_id, 8): OnuTempYellowAlarm,
613 (OntG.class_id, 9): OnuTempRedAlarm,
614 (OntG.class_id, 10): OnuVoltageYellowAlarm,
615 (OntG.class_id, 11): OnuVoltageRedAlarm,
616
617 (AniG.class_id, 0): OnuLowRxOpticalAlarm,
618 (AniG.class_id, 1): OnuHighRxOpticalAlarm,
619 (AniG.class_id, 4): OnuLowTxOpticalAlarm,
620 (AniG.class_id, 5): OnuHighTxOpticalAlarm,
621 (AniG.class_id, 6): OnuLaserBiasAlarm,
622 }
623 alarm_cls = alarm_map.get((class_id, alarm_number))
624
625 return alarm_cls(mgr, self._onu_id, intf_id) if alarm_cls is not None else None
626
627 def select_uni_port(self, class_id, entity_id):
628 """
629 Select the best possible UNI Port (logical) interface number for this ME class and
630 entity ID.
631
632 This base implementation will assume that a UNI Port object has been registered
633 on startup and supports both an 'entity_id' and also 'logical_port_number'
634 property. See both the Adtran and BroadCom OpenOMCI ONU DA for an example
635 of this UNI port object.
636
637 :param class_id: (int) ME Class ID for which the alarms belongs to
638 :param entity_id: (int) Instance ID
639
640 :return: (int) Logical Port number for the UNI port
641 """
642 # NOTE: Of the three class ID's supported in this version of code, only the CircuitPack,
643 # and PptpEthernetUni MEs will map to the UNI port
644 assert class_id in (CircuitPack.class_id, PptpEthernetUni.class_id)
645
646 return next((uni.logical_port_number for uni in self._uni_ports if
647 uni.entity_id == entity_id), None)
648
649 def select_ani_port(self, class_id, _entity_id):
650 """
651 Select the best possible ANI Port (physical) interface number for this ME class and
652 entity ID.
653
654 Currently the base implementation assumes only a single PON port and it will be
655 chosen. A future implementation may want to have a PON Port object (similar to
656 the BroadCom Open OMCI and Adtran ONU's UNI Port object) that provides a match
657 for entity ID. This does assume that the PON port object supports a property
658 of 'port_number' to return the physical port number.
659
660 :param class_id: (int) ME Class ID for which the alarms belongs to
661 :param _entity_id: (int) Instance ID
662
663 :return: (int) Logical Port number for the UNI port
664 """
665 # NOTE: Of the three class ID's supported in this version of code, only the AniG
666 # MEs will map to the ANI port. For some the OntG alarms (Dying Gasp) the
667 # PON interface will also be selected.
668 assert class_id in (AniG.class_id, OntG.class_id)
669
670 return self._ani_ports[0].port_number if len(self._ani_ports) else None