VOL-1606, VOL-1555 - olt reboot fix for voltha 1.x code
SEBA-669 - Authentication fails after removing a subscriber

Change-Id: I446c20c6ff3d68e3b65f58541aed8ccb45ac62bd
diff --git a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
index 5ca6bd2..b87d638 100644
--- a/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
+++ b/voltha/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
@@ -108,9 +108,26 @@
         self.mac_bridge_service_profile_entity_id = 0x201
         self.gal_enet_profile_entity_id = 0x1
 
-        self._tp_service_specific_task = dict()
+        # Stores the list of all the uni_ids for which mid download is completed.
+        self._mib_download_done = list()
+
+        # Dictionary with key being uni_id and value being the tp_path for that uni
         self._tech_profile_download_done = dict()
 
+        # Stores information related to 'in progress' tp tasks
+        # Dictionary with key being uni_id and value being the tp_path for that uni
+        self._in_progress_tp_task = dict()
+
+        # Stores information related to queued tp tasks
+        # Dictionary with key being uni_id and value being the tp_path for that uni
+        self._queued_tp_task = dict()
+
+        # Stores information related to queued vlan filter tasks
+        # Dictionary with key being uni_id and value being flow_cookie, add_tag flag,
+        # uni_port and vlan_id
+        self._queued_vlan_filter_task = dict()
+
+
         # Initialize KV store client
         self.args = registry('main').get_args()
         if self.args.backend == 'etcd':
@@ -394,61 +411,111 @@
         downstream_gem_port_attribute_list = tp['downstream_gem_port_attribute_list']
         self._create_gemports(uni_id, downstream_gem_port_attribute_list, alloc_id, "DOWNSTREAM")
 
-    def load_and_configure_tech_profile(self, uni_id, tp_path):
-        self.log.debug("loading-tech-profile-configuration", uni_id=uni_id, tp_path=tp_path)
+    def _execute_queued_vlan_filter_tasks(self, uni_id):
+        # During OLT Reboots, ONU Reboots, ONU Disable/Enable, it is seen that vlan_filter
+        # task is scheduled even before tp task. So we queue vlan-filter task if tp_task
+        # or initial-mib-download is not done. Once the tp_task is completed, we execute
+        # such queued vlan-filter tasks
+        try:
+            if uni_id in self._queued_vlan_filter_task:
+                self.log.info("executing-queued-vlan-filter-task",
+                              uni_id=uni_id)
+                filter_info = self._queued_vlan_filter_task[uni_id]
+                reactor.callLater(0, self._do_vlan_filter_task, filter_info.get("flow_cookie"),
+                                  uni_id, filter_info.get("add_tag"),
+                                  filter_info.get("uni_port"), filter_info.get("set_vlan_vid"))
+                # Now remove the entry from the dictionary
+                self._queued_vlan_filter_task[uni_id].clear()
+                self.log.debug("executed-queued-vlan-filter-task",
+                               uni_id=uni_id)
+        except Exception as e:
+            self.log.error("vlan-filter-congiuration-failed", uni_id=uni_id, error=e)
 
-        if uni_id not in self._tp_service_specific_task:
-            self._tp_service_specific_task[uni_id] = dict()
-
-        if uni_id not in self._tech_profile_download_done:
-            self._tech_profile_download_done[uni_id] = dict()
-
-        if tp_path not in self._tech_profile_download_done[uni_id]:
-            self._tech_profile_download_done[uni_id][tp_path] = False
-
-        if not self._tech_profile_download_done[uni_id][tp_path]:
+    def _execute_queued_tp_task(self):
+        # During OLT Reboots, ONU Reboots, ONU Disable/Enable, it is seen that tp_task
+        # scheduled even before initial-mib-download task. So we queue tp_task if
+        # initial-mib-download is not done. Once the initial-mib-download is completed,
+        # we execute such queued tp tasks
+        device = self.adapter_agent.get_device(self.device_id)
+        for uni_id in self._get_uni_ids(device):
             try:
-                if tp_path in self._tp_service_specific_task[uni_id]:
-                    self.log.info("tech-profile-config-already-in-progress",
-                                   tp_path=tp_path)
-                    return
-
-                tp = self.kv_client[tp_path]
-                tp = ast.literal_eval(tp)
-                self.log.debug("tp-instance", tp=tp)
-                self._do_tech_profile_configuration(uni_id, tp)
-
-                def success(_results):
-                    self.log.info("tech-profile-config-done-successfully")
-                    device = self.adapter_agent.get_device(self.device_id)
-                    device.reason = 'tech-profile-config-download-success'
-                    self.adapter_agent.update_device(device)
-                    if tp_path in self._tp_service_specific_task[uni_id]:
-                        del self._tp_service_specific_task[uni_id][tp_path]
-                    self._tech_profile_download_done[uni_id][tp_path] = True
-
-                def failure(_reason):
-                    self.log.warn('tech-profile-config-failure-retrying',
-                                   _reason=_reason)
-                    device = self.adapter_agent.get_device(self.device_id)
-                    device.reason = 'tech-profile-config-download-failure-retrying'
-                    self.adapter_agent.update_device(device)
-                    if tp_path in self._tp_service_specific_task[uni_id]:
-                        del self._tp_service_specific_task[uni_id][tp_path]
-                    self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self.load_and_configure_tech_profile,
-                                                       uni_id, tp_path)
-
-                self.log.info('downloading-tech-profile-configuration')
-                self._tp_service_specific_task[uni_id][tp_path] = \
-                       BrcmTpServiceSpecificTask(self.omci_agent, self, uni_id)
-                self._deferred = \
-                       self._onu_omci_device.task_runner.queue_task(self._tp_service_specific_task[uni_id][tp_path])
-                self._deferred.addCallbacks(success, failure)
+                if uni_id in self._queued_tp_task:
+                    self.log.info("executing-queued-tp-task",
+                                  uni_id=uni_id)
+                    reactor.callLater(0, self.load_and_configure_tech_profile,
+                                      uni_id, self._queued_tp_task[uni_id])
+                    self._queued_tp_task[uni_id].clear()
+                    self.log.debug("executed-queued-tp-task",
+                                   uni_id=uni_id)
 
             except Exception as e:
-                self.log.exception("error-loading-tech-profile", e=e)
+                self.log.error("tech-profile-congiuration-failed", uni_id=uni_id, error=e)
+
+    def load_and_configure_tech_profile(self, uni_id, tp_path):
+        if uni_id in self._mib_download_done:
+            self.log.debug("loading-tech-profile-configuration", uni_id=uni_id, tp_path=tp_path)
+
+            if uni_id not in self._in_progress_tp_task:
+                self._in_progress_tp_task[uni_id] = dict()
+
+            if uni_id not in self._tech_profile_download_done:
+                self._tech_profile_download_done[uni_id] = dict()
+
+            if tp_path not in self._tech_profile_download_done[uni_id]:
+                self._tech_profile_download_done[uni_id][tp_path] = False
+
+            if not self._tech_profile_download_done[uni_id][tp_path]:
+                try:
+                    if tp_path in self._in_progress_tp_task[uni_id]:
+                        self.log.info("tech-profile-config-already-in-progress",
+                                       tp_path=tp_path)
+                        return
+
+                    tp = self.kv_client[tp_path]
+                    tp = ast.literal_eval(tp)
+                    self.log.debug("tp-instance", tp=tp)
+                    self._do_tech_profile_configuration(uni_id, tp)
+
+                    def success(_results):
+                        self.log.info("tech-profile-config-done-successfully")
+                        device = self.adapter_agent.get_device(self.device_id)
+                        device.reason = 'tech-profile-config-download-success'
+                        self.adapter_agent.update_device(device)
+                        if tp_path in self._in_progress_tp_task[uni_id]:
+                            del self._in_progress_tp_task[uni_id][tp_path]
+                        self._tech_profile_download_done[uni_id][tp_path] = True
+                        # Now execute any vlan filter tasks that were queued for later
+                        self._execute_queued_vlan_filter_tasks(uni_id)
+
+                    def failure(_reason):
+                        self.log.warn('tech-profile-config-failure-retrying',
+                                       _reason=_reason)
+                        device = self.adapter_agent.get_device(self.device_id)
+                        device.reason = 'tech-profile-config-download-failure-retrying'
+                        self.adapter_agent.update_device(device)
+                        if tp_path in self._in_progress_tp_task[uni_id]:
+                            del self._in_progress_tp_task[uni_id][tp_path]
+                        self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self.load_and_configure_tech_profile,
+                                                           uni_id, tp_path)
+
+                    self.log.info('downloading-tech-profile-configuration')
+                    self._in_progress_tp_task[uni_id][tp_path] = \
+                           BrcmTpServiceSpecificTask(self.omci_agent, self, uni_id)
+                    self._deferred = \
+                           self._onu_omci_device.task_runner.queue_task(self._in_progress_tp_task[uni_id][tp_path])
+                    self._deferred.addCallbacks(success, failure)
+
+                except Exception as e:
+                    self.log.exception("error-loading-tech-profile", e=e)
+            else:
+                    self.log.info("tech-profile-config-already-done")
         else:
-            self.log.info("tech-profile-config-already-done")
+            self.log.info('mib-download-task-not-done-adding-request-to-local-cache',
+                          uni_id=uni_id)
+            self._queued_tp_task[uni_id] = tp_path
+            self._queued_vlan_filter_task[uni_id].clear()
+            self.log.info("cleared-vlan-task-list-as-mib-sync-is-going-to-happen-shortly",
+                          uni_id=uni_id, vlan_filter_task_queue=self._queued_vlan_filter_task)
 
     def update_pm_config(self, device, pm_config):
         # TODO: This has not been tested
@@ -502,9 +569,11 @@
                 if is_downstream(_in_port):
                     self.log.debug('downstream-flow', in_port=_in_port, out_port=_out_port)
                     uni_port = self.uni_port(_out_port)
+                    uni_id = self._get_uni_id(device, _out_port)
                 elif is_upstream(_in_port):
                     self.log.debug('upstream-flow', in_port=_in_port, out_port=_out_port)
                     uni_port = self.uni_port(_in_port)
+                    uni_id = self._get_uni_id(device, _in_port)
                 else:
                     raise Exception('port should be 1 or 2 by our convention')
 
@@ -608,7 +677,7 @@
                     self.log.warn('ignoring-flow-that-does-not-set-vlanid')
                 else:
                     self.log.warn('set-vlanid', uni_id=uni_port.port_number, set_vlan_vid=_set_vlan_vid)
