[VOL-2787] : When there is an active tech-profile task ongoing for an UNI port
             schedule new tech-profile tasks for same UNI for later execution.

Change-Id: I8d099c71be34e3390b334138a4ab775ecb8c8d4b
diff --git a/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py b/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
index da4de57..79c72fe 100644
--- a/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
+++ b/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
@@ -75,7 +75,7 @@
 OP = EntityOperations
 RC = ReasonCodes
 
-IS_MULTICAST='is_multicast'
+IS_MULTICAST = 'is_multicast'
 GEM_PORT_ID = 'gemport_id'
 _STARTUP_RETRY_WAIT = 10
 _PATH_SEPERATOR = "/"
@@ -101,7 +101,7 @@
         self._omcc_version = OMCCVersion.Unknown
         self._total_tcont_count = 0  # From ANI-G ME
         self._qos_flexibility = 0  # From ONT2_G ME
-        self._tp = dict()          #tp_id -> technology profile definition in KV Store.
+        self._tp = dict()  # tp_id -> technology profile definition in KV Store.
         self._onu_indication = None
         self._unis = dict()  # Port # -> UniPort
 
@@ -126,12 +126,18 @@
 
         self._tp_service_specific_task = dict()
         self._tech_profile_download_done = dict()
+
+        # When the vlan filter is being removed for a given TP ID on a given UNI,
+        # mark that we are expecting a tp delete to happen for this UNI.
+        # Unless the TP delete is complete to not allow new vlan add tasks to this TP ID
+        self._pending_delete_tp = dict()
+
         # Stores information related to queued vlan filter tasks
         # Dictionary with key being uni_id and value being device,uni port ,uni id and vlan id
 
         self._queued_vlan_filter_task = dict()
 
-        self._set_vlan = dict()  #uni_id, tp_id -> set_vlan_id
+        self._set_vlan = dict()  # uni_id, tp_id -> set_vlan_id
         # Initialize KV store client
         self.args = registry('main').get_args()
         if self.args.backend == 'etcd':
@@ -509,9 +515,9 @@
 
                     # Execute mcast task
                     for gem in gem_ports:
-                        self.log.debug("checking-multicast-service-for-gem ",  gem=gem)
+                        self.log.debug("checking-multicast-service-for-gem ", gem=gem)
                         if gem.mcast is True:
-                            self.log.info("found-multicast-service-for-gem ",  gem=gem, uni_id=uni_id, tp_id=tp_id)
+                            self.log.info("found-multicast-service-for-gem ", gem=gem, uni_id=uni_id, tp_id=tp_id)
                             reactor.callInThread(self.start_multicast_service, uni_id, tp_path)
                             self.log.debug("started_multicast_service-successfully", tconts=tconts, gems=gem_ports)
                             break
@@ -543,7 +549,17 @@
             except Exception as e:
                 self.log.exception("error-loading-tech-profile", e=e)
         else:
+            # There is an active tech-profile task ongoing on this UNI port. So, reschedule this task
+            # after a short interval
+            if uni_id in self._tp_service_specific_task and len(self._tp_service_specific_task[uni_id]):
+                self.log.debug("active-tp-tasks-in-progress-for-uni--scheduling-this-task-for-later",
+                               uni_id=uni_id, tp_path=tp_path)
+                reactor.callLater(0.2, self.load_and_configure_tech_profile,
+                                  uni_id, tp_path)
+                return
+
             self.log.info("tech-profile-config-already-done")
+
             # Could be a case where TP exists but new gem-ports are getting added dynamically
             tpstored = self.kv_client[tp_path]
             tpstring = tpstored.decode('ascii')
@@ -592,7 +608,8 @@
                 self._deferred = \
                     self._onu_omci_device.task_runner.queue_task(self._tp_service_specific_task[uni_id][tp_path])
                 self._deferred.addCallbacks(success, failure)
-    def start_multicast_service(self, uni_id, tp_path,retry_count=0):
+
+    def start_multicast_service(self, uni_id, tp_path, retry_count=0):
         self.log.debug("starting-multicast-service", uni_id=uni_id, tp_path=tp_path)
         tp_id = self.extract_tp_id_from_path(tp_path)
         if uni_id in self._set_vlan and tp_id in self._set_vlan[uni_id]:
@@ -609,39 +626,45 @@
                         self._tp[tp_id] = tp
 
                 self.log.debug("mcast-vlan-learned-before", self._set_vlan[uni_id][tp_id], uni_id=uni_id, tp_id=tp_id)
+
                 def success(_results):
                     self.log.debug('multicast-success', uni_id=uni_id)
                     self._multicast_task = None
 
                 def failure(_reason):
                     self.log.warn('multicast-failure', _reason=_reason)
-                    retry = _STARTUP_RETRY_WAIT * (random.randint(1,5))
+                    retry = _STARTUP_RETRY_WAIT * (random.randint(1, 5))
                     reactor.callLater(retry, self.start_multicast_service,
-                                                             uni_id, tp_path)
+                                      uni_id, tp_path)
 
                 self.log.debug('starting-multicast-task', mcast_vlan_id=self._set_vlan[uni_id][tp_id])
                 downstream_gem_port_attribute_list = tp['downstream_gem_port_attribute_list']
                 for i in range(len(downstream_gem_port_attribute_list)):
                     if IS_MULTICAST in downstream_gem_port_attribute_list[i] and \
                             downstream_gem_port_attribute_list[i][IS_MULTICAST] == 'True':
