VOL-1137: Support new shared KPI and Alarm libraries

Change-Id: Iaa51a1da59263a4079751f6b697a09c8effe3251
diff --git a/voltha/adapters/adtran_onu/adtran_onu.py b/voltha/adapters/adtran_onu/adtran_onu.py
index 1f7f366..1e05f02 100755
--- a/voltha/adapters/adtran_onu/adtran_onu.py
+++ b/voltha/adapters/adtran_onu/adtran_onu.py
@@ -39,10 +39,11 @@
                                                config=config,
                                                device_handler_class=AdtranOnuHandler,
                                                name='adtran_onu',
-                                               vendor='Adtran, Inc.',
-                                               version='0.11',
+                                               vendor='Adtran Inc.',
+                                               version='0.12',
                                                device_type='adtran_onu',
-                                               vendor_id='ADTN')
+                                               vendor_id='ADTN',
+                                               accepts_add_remove_flow_updates=False),  # TODO: Support flow-mods
         # Customize OpenOMCI for Adtran ONUs
         self.adtran_omci = deepcopy(OpenOmciAgentDefaults)
 
diff --git a/voltha/adapters/adtran_onu/adtran_onu_handler.py b/voltha/adapters/adtran_onu/adtran_onu_handler.py
index cdefaa0..ff69b22 100644
--- a/voltha/adapters/adtran_onu/adtran_onu_handler.py
+++ b/voltha/adapters/adtran_onu/adtran_onu_handler.py
@@ -15,7 +15,6 @@
 #
 
 import arrow
-import structlog
 
 from voltha.adapters.adtran_olt.xpon.adtran_xpon import AdtranXPON
 from pon_port import PonPort
@@ -23,8 +22,8 @@
 from heartbeat import HeartBeat
 from omci.omci import OMCI
 
-from voltha.adapters.adtran_olt.alarms.adapter_alarms import AdapterAlarms
-from onu_pm_metrics import OnuPmMetrics
+from voltha.extensions.alarms.adapter_alarms import AdapterAlarms
+from voltha.extensions.pki.onu.onu_pm_metrics import OnuPmMetrics
 
 from uuid import uuid4
 from twisted.internet import reactor
@@ -223,74 +222,85 @@
     def activate(self, device):
         self.log.info('activating')
 
-        # first we verify that we got parent reference and proxy info
-        assert device.parent_id, 'Invalid Parent ID'
-        assert device.proxy_address.device_id, 'Invalid Device ID'
+        try:
+            # first we verify that we got parent reference and proxy info
+            assert device.parent_id, 'Invalid Parent ID'
+            assert device.proxy_address.device_id, 'Invalid Device ID'
 
-        if device.vlan:
-            # vlan non-zero if created via legacy method (not xPON). Also
-            # Set a random serial number since not xPON based
-            self._olt_created = True
+            if device.vlan:
+                # vlan non-zero if created via legacy method (not xPON).
+                self._olt_created = True
 
-        # register for proxied messages right away
-        self.proxy_address = device.proxy_address
-        self.adapter_agent.register_for_proxied_messages(device.proxy_address)
+            # register for proxied messages right away
+            self.proxy_address = device.proxy_address
+            self.adapter_agent.register_for_proxied_messages(device.proxy_address)
 
-        # initialize device info
-        device.root = True
-        device.vendor = 'Adtran Inc.'
-        device.model = 'n/a'
-        device.hardware_version = 'n/a'
-        device.firmware_version = 'n/a'
-        device.reason = ''
-        device.connect_status = ConnectStatus.UNKNOWN
+            # initialize device info
+            device.root = True
+            device.vendor = 'Adtran Inc.'
+            device.model = 'n/a'
+            device.hardware_version = 'n/a'
+            device.firmware_version = 'n/a'
+            device.reason = ''
+            device.connect_status = ConnectStatus.UNKNOWN
 
