Adtran OLT: fix to gem-port creation and better resource manager cleanup during ONU delete

Change-Id: If2efb444c6c54a2abe456fb572e822e4c409e76d
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index a4c84a2..7f3f225 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -1280,9 +1280,29 @@
             self._netconf_client = None
 
         self._rest_client = None
+        mgr, self.resource_mgr = self.resource_mgr, None
+        if mgr is not None:
+            del mgr
 
         self.log.info('deleted', device_id=self.device_id)
 
+    def delete_child_device(self, proxy_address):
+        self.log.debug('sending-deactivate-onu',
+                       olt_device_id=self.device_id,
+                       proxy_address=proxy_address)
+        try:
+            children = self.adapter_agent.get_child_devices(self.device_id)
+            for child in children:
+                if child.proxy_address.onu_id == proxy_address.onu_id and \
+                        child.proxy_address.channel_id == proxy_address.channel_id:
+                    self.adapter_agent.delete_child_device(self.device_id,
+                                                           child.id,
+                                                           onu_device=child)
+                    break
+
+        except Exception as e:
+            self.log.error('adapter_agent error', error=e)
+
     def packet_out(self, egress_port, msg):
         raise NotImplementedError('Overload in a derived class')
 
diff --git a/voltha/adapters/adtran_olt/adtran_olt.py b/voltha/adapters/adtran_olt/adtran_olt.py
index fccee66..b64d6e4 100644
--- a/voltha/adapters/adtran_olt/adtran_olt.py
+++ b/voltha/adapters/adtran_olt/adtran_olt.py
@@ -52,7 +52,7 @@
         self.descriptor = Adapter(
             id=self.name,
             vendor='ADTRAN, Inc.',
-            version='1.30',
+            version='1.31',
             config=AdapterConfig(log_level=LogLevel.INFO)
         )
         log.debug('adtran_olt.__init__', adapter_agent=adapter_agent)
@@ -325,8 +325,24 @@
         handler = self.devices_handlers.get(device.id)
         if handler is not None:
             reactor.callLater(0, handler.delete)
+            del self.device_handlers[device.id]
+            del self.logical_device_id_to_root_device_id[device.parent_id]
+
         return device
 
