VOL-1395: Common shared libraries needed for Python based device adapters.

This is an initial check-in of code from the master branch.  Additional work
is expected on a few items to work with the new go-core and will be covered
by separate JIRAs and commits.

Change-Id: I0856ec6b79b8d3e49082c609eb9c7eedd75b1708
diff --git a/python/adapters/extensions/omci/onu_configuration.py b/python/adapters/extensions/omci/onu_configuration.py
new file mode 100644
index 0000000..1fa00fe
--- /dev/null
+++ b/python/adapters/extensions/omci/onu_configuration.py
@@ -0,0 +1,509 @@
+#
+# 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_option')
+
+    @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('onu_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('auto_detection_configuration', 0),
+                        'ethernet-loopback-config': inst_data[ATTRIBUTES_KEY].get('ethernet_loopback_configuration', 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('configuration_ind', 0),
+                        'max-frame-size':           inst_data[ATTRIBUTES_KEY].get('max_frame_size', 0),
+                        'dte-dce-ind':              inst_data[ATTRIBUTES_KEY].get('dte_or_dce_ind', 0),
+                        'pause-time':               inst_data[ATTRIBUTES_KEY].get('pause_time', 0),
+                        'bridged-ip-ind':           inst_data[ATTRIBUTES_KEY].get('bridged_or_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