VOL-2452: MIB Templating

- working for template and non-template cases
- works with resync
- convert binary string to ascii and strip out garbage, needed for json
- remove needless logging
- clearer mib dict get
- no need to swap equipment id, it assumed the same
- under load also need to increase kafka send/recv timeout
- if any omci element cannot be found allow normal upload to proceed

Change-Id: If42e4df812b5e4ab11e2dc81e075629099d556ff
diff --git a/VERSION b/VERSION
index bc4abe8..5aa7c52 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.3.8
+2.3.9
diff --git a/pyvoltha/adapters/extensions/omci/database/mib_db_dict.py b/pyvoltha/adapters/extensions/omci/database/mib_db_dict.py
index 92eaaec..e4066f6 100644
--- a/pyvoltha/adapters/extensions/omci/database/mib_db_dict.py
+++ b/pyvoltha/adapters/extensions/omci/database/mib_db_dict.py
@@ -301,6 +301,12 @@
                 elif isinstance(value, (list, dict)):
                     value = json.dumps(value, separators=(',', ':'))
 
+                if isinstance(value, six.string_types):
+                    value = value.rstrip('\x00')
+
+                if isinstance(value, six.binary_type):
+                    value = value.decode('ascii').rstrip('\x00')
+
                 assert db_value is None or isinstance(value, type(db_value)), \
                     "New value type for attribute '{}' type is changing from '{}' to '{}'".\
                     format(attribute, type(db_value), type(value))
@@ -467,19 +473,25 @@
     def _fix_attr_json_attribute(self, attr_data, eca):
 
         try:
-            if eca is not None:
-                field = eca.field
-                if hasattr(field, 'load_json'):
-                    value = field.load_json(attr_data)
+            if eca is not None and hasattr(eca.field, 'load_json'):
+                try:
+                    value = eca.field.load_json(attr_data)
                     return value
+                except ValueError:
+                    pass
 
-            return json.loads(attr_data) if isinstance(attr_data, six.string_types) else attr_data
+            if isinstance(attr_data, six.string_types):
+                try:
+                    value = json.loads(attr_data)
+                    return value
+                except ValueError:
+                    pass
 
-        except ValueError:
             return attr_data
 
         except Exception as e:
-            pass
+            self.log.error('could-not-parse-attribute-returning-as-is', field=eca.field, attr_data=attr_data, e=e)
+            return attr_data
 
     def update_supported_managed_entities(self, device_id, managed_entities):
         """
@@ -524,3 +536,31 @@
         except Exception as e:
             self.log.error('set-me-failure', e=e)
             raise
+
+    def load_from_template(self, device_id, template):
+        now = datetime.utcnow()
+        headerdata = {
+            DEVICE_ID_KEY: device_id,
+            CREATED_KEY: now,
+            LAST_SYNC_KEY: None,
+            MDS_KEY: 0,
+            VERSION_KEY: MibDbVolatileDict.CURRENT_VERSION,
+            ME_KEY: dict(),
+            MSG_TYPE_KEY: set()
+        }
+        template.update(headerdata)
+        self._data[device_id] = template
+
+    def dump_to_json(self, device_id):
+        device_db = self._data.get(device_id, dict())
+        device_db = self._fix_dev_json_attributes(device_db, device_id)
+
+        def json_converter(o):
+            if isinstance(o, datetime):
+                return o.__str__()
+            if isinstance(o, six.binary_type):
+                return o.decode('ascii')
+
+        json_string = json.dumps(device_db, default=json_converter, indent=4)
+
+        return json_string
diff --git a/pyvoltha/adapters/extensions/omci/database/mib_template_db.py b/pyvoltha/adapters/extensions/omci/database/mib_template_db.py
new file mode 100644
index 0000000..a452c4f
--- /dev/null
+++ b/pyvoltha/adapters/extensions/omci/database/mib_template_db.py
@@ -0,0 +1,108 @@
+#
+# Copyright 2020 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.
+#
+from __future__ import absolute_import
+from .mib_db_api import CREATED_KEY, MODIFIED_KEY
+import json
+from datetime import datetime
+import structlog
+from pyvoltha.common.utils.registry import registry
+from pyvoltha.common.config.config_backend import EtcdStore
+import six
+
+
+class MibTemplateDb(object):
+
+    BASE_PATH = 'service/voltha/omci_mibs/templates'
+    TEMPLATE_PATH = '{}/{}/{}'
+
+    def __init__(self, vendor_id, equipment_id, software_version, serial_number, mac_address):
+        self.log = structlog.get_logger()
+        self._jsonstring = b''
+
+        # lookup keys
+        self._vendor_id = vendor_id
+        self._equipment_id = equipment_id
+        self._software_version = software_version
+
+        # replacement values
+        self._serial_number = serial_number
+        self._mac_address = mac_address
+
+        self.args = registry('main').get_args()
+        host, port = self.args.etcd.split(':', 1)
+        self._kv_store = EtcdStore(host, port, MibTemplateDb.BASE_PATH)
+        self.loaded = False
+        self._load_template()
+
+    def get_template_instance(self):
+        # swap out tokens with specific data
+        fixup = self._jsonstring.decode('ascii')
+        fixup = fixup.replace('%SERIAL_NUMBER%', self._serial_number)
+        fixup = fixup.replace('%MAC_ADDRESS%', self._mac_address)
+
+        # convert to a dict() compatible with mib_db_dict
+        newdb = self._load_from_json(fixup)
+        now = datetime.utcnow()
+
+        # populate timestamps as if it was mib uploaded
+        for cls_id, cls_data in newdb.items():
+            if isinstance(cls_id, int):
+                for inst_id, inst_data in cls_data.items():
+                    if isinstance(inst_id, int):
+                        newdb[cls_id][inst_id][CREATED_KEY] = now
+                        newdb[cls_id][inst_id][MODIFIED_KEY] = now
+
+        return newdb
+
+    def _load_template(self):
+        path = self._get_template_path()
+        try:
+            self._jsonstring = self._kv_store[path]
+            self.log.debug('found-template-data', path=path)
+            self.loaded = True
+        except KeyError:
+            self.log.warn('no-template-found', path=path)
+
+    def _get_template_path(self):
+        if not isinstance(self._vendor_id, six.string_types):
+            raise TypeError('Vendor ID is a string')
+
+        if not isinstance(self._equipment_id, six.string_types):
+            raise TypeError('Equipment ID is a string')
+
+        if not isinstance(self._software_version, six.string_types):
+            raise TypeError('Software Version is a string')
+
+        fmt = MibTemplateDb.TEMPLATE_PATH
+        return fmt.format(self._vendor_id, self._equipment_id, self._software_version)
+
+    def _load_from_json(self, jsondata):
+
+        def json_obj_parser(x):
+            if isinstance(x, dict):
+                results = dict()
+                for (key, value) in x.items():
+                    try:
+                        key = int(key)
+                    except (ValueError, TypeError):
+                        pass
+
+                    results.update({key: value})
+                return results
+            return x
+
+        template_data = json.loads(jsondata, object_hook=json_obj_parser)
+        return template_data
diff --git a/pyvoltha/adapters/extensions/omci/omci_cc.py b/pyvoltha/adapters/extensions/omci/omci_cc.py
index cc989a1..66c63b0 100644
--- a/pyvoltha/adapters/extensions/omci/omci_cc.py
+++ b/pyvoltha/adapters/extensions/omci/omci_cc.py
@@ -873,46 +873,26 @@
     # MIB Action shortcuts
 
     def send_mib_reset(self, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
-        """
-        Perform a MIB Reset
-        """
-        self.log.debug('send-mib-reset')
-
         frame = OntDataFrame().mib_reset()
         return self.send(frame, timeout=timeout, high_priority=high_priority)
 
     def send_mib_upload(self, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
-        self.log.debug('send-mib-upload')
-
         frame = OntDataFrame().mib_upload()
         return self.send(frame, timeout=timeout, high_priority=high_priority)
 
     def send_mib_upload_next(self, seq_no, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
-        self.log.debug('send-mib-upload-next')
-
         frame = OntDataFrame(sequence_number=seq_no).mib_upload_next()
         return self.send(frame, timeout=timeout, high_priority=high_priority)
 
     def send_reboot(self, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
-        """
-        Send an ONU Device reboot request (ONU-G ME).
-
-        NOTICE: This method is being deprecated and replaced with a tasks to preform this function
-        """
-        self.log.debug('send-mib-reboot')
-
         frame = OntGFrame().reboot()
         return self.send(frame, timeout=timeout, high_priority=high_priority)
 
     def send_get_all_alarm(self, alarm_retrieval_mode=0, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
-        self.log.debug('send_get_alarm')
-
         frame = OntDataFrame().get_all_alarm(alarm_retrieval_mode)
         return self.send(frame, timeout=timeout, high_priority=high_priority)
 
     def send_get_all_alarm_next(self, seq_no, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
-        self.log.debug('send_get_alarm_next')
-
         frame = OntDataFrame().get_all_alarm_next(seq_no)
         return self.send(frame, timeout=timeout, high_priority=high_priority)
 
@@ -921,27 +901,16 @@
         return self.send(frame, timeout, 3, high_priority=high_priority)
 
     def send_download_section(self, image_inst_id, section_num, data, size=DEFAULT_OMCI_DOWNLOAD_SECTION_SIZE, timeout=0, high_priority=False):
-        """
-        # timeout=0 indicates no repons needed
-        """
-        # self.log.debug("send_download_section", instance_id=image_inst_id, section=section_num, timeout=timeout)
+        # timeout=0 indicates no response needed
         if timeout > 0:
             frame = SoftwareImageFrame(image_inst_id).download_section(True, section_num, data)
         else:
             frame = SoftwareImageFrame(image_inst_id).download_section(False, section_num, data)
         return self.send(frame, timeout, high_priority=high_priority)
 
-        # if timeout > 0:
-        #     self.reactor.callLater(0, self.sim_receive_download_section_resp,
-        #                            frame.fields["transaction_id"],
-        #                            frame.fields["omci_message"].fields["section_number"])
-        # return d
-
     def send_end_software_download(self, image_inst_id, crc32, image_size, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
         frame = SoftwareImageFrame(image_inst_id).end_software_download(crc32, image_size)
         return self.send(frame, timeout, high_priority=high_priority)
-        # self.reactor.callLater(0, self.sim_receive_end_software_download_resp, frame.fields["transaction_id"])
-        # return d
 
     def send_active_image(self, image_inst_id, flag=0, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False):
         frame = SoftwareImageFrame(image_inst_id).activate_image(flag)
diff --git a/pyvoltha/adapters/extensions/omci/omci_me.py b/pyvoltha/adapters/extensions/omci/omci_me.py
index 1c12266..57412a1 100644
--- a/pyvoltha/adapters/extensions/omci/omci_me.py
+++ b/pyvoltha/adapters/extensions/omci/omci_me.py
@@ -569,12 +569,16 @@
     """
     This managed entity models an executable software image stored in the ONU.
     """
-    def __init__(self, entity_id):
+    def __init__(self, entity_id, attributes=None):
         """
         :param entity_id: (int) This attribute uniquely identifies each instance of
                                 this managed entity. (0..65535)