-                    self._do_vlan_filter_task(device, flow.cookie, add_tag=True,
+                    self._do_vlan_filter_task(flow.cookie, uni_id, add_tag=True,
                                               uni_port=uni_port, _set_vlan_vid=_set_vlan_vid)
 
             except Exception as e:
@@ -662,9 +731,11 @@
                     if is_downstream(_in_port):
                         self.log.debug('downstream-flow', in_port=_in_port, out_port=_out_port)
                         uni_port = self.uni_port(_out_port)
+                        uni_id = self._get_uni_id(device, _out_port)
                     elif is_upstream(_in_port):
                         self.log.debug('upstream-flow', in_port=_in_port, out_port=_out_port)
                         uni_port = self.uni_port(_in_port)
+                        uni_id = self._get_uni_id(device, _in_port)
                     else:
                         raise Exception('port should be 1 or 2 by our convention')
 
@@ -768,7 +839,7 @@
                         self.log.warn('ignoring-flow-that-does-not-set-vlanid')
                     else:
                         self.log.warn('set-vlanid', uni_id=uni_port.port_number, set_vlan_vid=_set_vlan_vid)
-                        self._do_vlan_filter_task(device, flow.cookie, add_tag=True,
+                        self._do_vlan_filter_task(flow.cookie, uni_id, add_tag=True,
                                                   uni_port=uni_port, _set_vlan_vid=_set_vlan_vid)
 
                 except Exception as e:
@@ -804,57 +875,119 @@
                     if is_downstream(_in_port):
                         self.log.debug('downstream-flow', in_port=_in_port, out_port=_out_port)
                         uni_port = self.uni_port(_out_port)
+                        uni_id = self._get_uni_id(device, _out_port)
                     elif is_upstream(_in_port):
                         self.log.debug('upstream-flow', in_port=_in_port, out_port=_out_port)
                         uni_port = self.uni_port(_in_port)
+                        uni_id = self._get_uni_id(device, _in_port)
                     else:
                         raise Exception('port should be 1 or 2 by our convention')
 
                     self.log.debug('flow-ports', in_port=_in_port, out_port=_out_port, uni_port=str(uni_port))
+                    _set_vlan_vid = None
+                    for action in fd.get_actions(flow):
+
+                        if action.type == fd.SET_FIELD:
+                            _field = action.set_field.field.ofb_field
+                            assert (action.set_field.field.oxm_class ==
+                                    OFPXMC_OPENFLOW_BASIC)
+                            self.log.debug('action-type-set-field',
+                                           field=_field, in_port=_in_port)
+                            if _field.type == fd.VLAN_VID:
+                                _set_vlan_vid = _field.vlan_vid & 0xfff
+                                self.log.debug('set-field-type-vlan-vid',
+                                               vlan_vid=_set_vlan_vid)
+                            else:
+                                self.log.error('unsupported-action-set-field-type',
+                                               field_type=_field.type)
 
                     # Deleting flow from ONU.
-                    self._do_vlan_filter_task(device, flow.cookie, add_tag=False, uni_port=uni_port)
+                    if _set_vlan_vid is not None and _set_vlan_vid != 0:
+                        self._do_vlan_filter_task(flow.cookie, uni_id, add_tag=False, uni_port=uni_port,
+                                                  _set_vlan_vid=_set_vlan_vid)
                 except Exception as e:
                     self.log.exception('failed-to-remove-flow', e=e)
 
-    def _do_vlan_filter_task(self, device, flow_cookie, add_tag=True, uni_port=None, _set_vlan_vid=None):
-        task_name = 'removing-vlan-tag'
-        if add_tag:
-            assert uni_port is not None
-            task_name = 'setting-vlan-tag'
+    def _get_uni_id(self, device, port):
+       # TODO: This knowledge is locked away in openolt.
+       # and it assumes one onu equals one uni...
+       parent_device = self.adapter_agent.get_device(device.parent_id)
+       parent_adapter_agent = registry('adapter_loader').get_agent(parent_device.adapter)
+       if parent_adapter_agent is None:
+           self.log.error('parent-adapter-could-not-be-retrieved')
 
-        def success(_results):
+       parent_adapter = parent_adapter_agent.adapter.devices[parent_device.id]
+       return parent_adapter.platform.uni_id_from_port_num(port)
+
+    def _get_uni_ids(self, device):
+        uni_ids = list()
+        ports = self.adapter_agent.get_ports(device.id, Port.ETHERNET_UNI)
+        uni_ports = map((lambda port: port.port_no), ports)
+        for uni_port in uni_ports:
+            uni_ids.append(self._get_uni_id(device, uni_port))
+        self.log.debug("uni-ids", uni_ids=uni_ids)
+        return uni_ids
+
+    def _do_vlan_filter_task(self, flow_cookie, uni_id, add_tag=True, uni_port=None, _set_vlan_vid=None):
+        self.log.debug("Starting-vlan-filter-task", flow_cookie=flow_cookie, uni_id=uni_id, add_tag=add_tag,
+                       uni_port=uni_port, set_vlan_vid=_set_vlan_vid)
+
+        if uni_id in self._tech_profile_download_done and self._tech_profile_download_done[uni_id] != {}:
+            task_name = 'removing-vlan-tag'
             if add_tag:
-                self.log.info('vlan-tagging-success', _results=_results)
-                device.reason = 'omci-flows-pushed'
-                self.log.debug('Flow-addition-success', cookie=flow_cookie)
-            else:
-                self.log.info('vlan-untagging-success', _results=_results)
-                device.reason = 'omci-flows-deleted'
-                self.log.debug('Flow-removal-success', cookie=flow_cookie)
+                assert uni_port is not None
+                task_name = 'setting-vlan-tag'
 
-            self._vlan_filter_task = None
+            def success(_results):
+                device = self.adapter_agent.get_device(self.device_id)
+                if add_tag:
+                    self.log.info('vlan-tagging-success', _results=_results)
+                    device.reason = 'omci-flows-pushed'
+                    self.log.debug('Flow-addition-success', cookie=flow_cookie)
+                else:
+                    self.log.info('vlan-untagging-success', _results=_results)
+                    device.reason = 'omci-flows-deleted'
+                    self.log.debug('Flow-removal-success', cookie=flow_cookie)
 
-        def failure(_reason):
-            if add_tag:
-                self.log.warn('vlan-tagging-failure', _reason=_reason)
-                device.reason = 'omci-flows-addition-failed-retrying'
-                self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
-                                                           self._do_vlan_filter_task, device, flow_cookie,
-                                                           add_tag=True, uni_port=uni_port,
-                                                           _set_vlan_vid=_set_vlan_vid)
-            else:
-                self.log.warn('vlan-untagging-failure', _reason=_reason)
-                device.reason = 'omci-flows-deletion-failed-retrying'
-                self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
-                                                           self._do_vlan_filter_task, device, flow_cookie,
-                                                           add_tag=False)
+                self.adapter_agent.update_device(device)
+                self._vlan_filter_task = None
 