-                        dynamic_access_control_list_table = downstream_gem_port_attribute_list[i]['dynamic_access_control_list'].split("-")
-                        static_access_control_list_table = downstream_gem_port_attribute_list[i]['static_access_control_list'].split("-")
+                        dynamic_access_control_list_table = downstream_gem_port_attribute_list[i][
+                            'dynamic_access_control_list'].split("-")
+                        static_access_control_list_table = downstream_gem_port_attribute_list[i][
+                            'static_access_control_list'].split("-")
                         multicast_gem_id = downstream_gem_port_attribute_list[i]['multicast_gem_id']
                         break
 
                 self._multicast_task = BrcmMcastTask(self.omci_agent, self, self.device_id, uni_id, tp_id,
-                                                     self._set_vlan[uni_id][tp_id],dynamic_access_control_list_table, static_access_control_list_table, multicast_gem_id)
+                                                     self._set_vlan[uni_id][tp_id], dynamic_access_control_list_table,
+                                                     static_access_control_list_table, multicast_gem_id)
                 self._deferred = self._onu_omci_device.task_runner.queue_task(self._multicast_task)
                 self._deferred.addCallbacks(success, failure)
             except Exception as e:
                 self.log.exception("error-loading-multicast", e=e)
         else:
-            if retry_count<30:
+            if retry_count < 30:
                 retry_count = +1
-                self.log.debug("going-to-wait-for-flow-to-learn-mcast-vlan", uni_id=uni_id, tp_id=tp_id, retry=retry_count)
+                self.log.debug("going-to-wait-for-flow-to-learn-mcast-vlan", uni_id=uni_id, tp_id=tp_id,
+                               retry=retry_count)
                 reactor.callLater(0.5, self.start_multicast_service, uni_id, tp_path, retry_count)
             else:
-                self.log.error("mcast-vlan-not-configured-yet-failing-mcast-service-conf", uni_id=uni_id, tp_id=tp_id, retry=retry_count)
+                self.log.error("mcast-vlan-not-configured-yet-failing-mcast-service-conf", uni_id=uni_id, tp_id=tp_id,
+                               retry=retry_count)
 
     def delete_tech_profile(self, uni_id, tp_path, alloc_id=None, gem_port_id=None):
         try:
@@ -690,7 +713,11 @@
                     # The deletion of TCONT marks the complete deletion of tech-profile
                     try:
                         del self._tech_profile_download_done[uni_id][tp_table_id]
+                        self.log.debug("tp-profile-download-flag-cleared", uni_id=uni_id, tp_id=tp_table_id)
                         del self._tp_service_specific_task[uni_id][tp_path]
+                        self.log.debug("tp-service-specific-task-cleared", uni_id=uni_id, tp_id=tp_table_id)
+                        del self._pending_delete_tp[uni_id][tp_table_id]
+                        self.log.debug("pending-delete-tp-task-flag-cleared", uni_id=uni_id, tp_id=tp_table_id)
                     except Exception as ex:
                         self.log.error("del-tp-state-info", e=ex)
 
@@ -790,9 +817,20 @@
                     self.log.debug('flow-ports', in_port=_in_port, out_port=_out_port, uni_port=str(uni_port))
 
                     tp_id = self.get_tp_id_in_flow(flow)
+                    # The vlan filter remove should be followed by a TP deleted for that TP ID.
+                    # Use this information to re-schedule any vlan filter add tasks for the same TP ID again.
+                    # First check if the TP download was done, before we access that TP delete is necessary
+                    if uni_id in self._tech_profile_download_done and tp_id in self._tech_profile_download_done[uni_id] and \
+                            self._tech_profile_download_done[uni_id][tp_id] is True:
+                        if uni_id not in self._pending_delete_tp:
+                            self._pending_delete_tp[uni_id] = dict()
+                            self._pending_delete_tp[uni_id][tp_id] = True
+                        else:
+                            self._pending_delete_tp[uni_id][tp_id] = True
                     # Deleting flow from ONU.
                     self._remove_vlan_filter_task(device, uni_id, uni_port=uni_port, _set_vlan_vid=_vlan_vid,
                                                   match_vlan=_vlan_vid, tp_id=tp_id)
+
                     # TODO:Delete TD task.
                 except Exception as e:
                     self.log.exception('failed-to-remove-flow', e=e)
@@ -1185,6 +1223,13 @@
 
     def _add_vlan_filter_task(self, device, uni_id, uni_port=None, match_vlan=0,
                               _set_vlan_vid=None, _set_vlan_pcp=8, tp_id=0):
+        if uni_id in self._pending_delete_tp and tp_id in self._pending_delete_tp[uni_id] and \
+                self._pending_delete_tp[uni_id][tp_id] is True:
+            self.log.debug("pending-del-tp--scheduling-add-vlan-filter-task-for-later")
+            reactor.callLater(0.2, self._add_vlan_filter_task, device, uni_id, uni_port, match_vlan,
+                              _set_vlan_vid, _set_vlan_pcp, tp_id)
+            return
+
         self.log.info('_adding_vlan_filter_task', uni_port=uni_port, uni_id=uni_id, tp_id=tp_id, match_vlan=match_vlan,
                       vlan=_set_vlan_vid, vlan_pcp=_set_vlan_pcp)
         assert uni_port is not None
@@ -1746,11 +1791,11 @@
 
             self.log.debug("Trying-to-raise-onu-disabled-event")
             OnuDisabledEvent(self.events, self.device_id,
-                           self._onu_indication.intf_id,
-                           device.serial_number,
-                           str(self.device_id),
-                           olt_serial_number, raised_ts,
-                           onu_id=self._onu_indication.onu_id).send(True)
+                             self._onu_indication.intf_id,
+                             device.serial_number,
+                             str(self.device_id),
+                             olt_serial_number, raised_ts,
+                             onu_id=self._onu_indication.onu_id).send(True)
         except Exception as active_event_error:
             self.log.exception('onu-disabled-event-error',
                                errmsg=active_event_error.message)