+        :param attributes: (basestring, list, set, dict) attributes. For gets
+                           a string, list, or set can be provided. For create/set
+                           operations, a dictionary should be provided, for
+                           deletes None may be specified.
         """
-        super(SoftwareImageFrame, self).__init__(SoftwareImage, entity_id, None)
+        super(SoftwareImageFrame, self).__init__(SoftwareImage, entity_id, MEFrame._attr_to_data(attributes))
 
 
 class TcontFrame(MEFrame):
diff --git a/pyvoltha/adapters/extensions/omci/openomci_agent.py b/pyvoltha/adapters/extensions/omci/openomci_agent.py
index 5595816..0bf9d2b 100644
--- a/pyvoltha/adapters/extensions/omci/openomci_agent.py
+++ b/pyvoltha/adapters/extensions/omci/openomci_agent.py
@@ -20,6 +20,7 @@
 from pyvoltha.adapters.extensions.omci.database.mib_db_ext import MibDbExternal
 from pyvoltha.adapters.extensions.omci.state_machines.mib_sync import MibSynchronizer
 from pyvoltha.adapters.extensions.omci.tasks.mib_upload import MibUploadTask
+from pyvoltha.adapters.extensions.omci.tasks.mib_template_task import MibTemplateTask
 from pyvoltha.adapters.extensions.omci.tasks.get_mds_task import GetMdsTask
 from pyvoltha.adapters.extensions.omci.tasks.mib_resync_task import MibResyncTask
 from pyvoltha.adapters.extensions.omci.tasks.mib_reconcile_task import MibReconcileTask
@@ -48,6 +49,7 @@
         'audit-delay': 60,                 # Time to wait between MIB audits.  0 to disable audits.
         'tasks': {
             'mib-upload': MibUploadTask,
+            'mib-template': MibTemplateTask,
             'get-mds': GetMdsTask,
             'mib-audit': GetMdsTask,
             'mib-resync': MibResyncTask,
diff --git a/pyvoltha/adapters/extensions/omci/state_machines/mib_sync.py b/pyvoltha/adapters/extensions/omci/state_machines/mib_sync.py
index f7f22fd..640da14 100644
--- a/pyvoltha/adapters/extensions/omci/state_machines/mib_sync.py
+++ b/pyvoltha/adapters/extensions/omci/state_machines/mib_sync.py
@@ -42,15 +42,17 @@
     """
     OpenOMCI MIB Synchronizer state machine
     """
-    DEFAULT_STATES = ['disabled', 'starting', 'uploading', 'examining_mds',
+    DEFAULT_STATES = ['disabled', 'starting', 'loading_mib_template', 'uploading', 'examining_mds',
                       'in_sync', 'out_of_sync', 'auditing', 'resynchronizing']
 
     DEFAULT_TRANSITIONS = [
         {'trigger': 'start', 'source': 'disabled', 'dest': 'starting'},
 
-        {'trigger': 'upload_mib', 'source': 'starting', 'dest': 'uploading'},
+        {'trigger': 'load_mib_template', 'source': 'starting', 'dest': 'loading_mib_template'},
+        {'trigger': 'upload_mib', 'source': 'loading_mib_template', 'dest': 'uploading'},
         {'trigger': 'examine_mds', 'source': 'starting', 'dest': 'examining_mds'},
 
+        {'trigger': 'success', 'source': 'loading_mib_template', 'dest': 'in_sync'},
         {'trigger': 'success', 'source': 'uploading', 'dest': 'in_sync'},
 
         {'trigger': 'success', 'source': 'examining_mds', 'dest': 'in_sync'},
@@ -122,6 +124,7 @@
         self._resync_fail_limit = resync_fail_limit
 
         self._upload_task = mib_sync_tasks['mib-upload']
+        self._load_mib_template_task = mib_sync_tasks['mib-template']
         self._get_mds_task = mib_sync_tasks['get-mds']
         self._audit_task = mib_sync_tasks['mib-audit']
         self._resync_task = mib_sync_tasks['mib-resync']
@@ -366,13 +369,48 @@
         if self.is_new_onu:
             # clear resync failure counter if we "started over"
             self._failed_resync_count = 0
-            # Start full MIB upload
-            self._deferred = reactor.callLater(0, self.upload_mib)
+            # Attempt to load a MIB template then start full MIB upload if needed
+            self._deferred = reactor.callLater(0, self.load_mib_template)
 
         else:
             # Examine the MIB Data Sync
             self._deferred = reactor.callLater(0, self.examine_mds)
 
+    def on_enter_loading_mib_template(self):
+        """
+        Find and load a mib template.  If not found proceed with mib_upload
+        """
+        self.advertise(OpenOmciEventType.state_change, self.state)
+
+        def success(template):
+            self.log.debug('mib-template-load-success')
+            self._current_task = None
+            self._next_resync = datetime.utcnow() + timedelta(seconds=self._resync_delay)
+
+            if template is not None:
+                self._database.load_from_template(self._device_id, template)
+
+                # DEBUG: Dump raw json db:
+                jsondb = self._database.dump_to_json(self.device_id)
+                self.log.debug('in-sync-device-db', json=jsondb)
+
+                self._deferred = reactor.callLater(0, self.success)
+            else:
+                # Start full MIB upload
+                self._deferred = reactor.callLater(0, self.upload_mib)
+
+        def failure(reason):
+            self.log.info('mib-template-load-failure', reason=reason)
+            self._current_task = None
+            self._deferred = reactor.callLater(self._timeout_delay, self.timeout)
+
+        self._device.mib_db_in_sync = False
+        self._current_task = self._load_mib_template_task(self._agent, self._device_id)
+
+        self.log.debug('starting-mib-template', task=self._current_task)
+        self._task_deferred = self._device.task_runner.queue_task(self._current_task)
+        self._task_deferred.addCallbacks(success, failure)
+
     def on_enter_uploading(self):
         """
         Begin full MIB data upload, starting with a MIB RESET