-        ############################################################################
-        # Setup PM configuration for this device
+            # Register physical ports.  Should have at least one of each
+            self._pon = PonPort.create(self, self._next_port_number)
+            self.adapter_agent.add_port(device.id, self._pon.get_port())
 
-        self.pm_metrics = OnuPmMetrics(self, device, grouped=True, freq_override=False)
-        pm_config = self.pm_metrics.make_proto()
-        self.log.info("initial-pm-config", pm_config=pm_config)
-        self.adapter_agent.update_device_pm_config(pm_config, init=True)
+            if self._olt_created:
+                # vlan non-zero if created via legacy method (not xPON). Also
+                # Set a random serial number since not xPON based
 
-        ############################################################################
-        # Setup Alarm handler
+                uni_port = UniPort.create(self, self._next_port_number, device.vlan,
+                                          'deprecated', device.vlan, None)
+                self._unis[uni_port.port_number] = uni_port
+                self.adapter_agent.add_port(device.id, uni_port.get_port())
 
-        self.alarms = AdapterAlarms(self.adapter, device.id)
+                device.serial_number = uuid4().hex
+                uni_port.add_logical_port(device.vlan, subscriber_vlan=device.vlan)
 
-        # reference of uni_port is required when re-enabling the device if
-        # it was disabled previously
-        # Need to query ONU for number of supported uni ports
-        # For now, temporarily set number of ports to 1 - port #2
+                # Start things up for this ONU Handler.
+                self.enabled = True
 
-        parent_device = self.adapter_agent.get_device(device.parent_id)
-        self.logical_device_id = parent_device.parent_id
-        assert self.logical_device_id, 'Invalid logical device ID'
+            ############################################################################
+            # Setup PM configuration for this device
+            # Pass in ONU specific options
+            kwargs = {
+                'heartbeat': self.heartbeat,
+                'omci-cc': self.openomci.omci_cc
+            }
+            self.pm_metrics = OnuPmMetrics(self.adapter_agent, self.device_id,
+                                           grouped=True, freq_override=False,
+                                           **kwargs)
+            pm_config = self.pm_metrics.make_proto()
+            self.openomci.set_pm_config(self.pm_metrics.omci_pm.openomci_interval_pm)
+            self.log.info("initial-pm-config", pm_config=pm_config)
+            self.adapter_agent.update_device_pm_config(pm_config, init=True)
 
-        # Register physical ports.  Should have at least one of each
+            # reference of uni_port is required when re-enabling the device if
+            # it was disabled previously
+            # Need to query ONU for number of supported uni ports
+            # For now, temporarily set number of ports to 1 - port #2
+            parent_device = self.adapter_agent.get_device(device.parent_id)
+            self.logical_device_id = parent_device.parent_id
+            assert self.logical_device_id, 'Invalid logical device ID'
+            self.adapter_agent.update_device(device)
 
-        self._pon = PonPort.create(self, self._next_port_number)
-        self.adapter_agent.add_port(device.id, self._pon.get_port())
+            ############################################################################
+            # Setup Alarm handler
+            self.alarms = AdapterAlarms(self.adapter_agent, device.id, self.logical_device_id)
 
-        if self._olt_created:
-            # vlan non-zero if created via legacy method (not xPON). Also
-            # Set a random serial number since not xPON based
+            ############################################################################
+            # Start collecting stats from the device after a brief pause
+            reactor.callLater(30, self.pm_metrics.start_collector)
 
-            uni_port = UniPort.create(self, self._next_port_number, device.vlan,
-                                      'deprecated', device.vlan, None)
-            self._unis[uni_port.port_number] = uni_port
-            self.adapter_agent.add_port(device.id, uni_port.get_port())
-
-            device.serial_number = uuid4().hex
-            uni_port.add_logical_port(device.vlan, subscriber_vlan=device.vlan)
-
-            # Start things up for this ONU Handler.
-            self.enabled = True
-
-        # Start collecting stats from the device after a brief pause
-        reactor.callLater(30, self.start_kpi_collection, device.id)
-
-        self.adapter_agent.update_device(device)
+        except Exception as e:
+            self.log.exception('activate-failure', e=e)
+            device.reason = 'Failed to activate: {}'.format(e.message)
+            device.connect_status = ConnectStatus.UNREACHABLE
+            device.oper_status = OperStatus.FAILED
+            self.adapter_agent.update_device(device)
 
     def reconcile(self, device):
         self.log.info('reconciling-ONU-device-starts')
