VOL-701: Added OMCI Alarm table support to CLI

Change-Id: Ia2250579c0cc274406837fc546e80dedfbeece93
diff --git a/cli/omci.py b/cli/omci.py
index 17e24ba..9b11d32 100644
--- a/cli/omci.py
+++ b/cli/omci.py
@@ -27,6 +27,8 @@
 from voltha.protos import voltha_pb2
 from voltha.protos.omci_mib_db_pb2 import MibDeviceData, MibClassData, \
     MibInstanceData
+from voltha.protos.omci_alarm_db_pb2 import AlarmDeviceData, AlarmClassData, \
+    AlarmInstanceData
 from os import linesep
 
 _ = third_party
@@ -96,8 +98,8 @@
         try:
             res = stub.GetMibDeviceData(voltha_pb2.ID(id=device_id),
                                         metadata=(('get-depth', str(depth)), ))
-        except Exception as e:
-            pass
+        except Exception as _e:
+            res = None
 
         return res
 
@@ -145,6 +147,11 @@
                                        .format(device_id), 'red'))
             return
 
+        if mib_db is None:
+            self.poutput(self.colorize('MIB database for ONU {} is not currently available'
+                                       .format(device_id), 'red'))
+            return
+
         mib = self._device_to_dict(mib_db)
 
         self.poutput('OpenOMCI MIB Database for ONU {}'.format(device_id))
@@ -212,8 +219,8 @@
         return ' '.join([v[0].upper() + v[1:] for v in attr.split('_')])
 
     def _instance_to_dict(self, instance):