@@ -383,6 +421,11 @@
             self.log.debug('mib-upload-success', results=results)
             self._current_task = None
             self._next_resync = datetime.utcnow() + timedelta(seconds=self._resync_delay)
+
+            # DEBUG: Dump raw json db:
+            jsondb = self._database.dump_to_json(self.device_id)
+            self.log.debug('in-sync-device-db', json=jsondb)
+
             self._deferred = reactor.callLater(0, self.success)
 
         def failure(reason):
@@ -393,6 +436,7 @@
         self._device.mib_db_in_sync = False
         self._current_task = self._upload_task(self._agent, self._device_id)
 
+        self.log.debug('starting-mib-upload', task=self._current_task)
         self._task_deferred = self._device.task_runner.queue_task(self._current_task)
         self._task_deferred.addCallbacks(success, failure)
 
@@ -589,7 +633,8 @@
             response = msg[RX_RESPONSE_KEY]
 
             # Check if expected in current mib_sync state
-            if self.state != 'uploading' or self._omci_cc_subscriptions[RxEvent.MIB_Reset] is None:
+            if self.state not in ['uploading', 'loading_mib_template'] or \
+                    self._omci_cc_subscriptions[RxEvent.MIB_Reset] is None:
                 self.log.error('rx-in-invalid-state', state=self.state)
 
             else:
@@ -721,7 +766,7 @@
         self.log.debug('on-create-response', state=self.state)
 
         if self._omci_cc_subscriptions[RxEvent.Create]:
-            if self.state in ['disabled', 'uploading']:
+            if self.state in ['disabled', 'uploading', 'loading_mib_template']:
                 self.log.error('rx-in-invalid-state', state=self.state)
                 return
             try:
@@ -808,7 +853,7 @@
         self.log.debug('on-delete-response', state=self.state)
 
         if self._omci_cc_subscriptions[RxEvent.Delete]:
-            if self.state in ['disabled', 'uploading']:
+            if self.state in ['disabled', 'uploading', 'loading_mib_template']:
                 self.log.error('rx-in-invalid-state', state=self.state)
                 return
             try:
@@ -847,7 +892,7 @@
         self.log.debug('on-set-response', state=self.state)
 
         if self._omci_cc_subscriptions[RxEvent.Set]:
-            if self.state in ['disabled', 'uploading']:
+            if self.state in ['disabled', 'uploading', 'loading_mib_template']:
                 self.log.error('rx-in-invalid-state', state=self.state)
                 return
             try:
@@ -922,7 +967,7 @@
 
         # All events for software run this method, checking for one is good enough
         if self._omci_cc_subscriptions[RxEvent.Start_Software_Download]:
-            if self.state in ['disabled', 'uploading']:
+            if self.state in ['disabled', 'uploading', 'loading_mib_template']:
                 self.log.error('rx-in-invalid-state', state=self.state)
                 return
             try:
diff --git a/pyvoltha/adapters/extensions/omci/tasks/mib_template_task.py b/pyvoltha/adapters/extensions/omci/tasks/mib_template_task.py
new file mode 100644
index 0000000..505a444
--- /dev/null
+++ b/pyvoltha/adapters/extensions/omci/tasks/mib_template_task.py
@@ -0,0 +1,169 @@
+#
+# Copyright 2020 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.
+#
+from __future__ import absolute_import
+from .task import Task
+from twisted.internet.defer import inlineCallbacks, TimeoutError, failure, AlreadyCalledError, returnValue
+from twisted.internet import reactor
+from pyvoltha.adapters.extensions.omci.omci_defs import ReasonCodes
+from pyvoltha.adapters.extensions.omci.omci_me import OntGFrame, Ont2GFrame, SoftwareImageFrame, IpHostConfigDataFrame
+from pyvoltha.adapters.extensions.omci.database.mib_template_db import MibTemplateDb
+
+RC = ReasonCodes
+
+
+class MibTemplateTask(Task):
+    """
+    OpenOMCI MIB Template task
+
+    On successful completion, this task will call the 'callback' method of the
+    deferred returned by the start method.  If successful either a new mib db
+    instance is returned or None, depending on if a template could be found.
+
+    Its expected if None is returned the caller will perform a full MIB upload
+
+    """
+    task_priority = 250
+    name = "MIB Template Task"
+
+    def __init__(self, omci_agent, device_id):
+        """
+        Class initialization
+
+        :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+        :param device_id: (str) ONU Device ID
+        """
+        super(MibTemplateTask, self).__init__(MibTemplateTask.name,
+                                              omci_agent,
+                                              device_id,
+                                              priority=MibTemplateTask.task_priority)
+        self._device = omci_agent.get_device(device_id)
+        self._local_deferred = None
+
+    def cancel_deferred(self):
+        super(MibTemplateTask, self).cancel_deferred()
+
+        d, self._local_deferred = self._local_deferred, None
+        try:
+            if d is not None and not d.called:
+                d.cancel()
+        except:
+            pass
+
+    def start(self):
+        """
+        Start MIB Template tasks
+        """
+        super(MibTemplateTask, self).start()
+        self._local_deferred = reactor.callLater(0, self.create_template_instance)
+
+    def stop(self):
+        """
+        Shutdown MIB Template tasks
+        """
+        self.log.debug('stopping')
+
+        self.cancel_deferred()
+        super(MibTemplateTask, self).stop()
+
+    @inlineCallbacks
+    def create_template_instance(self):
+        """
+        Gather unique identifying elements from the ONU.  Lookup template in persistent storage and return
+        If no template is found return None so normal MIB Upload sequence can happen
+        """
+        self.log.debug('create-mib-template-instance')
+
+        try:
+            # MIB Reset start fresh
+            self.strobe_watchdog()
+            results = yield self._device.omci_cc.send_mib_reset()
+
+            status = results.fields['omci_message'].fields['success_code']
+            if status != ReasonCodes.Success.value:
+                raise Exception('MIB Reset request failed with status code: {}'.format(status))
+
+            self.log.debug('gather-onu-info')
+
+            # Query for Vendor ID, Equipment ID and Software Version
+            results = yield self._get_omci(OntGFrame(attributes=['vendor_id', 'serial_number']))
+            self.log.debug('got-ontg', results=results)
+
+            vendor_id = results.get('vendor_id', b'').decode('ascii').rstrip('\x00')
+            serial_number = results.get('serial_number', '')
+
+            results = yield self._get_omci(Ont2GFrame(attributes='equipment_id'))
+            self.log.debug('got-ont2g', results=results)
+
+            equipment_id = results.get('equipment_id', b'').decode('ascii').rstrip('\x00')
+
+            # check only two software slots for active version.
+            results1 = yield self._get_omci(SoftwareImageFrame(0, attributes=['is_active', 'version']))
+            results2 = yield self._get_omci(SoftwareImageFrame(1, attributes=['is_active', 'version']))
+            self.log.debug('got-software', results1=results1, results2=results2)
+
+            software_version = ''
+            if results1.get('is_active') == 1:
+                software_version = results1.get('version', b'').decode('ascii').rstrip('\x00')
+            elif results2.get('is_active') == 1:
+                software_version = results2.get('version', b'').decode('ascii').rstrip('\x00')
+
+            results = yield self._get_omci(IpHostConfigDataFrame(1, attributes='mac_address'))
+            self.log.debug('got-ip-host-config', results=results)
+
+            mac_address = results.get('mac_address', '')
+
+            # Lookup template base on unique onu type info
+            template = None
+            if vendor_id and equipment_id and software_version:
+                self.log.debug('looking-up-template', vendor_id=vendor_id, equipment_id=equipment_id,
+                               software_version=software_version)
+                template = MibTemplateDb(vendor_id, equipment_id, software_version, serial_number, mac_address)
+            else:
+                self.log.info('no-usable-template-lookup-data', vendor_id=vendor_id, equipment_id=equipment_id,
+                              software_version=software_version)
+
+            if template and template.loaded:
+                # generate db instance
+                loaded_template_instance = template.get_template_instance()
+                self.deferred.callback(loaded_template_instance)
+            else:
+                self.deferred.callback(None)
+
+        except TimeoutError as e:
+            self.log.warn('mib-template-timeout', e=e)
+            self.deferred.errback(failure.Failure(e))
+
+        except AlreadyCalledError:
+            # Can occur if task canceled due to MIB Sync state change
+            self.log.debug('already-called-exception')
+            assert self.deferred.called, 'Unexpected AlreadyCalledError exception'
+        except Exception as e:
+            self.log.exception('mib-template', e=e)
+            self.deferred.errback(failure.Failure(e))
+
+    @inlineCallbacks
+    def _get_omci(self, frame):
+        self.strobe_watchdog()
+        results = yield self._device.omci_cc.send(frame.get())
+
+        results_fields = results.fields['omci_message'].fields
+        status = results_fields['success_code']
+
+        return_results = dict()
+        if status == RC.Success.value:
+            return_results = results_fields.get('data', dict())
+
+        returnValue(return_results)
diff --git a/pyvoltha/adapters/extensions/omci/tasks/mib_upload.py b/pyvoltha/adapters/extensions/omci/tasks/mib_upload.py
index d25acae..91c0a0d 100644
--- a/pyvoltha/adapters/extensions/omci/tasks/mib_upload.py
+++ b/pyvoltha/adapters/extensions/omci/tasks/mib_upload.py
@@ -95,18 +95,6 @@
         try:
             device = self.omci_agent.get_device(self.device_id)
 