@@ -329,34 +339,6 @@
         self.log.info('update_pm_config', pm_config=pm_config)
         self.pm_metrics.update(pm_config)
 
-    def start_kpi_collection(self, device_id):
-        # TODO: This has not been tested
-        def _collect(device_id, prefix):
-            from voltha.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
-
-            if self.enabled:
-                try:
-                    # Step 1: gather metrics from device
-                    port_metrics = self.pm_metrics.collect_port_metrics()
-
-                    # Step 2: prepare the KpiEvent for submission
-                    # we can time-stamp them here or could use time derived from OLT
-                    ts = arrow.utcnow().timestamp
-                    kpi_event = KpiEvent(
-                        type=KpiEventType.slice,
-                        ts=ts,
-                        prefixes={
-                            prefix + '.{}'.format(k): MetricValuePairs(metrics=port_metrics[k])
-                            for k in port_metrics.keys()}
-                    )
-                    # Step 3: submit
-                    self.adapter_agent.submit_kpis(kpi_event)
-
-                except Exception as e:
-                    self.log.exception('failed-to-submit-kpis', e=e)
-
-        self.pm_metrics.start_collector(_collect)
-
     @inlineCallbacks
     def update_flow_table(self, device, flows):
         #
diff --git a/voltha/adapters/adtran_onu/heartbeat.py b/voltha/adapters/adtran_onu/heartbeat.py
index 2d5796f..f0efe30 100644
--- a/voltha/adapters/adtran_onu/heartbeat.py
+++ b/voltha/adapters/adtran_onu/heartbeat.py
@@ -149,7 +149,7 @@
         device = self._handler.adapter_agent.get_device(self._device_id)
 
         try:
-            from ..adtran_olt.alarms.heartbeat_alarm import HeartbeatAlarm
+            from voltha.extensions.alarms.heartbeat_alarm import HeartbeatAlarm
 
             if self._heartbeat_miss >= self.heartbeat_failed_limit:
                 if device.connect_status == ConnectStatus.REACHABLE:
@@ -158,7 +158,7 @@
                     device.oper_status = OperStatus.FAILED
                     device.reason = self.heartbeat_last_reason
                     self._handler.adapter_agent.update_device(device)
-                    HeartbeatAlarm(self._handler, 'onu', self._heartbeat_miss).raise_alarm()
+                    HeartbeatAlarm(self._handler.alarms, 'onu', self._heartbeat_miss).raise_alarm()
                     self._alarm_active = True
                     self.on_heartbeat_alarm(True)
             else:
@@ -168,7 +168,7 @@
                     device.oper_status = OperStatus.ACTIVE
                     device.reason = ''
                     self._handler.adapter_agent.update_device(device)
-                    HeartbeatAlarm(self._handler, 'onu').clear_alarm()
+                    HeartbeatAlarm(self._handler.alarms, 'onu').clear_alarm()
 
                     self._alarm_active = False
                     self._alarms_raised_count += 1
diff --git a/voltha/adapters/adtran_onu/omci/omci.py b/voltha/adapters/adtran_onu/omci/omci.py
index 0e4791f..2b008a5 100644
--- a/voltha/adapters/adtran_onu/omci/omci.py
+++ b/voltha/adapters/adtran_onu/omci/omci.py
@@ -57,7 +57,7 @@
 
         self._onu_omci_device = omci_agent.add_device(handler.device_id,
                                                       handler.adapter_agent,
-                                                      onu_custom_me_entities(),
+                                                      custom_me_map=onu_custom_me_entities(),
                                                       support_classes=handler.adapter.adtran_omci)
 
     def __str__(self):