+    def delete_child_device(self, parent_device_id, child_device):
+        # TODO: Remove if no longer called (may be deprecated xPON interface)
+        log.info('delete-child_device', parent_device_id=parent_device_id,
+                 child_device=child_device)
+        handler = self.devices_handlers[parent_device_id]
+        if handler is not None:
+            reactor.callLater(0, handler.delete_child_device, child_device)
+
+        else:
+            log.error('Could not find matching handler',
+                      looking_for_device_id=parent_device_id,
+                      available_handlers=self.devices.keys())
+
     def get_device_details(self, device):
         """
         This is called to get additional device details based on a NBI call.
diff --git a/voltha/adapters/adtran_olt/adtran_olt_handler.py b/voltha/adapters/adtran_olt/adtran_olt_handler.py
index 2a36cb4..acf2802 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -617,6 +617,17 @@
 
         super(AdtranOltHandler, self).delete()
 
+    def delete_child_device(self, proxy_address):
+        super(AdtranOltHandler, self).delete_child_device(proxy_address)
+
+        # TODO: Verify that ONU object cleanup of ONU will also clean
+        #       up logical id and physical port
+        pon_intf_id_onu_id = (proxy_address.channel_id,
+                              proxy_address.onu_id)
+
+        # Free any PON resources that were reserved for the ONU
+        self.resource_mgr.free_pon_resources_for_onu(pon_intf_id_onu_id)
+
     def rx_pa_packet(self, packets):
         if self._pon_agent is not None:
             for packet in packets:
diff --git a/voltha/adapters/adtran_olt/net/pio_zmq.py b/voltha/adapters/adtran_olt/net/pio_zmq.py
index 6b57295..c5f9814 100644
--- a/voltha/adapters/adtran_olt/net/pio_zmq.py
+++ b/voltha/adapters/adtran_olt/net/pio_zmq.py
@@ -18,6 +18,7 @@
 from enum import IntEnum
 
 DEFAULT_PIO_TCP_PORT = 5555
+#DEFAULT_PIO_TCP_PORT = 5657
 
 
 class PioClient(AdtranZmqClient):
diff --git a/voltha/adapters/adtran_olt/nni_port.py b/voltha/adapters/adtran_olt/nni_port.py
index deee9c7..59ec56b 100644
--- a/voltha/adapters/adtran_olt/nni_port.py
+++ b/voltha/adapters/adtran_olt/nni_port.py
@@ -47,7 +47,6 @@
         # when we enumerated the ports
         self._physical_port_name = kwargs.get('name', 'nni-{}'.format(self._port_no))
         self._logical_port_name = 'nni-{}'.format(self._port_no)
-
         self._logical_port = None
 
         self.sync_tick = 10.0
diff --git a/voltha/adapters/adtran_olt/onu.py b/voltha/adapters/adtran_olt/onu.py
index 41e8b0f..37be2d8 100644
--- a/voltha/adapters/adtran_olt/onu.py
+++ b/voltha/adapters/adtran_olt/onu.py
@@ -359,7 +359,7 @@
 
         try:
             yield defer.gatherResults(dl, consumeErrors=True)
-        except Exception:
+        except Exception as _e:
             pass
 
         dl = []
@@ -368,7 +368,7 @@
 
         try:
             yield defer.gatherResults(dl, consumeErrors=True)
-        except Exception as e:
+        except Exception as _e:
              pass
 
         self._gem_ports.clear()
@@ -379,6 +379,7 @@
                                                 self._serial_number_base64, self._enabled)
         try:
             yield self.olt.rest_client.request('DELETE', uri, name=name)
+            self._olt = None
 
         except RestInvalidResponseCode as e:
             if e.code != 404:
@@ -387,7 +388,6 @@
         except Exception as e:
             self.log.exception('onu-delete', e=e)
 
-        self._olt = None
         returnValue('deleted')
 
     def start(self):
@@ -679,6 +679,7 @@
         del self._tconts[alloc_id]
         try:
             results = yield tcont.remove_from_hardware(self.olt.rest_client)
+
         except RestInvalidResponseCode as e:
             results = None
             if e.code != 404:
diff --git a/voltha/adapters/adtran_olt/pon_port.py b/voltha/adapters/adtran_olt/pon_port.py
index 7aa36bd..f6cef70 100644
--- a/voltha/adapters/adtran_olt/pon_port.py
+++ b/voltha/adapters/adtran_olt/pon_port.py
@@ -25,6 +25,7 @@
 from codec.olt_config import OltConfig
 from onu import Onu
 from voltha.extensions.alarms.onu.onu_los_alarm import OnuLosAlarm
+from voltha.extensions.alarms.onu.onu_discovery_alarm import OnuDiscoveryAlarm
 from voltha.protos.common_pb2 import AdminState
 from voltha.protos.device_pb2 import Port
 from voltha.protos.bbf_fiber_tcont_body_pb2 import TcontsConfigData
@@ -34,12 +35,6 @@
 import resources.adtranolt_platform as platform
 
 
-try:
-    from voltha.extensions.alarms.onu.onu_discovery_alarm import OnuDiscoveryAlarm
-except ImportError:
-    from voltha.extensions.alarms.onu.onu_discovery_alarm import OnuDiscoveryAlarm
-
-
 class PonPort(AdtnPort):
     """
     GPON Port
@@ -434,7 +429,7 @@
         self._admin_state = AdminState.ENABLED if enable else AdminState.DISABLED
 
         try:
-            # Walk the provisioned ONU list and disable any exiting ONUs
+            # Walk the provisioned ONU list and disable any existing ONUs
             results = yield self._get_onu_config()
 
             if isinstance(results, list) and len(results) > 0:
@@ -574,17 +569,16 @@
 
                     # ONU's have their own sync task, extra (should be deleted) are
                     # handled here.
-
                     hw_onu_ids = frozenset(hw_onus.keys())
                     my_onu_ids = frozenset(self._onu_by_id.keys())
 
                     extra_onus = hw_onu_ids - my_onu_ids
-                    dl = [self.delete_onu(onu_id) for onu_id in extra_onus]
+                    dl = [self.delete_onu(onu_id, hw_only=True) for onu_id in extra_onus]
 
                     if self.activation_method == "autoactivate":
                         # Autoactivation of ONUs requires missing ONU detection. If
-                        # not found, create them here but let TCont/GEM-Port restore be
-                        # handle by ONU H/w sync logic.
+                        # not found, create them here but let the TCont/GEM-Port restore
+                        # be handle by ONU H/w sync logic.
                         for onu in [self._onu_by_id[onu_id] for onu_id in my_onu_ids - hw_onu_ids
                                     if self._onu_by_id.get(onu_id) is not None]:
                             dl.append(onu.create(dict(), dict(), reflow=True))
@@ -699,12 +693,12 @@
 
         for onu_id in cleared_alarms:
             self._active_los_alarms.remove(onu_id)
-            OnuLosAlarm(self.olt.alarms, onu_id).clear_alarm()
+            OnuLosAlarm(self.olt.alarms, onu_id, self.port_no).clear_alarm()
 
         for onu_id in new_alarms:
             self._active_los_alarms.add(onu_id)