-        if not isinstance(instance, MibInstanceData):
-            raise TypeError('{} is not of type MibInstanceData'.format(type(instance)))
+        if not isinstance(instance, (MibInstanceData, AlarmInstanceData)):
+            raise TypeError('{} is not of type MIB/Alarm Instance Data'.format(type(instance)))
 
         data = {
             OmciCli.INSTANCE_ID_KEY: instance.instance_id,
@@ -227,8 +234,8 @@
         return data
 
     def _class_to_dict(self, val):
-        if not isinstance(val, MibClassData):
-            raise TypeError('{} is not of type MibClassData'.format(type(val)))
+        if not isinstance(val, (MibClassData, AlarmClassData)):
+            raise TypeError('{} is not of type MIB/Alarm Class Data'.format(type(val)))
 
         data = {
             OmciCli.CLASS_ID_KEY: val.class_id,
@@ -239,7 +246,7 @@
 
     def _device_to_dict(self, val):
         if not isinstance(val, MibDeviceData):
-            raise TypeError('{} is not of type MibDeviceData'.format(type(val)))
+            raise TypeError('{} is not of type MIB Device Data'.format(type(val)))
 
         data = {
             OmciCli.DEVICE_ID_KEY: val.device_id,
@@ -279,6 +286,10 @@
 
         try:
             mib_db = self.get_device_mib(device_id, depth=1)
+            if mib_db is None:
+                self.poutput(self.colorize('Supported ME information for ONU {} is not currently available'
+                                           .format(device_id), 'red'))
+                return
             mib = self._device_to_dict(mib_db)
 
         except Exception:   # UnboundLocalError if Device ID not found in DB
@@ -308,6 +319,11 @@
 
         try:
             mib_db = self.get_device_mib(device_id, depth=1)
+            if mib_db is None:
+                self.poutput(self.colorize('Message Types for ONU {} are not currently available'
+                                           .format(device_id), 'red'))
+                return
+
             mib = self._device_to_dict(mib_db)
 
         except Exception:   # UnboundLocalError if Device ID not found in DB
@@ -347,7 +363,7 @@
         print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
 
     def help_devices(self):
-        self.poutput('TODO: Provide some help')
+        self.poutput('List devices registered in Voltha')
 
     def poutput(self, msg):
         """Convenient shortcut for self.stdout.write(); adds newline if necessary."""
@@ -359,3 +375,106 @@
     def do_show(self, _):
         """Show detailed omci information"""
         self.poutput('Use show_mib, show_alarms, show_me, show_msg_types for detailed OMCI information')
+
+    def get_alarm_table(self, device_id, depth=-1):
+        stub = self.get_stub()
+
+        try:
+            res = stub.GetAlarmDeviceData(voltha_pb2.ID(id=device_id),
+                                          metadata=(('get-depth', str(depth)), ))
+        except Exception as _e:
+            res = None
+
+        return res
+
+    def _alarms_to_dict(self, val):
+        if not isinstance(val, AlarmDeviceData):
+            raise TypeError('{} is not of type Alarm Device Data'.format(type(val)))
+
+        data = {
+            OmciCli.DEVICE_ID_KEY: val.device_id,
+            OmciCli.CREATED_KEY: self._string_to_time(val.created),
+            OmciCli.VERSION_KEY: val.version
+        }
+        for class_data in val.classes:
+            data[class_data.class_id] = self._class_to_dict(class_data)
+
+        return data
+
+    def help_show_alarms(self):
+        self.poutput('show_alarms [-d <device-id>]' +
+                     linesep + '-d: <device-id>   ONU Device ID')
+
+    @options([
+        make_option('-d', '--device-id', action="store", dest='device_id', type='string',
+                    help='ONU Device ID', default=None),
+    ])
+    def do_show_alarms(self, _line, opts):
+        """ Show contents of the alarm table"""
+        device_id = opts.device_id or self.device_id
+
+        try:
+            alarm_db = self.get_alarm_table(device_id, depth=-1)
+            if alarm_db is None:
+                self.poutput(self.colorize('Alarm Table for ONU {} is not currently available'
+                                           .format(device_id), 'red'))
+                return
+
+        except Exception:   # UnboundLocalError if Device ID not found in DB
+            self.poutput(self.colorize('Failed to get Alarm Table for ONU {}'
+                                       .format(device_id), 'red'))
+            return
+
+        alarms = self._alarms_to_dict(alarm_db)
+        self.poutput('OpenOMCI Alarm Table for ONU {}'.format(device_id))
+        self.poutput('Version            : {}'.format(alarms[OmciCli.VERSION_KEY]))
+        self.poutput('Created            : {}'.format(alarms[OmciCli.CREATED_KEY]))
+
+        class_ids = [k for k in alarms.iterkeys() if isinstance(k, int)]
+        class_ids.sort()
+
+        if len(class_ids) == 0:
+            self.poutput('No active alarms')
+            return
+
+        for cls_id in class_ids:
+            from omci_alarm_info import _alarm_info
+            class_data = alarms[cls_id]
+            info = _alarm_info.get(cls_id)
+
+            self.poutput('  ----------------------------------------------')
+            self.poutput('  Class ID: {0} - ({0:#x}): {1}'.
+                         format(cls_id,
+                                info.get('name') if info is not None else 'Unknown Class ID'))
+
+            inst_ids = [k for k in class_data.iterkeys() if isinstance(k, int)]
+            inst_ids.sort()
+
+            for inst_id in inst_ids:
+                inst_data = class_data[inst_id]
+                self.poutput('    Instance ID  : {0} - ({0:#x})'.format(inst_id))
+                self.poutput('    Created      : {}'.format(inst_data[OmciCli.CREATED_KEY]))
+                self.poutput('    Modified     : {}'.format(inst_data[OmciCli.MODIFIED_KEY]))
+
+                try:
+                    alarm_value = int(inst_data[OmciCli.ATTRIBUTES_KEY]['alarm_bit_map'])
+                except ValueError:
+                    alarm_value = 0
+
+                if alarm_value == 0:
+                    self.poutput('    Active Alarms: No Active Alarms')
+
+                else:
+                    padding = '    Active Alarms:'
+                    for alarm_no in xrange(0, 224):
+                        if (1 << (223 - alarm_no)) & alarm_value:
+                            if info is None:
+                                txt = 'Unknown alarm number'
+                            else:
+                                txt = info.get(alarm_no, 'Unknown alarm number')
+
+                            self.poutput('{} {}: {}'.format(padding, alarm_no, txt))
+                            padding = '                  '
+
+                    if inst_id is not inst_ids[-1]:
+                        self.poutput(linesep)
diff --git a/cli/omci_alarm_info.py b/cli/omci_alarm_info.py
new file mode 100644
index 0000000..31e6c08
--- /dev/null
+++ b/cli/omci_alarm_info.py
@@ -0,0 +1,172 @@
+#
+# 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.
+#
+
+_alarm_info = {
+    5: {
+        'name': 'CardHolder',
+        0: 'Plug-in circuit pack missing',
+        1: 'Plug-in type mismatch alarm',
+        2: 'Improper card removal',
+        3: 'Plug-in equipment ID mismatch alarm',
+        4: 'Protection switch',
+    },
+    6: {
+        'name': 'CircuitPack',
+        0: 'Equipment alarm',
+        1: 'Powering alarm',
+        2: 'Self-test failure',
+        3: 'Laser end of life',
+        4: 'Temperature yellow',
+        5: 'Temperature red',
+
+    },
+    11: {
+        'name': 'PptpEthernetUni',
+        0: 'LAN Loss Of Signal',
+    },
+    47: {
+        'name': 'MacBridgePortConfigurationData',
+        0: 'Port blocking',
+    },
+    256: {
+        'name': 'OntG',
+        0: 'Equipment alarm',
+        1: 'Powering alarm',
+        2: 'Battery missing',
+        3: 'Battery failure',
+        4: 'Battery low',
+        5: 'Physical intrusion',
+        6: 'Self-test failure',
+        7: 'Dying gasp',
+        8: 'Temperature yellow',
+        9: 'Temperature red',
+        10: 'Voltage yellow',
+        11: 'Voltage red',
+        12: 'ONU manual power off',
+        13: 'Invalid image',
+        14: 'PSE overload yellow',
+        15: 'PSE overload red',
+    },
+    263: {
+        'name': 'AniG',
+        0: 'Low received optical power',
+        1: 'High received optical power',
+        2: 'Signal fail',
+        3: 'Signal degrade',
+        4: 'Low transmit optical power',
+        5: 'High transmit optical power',
+        6: 'Laser bias current',
+    },
+    266: {
+        'name': 'GemInterworkingTp',
+        6: 'Operational state change',
+    },
+    268: {
+        'name': 'GemPortNetworkCtp',
+        5: 'End-to-end loss of continuity',
+    },
+    277: {
+        'name': 'PriorityQueueG',
+        0: 'Block loss',
+    },
+    281: {
+        'name': 'MulticastGemInterworkingTp',
+        0: 'Deprecated',
+    },
+    309: {
+        'name': 'MulticastOperationsProfile',
+        0: 'Lost multicast group',
+    },
+    329: {
+        'name': 'VirtualEthernetInterfacePt',
+        0: 'Connecting function fail',
+    },
+    24: {
+        'name': 'EthernetPMMonitoringHistoryData',
+        0: 'FCS errors',
+        1: 'Excessive collision counter',
+        2: 'Late collision counter',
+        3: 'Frames too long',
+        4: 'Buffer overflows on receive',
+        5: 'Buffer overflows on transmit',
+        6: 'Single collision frame counter',
+        7: 'Multiple collision frame counter',
+        8: 'SQE counter',
+        9: 'Deferred transmission counter',
+        10: 'Internal MAC transmit error counter',
+        11: 'Carrier sense error counter',
+        12: 'Alignment error counter',
+        13: 'Internal MAC receive error counter',
+    },
+    312: {
+        'name': 'FecPerformanceMonitoringHistoryData',
+        0: 'Corrected bytes',
+        1: 'Corrected code words',
+        2: 'Uncorrectable code words',
+        4: 'FEC seconds',
+    },
+    321: {
+        'name': 'EthernetFrameDownstreamPerformanceMonitoringHistoryData',
+        0: 'Drop events',
+        1: 'CRC errored packets',
+        2: 'Undersize packets',
+        3: 'Oversize packets',
+    },
+    322: {
+        'name': 'EthernetFrameUpstreamPerformanceMonitoringHistoryData',
+        0: 'Drop events',
+        1: 'CRC errored packets',
+        2: 'Undersize packets',
+        3: 'Oversize packets',
+    },
+    329: {
+        'name': 'VeipUni',
+        0: 'Connecting function fail'
+    },
+    334: {
+        'name': 'EthernetFrameExtendedPerformanceMonitoring',
+        0: 'Drop events',
+        1: 'CRC errored packets',
+        2: 'Undersize packets',
+        3: 'Oversize packets',
+    },
+    426: {
+        'name': 'EthernetFrameExtendedPerformanceMonitoring64Bit',
+        0: 'Drop events',
+        1: 'CRC errored packets',
+        2: 'Undersize packets',
+        3: 'Oversize packets',
+    },
+    341: {
+        'name': 'GemPortNetworkCtpMonitoringHistoryData',
+        1: 'Encryption key errors',
+    },
+    344: {
+        'name': 'XgPonTcPerformanceMonitoringHistoryData',
+        1: 'PSBd HEC error count',
+        2: 'XGTC HEC error count',
+        3: 'Unknown profile count',
+        4: 'XGEM HEC loss count',
+        5: 'XGEM key errors',
+        6: 'XGEM HEC error count',
+    },
+    345: {
+        'name': 'anceMonitoringHistoryData',
+        1: 'PLOAM MIC error count',
+        2: 'OMCI MIC error count',
+    },
+}
+