-            #########################################
-            # MIB Reset
-            self.strobe_watchdog()
-            results = yield device.omci_cc.send_mib_reset()
-
-            status = results.fields['omci_message'].fields['success_code']
-            if status != ReasonCodes.Success.value:
-                raise MibUploadFailure('MIB Reset request failed with status code: {}'.
-                                       format(status))
-
-            ########################################
-            # Begin MIB Upload
             self.strobe_watchdog()
             results = yield device.omci_cc.send_mib_upload()
 
@@ -139,7 +127,7 @@
                         yield asleep(0.3)
 
             # Successful if here
-            self.log.info('mib-synchronized')
+            self.log.info('mib-uploaded')
             self.deferred.callback('success, loaded {} ME Instances'.
                                    format(number_of_commands))
 
diff --git a/pyvoltha/adapters/kafka/container_proxy.py b/pyvoltha/adapters/kafka/container_proxy.py
index 4e2fa12..578be3b 100644
--- a/pyvoltha/adapters/kafka/container_proxy.py
+++ b/pyvoltha/adapters/kafka/container_proxy.py
@@ -43,7 +43,7 @@
         self.kafka_proxy = kafka_proxy
         self.listening_topic = my_listening_topic
         self.remote_topic = remote_topic
-        self.default_timeout = 3
+        self.default_timeout = 6
 
     def start(self):
         log.info('started')
diff --git a/tox.ini b/tox.ini
index 7572850..d6f9451 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,7 +13,7 @@
 ; limitations under the License.
 
 [tox]
-envlist = py27,py35,py36
+envlist = py35,py36
 skip_missing_interpreters = true
 
 [testenv]