-            OnuLosAlarm(self.olt.alarms, onu_id).raise_alarm()
-            self.delete_onu(onu_id)
+            OnuLosAlarm(self.olt.alarms, onu_id, self.port_no).raise_alarm()
+            reactor.callLater(0, self.delete_onu, onu_id)
 
     def _process_status_onu_discovered_list(self, discovered_onus):
         """
@@ -903,20 +897,23 @@
             self.log.exception('onu-hw-delete', onu_id=onu_id, e=e)
 
     @inlineCallbacks
-    def delete_onu(self, onu_id):
+    def delete_onu(self, onu_id, hw_only=False):
         onu = self._onu_by_id.get(onu_id)
 
         # Remove from any local dictionary
         if onu_id in self._onu_by_id:
             del self._onu_by_id[onu_id]
-            self.release_onu_id(onu.onu_id)
 
         for sn_64 in [onu.serial_number_64 for onu in self.onus if onu.onu_id == onu_id]:
             del self._onus[sn_64]
 
         if onu is not None:
-            proxy = onu.proxy_address
             try:
+                # And removal from VOLTHA adapter agent
+                if not hw_only:
+                    self._parent.delete_child_device(onu.proxy_address)
+
+                # Remove from hardware
                 onu.delete()
 
             except Exception as e:
@@ -927,7 +924,7 @@
                 yield self._remove_from_hardware(onu_id)
 
             except Exception as e:
-                self.log.exception('onu-remove', serial_number=onu.serial_number, e=e)
+                self.log.debug('onu-remove', serial_number=onu.serial_number, e=e)
 
         # Remove from LOS list if needed
         if onu.id in self._active_los_alarms:
@@ -1011,7 +1008,7 @@
                 gem_port, gp = self.create_xpon_gem_port(onu_id, index, tcont)
 
                 from xpon.olt_gem_port import OltGemPort
-                gp['object'] = OltGemPort.create(self, gp, self.pon_id, onu_id)
+                gp['object'] = OltGemPort.create(self, gp, tcont.alloc_id, self.pon_id, onu_id)
                 self._gem_ports[gem_port.name] = gp['object']
                 gem_ports.append(gp)
 
@@ -1020,8 +1017,10 @@
     def create_xpon_gem_port(self, onu_id, index, tcont):
         # gem port creation (this initial one is for untagged ONU data support / EAPOL)
         gem_port = GemportsConfigData()
-        gem_port.gemport_id = platform.mk_gemport_id(self.pon_id, onu_id, idx=index)
         gem_port.name = 'gem-{}-{}-{}'.format(self.pon_id, onu_id, gem_port.gemport_id)
+        pon_intf_onu_id = (self.pon_id, onu_id)
+        gem_port.gemport_id = self._parent.resource_mgr.get_gemport_id(pon_intf_onu_id)
+        # TODO: Add release of alloc_id on ONU delete and/or TCONT delete
 
         gem_port.tcont_ref = tcont.name
         gp = {
diff --git a/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py b/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py
index 97ec852..fb26b4a 100644
--- a/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py
+++ b/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py
@@ -158,31 +158,45 @@
         return gemport
 
     def free_pon_resources_for_onu(self, pon_intf_id_onu_id):
+        """ Typically called on ONU delete """
 
-        alloc_ids = \
-            self.resource_mgr.get_current_alloc_ids_for_onu(pon_intf_id_onu_id)
         pon_intf_id = pon_intf_id_onu_id[0]
         onu_id = pon_intf_id_onu_id[1]
-        self.resource_mgr.free_resource_id(pon_intf_id,
-                                           PONResourceManager.ALLOC_ID,
-                                           alloc_ids)
+        try:
+            alloc_ids = self.resource_mgr.get_current_alloc_ids_for_onu(pon_intf_id_onu_id)
+            if alloc_ids is not None:
+                self.resource_mgr.free_resource_id(pon_intf_id,
+                                                   PONResourceManager.ALLOC_ID,
+                                                   alloc_ids, onu_id=onu_id)
+        except:
+            pass
 
-        gemport_ids = \
-            self.resource_mgr.get_current_gemport_ids_for_onu(pon_intf_id_onu_id)
-        self.resource_mgr.free_resource_id(pon_intf_id,
-                                           PONResourceManager.GEMPORT_ID,
-                                           gemport_ids)
+        try:
+            gemport_ids = self.resource_mgr.get_current_gemport_ids_for_onu(pon_intf_id_onu_id)
+            if gemport_ids is not None:
+                self.resource_mgr.free_resource_id(pon_intf_id,
+                                                   PONResourceManager.GEMPORT_ID,
+                                                   gemport_ids)
+        except:
+            pass
 
-        self.resource_mgr.free_resource_id(pon_intf_id,
-                                           PONResourceManager.ONU_ID,
-                                           onu_id)
+        try:
+            self.resource_mgr.free_resource_id(pon_intf_id,
+                                               PONResourceManager.ONU_ID,
+                                               onu_id)
+        except:
+            pass
 
         # Clear resource map associated with (pon_intf_id, gemport_id) tuple.
         self.resource_mgr.remove_resource_map(pon_intf_id_onu_id)
 
         # Clear the ONU Id associated with the (pon_intf_id, gemport_id) tuple.
-        for gemport_id in gemport_ids:
-            del self.kv_store[str((pon_intf_id, gemport_id))]
+        if gemport_ids is not None:
+            for gemport_id in gemport_ids:
+                try:
+                    del self.kv_store[str((pon_intf_id, gemport_id))]
+                except:
+                    pass
 
     def initialize_device_resource_range_and_pool(self):
         if not self.use_device_info:
diff --git a/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py b/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
index 83f8e33..cfb3890 100644
--- a/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
+++ b/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
@@ -52,16 +52,6 @@
                                        resource_type=PONResourceManager.ALLOC_ID,
                                        resource_map=alloc_id_map)
 
-            # TODO: I believe that only alloc_id's are constrained to ONU ID.  If so, use
-            #       the generic pool approach
-            # gemport_id_map = dict()
-            # for onu_id in range(platform.MAX_GEM_PORTS_PER_ONU):
-            #     gemport_id_map[onu_id] = [platform.mk_gemport_id(pon_id, onu_id, idx)
-            #                               for idx in xrange(platform.MAX_TCONTS_PER_ONU)]
-            #
-            # self.init_resource_id_pool(pon_intf_id=pon_id,
-            #                            resource_type=PONResourceManager.GEMPORT_ID,
-            #                            resource_map=gemport_id_map)
             self.init_resource_id_pool(
                 pon_intf_id=pon_id,
                 resource_type=PONResourceManager.GEMPORT_ID,
diff --git a/voltha/adapters/adtran_olt/resources/adtranolt_platform.py b/voltha/adapters/adtran_olt/resources/adtranolt_platform.py
index 22bc8c1..de0f317 100644
--- a/voltha/adapters/adtran_olt/resources/adtranolt_platform.py
+++ b/voltha/adapters/adtran_olt/resources/adtranolt_platform.py
@@ -163,19 +163,19 @@
     return alloc_id
 
 
-def mk_gemport_id(_, onu_id, idx=0):
-    """
-    Allocate a GEM-PORT ID.    This is only called by the OLT
-
-    A 4-bit mask was used since we need a gvid for untagged-EAPOL
-    traffic and then up to 8 more for user-user data priority
-    levels.
-
-    :param _: (int)         PON ID (0..n) - not used
-    :param onu_id: (int)    ONU ID (0..MAX_ONUS_PER_PON-1)
-    :param idx: (int)       GEM_PORT Index (0..15)
-    """
-    return MIN_GEM_PORT_ID + (onu_id << 4) + idx
+# def mk_gemport_id(_, onu_id, idx=0):        #  Deprecated, moved to resource manager
+#     """
+#     Allocate a GEM-PORT ID.    This is only called by the OLT
+#
+#     A 4-bit mask was used since we need a gvid for untagged-EAPOL
+#     traffic and then up to 8 more for user-user data priority
+#     levels.
+#
+#     :param _: (int)         PON ID (0..n) - not used
+#     :param onu_id: (int)    ONU ID (0..MAX_ONUS_PER_PON-1)
+#     :param idx: (int)       GEM_PORT Index (0..15)
+#     """
+#     return MIN_GEM_PORT_ID + (onu_id << 4) + idx
 
 
 def intf_id_to_port_no(intf_id, intf_type):
diff --git a/voltha/adapters/adtran_olt/xpon/olt_gem_port.py b/voltha/adapters/adtran_olt/xpon/olt_gem_port.py
index 6e37474..d4abe27 100644
--- a/voltha/adapters/adtran_olt/xpon/olt_gem_port.py
+++ b/voltha/adapters/adtran_olt/xpon/olt_gem_port.py
@@ -51,11 +51,11 @@
         self.data = pb_data     # Needed for non-xPON mode
 
     @staticmethod
-    def create(handler, gem_port, pon_id, onu_id):
+    def create(handler, gem_port, alloc_id, pon_id, onu_id):
         mcast = False           # gem_port['gemport-id'] in [4095]    # TODO: Perform proper lookup
 
         return OltGemPort(gem_port['gemport-id'],
-                          None,
+                          alloc_id,
                           pon_id, onu_id,
                           encryption=gem_port['encryption'],  # aes_indicator,
                           tcont_ref=gem_port['tcont-ref'],