-        self.log.info(task_name)
-        self._vlan_filter_task = BrcmVlanFilterTask(self.omci_agent, self.device_id, uni_port, _set_vlan_vid,
-                                                    add_tag=add_tag)
-        self._deferred = self._onu_omci_device.task_runner.queue_task(self._vlan_filter_task)
-        self._deferred.addCallbacks(success, failure)
+            def failure(_reason):
+                device = self.adapter_agent.get_device(self.device_id)
+                if add_tag:
+                    self.log.warn('vlan-tagging-failure', _reason=_reason)
+                    device.reason = 'omci-flows-addition-failed-retrying'
+                    self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
+                                                               self._do_vlan_filter_task, flow_cookie,
+                                                               uni_id, add_tag=True, uni_port=uni_port,
+                                                               _set_vlan_vid=_set_vlan_vid)
+                else:
+                    self.log.warn('vlan-untagging-failure', _reason=_reason)
+                    device.reason = 'omci-flows-deletion-failed-retrying'
+                    self._vlan_filter_task = reactor.callLater(_STARTUP_RETRY_WAIT,
+                                                               self._do_vlan_filter_task, flow_cookie,
+                                                               uni_id, add_tag=False)
+                self.adapter_agent.update_device(device)
+
+            self.log.info(task_name)
+            self._vlan_filter_task = BrcmVlanFilterTask(self.omci_agent, self.device_id, uni_port, _set_vlan_vid,
+                                                        add_tag=add_tag)
+            self._deferred = self._onu_omci_device.task_runner.queue_task(self._vlan_filter_task)
+            self._deferred.addCallbacks(success, failure)
+
+            if add_tag == False:
+                # The flow is going to get deleted eventually.
+                # Let TP download happen again
+                for uni_id in self._in_progress_tp_task:
+                    self._in_progress_tp_task[uni_id].clear()
+                for uni_id in self._tech_profile_download_done:
+                    self._tech_profile_download_done[uni_id].clear()
+
+        else:
+            self.log.info('tp-service-specific-task-not-done-adding-request-to-local-cache',
+                          uni_id=uni_id)
+            self._queued_vlan_filter_task[uni_id]= {"flow_cookie": flow_cookie, \
+                "add_tag": add_tag, "uni_port": uni_port, "set_vlan_vid": _set_vlan_vid}
 
     def get_tx_id(self):
         self.log.debug('function-entry')
@@ -889,9 +1022,12 @@
             self.log.debug('stopping-openomci-statemachine')
             reactor.callLater(0, self._onu_omci_device.stop)
 
+            # Clear mib_download_done list as OMCI SM is going to reset
+            for uni_id in self._get_uni_ids(onu_device):
+                self._mib_download_done.remove(uni_id)
             # Let TP download happen again
