VOL-700: OpenOMCI MIB Database CLI/NBI (REST) implementation

Change-Id: Ib53530ad99854ecae2424cf01944baeeb731ce02
diff --git a/cli/main.py b/cli/main.py
index 544b774..7674182 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -30,6 +30,7 @@
 
 from cli.device import DeviceCli
 from cli.xpon import XponCli
+from cli.omci import OmciCli
 from cli.alarm_filters import AlarmFiltersCli
 from cli.logical_device import LogicalDeviceCli
 from cli.table import print_pb_list_as_table
@@ -277,6 +278,15 @@
         sub = XponCli(self.get_channel, device_id)
         sub.cmdloop()
 
+    def do_omci(self, line):
+        """omci <device_ID> - Enter OMCI level command mode"""
+
+        device_id = line.strip() or self.default_device_id
+        if not device_id:
+            raise Exception('<device-id> parameter needed')
+        sub = OmciCli(device_id, self.get_stub)
+        sub.cmdloop()
+
     def do_pdb(self, line):
         """Launch PDB debug prompt in CLI (for CLI development)"""
         from pdb import set_trace
@@ -348,7 +358,7 @@
                     break
                 self.poutput('waiting for device to be enabled...')
                 sleep(.5)
-        except Exception as  e:
+        except Exception as e:
             self.poutput('Error enabling {}.  Error:{}'.format(device_id, e))
 
     complete_activate_olt = complete_device
diff --git a/cli/omci.py b/cli/omci.py
new file mode 100644
index 0000000..146f296
--- /dev/null
+++ b/cli/omci.py
@@ -0,0 +1,356 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+"""
+OpenOMCI level CLI commands
+"""
+from optparse import make_option
+from cmd2 import Cmd, options
+from datetime import datetime
+from google.protobuf.empty_pb2 import Empty
+from cli.table import print_pb_list_as_table
+from voltha.protos import third_party
+from voltha.protos import voltha_pb2
+from voltha.protos.omci_mib_db_pb2 import MibDeviceData, MibClassData, \
+    MibInstanceData
+from os import linesep
+
+_ = third_party
+
+
+class OmciCli(Cmd):
+    CREATED_KEY = 'created'
+    MODIFIED_KEY = 'modified'
+    MDS_KEY = 'mib_data_sync'
+    LAST_SYNC_KEY = 'last_mib_sync'
+    VERSION_KEY = 'version'
+    DEVICE_ID_KEY = 'device_id'
+    CLASS_ID_KEY = 'class_id'
+    INSTANCE_ID_KEY = 'instance_id'
+    ATTRIBUTES_KEY = 'attributes'
+    TIME_FORMAT = '%Y%m%d-%H%M%S.%f'
+    ME_KEY = 'managed_entities'
+    MSG_TYPE_KEY = 'message_types'
+
+    MSG_TYPE_TO_NAME = {
+        4: 'Create',
+        5: 'Create Complete',
+        6: 'Delete',
+        8: 'Set',
+        9: 'Get',
+        10: 'Get Complete',
+        11: 'Get All Alarms',
+        12: 'Get All Alarms Next',
+        13: 'Mib Upload',
+        14: 'Mib Upload Next',
+        15: 'Mib Reset',
+        16: 'Alarm Notification',
+        17: 'Attribute Value Change',
+        18: 'Test',
+        19: 'Start Software Download',
+        20: 'Download Section',
+        21: 'End Software Download',
+        22: 'Activate Software',
+        23: 'Commit Software',
+        24: 'Synchronize Time',
+        25: 'Reboot',
+        26: 'Get Next',
+        27: 'Test Result',
+        28: 'Get Current Data',
+        29: 'Set Table'
+    }
+
+    def __init__(self, device_id, get_stub):
+        Cmd.__init__(self)
+        self.get_stub = get_stub
+        self.device_id = device_id
+        self.prompt = '(' + self.colorize(
+            self.colorize('omci {}'.format(device_id), 'green'),
+            'bold') + ') '
+
+    def cmdloop(self, intro=None):
+        self._cmdloop()
+
+    do_exit = Cmd.do_quit
+
+    def do_quit(self, line):
+        return self._STOP_AND_EXIT
+
+    def get_device_mib(self, device_id, depth=-1):
+        stub = self.get_stub()
+
+        try:
+            res = stub.GetMibDeviceData(voltha_pb2.ID(id=device_id),
+                                        metadata=(('get-depth', str(depth)), ))
+        except Exception as e:
+            pass
+
+        return res
+
+    def help_show_mib(self):
+        self.poutput('show_mib [-d <device-id>] [-c <class-id> [-i <instance-id>]]' +
+                     linesep + '-d: <device-id>   ONU Device ID' +
+                     linesep + '-c: <class-id>    Managed Entity Class ID' +
+                     linesep + '-i: <instance-id> ME Instance ID')
+
+    @options([
+        make_option('-d', '--device-id', action="store", dest='device_id', type='string',
+                    help='ONU Device ID', default=None),
+        make_option('-c', '--class-id', action="store", dest='class_id',
+                    type='int', help='Managed Entity Class ID', default=None),
+        make_option('-i', '--instance-id', action="store", dest='instance_id',
+                    type='int', help='ME Instance ID', default=None)
+    ])
+    def do_show_mib(self, _line, opts):
+        """
+        Show OMCI MIB Database Information
+        """
+        device_id = opts.device_id or self.device_id
+
+        if opts.class_id is not None and not 1 <= opts.class_id <= 0xFFFF:
+            self.poutput(self.colorize('Error: ', 'red') +
+                         self.colorize('Class ID must be 1..65535', 'blue'))
+            return
+
+        if opts.instance_id is not None and opts.class_id is None:
+            self.poutput(self.colorize('Error: ', 'red') +
+                         self.colorize('Class ID required if specifying an Instance ID',
+                                       'blue'))
+            return
+
+        if opts.instance_id is not None and not 0 <= opts.instance_id <= 0xFFFF:
+            self.poutput(self.colorize('Error: ', 'red') +
+                         self.colorize('Instance ID must be 0..65535', 'blue'))
+            return
+
+        try:
+            mib_db = self.get_device_mib(device_id, depth=-1)
+
+        except Exception:   # UnboundLocalError if Device ID not found in DB
+            self.poutput(self.colorize('Failed to get MIB database for ONU {}'
+                                       .format(device_id), 'red'))
+            return
+
+        mib = self._device_to_dict(mib_db)
+
+        self.poutput('OpenOMCI MIB Database for ONU {}'.format(device_id))
+
+        if opts.class_id is None and opts.instance_id is None:
+            self.poutput('Version            : {}'.format(mib[OmciCli.VERSION_KEY]))
+            self.poutput('Created            : {}'.format(mib[OmciCli.CREATED_KEY]))
+            self.poutput('Last In-Sync Time  : {}'.format(mib[OmciCli.LAST_SYNC_KEY]))
+            self.poutput('MIB Data Sync Value: {}'.format(mib[OmciCli.MDS_KEY]))
+
+        class_ids = [k for k in mib.iterkeys()
+                     if isinstance(k, int) and
+                     (opts.class_id is None or opts.class_id == k)]
+        class_ids.sort()
+
+        if len(class_ids) == 0 and opts.class_id is not None:
+            self.poutput(self.colorize('Class ID {} not found in MIB Database'
+                                       .format(opts.class_id), 'red'))
+            return
+
+        for cls_id in class_ids:
+            class_data = mib[cls_id]
+            self.poutput('  ----------------------------------------------')
+            self.poutput('  Class ID: {0} - ({0:#x})'.format(cls_id))
+
+            inst_ids = [k for k in class_data.iterkeys()
+                        if isinstance(k, int) and
+                        (opts.instance_id is None or opts.instance_id == k)]
+            inst_ids.sort()
+
+            if len(inst_ids) == 0 and opts.instance_id is not None:
+                self.poutput(self.colorize('Instance ID {} of Class ID {} not ' +
+                                           'found in MIB Database'.
+                                           format(opts.instance_id, opts.class_id),
+                                           'red'))
+                return
+
+            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]))
+
+                attributes = inst_data[OmciCli.ATTRIBUTES_KEY]
+                attr_names = attributes.keys()
+                attr_names.sort()
+                max_len = max([len(attr) for attr in attr_names])
+
+                for attr in attr_names:
+                    name = self._cleanup_attribute_name(attr).ljust(max_len)
+                    value = attributes[attr]
+                    try:
+                        ivalue = int(value)
+                        self.poutput('      {0}: {1} - ({1:#x})'.format(name, ivalue))
+
+                    except ValueError:
+                        self.poutput('      {}: {}'.format(name, value))
+
+                if inst_id is not inst_ids[-1]:
+                    self.poutput(linesep)
+
+    def _cleanup_attribute_name(self, attr):
+        """Change underscore to space and capitalize first character"""
+        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)))
+
+        data = {
+            OmciCli.INSTANCE_ID_KEY: instance.instance_id,
+            OmciCli.CREATED_KEY: self._string_to_time(instance.created),
+            OmciCli.MODIFIED_KEY: self._string_to_time(instance.modified),
+            OmciCli.ATTRIBUTES_KEY: dict()
+        }
+        for attribute in instance.attributes:
+            data[OmciCli.ATTRIBUTES_KEY][attribute.name] = str(attribute.value)
+
+        return data
+
+    def _class_to_dict(self, val):
+        if not isinstance(val, MibClassData):
+            raise TypeError('{} is not of type MibClassData'.format(type(val)))
+
+        data = {
+            OmciCli.CLASS_ID_KEY: val.class_id,
+        }
+        for instance in val.instances:
+            data[instance.instance_id] = self._instance_to_dict(instance)
+        return data
+
+    def _device_to_dict(self, val):
+        if not isinstance(val, MibDeviceData):
+            raise TypeError('{} is not of type MibDeviceData'.format(type(val)))
+
+        data = {
+            OmciCli.DEVICE_ID_KEY: val.device_id,
+            OmciCli.CREATED_KEY: self._string_to_time(val.created),
+            OmciCli.LAST_SYNC_KEY: self._string_to_time(val.last_sync_time),
+            OmciCli.MDS_KEY: val.mib_data_sync,
+            OmciCli.VERSION_KEY: val.version,
+            OmciCli.ME_KEY: dict(),
+            OmciCli.MSG_TYPE_KEY: set()
+        }
+        for class_data in val.classes:
+            data[class_data.class_id] = self._class_to_dict(class_data)
+
+        for managed_entity in val.managed_entities:
+            data[OmciCli.ME_KEY][managed_entity.class_id] = managed_entity.name
+
+        for msg_type in val.message_types:
+            data[OmciCli.MSG_TYPE_KEY].add(msg_type.message_type)
+
+        return data
+
+    def _string_to_time(self, time):
+        return datetime.strptime(time, OmciCli.TIME_FORMAT) if len(time) else None
+
+    def help_show_me(self):
+        self.poutput('show_me [-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_me(self, _line, opts):
+        """ Show supported OMCI Managed Entities"""
+
+        device_id = opts.device_id or self.device_id
+
+        try:
+            mib_db = self.get_device_mib(device_id, depth=1)
+            mib = self._device_to_dict(mib_db)
+
+        except Exception:   # UnboundLocalError if Device ID not found in DB
+            self.poutput(self.colorize('Failed to get supported ME information for ONU {}'
+                                       .format(device_id), 'red'))
+            return
+
+        class_ids = [class_id for class_id in mib[OmciCli.ME_KEY].keys()]
+        class_ids.sort()
+
+        self.poutput('Supported Managed Entities for ONU {}'.format(device_id))
+        for class_id in class_ids:
+            self.poutput('    {0} - ({0:#x}): {1}'.format(class_id,
+                                                          mib[OmciCli.ME_KEY][class_id]))
+
+    def help_show_msg_types(self):
+        self.poutput('show_msg_types [-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_msg_types(self, _line, opts):
+        """ Show supported OMCI Message Types"""
+        device_id = opts.device_id or self.device_id
+
+        try:
+            mib_db = self.get_device_mib(device_id, depth=1)
+            mib = self._device_to_dict(mib_db)
+
+        except Exception:   # UnboundLocalError if Device ID not found in DB
+            self.poutput(self.colorize('Failed to get supported Message Types for ONU {}'
+                                       .format(device_id), 'red'))
+            return
+
+        msg_types = [msg_type for msg_type in mib[OmciCli.MSG_TYPE_KEY]]
+        msg_types.sort()
+
+        self.poutput('Supported Message Types for ONU {}'.format(device_id))
+        for msg_type in msg_types:
+            self.poutput('    {0} - ({0:#x}): {1}'.
+                         format(msg_type,
+                                OmciCli.MSG_TYPE_TO_NAME.get(msg_type, 'Unknown')))
+
+    def get_devices(self):
+        stub = self.get_stub()
+        res = stub.ListDevices(Empty())
+        return res.items
+
+    def do_devices(self, line):
+        """List devices registered in Voltha reduced for OMCI menu"""
+        devices = self.get_devices()
+        omit_fields = {
+            'adapter',
+            'model',
+            'hardware_version',
+            'images',
+            'firmware_version',
+            'serial_number',
+            'vlan',
+            'root',
+            'extra_args',
+            'proxy_address',
+        }
+        print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
+
+    def help_devices(self):
+        self.poutput('TODO: Provide some help')
+
+    def poutput(self, msg):
+        """Convenient shortcut for self.stdout.write(); adds newline if necessary."""
+        if msg:
+            self.stdout.write(msg)
+            if msg[-1] != '\n':
+                self.stdout.write('\n')
diff --git a/voltha/core/global_handler.py b/voltha/core/global_handler.py
index bfaaf04..ee7901a 100644
--- a/voltha/core/global_handler.py
+++ b/voltha/core/global_handler.py
@@ -1741,3 +1741,21 @@
         else:
             log.debug('grpc-success-response', response=response)
             returnValue(response)
+
+    @twisted_async
+    @inlineCallbacks
+    def GetMibDeviceData(self, request, context):
+        log.info('grpc-request', request=request)
+        response = yield self.dispatcher.dispatch('GetMibDeviceData',
+                                                  request,
+                                                  context,
+                                                  id=request.id)
+        log.debug('grpc-response', response=response)
+        if isinstance(response, DispatchError):
+            log.warn('grpc-error-response', error=response.error_code)
+            context.set_details('Device \'{}\' error'.format(request.id))
+            context.set_code(response.error_code)
+            returnValue(MibDeviceData())
+        else:
+            log.debug('grpc-success-response', response=response)
+            returnValue(response)
diff --git a/voltha/core/local_handler.py b/voltha/core/local_handler.py
index 55a0cfc..7f5d2ad 100644
--- a/voltha/core/local_handler.py
+++ b/voltha/core/local_handler.py
@@ -36,6 +36,7 @@
 from voltha.protos.common_pb2 import OperationResp
 from voltha.protos.bbf_fiber_base_pb2 import AllMulticastDistributionSetData, AllMulticastGemportsConfigData
 from voltha.registry import registry
+from voltha.protos.omci_mib_db_pb2 import MibDeviceData
 from requests.api import request
 from common.utils.asleep import asleep
 
@@ -1332,3 +1333,25 @@
                      current=self.subscriber)
 
         return self.subscriber
+
+    @twisted_async
+    def GetMibDeviceData(self, request, context):
+        log.info('grpc-request', request=request)
+
+        depth = int(dict(context.invocation_metadata()).get('get-depth', -1))
+
+        if '/' in request.id:
+            context.set_details(
+                'Malformed device id \'{}\''.format(request.id))
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            return MibDeviceData()
+
+        try:
+            return self.root.get('/omci_mibs/' + request.id, depth=depth)
+
+        except KeyError:
+            context.set_details(
+                'OMCI MIB for Device \'{}\' not found'.format(request.id))
+            context.set_code(StatusCode.NOT_FOUND)
+            return MibDeviceData()
+
diff --git a/voltha/extensions/omci/database/mib_db_api.py b/voltha/extensions/omci/database/mib_db_api.py
index 38f5061..eb93323 100644
--- a/voltha/extensions/omci/database/mib_db_api.py
+++ b/voltha/extensions/omci/database/mib_db_api.py
@@ -30,6 +30,8 @@
 CLASS_ID_KEY = 'class_id'
 INSTANCE_ID_KEY = 'instance_id'
 ATTRIBUTES_KEY = 'attributes'
+ME_KEY = 'managed_entities'
+MSG_TYPE_KEY = 'message_types'
 
 
 class DatabaseStateError(Exception):
@@ -223,3 +225,21 @@
         :return: (int) The Value or None if not found
         """
         raise NotImplementedError('Implement this in your derive class')
+
+    def update_supported_managed_entities(self, device_id, managed_entities):
+        """
+        Update the supported OMCI Managed Entities for this device
+
+        :param device_id: (str) ONU Device ID
+        :param managed_entities: (set) Managed Entity class IDs
+        """
+        raise NotImplementedError('Implement this in your derive class')
+
+    def update_supported_message_types(self, device_id, msg_types):
+        """
+        Update the supported OMCI Managed Entities for this device
+
+        :param device_id: (str) ONU Device ID
+        :param msg_types: (set) Message Type values (ints)
+        """
+        raise NotImplementedError('Implement this in your derive class')
diff --git a/voltha/extensions/omci/database/mib_db_dict.py b/voltha/extensions/omci/database/mib_db_dict.py
index 663ce36..ae2f3a5 100644
--- a/voltha/extensions/omci/database/mib_db_dict.py
+++ b/voltha/extensions/omci/database/mib_db_dict.py
@@ -82,7 +82,9 @@
             CREATED_KEY: now,
             LAST_SYNC_KEY: None,
             MDS_KEY: 0,
-            VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION
+            VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION,
+            ME_KEY: dict(),
+            MSG_TYPE_KEY: set()
         }
 
     def remove(self, device_id):
@@ -125,7 +127,9 @@
             CREATED_KEY: device_db[CREATED_KEY],
             LAST_SYNC_KEY: device_db[LAST_SYNC_KEY],
             MDS_KEY: 0,
-            VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION
+            VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION,
+            ME_KEY: device_db[ME_KEY],
+            MSG_TYPE_KEY: device_db[MSG_TYPE_KEY]
         }
 
     def save_mib_data_sync(self, device_id, value):
@@ -430,3 +434,47 @@
 
         except Exception as e:
             pass
+
+    def update_supported_managed_entities(self, device_id, managed_entities):
+        """
+        Update the supported OMCI Managed Entities for this device
+
+        :param device_id: (str) ONU Device ID
+        :param managed_entities: (set) Managed Entity class IDs
+        """
+        now = datetime.utcnow()
+        try:
+            device_db = self._data[device_id]
+
+            entities = {class_id: self._managed_entity_to_name(device_id, class_id)
+                        for class_id in managed_entities}
+
+            device_db[ME_KEY] = entities
+            self._modified = now
+
+        except Exception as e:
+            self.log.error('set-me-failure', e=e)
+            raise
+
+    def _managed_entity_to_name(self, device_id, class_id):
+        me_map = self._omci_agent.get_device(device_id).me_map
+        entity = me_map.get(class_id)
+
+        return entity.__name__ if entity is not None else 'UnknownManagedEntity'
+
+    def update_supported_message_types(self, device_id, msg_types):
+        """
+        Update the supported OMCI Managed Entities for this device
+
+        :param device_id: (str) ONU Device ID
+        :param msg_types: (set) Message Type values (ints)
+        """
+        now = datetime.utcnow()
+        try:
+            msg_type_set = {msg_type.value for msg_type in msg_types}
+            self._data[device_id][MSG_TYPE_KEY] = msg_type_set
+            self._modified = now
+
+        except Exception as e:
+            self.log.error('set-me-failure', e=e)
+            raise
diff --git a/voltha/extensions/omci/database/mib_db_ext.py b/voltha/extensions/omci/database/mib_db_ext.py
index c1b84dd..0c8b179 100644
--- a/voltha/extensions/omci/database/mib_db_ext.py
+++ b/voltha/extensions/omci/database/mib_db_ext.py
@@ -15,7 +15,7 @@
 #
 from mib_db_api import *
 from voltha.protos.omci_mib_db_pb2 import MibInstanceData, MibClassData, \
-    MibDeviceData, MibAttributeData
+    MibDeviceData, MibAttributeData, MessageType, ManagedEntity
 from voltha.extensions.omci.omci_entities import *
 from scapy.fields import StrField, FieldListField
 
@@ -838,9 +838,77 @@
             CREATED_KEY: self._string_to_time(val.created),
             LAST_SYNC_KEY: self._string_to_time(val.last_sync_time),
             MDS_KEY: val.mib_data_sync,
-            VERSION_KEY: val.version
+            VERSION_KEY: val.version,
+            ME_KEY: dict(),
+            MSG_TYPE_KEY: set()
         }
         for class_data in val.classes:
             data[class_data.class_id] = self._class_to_dict(val.device_id,
                                                             class_data)
+        for managed_entity in val.managed_entities:
+            data[ME_KEY][managed_entity.class_id] = managed_entity.name
+
+        for msg_type in val.message_types:
+            data[MSG_TYPE_KEY].add(msg_type.message_type)
+
         return data
+
+    def _managed_entity_to_name(self, device_id, class_id):
+        me_map = self._omci_agent.get_device(device_id).me_map
+        entity = me_map.get(class_id)
+
+        return entity.__name__ if entity is not None else 'UnknownManagedEntity'
+
+    def update_supported_managed_entities(self, device_id, managed_entities):
+        """
+        Update the supported OMCI Managed Entities for this device
+        :param device_id: (str) ONU Device ID
+        :param managed_entities: (set) Managed Entity class IDs
+        """
+        try:
+            me_list = [ManagedEntity(class_id=class_id,
+                                     name=self._managed_entity_to_name(device_id,
+                                                                       class_id))
+                       for class_id in managed_entities]
+
+            device_proxy = self._device_proxy(device_id)
+            data = device_proxy.get(depth=0)
+
+            now = datetime.utcnow()
+            data.managed_entities.extend(me_list)
+
+            # Update
+            self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id),
+                                    data)
+            self._modified = now
+            self.log.debug('save-me-list-complete', device_id=device_id)
+
+        except Exception as e:
+            self.log.exception('add-me-failure', e=e, me_list=managed_entities)
+            raise
+
+    def update_supported_message_types(self, device_id, msg_types):
+        """
+        Update the supported OMCI Managed Entities for this device
+        :param device_id: (str) ONU Device ID
+        :param msg_types: (set) Message Type values (ints)
+        """
+        try:
+            msg_type_list = [MessageType(message_type=msg_type.value)
+                             for msg_type in msg_types]
+
+            device_proxy = self._device_proxy(device_id)
+            data = device_proxy.get(depth=0)
+
+            now = datetime.utcnow()
+            data.message_types.extend(msg_type_list)
+
+            # Update
+            self._root_proxy.update(MibDbExternal.DEVICE_PATH.format(device_id),
+                                    data)
+            self._modified = now
+            self.log.debug('save-msg-types-complete', device_id=device_id)
+
+        except Exception as e:
+            self.log.exception('add-msg-types-failure', e=e, msg_types=msg_types)
+            raise
diff --git a/voltha/extensions/omci/onu_device_entry.py b/voltha/extensions/omci/onu_device_entry.py
index 9e3915d..6a5497d 100644
--- a/voltha/extensions/omci/onu_device_entry.py
+++ b/voltha/extensions/omci/onu_device_entry.py
@@ -31,6 +31,8 @@
 ACTIVE_KEY = 'active'
 IN_SYNC_KEY = 'in-sync'
 LAST_IN_SYNC_KEY = 'last-in-sync-time'
+SUPPORTED_MESSAGE_ENTITY_KEY = 'managed-entities'
+SUPPORTED_MESSAGE_TYPES_KEY = 'message-type'
 
 
 class OnuDeviceEvents(IntEnum):
@@ -296,7 +298,11 @@
         """
         topic = OnuDeviceEntry.event_bus_topic(self.device_id,
                                                OnuDeviceEvents.OmciCapabilitiesEvent)
-        self.event_bus.publish(topic=topic, msg=None)
+        msg = {
+            SUPPORTED_MESSAGE_ENTITY_KEY: self.omci_capabilities.supported_managed_entities,
+            SUPPORTED_MESSAGE_TYPES_KEY: self.omci_capabilities.supported_message_types
+        }
+        self.event_bus.publish(topic=topic, msg=msg)
 
     def delete(self):
         """
diff --git a/voltha/extensions/omci/state_machines/mib_sync.py b/voltha/extensions/omci/state_machines/mib_sync.py
index 3e1bad1..854daf2 100644
--- a/voltha/extensions/omci/state_machines/mib_sync.py
+++ b/voltha/extensions/omci/state_machines/mib_sync.py
@@ -22,10 +22,13 @@
     AttributeAccess
 from voltha.extensions.omci.omci_cc import OmciCCRxEvents, OMCI_CC, TX_REQUEST_KEY, \
     RX_RESPONSE_KEY
+from voltha.extensions.omci.onu_device_entry import OnuDeviceEvents, OnuDeviceEntry, \
+    SUPPORTED_MESSAGE_ENTITY_KEY, SUPPORTED_MESSAGE_TYPES_KEY
 from voltha.extensions.omci.omci_entities import OntData
 from common.event_bus import EventBusClient
 
 RxEvent = OmciCCRxEvents
+DevEvent = OnuDeviceEvents
 OP = EntityOperations
 RC = ReasonCodes
 AA = AttributeAccess
@@ -121,24 +124,31 @@
         self._attr_diffs = None
 
         self._event_bus = EventBusClient()
-        self._subscriptions = {               # RxEvent.enum -> Subscription Object
+        self._omci_cc_subscriptions = {               # RxEvent.enum -> Subscription Object
             RxEvent.MIB_Reset: None,
             RxEvent.AVC_Notification: None,
             RxEvent.MIB_Upload: None,
             RxEvent.MIB_Upload_Next: None,
             RxEvent.Create: None,
             RxEvent.Delete: None,
-            RxEvent.Set: None
+            RxEvent.Set: None,
         }
-        self._sub_mapping = {
+        self._omci_cc_sub_mapping = {
             RxEvent.MIB_Reset: self.on_mib_reset_response,
             RxEvent.AVC_Notification: self.on_avc_notification,
             RxEvent.MIB_Upload: self.on_mib_upload_response,
             RxEvent.MIB_Upload_Next: self.on_mib_upload_next_response,
             RxEvent.Create: self.on_create_response,
             RxEvent.Delete: self.on_delete_response,
-            RxEvent.Set: self.on_set_response
+            RxEvent.Set: self.on_set_response,
         }
+        self._onu_dev_subscriptions = {               # DevEvent.enum -> Subscription Object
+            DevEvent.OmciCapabilitiesEvent: None
+        }
+        self._onu_dev_sub_mapping = {
+            DevEvent.OmciCapabilitiesEvent: self.on_capabilities_event
+        }
+
         # Statistics and attributes
         # TODO: add any others if it will support problem diagnosis
 
@@ -147,7 +157,8 @@
                                transitions=transitions,
                                initial=initial_state,
                                queued=True,
-                               name='{}'.format(self.__class__.__name__))
+                               name='{}-{}'.format(self.__class__.__name__,
+                                                   device_id))
 
     def _cancel_deferred(self):
         d1, self._deferred = self._deferred, None
@@ -223,11 +234,16 @@
             task.stop()
 
         # Drop Response and Autonomous notification subscriptions
-        for event, sub in self._subscriptions.iteritems():
+        for event, sub in self._omci_cc_subscriptions.iteritems():
             if sub is not None:
-                self._subscriptions[event] = None
+                self._omci_cc_subscriptions[event] = None
                 self._device.omci_cc.event_bus.unsubscribe(sub)
 
+        for event, sub in self._onu_dev_subscriptions.iteritems():
+            if sub is not None:
+                self._onu_dev_subscriptions[event] = None
+                self._device.event_bus.unsubscribe(sub)
+
         # TODO: Stop and remove any currently running or scheduled tasks
         # TODO: Anything else?
 
@@ -262,15 +278,27 @@
 
         # Set up Response and Autonomous notification subscriptions
         try:
-            for event, sub in self._sub_mapping.iteritems():
-                if self._subscriptions[event] is None:
-                    self._subscriptions[event] = \
+            for event, sub in self._omci_cc_sub_mapping.iteritems():
+                if self._omci_cc_subscriptions[event] is None:
+                    self._omci_cc_subscriptions[event] = \
                         self._device.omci_cc.event_bus.subscribe(
                             topic=OMCI_CC.event_bus_topic(self._device_id, event),
                             callback=sub)
 
         except Exception as e:
-            self.log.exception('subscription-setup', e=e)
+            self.log.exception('omci-cc-subscription-setup', e=e)
+
+        # Set up ONU device subscriptions
+        try:
+            for event, sub in self._onu_dev_sub_mapping.iteritems():
+                if self._onu_dev_subscriptions[event] is None:
+                    self._onu_dev_subscriptions[event] = \
+                        self._device.event_bus.subscribe(
+                                topic=OnuDeviceEntry.event_bus_topic(self._device_id, event),
+                                callback=sub)
+
+        except Exception as e:
+            self.log.exception('dev-subscription-setup', e=e)
 
         # Determine if this ONU has ever synchronized
         if self.is_new_onu:
@@ -465,6 +493,10 @@
                 #       MDS value if different. Also remember that setting the MDS on
                 #       the ONU to 'n' is a set command and it will be 'n+1' after the
                 #       set.
+                #
+                # TODO: Also look into attributes covered by AVC and treat appropriately
+                #       since may have missed the AVC
+
                 self._deferred = reactor.callLater(0, self.success)
             else:
                 self._deferred = reactor.callLater(0, self.diffs_found)
@@ -493,7 +525,7 @@
             response = msg[RX_RESPONSE_KEY]
 
             # Check if expected in current mib_sync state
-            if self.state != 'uploading' or self._subscriptions[RxEvent.MIB_Reset] is None:
+            if self.state != 'uploading' or self._omci_cc_subscriptions[RxEvent.MIB_Reset] is None:
                 self.log.error('rx-in-invalid-state', state=self.state)
 
             else:
@@ -525,7 +557,7 @@
         """
         self.log.debug('on-avc-notification', state=self.state)
 
-        if self._subscriptions[RxEvent.AVC_Notification]:
+        if self._omci_cc_subscriptions[RxEvent.AVC_Notification]:
             try:
                 notification = msg[RX_RESPONSE_KEY]
 
@@ -568,7 +600,7 @@
         """
         self.log.debug('on-mib-upload-next-response', state=self.state)
 
-        if self._subscriptions[RxEvent.MIB_Upload]:
+        if self._omci_cc_subscriptions[RxEvent.MIB_Upload]:
             # Check if expected in current mib_sync state
             if self.state == 'resynchronizing':
                 # The resync task handles this
@@ -587,7 +619,7 @@
         """
         self.log.debug('on-mib-upload-next-response', state=self.state)
 
-        if self._subscriptions[RxEvent.MIB_Upload_Next]:
+        if self._omci_cc_subscriptions[RxEvent.MIB_Upload_Next]:
             try:
                 if self.state == 'resynchronizing':
                     # The resync task handles this
@@ -632,7 +664,7 @@
         """
         self.log.debug('on-create-response', state=self.state)
 
-        if self._subscriptions[RxEvent.Create]:
+        if self._omci_cc_subscriptions[RxEvent.Create]:
             if self.state in ['disabled', 'uploading']:
                 self.log.error('rx-in-invalid-state', state=self.state)
                 return
@@ -717,7 +749,7 @@
         """
         self.log.debug('on-delete-response', state=self.state)
 
-        if self._subscriptions[RxEvent.Delete]:
+        if self._omci_cc_subscriptions[RxEvent.Delete]:
             if self.state in ['disabled', 'uploading']:
                 self.log.error('rx-in-invalid-state', state=self.state)
                 return
@@ -758,7 +790,7 @@
         """
         self.log.debug('on-set-response', state=self.state)
 
-        if self._subscriptions[RxEvent.Set]:
+        if self._omci_cc_subscriptions[RxEvent.Set]:
             if self.state in ['disabled', 'uploading']:
                 self.log.error('rx-in-invalid-state', state=self.state)
             try:
@@ -791,6 +823,16 @@
                 pass            # NOP
             except Exception as e:
                 self.log.exception('set', e=e)
+    def on_capabilities_event(self, _topic, msg):
+        """
+        Process a OMCI capabilties event
+        :param _topic: (str) OnuDeviceEntry Capabilities event
+        :param msg: (dict) Message Entities & Message Types supported
+        """
+        self._database.update_supported_managed_entities(self.device_id,
+                                                         msg[SUPPORTED_MESSAGE_ENTITY_KEY])
+        self._database.update_supported_message_types(self.device_id,
+                                                      msg[SUPPORTED_MESSAGE_TYPES_KEY])
 
     def _status_to_text(self, success_code):
         return {
diff --git a/voltha/extensions/omci/state_machines/omci_onu_capabilities.py b/voltha/extensions/omci/state_machines/omci_onu_capabilities.py
index f288929..291999f 100644
--- a/voltha/extensions/omci/state_machines/omci_onu_capabilities.py
+++ b/voltha/extensions/omci/state_machines/omci_onu_capabilities.py
@@ -84,7 +84,8 @@
                                transitions=transitions,
                                initial=initial_state,
                                queued=True,
-                               name='{}'.format(self.__class__.__name__))
+                               name='{}-{}'.format(self.__class__.__name__,
+                                                   device_id))
 
     def _cancel_deferred(self):
         d1, self._deferred = self._deferred, None
diff --git a/voltha/protos/omci_mib_db.proto b/voltha/protos/omci_mib_db.proto
index 1c518c8..89d2e65 100644
--- a/voltha/protos/omci_mib_db.proto
+++ b/voltha/protos/omci_mib_db.proto
@@ -43,6 +43,15 @@
     [(voltha.child_node) = {key: "instance_id"}];
 }
 
+message ManagedEntity {
+    uint32 class_id  = 1 [(voltha.access) = READ_ONLY];
+    string name      = 2 [(voltha.access) = READ_ONLY];
+}
+
+message MessageType {
+    uint32 message_type = 1 [(voltha.access) = READ_ONLY];
+}
+
 message MibDeviceData {
     string device_id        = 1 [(voltha.access) = READ_ONLY];
     string created          = 2;
@@ -52,4 +61,8 @@
 
     repeated MibClassData classes = 6
     [(voltha.child_node) = {key: "class_id"}];
+
+    repeated ManagedEntity managed_entities = 7;
+    repeated MessageType message_types = 8;
 }
+
diff --git a/voltha/protos/voltha.proto b/voltha/protos/voltha.proto
index 7f5a08f..b106289 100644
--- a/voltha/protos/voltha.proto
+++ b/voltha/protos/voltha.proto
@@ -949,6 +949,13 @@
             post: "/api/v1/devices/{id}/self_test"
         };
     }
+
+    // OpenOMCI MIB information
+    rpc GetMibDeviceData(ID) returns(omci.MibDeviceData) {
+        option (google.api.http) = {
+            get: "/api/v1/openomci/{id}/mib"
+        };
+    }
 }
 
 /*
@@ -1677,4 +1684,11 @@
     }
 
     rpc Subscribe(OfAgentSubscriber) returns (OfAgentSubscriber) {}
+
+    // OpenOMCI MIB information
+    rpc GetMibDeviceData(ID) returns(omci.MibDeviceData) {
+        option (google.api.http) = {
+            get: "/api/v1/openomci/{id}/mib"
+        };
+    }
 }