@@ -151,6 +151,15 @@
     def onu_omci_device(self):
         return self._onu_omci_device
 
+    def set_pm_config(self, pm_config):
+        """
+        Set PM interval configuration
+
+        :param pm_config: (OnuPmIntervalMetrics) PM Interval configuration
+        :return:
+        """
+        self.onu_omci_device.set_pm_config(pm_config)
+
     def _mib_in_sync(self):
         """
         This method is ran whenever the ONU MIB database is in-sync. This is often after
@@ -233,7 +242,7 @@
 
         except Exception as e:
             self.log.exception('device-info-load', e=e)
-            self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self._mib_in_sync())
+            self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self._mib_in_sync)
 
     def gem_or_tcont_added(self):
         if self._in_sync_reached:
diff --git a/voltha/adapters/adtran_onu/onu_pm_metrics.py b/voltha/adapters/adtran_onu/onu_pm_metrics.py
deleted file mode 100644
index 20a45b3..0000000
--- a/voltha/adapters/adtran_onu/onu_pm_metrics.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# Copyright 2017-present Adtran, Inc.
-#
-# 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.
-
-from voltha.protos.device_pb2 import PmConfig, PmConfigs, PmGroupConfig
-from ..adtran_olt.pki.adapter_pm_metrics import AdapterPmMetrics
-
-
-class OnuPmMetrics(AdapterPmMetrics):
-    def __init__(self, handler, device, grouped=False, freq_override=False):
-        super(OnuPmMetrics, self).__init__(handler, device,
-                                           grouped=grouped, freq_override=freq_override)
-
-        # PM Config Types are COUNTER, GUAGE, and STATE  # Note: GAUGE is misspelled in device.proto
-        self.omci_pm_names = {
-            ('enabled', PmConfig.STATE),
-            ('tx_frames', PmConfig.COUNTER),
-            ('tx_errors', PmConfig.COUNTER),
-            ('rx_frames', PmConfig.COUNTER),
-            ('rx_unknown_tid', PmConfig.COUNTER),
-            ('rx_onu_frames', PmConfig.COUNTER),        # Rx ONU autonomous messages
-            ('rx_alarm_overflow', PmConfig.COUNTER),    # Autonomous ONU generated alarm message overflows
-            ('rx_avc_overflow', PmConfig.COUNTER),      # Autonomous ONU generated AVC message overflows
-            ('rx_onu_discards', PmConfig.COUNTER),      # Autonomous ONU message unknown type discards
-            ('rx_unknown_me', PmConfig.COUNTER),        # Managed Entities without a decode definition
-            ('rx_timeouts', PmConfig.COUNTER),
-            ('consecutive_errors', PmConfig.COUNTER),
-            ('reply_min', PmConfig.GUAGE),      # Milliseconds
-            ('reply_max', PmConfig.GUAGE),      # Milliseconds
-            ('reply_average', PmConfig.GUAGE),  # Milliseconds
-        }
-        self.health_pm_names = {
-            ('enabled', PmConfig.STATE),
-            ('alarm_active', PmConfig.STATE),
-            ('heartbeat_count', PmConfig.COUNTER),
-            ('heartbeat_miss', PmConfig.COUNTER),
-            ('alarms_raised_count', PmConfig.COUNTER),
-            ('heartbeat_failed_limit', PmConfig.COUNTER),
-            ('heartbeat_interval', PmConfig.COUNTER),
-        }
-        # TODO Add PON Port PM
-        # TODO Add UNI Port PM
-
-        self.omci_metrics_config = {m: PmConfig(name=m, type=t, enabled=True)
-                                    for (m, t) in self.omci_pm_names}
-        self.health_metrics_config = {m: PmConfig(name=m, type=t, enabled=True)
-                                      for (m, t) in self.health_pm_names}
-
-    def update(self, pm_config):
-        # TODO: Test both 'group' and 'non-group' functionality
-        # TODO: Test frequency override capability for a particular group
-        if self.default_freq != pm_config.default_freq:
-            # Update the callback to the new frequency.
-            self.default_freq = pm_config.default_freq
-            self.lc.stop()
-            self.lc.start(interval=self.default_freq / 10)
-
-        if pm_config.grouped is True:
-            for m in pm_config.groups:
-                pass
-                # self.pm_group_metrics[m.group_name].config.enabled = m.enabled
-                # if m.enabled is True:,
-            ('tx_errors', PmConfig.COUNTER),
-            ('rx_frames', PmConfig.COUNTER),
-                #     self.enable_pm_collection(m.group_name, remote)
-                # else:
-                #     self.disable_pm_collection(m.group_name, remote)
-        else:
-            for m in pm_config.metrics:
-                self.omci_metrics_config[m.name].enabled = m.enabled
-                self.health_metrics_config[m.name].enabled = m.enabled
-
-    def make_proto(self):
-        pm_config = PmConfigs(id=self.id, default_freq=self.default_freq,
-                              grouped=self.grouped,
-                              freq_override=self.freq_override)
-        metrics = set()
-
-        if self.grouped:
-            pm_omci_stats = PmGroupConfig(group_name='OMCI',
-                                          group_freq=self.default_freq,
-                                          enabled=True)
-
-            pm_health_stats = PmGroupConfig(group_name='Heartbeat',
-                                            group_freq=self.default_freq,
-                                            enabled=True)
-            # TODO Add PON Port PM
-            # TODO Add UNI Port PM
-        else:
-            pm_omci_stats = pm_config
-            pm_health_stats = pm_config
-            # TODO Add PON Port PM
-            # TODO Add UNI Port PM
-
-        for m in sorted(self.omci_metrics_config):
-            pm = self.omci_metrics_config[m]
-            if not self.grouped:
-                if pm.name in metrics:
-                    continue
-                metrics.add(pm.name)
-
-            pm_omci_stats.metrics.extend([PmConfig(name=pm.name,
-                                                   type=pm.type,
-                                                   enabled=pm.enabled)])
-
-        for m in sorted(self.health_metrics_config):
-            pm = self.health_metrics_config[m]
-            if not self.grouped:
-                if pm.name in metrics:
-                    continue
-                metrics.add(pm.name)
-
-            pm_health_stats.metrics.extend([PmConfig(name=pm.name,
-                                                     type=pm.type,
-                                                     enabled=pm.enabled)])
-
-        return pm_config
-
-    def collect_port_metrics(self):
-        metrics = dict()
-        metrics['omci'] = self.collect_metrics(self.handler.openomci.omci_cc,
-                                               self.omci_pm_names,
-                                               self.omci_metrics_config)
-
-        metrics['heartbeat'] = self.collect_metrics(self.handler.heartbeat,
-                                                    self.health_pm_names,
-                                                    self.health_metrics_config)
-
-        # TODO Add PON Port PM
-        # TODO Add UNI Port PM
-
-        return metrics
-
-
-
-
-
diff --git a/voltha/adapters/adtran_onu/uni_port.py b/voltha/adapters/adtran_onu/uni_port.py
index 8663b38..32636a2 100644
--- a/voltha/adapters/adtran_onu/uni_port.py
+++ b/voltha/adapters/adtran_onu/uni_port.py
@@ -212,9 +212,9 @@
         if self._logical_port_number is not None:
             # delete old logical port if it exists
             try:
-                port = self.adapter_agent.get_logical_port(self._handler.logical_device_id,
-                                                           self.port_id_name())
-                self.adapter_agent.delete_logical_port(self._handler.logical_device_id, port)
+                port = self._handler.adapter_agent.get_logical_port(self._handler.logical_device_id,
+                                                                    self.port_id_name())
+                self._handler.adapter_agent.delete_logical_port(self._handler.logical_device_id, port)
 
             except Exception as e:
                 # assume this exception was because logical port does not already exist