VOL-3398:
 - Re-org TP State managment logic
 - Mark TP as deleted only after last PON resource referencing
   the TP is deleted

Change-Id: If12a164bb6bc7f3b3c0fd7f5220d71a48a859e84
diff --git a/python/adapters/brcm_openomci_onu/tp_state.py b/python/adapters/brcm_openomci_onu/tp_state.py
new file mode 100644
index 0000000..b6820ea
--- /dev/null
+++ b/python/adapters/brcm_openomci_onu/tp_state.py
@@ -0,0 +1,126 @@
+#
+# 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
+
+import random
+import structlog
+from twisted.internet import reactor
+
+
+class TpState:
+    ALLOC_ID = "alloc_id"
+    GEM_ID = "gem_id"
+
+    def __init__(self, handler, uni_id, tp_path):
+        self.log = structlog.get_logger(device_id=handler.device_id, uni_id=uni_id, tp_path=tp_path)
+        self._handler = handler
+        self._uni_id = uni_id
+        self._tp_path = tp_path
+        self._tp_task_ref = None
+        self._tp_setup_done = False
+        # 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._is_tp_delete_pending = False
+        # Map maintains details of PON resources (alloc_id and gem_port_id(s) to be deleted) for a given TP
+        self._pending_delete_pon_res_map = dict()
+
+    @property
+    def tp_task_ref(self):
+        return self._tp_task_ref
+
+    @tp_task_ref.setter
+    def tp_task_ref(self, tp_task_ref):
+        self._tp_task_ref = tp_task_ref
+
+    @property
+    def tp_setup_done(self):
+        return self._tp_setup_done
+
+    @tp_setup_done.setter
+    def tp_setup_done(self, tp_setup_done):
+        self._tp_setup_done = tp_setup_done
+
+    @property
+    def is_tp_delete_pending(self):
+        return self._is_tp_delete_pending
+
+    @is_tp_delete_pending.setter
+    def is_tp_delete_pending(self, is_tp_delete_pending):
+        self._is_tp_delete_pending = is_tp_delete_pending
+
+    def queue_pending_delete_pon_resource(self, res_type, res):
+        if res_type not in self._pending_delete_pon_res_map:
+            if res_type == TpState.ALLOC_ID:
+                # There is only one alloc-id for a TP
+                self._pending_delete_pon_res_map[TpState.ALLOC_ID] = res
+            elif res_type == TpState.GEM_ID:
+                # There can be more than one gem-port-id for a TP
+                self._pending_delete_pon_res_map[TpState.GEM_ID] = list()
+                self._pending_delete_pon_res_map[TpState.GEM_ID].append(res)
+            else:
+                self.log.error("unknown-res-type", res_type=res_type)
+        else:
+            if res_type == TpState.ALLOC_ID:
+                self.log.warn("alloc-id-already-pending-for-deletion", alloc_id=res)
+            elif res_type == TpState.GEM_ID:
+                # Make sure that we are not adding duplicate gem-port-id to the list
+                for v in self._pending_delete_pon_res_map[TpState.GEM_ID]:
+                    if v.gem_id == res.gem_id:
+                        self.log.warn("gem-id-already-pending-for-deletion", gem_id=res.gem_id)
+                        return
+                self._pending_delete_pon_res_map[TpState.GEM_ID].append(res)
+            else:
+                self.log.error("unknown-res-type", res_type=res_type)
+
+    def pon_resource_delete_complete(self, res_type, res_id):
+        if res_type not in self._pending_delete_pon_res_map:
+            self.log.error("resource-was-not-queued-for-delete", res_type=res_type, res_id=res_id)
+            return
+        if res_type == TpState.ALLOC_ID:
+            # After removing the TCONT, remove the ALLOC_ID key
+            del self._pending_delete_pon_res_map[res_type]
+        else:
+            for v in self._pending_delete_pon_res_map[TpState.GEM_ID]:
+                if v.gem_id == res_id:
+                    self._pending_delete_pon_res_map[TpState.GEM_ID].remove(v)
+                    if len(self._pending_delete_pon_res_map[TpState.GEM_ID]) == 0:
+                        del self._pending_delete_pon_res_map[TpState.GEM_ID]
+                    return
+            self.log.warn("gem-id-was-not-queued-for-delete", gem_id=res_id)
+
+    def get_queued_resource_for_delete(self, res_type, res_id):
+        if res_type not in self._pending_delete_pon_res_map:
+            self.log.warn("resource-was-not-queued-for-delete", res_type=res_type, res_id=res_id)
+            return None
+        if res_type == TpState.ALLOC_ID:
+            # After removing the TCONT, remove the ALLOC_ID key
+            return self._pending_delete_pon_res_map[res_type]
+        elif res_type == TpState.GEM_ID:
+            for i, v in enumerate(self._pending_delete_pon_res_map[res_type]):
+                if v.gem_id == res_id:
+                    return self._pending_delete_pon_res_map[res_type][i]
+        return None
+
+    def is_all_pon_resource_delete_complete(self):
+        return len(self._pending_delete_pon_res_map) == 0
+
+    def reset_tp_state(self):
+        self._tp_task_ref = None
+        self._tp_setup_done = False
+        self._is_tp_delete_pending = False
+        self._pending_delete_pon_res_map.clear()
+        self.log.info("reset-tp-success")