-            for uni_id in self._tp_service_specific_task:
-                self._tp_service_specific_task[uni_id].clear()
+            for uni_id in self._in_progress_tp_task:
+                self._in_progress_tp_task[uni_id].clear()
             for uni_id in self._tech_profile_download_done:
                 self._tech_profile_download_done[uni_id].clear()
 
@@ -912,9 +1048,12 @@
         self.log.debug('stopping-openomci-statemachine')
         reactor.callLater(0, self._onu_omci_device.stop)
 
+        # Clear mib_download_done list as OMCI SM is going to reset
+        for uni_id in self._get_uni_ids(onu_device):
+            self._mib_download_done.remove(uni_id)
         # Let TP download happen again
-        for uni_id in self._tp_service_specific_task:
-            self._tp_service_specific_task[uni_id].clear()
+        for uni_id in self._in_progress_tp_task:
+            self._in_progress_tp_task[uni_id].clear()
         for uni_id in self._tech_profile_download_done:
             self._tech_profile_download_done[uni_id].clear()
 
@@ -960,11 +1099,17 @@
                 self.log.debug('stopping-openomci-statemachine')
                 reactor.callLater(0, self._onu_omci_device.stop)
 
+                # Clear mib_download_done list as OMCI SM is going to reset
+                for uni_id in self._get_uni_ids(device):
+                    self._mib_download_done.remove(uni_id)
                 # Let TP download happen again
-                for uni_id in self._tp_service_specific_task:
-                    self._tp_service_specific_task[uni_id].clear()
+                for uni_id in self._in_progress_tp_task:
+                    self._in_progress_tp_task[uni_id].clear()
                 for uni_id in self._tech_profile_download_done:
                     self._tech_profile_download_done[uni_id].clear()
+                self.log.debug('cleared-tech-profile-cache',
+                               tp_service_specific_task=self._in_progress_tp_task,
+                               tech_profile_download_done=self._tech_profile_download_done)
 
                 self.disable_ports(device)
                 device.oper_status = OperStatus.UNKNOWN
@@ -1192,6 +1337,8 @@
                     self.enable_ports(device)
                     self.adapter_agent.update_device(device)
                     self._mib_download_task = None
+                    self._mib_download_done.extend(self._get_uni_ids(device))
+                    self._execute_queued_tp_task()
 
                 def failure(_reason):
                     self.log.warn('mib-download-failure-retrying', _reason=_reason)
diff --git a/voltha/adapters/openolt/openolt_flow_mgr.py b/voltha/adapters/openolt/openolt_flow_mgr.py
index 0b80e54..b00a60f 100644
--- a/voltha/adapters/openolt/openolt_flow_mgr.py
+++ b/voltha/adapters/openolt/openolt_flow_mgr.py
@@ -1101,7 +1101,11 @@
                 self.log.exception('Problem reading this flow', e=e)
 
     def reset_flows(self):
-        self.flows_proxy.update('/', Flows())
+        self.flows_proxy.update('/', Flows(items=[]))
+        self.log.debug("purged-all-device-flows")
+
+        self.logical_flows_proxy.update('/', Flows(items=[]))
+        self.log.debug("purged-all-logical-flows")
 
     """ Add a downstream DHCP trap flow on the NNI interface
     """
diff --git a/voltha/core/device_agent.py b/voltha/core/device_agent.py
index 25eda22..8b280d1 100644
--- a/voltha/core/device_agent.py
+++ b/voltha/core/device_agent.py
@@ -327,8 +327,18 @@
     @inlineCallbacks
     def update_device(self, device):
         self.log.debug('updating-device', device=device.id)
+        last_data = self.last_data
         self.last_data = device  # so that we don't propagate back
         self.proxy.update('/', device)
+
+        # If the last device data also is enabled, active and reachable,
+        # dont do anything.
+        if last_data.admin_state == AdminState.ENABLED and \
+                last_data.oper_status == OperStatus.ACTIVE and \
+                last_data.connect_status == ConnectStatus.REACHABLE:
+            self.log.info("device-status-not-changed--nothing-to-do")
+            return
+
         if device.admin_state == AdminState.ENABLED and \
                 device.oper_status == OperStatus.ACTIVE and \
                 device.connect_status == ConnectStatus.REACHABLE: