| # |
| # Copyright 2018 the original author or authors. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| import structlog |
| |
| from voltha_protos.device_pb2 import Image |
| from omci_entities import * |
| from database.mib_db_api import * |
| from enum import IntEnum |
| |
| |
| class OMCCVersion(IntEnum): |
| Unknown = 0 # Unknown or unsupported version |
| G_984_4 = 0x80 # (06/04) |
| G_984_4_2005_Amd_1 = 0x81 # Amd.1 (06/05) |
| G_984_4_2006_Amd_2 = 0x82 # Amd.2 (03/06) |
| G_984_4_2006_Amd_3 = 0x83 # Amd.3 (12/06) |
| G_984_4_2008 = 0x84 # (02/08) |
| G_984_4_2009_Amd_1 = 0x85 # Amd.1 (06/09) |
| G_984_4_2009_Amd_2_Base = 0x86 # Amd.2 (2009) Baseline message set only, w/o the extended message set option |
| G_984_4_2009_Amd_2 = 0x96 # Amd.2 (2009) Extended message set option + baseline message set. |
| G_988_2010_Base = 0xA0 # (2010) Baseline message set only, w/o the extended message set option |
| G_988_2011_Amd_1_Base = 0xA1 # Amd.1 (2011) Baseline message set only |
| G_988_2012_Amd_2_Base = 0xA2 # Amd.2 (2012) Baseline message set only |
| G_988_2012_Base = 0xA3 # (2012) Baseline message set only |
| G_988_2010 = 0xB0 # (2010) Baseline and extended message set |
| G_988_2011_Amd_1 = 0xB1 # Amd.1 (2011) Baseline and extended message set |
| G_988_2012_Amd_2 = 0xB2 # Amd.2 (2012) Baseline and extended message set |
| G_988_2012 = 0xB3 # (2012)Baseline and extended message set |
| |
| @staticmethod |
| def values(): |
| return {OMCCVersion[member].value for member in OMCCVersion.__members__.keys()} |
| |
| @staticmethod |
| def to_enum(value): |
| return next((v for k, v in OMCCVersion.__members__.items() |
| if v.value == value), OMCCVersion.Unknown) |
| |
| |
| class OnuConfiguration(object): |
| """ |
| Utility class to query OMCI MIB Database for various ONU/OMCI Configuration |
| and capabilties. These capabilities revolve around read-only MEs discovered |
| during the MIB Upload process. |
| |
| There is also a 'omci_onu_capabilities' State Machine and an |
| 'onu_capabilities_task.py' OMCI Task that will query the ONU, via the |
| OMCI (ME#287) Managed entity to get the full list of supported OMCI ME's |
| and available actions/message-types supported. |
| |
| NOTE: Currently this class is optimized/tested for ONUs that support the |
| OpenOMCI implementation. |
| """ |
| def __init__(self, omci_agent, device_id): |
| """ |
| Initialize this instance of the OnuConfiguration class |
| |
| :param omci_agent: (OpenOMCIAgent) agent reference |
| :param device_id: (str) ONU Device ID |
| |
| :raises KeyError: If ONU Device is not registered with OpenOMCI |
| """ |
| self.log = structlog.get_logger(device_id=device_id) |
| self._device_id = device_id |
| self._onu_device = omci_agent.get_device(device_id) |
| |
| # The capabilities |
| self._attributes = None |
| self.reset() |
| |
| def _get_capability(self, attr, class_id, instance_id=None): |
| """ |
| Get the OMCI capabilities for this device |
| |
| :param attr: (str) OnuConfiguration attribute field |
| :param class_id: (int) ME Class ID |
| :param instance_id: (int) Instance ID. If not provided, all instances of the |
| specified class ID are returned if present in the DB. |
| |
| :return: (dict) Class and/or Instances. None is returned if the CLASS is not present |
| """ |
| try: |
| assert self._onu_device.mib_synchronizer.last_mib_db_sync is not None, \ |
| 'MIB Database for ONU {} has never been synchronized'.format(self._device_id) |
| |
| # Get the requested information |
| if self._attributes[attr] is None: |
| value = self._onu_device.query_mib(class_id, instance_id=instance_id) |
| |
| if isinstance(value, dict) and len(value) > 0: |
| self._attributes[attr] = value |
| |
| return self._attributes[attr] |
| |
| except Exception as e: |
| self.log.exception('onu-capabilities', e=e, class_id=class_id, |
| instance_id=instance_id) |
| raise |
| |
| def reset(self): |
| """ |
| Reset the cached database entries to None. This method should be |
| called after any communications loss to the ONU (reboot, PON down, ...) |
| in case a new software load with different capabilities is available. |
| """ |
| self._attributes = { |
| '_ont_g': None, |
| '_ont_2g': None, |
| '_ani_g': None, |
| '_uni_g': None, |
| '_cardholder': None, |
| '_circuit_pack': None, |
| '_software': None, |
| '_pptp': None, |
| '_veip': None |
| } |
| |
| @property |
| def version(self): |
| """ |
| This attribute identifies the version of the ONU as defined by the vendor |
| """ |
| ontg = self._get_capability('_ont_g', OntG.class_id, 0) |
| if ontg is None or ATTRIBUTES_KEY not in ontg: |
| return None |
| |
| return ontg[ATTRIBUTES_KEY].get('version') |
| |
| @property |
| def serial_number(self): |
| """ |
| The serial number is unique for each ONU |
| """ |
| ontg = self._get_capability('_ont_g', OntG.class_id, 0) |
| if ontg is None or ATTRIBUTES_KEY not in ontg: |
| return None |
| |
| return ontg[ATTRIBUTES_KEY].get('serial_number') |
| |
| @property |
| def traffic_management_option(self): |
| """ |
| This attribute identifies the upstream traffic management function |
| implemented in the ONU. There are three options: |
| |
| 0 Priority controlled and flexibly scheduled upstream traffic. The traffic |
| scheduler and priority queue mechanism are used for upstream traffic. |
| |
| 1 Rate controlled upstream traffic. The maximum upstream traffic of each |
| individual connection is guaranteed by shaping. |
| |
| 2 Priority and rate controlled. The traffic scheduler and priority queue |
| mechanism are used for upstream traffic. The maximum upstream traffic |
| of each individual connection is guaranteed by shaping. |
| """ |
| ontg = self._get_capability('_ont_g', OntG.class_id, 0) |
| if ontg is None or ATTRIBUTES_KEY not in ontg: |
| return None |
| |
| return ontg[ATTRIBUTES_KEY].get('traffic_management_options') |
| |
| @property |
| def onu_survival_time(self): |
| """ |
| This attribute indicates the minimum guaranteed time in milliseconds |
| between the loss of external power and the silence of the ONU. This does not |
| include survival time attributable to a backup battery. The value zero implies that |
| the actual time is not known. |
| |
| Optional |
| """ |
| ontg = self._get_capability('_ont_g', OntG.class_id, 0) |
| if ontg is None or ATTRIBUTES_KEY not in ontg: |
| return None |
| |
| return ontg[ATTRIBUTES_KEY].get('ont_survival_time', 0) |
| |
| @property |
| def equipment_id(self): |
| """ |
| This attribute may be used to identify the specific type of ONU. In some |
| environments, this attribute may include the equipment CLEI code. |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('equipment_id') |
| |
| @property |
| def omcc_version(self): |
| """ |
| This attribute identifies the version of the OMCC protocol being used by the |
| ONU. This allows the OLT to manage a network with ONUs that support different |
| OMCC versions. Release levels of [ITU-T G.984.4] are supported with code |
| points of the form 0x8y and 0x9y, where y is a hexadecimal digit in the range |
| 0..F. Support for continuing revisions of this Recommendation is defined in |
| the 0xAy range. |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return OMCCVersion.to_enum(ont2g[ATTRIBUTES_KEY].get('omcc_version', 0)) |
| |
| @property |
| def vendor_product_code(self): |
| """ |
| This attribute contains a vendor-specific product code for the ONU |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('vendor_product_code') |
| |
| @property |
| def total_priority_queues(self): |
| """ |
| This attribute reports the total number of upstream priority queues |
| that are not associated with a circuit pack, but with the ONU in its entirety |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('total_priority_queue_number') |
| |
| @property |
| def total_traffic_schedulers(self): |
| """ |
| This attribute reports the total number of traffic schedulers that |
| are not associated with a circuit pack, but with the ONU in its entirety. |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('total_traffic_scheduler_number') |
| |
| @property |
| def total_gem_ports(self): |
| """ |
| This attribute reports the total number of GEM port-IDs supported |
| by the ONU. |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('total_gem_port_id_number') |
| |
| @property |
| def uptime(self): |
| """ |
| This attribute counts 10 ms intervals since the ONU was last initialized. |
| It rolls over to 0 when full |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('sys_uptime') |
| |
| @property |
| def connectivity_capability(self): |
| """ |
| This attribute indicates the Ethernet connectivity models that the ONU |
| can support. The value 0 indicates that the capability is not supported; 1 signifies |
| support. |
| |
| Bit Model [Figure reference ITU-T 988] |
| 1 (LSB) N:1 bridging, Figure 8.2.2-3 |
| 2 1:M mapping, Figure 8.2.2-4 |
| 3 1:P filtering, Figure 8.2.2-5 |
| 4 N:M bridge-mapping, Figure 8.2.2-6 |
| 5 1:MP map-filtering, Figure 8.2.2-7 |
| 6 N:P bridge-filtering, Figure 8.2.2-8 |
| 7 to refer to N:MP bridge-map-filtering, Figure 8.2.2-9 |
| 8...16 Reserved |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('connectivity_capability') |
| |
| @property |
| def qos_configuration_flexibility(self): |
| """ |
| This attribute reports whether various managed entities in the |
| ONU are fixed by the ONU's architecture or whether they are configurable. For |
| backward compatibility, and if the ONU does not support this attribute, all such |
| attributes are understood to be hard-wired. |
| |
| Bit Interpretation when bit value = 1 |
| 1 (LSB) Priority queue ME: Port field of related port attribute is |
| read-write and can point to any T-CONT or UNI port in the |
| same slot |
| 2 Priority queue ME: The traffic scheduler pointer is permitted |
| to refer to any other traffic scheduler in the same slot |
| 3 Traffic scheduler ME: T-CONT pointer is read-write |
| 4 Traffic scheduler ME: Policy attribute is read-write |
| 5 T-CONT ME: Policy attribute is read-write |
| 6 Priority queue ME: Priority field of related port attribute is |
| read-write |
| 7..16 Reserved |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('qos_configuration_flexibility') |
| |
| @property |
| def priority_queue_scale_factor(self): |
| """ |
| This specifies the scale factor of several attributes of the priority |
| queue managed entity of section 5.2.8 |
| """ |
| ont2g = self._get_capability('_ont_2g', Ont2G.class_id, 0) |
| if ont2g is None or ATTRIBUTES_KEY not in ont2g: |
| return None |
| |
| return ont2g[ATTRIBUTES_KEY].get('priority_queue_scale_factor', 1) |
| |
| @property |
| def cardholder_entities(self): |
| """ |
| Return a dictionary containing some overall information on the CardHolder |
| instances for this ONU. |
| """ |
| ch = self._get_capability('_cardholder', Cardholder.class_id) |
| results = dict() |
| |
| if ch is not None: |
| for inst, inst_data in ch.items(): |
| if isinstance(inst, int): |
| results[inst] = { |
| 'entity-id': inst, |
| 'is-single-piece': inst >= 256, |
| 'slot-number': inst & 0xff, |
| 'actual-plug-in-type': inst_data[ATTRIBUTES_KEY].get('actual_plugin_unit_type', 0), |
| 'actual-equipment-id': inst_data[ATTRIBUTES_KEY].get('actual_equipment_id', 0), |
| 'protection-profile-ptr': inst_data[ATTRIBUTES_KEY].get('protection_profile_pointer', 0), |
| } |
| return results if len(results) else None |
| |
| @property |
| def circuitpack_entities(self): |
| """ |
| This specifies the scale factor of several attributes of the priority |
| queue managed entity of section 5.2.8 |
| """ |
| cp = self._get_capability('_circuit_pack', CircuitPack.class_id) |
| results = dict() |
| |
| if cp is not None: |
| for inst, inst_data in cp.items(): |
| if isinstance(inst, int): |
| results[inst] = { |
| 'entity-id': inst, |
| 'number-of-ports': inst_data[ATTRIBUTES_KEY].get('number_of_ports', 0), |
| 'serial-number': inst_data[ATTRIBUTES_KEY].get('serial_number', 0), |
| 'version': inst_data[ATTRIBUTES_KEY].get('version', 0), |
| 'vendor-id': inst_data[ATTRIBUTES_KEY].get('vendor_id', 0), |
| 'total-tcont-count': inst_data[ATTRIBUTES_KEY].get('total_tcont_buffer_number', 0), |
| 'total-priority-queue-count': inst_data[ATTRIBUTES_KEY].get('total_priority_queue_number', 0), |
| 'total-traffic-sched-count': inst_data[ATTRIBUTES_KEY].get('total_traffic_scheduler_number', 0), |
| } |
| |
| return results if len(results) else None |
| |
| @property |
| def software_images(self): |
| """ |
| Get a list of software image information for the ONU. The information is provided |
| so that it may be directly added to the protobuf Device information software list. |
| """ |
| sw = self._get_capability('_software', SoftwareImage.class_id) |
| images = list() |
| |
| if sw is not None: |
| for inst, inst_data in sw.items(): |
| if isinstance(inst, int): |
| is_active = inst_data[ATTRIBUTES_KEY].get('is_active', False) |
| |
| images.append(Image(name='running-revision' if is_active else 'candidate-revision', |
| version=str(inst_data[ATTRIBUTES_KEY].get('version', |
| 'Not Available').rstrip('\0')), |
| is_active=is_active, |
| is_committed=inst_data[ATTRIBUTES_KEY].get('is_committed', |
| False), |
| is_valid=inst_data[ATTRIBUTES_KEY].get('is_valid', |
| False), |
| install_datetime='Not Available', |
| hash=str(inst_data[ATTRIBUTES_KEY].get('image_hash', |
| 'Not Available').rstrip('\0')))) |
| return images if len(images) else None |
| |
| @property |
| def ani_g_entities(self): |
| """ |
| This managed entity organizes data associated with each access network |
| interface supported by a G-PON ONU. The ONU automatically creates one |
| instance of this managed entity for each PON physical port. |
| """ |
| ag = self._get_capability('_ani_g', AniG.class_id) |
| results = dict() |
| |
| if ag is not None: |
| for inst, inst_data in ag.items(): |
| if isinstance(inst, int): |
| results[inst] = { |
| 'entity-id': inst, |
| 'slot-number': (inst >> 8) & 0xff, |
| 'port-number': inst & 0xff, |
| 'total-tcont-count': inst_data[ATTRIBUTES_KEY].get('total_tcont_number', 0), |
| 'piggyback-dba-reporting': inst_data[ATTRIBUTES_KEY].get('piggyback_dba_reporting', 0), |
| } |
| return results if len(results) else None |
| |
| @property |
| def uni_g_entities(self): |
| """ |
| This managed entity organizes data associated with user network interfaces |
| (UNIs) supported by GEM. One instance of the UNI-G managed entity exists |
| for each UNI supported by the ONU. |
| |
| The ONU automatically creates or deletes instances of this managed entity |
| upon the creation or deletion of a real or virtual circuit pack managed |
| entity, one per port. |
| """ |
| ug = self._get_capability('_uni_g', UniG.class_id) |
| results = dict() |
| |
| if ug is not None: |
| for inst, inst_data in ug.items(): |
| if isinstance(inst, int): |
| results[inst] = { |
| 'entity-id': inst, |
| 'management-capability': inst_data[ATTRIBUTES_KEY].get('management_capability', 0) |
| } |
| return results if len(results) else None |
| |
| @property |
| def pptp_entities(self): |
| """ |
| Returns discovered PPTP Ethernet entities. TODO more detail here |
| """ |
| pptp = self._get_capability('_pptp', PptpEthernetUni.class_id) |
| results = dict() |
| |
| if pptp is not None: |
| for inst, inst_data in pptp.items(): |
| if isinstance(inst, int): |
| results[inst] = { |
| 'entity-id': inst, |
| 'expected-type': inst_data[ATTRIBUTES_KEY].get('expected_type', 0), |
| 'sensed-type': inst_data[ATTRIBUTES_KEY].get('sensed_type', 0), |
| 'autodetection-config': inst_data[ATTRIBUTES_KEY].get('autodetection_config', 0), |
| 'ethernet-loopback-config': inst_data[ATTRIBUTES_KEY].get('ethernet_loopback_config', 0), |
| 'administrative-state': inst_data[ATTRIBUTES_KEY].get('administrative_state', 0), |
| 'operational-state': inst_data[ATTRIBUTES_KEY].get('operational_state', 0), |
| 'config-ind': inst_data[ATTRIBUTES_KEY].get('config_ind', 0), |
| 'max-frame-size': inst_data[ATTRIBUTES_KEY].get('max_frame_size', 0), |
| 'dte-dce-ind': inst_data[ATTRIBUTES_KEY].get('dte_dce_ind', 0), |
| 'pause-time': inst_data[ATTRIBUTES_KEY].get('pause_time', 0), |
| 'bridged-ip-ind': inst_data[ATTRIBUTES_KEY].get('bridged_ip_ind', 0), |
| 'arc': inst_data[ATTRIBUTES_KEY].get('arc', 0), |
| 'arc-interval': inst_data[ATTRIBUTES_KEY].get('arc_interval', 0), |
| 'pppoe-filter': inst_data[ATTRIBUTES_KEY].get('ppoe_filter', 0), |
| 'power-control': inst_data[ATTRIBUTES_KEY].get('power_control', 0) |
| } |
| return results if len(results) else None |
| |
| @property |
| def veip_entities(self): |
| """ |
| Returns discovered VEIP entities. TODO more detail here |
| """ |
| veip = self._get_capability('_veip', VeipUni.class_id) |
| results = dict() |
| |
| if veip is not None: |
| for inst, inst_data in veip.items(): |
| if isinstance(inst, int): |
| results[inst] = { |
| 'entity-id': inst, |
| 'administrative-state': inst_data[ATTRIBUTES_KEY].get('administrative_state', 0), |
| 'operational-state': inst_data[ATTRIBUTES_KEY].get('operational_state', 0), |
| 'interdomain-name': inst_data[ATTRIBUTES_KEY].get('interdomain_name', ""), |
| 'tcp-udp-pointer': inst_data[ATTRIBUTES_KEY].get('tcp_udp_pointer', 0), |
| 'iana-assigned-port': inst_data[ATTRIBUTES_KEY].get('iana_assigned_port', 0) |
| } |
| return results if len(results) else None |