ADTRAN Adapters: Fixes to dataflow logic after xPON deprecation refactoring

Change-Id: I79f9a5d1aa91e4b1adcf43a6c9eec986e740bd35
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..6b765e6 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.33',
             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..85df61b 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:
@@ -744,6 +755,7 @@
                 exceptiontype = None
                 if pkt.type == FlowEntry.EtherType.EAPOL:
                     exceptiontype = 'eapol'
+                    ctag = 4091
                 elif pkt.type == 2:
                     exceptiontype = 'igmp'
                 elif pkt.type == FlowEntry.EtherType.IPv4:
@@ -843,8 +855,7 @@
             priority=200,
             match_fields=[
                 in_port(nni_port),
-                vlan_vid(ofp.OFPVID_PRESENT + self.utility_vlan),
-                # eth_type(FlowEntry.EtherType.EAPOL)       ?? TODO: is this needed
+                vlan_vid(ofp.OFPVID_PRESENT + self.utility_vlan)
             ],
             actions=[output(pon_port)]
         )
diff --git a/voltha/adapters/adtran_olt/flow/evc.py b/voltha/adapters/adtran_olt/flow/evc.py
index ecc0593..6aa8990 100644
--- a/voltha/adapters/adtran_olt/flow/evc.py
+++ b/voltha/adapters/adtran_olt/flow/evc.py
@@ -221,8 +221,10 @@
 
     def add_evc_map(self, evc_map):
         if self._evc_maps is None:
-            self._evc_maps = {}
-        self._evc_maps[evc_map.name] = evc_map
+            self._evc_maps = dict()
+
+        if evc_map.name not in self._evc_maps:
+            self._evc_maps[evc_map.name] = evc_map
 
     def remove_evc_map(self, evc_map):
         if self._evc_maps is not None and evc_map.name in self._evc_maps:
@@ -266,8 +268,7 @@
             xml += '<enabled>{}</enabled>'.format('true' if self._enabled else 'false')
 
             if self._ce_vlan_preservation is not None:
-                xml += '<ce-vlan-preservation>{}</ce-vlan-preservation>'.\
-                    format('true' if self._ce_vlan_preservation else 'false')
+                xml += '<ce-vlan-preservation>{}</ce-vlan-preservation>'.format('false')
 
             if self._s_tag is not None:
                 xml += '<stag>{}</stag>'.format(self._s_tag)
diff --git a/voltha/adapters/adtran_olt/flow/evc_map.py b/voltha/adapters/adtran_olt/flow/evc_map.py
index 33c9360..dc8cc2d 100644
--- a/voltha/adapters/adtran_olt/flow/evc_map.py
+++ b/voltha/adapters/adtran_olt/flow/evc_map.py
@@ -202,7 +202,7 @@
         return '</evc-map></evc-maps>'
 
     def get_evcmap_name(self, onu_id, gem_id):
-        return'{}.{}.{}'.format(self.name, onu_id, gem_id)
+        return'{}.{}.{}.{}'.format(self.name, self.pon_id, onu_id, gem_id)
 
     def _common_install_xml(self):
         xml = '<enabled>{}</enabled>'.format('true' if self._enabled else 'false')
@@ -258,10 +258,11 @@
                 xml += '<ce-vlan-id>{}</ce-vlan-id>'.format(Onu.gem_id_to_gvid(gem_id))
 
                 # GEM-IDs are a sorted list (ascending). First gemport handles downstream traffic
-                if first_gem_id and vid is not None:
+                if first_gem_id and (self._c_tag is not None or vid is not None):
                     first_gem_id = False
+                    vlan = vid or self._c_tag
                     xml += '<network-ingress-filter>'
-                    xml += '<men-ctag>{}</men-ctag>'.format(vid)  # Added in August 2017 model
+                    xml += '<men-ctag>{}</men-ctag>'.format(vlan)  # Added in August 2017 model
                     xml += '</network-ingress-filter>'
 
                 if len(acl_list):
@@ -319,6 +320,7 @@
             work_acls = self._new_acls.copy()
             self._new_acls = dict()
 
+            log.debug('install-evc-map-acls', install_acls=len(work_acls))
             for acl in work_acls.itervalues():
                 try:
                     yield acl.install()
@@ -328,6 +330,13 @@
                     self._new_acls.update(work_acls)
                     raise
 
+            # Any user-data flows attached to this map ?
+            c_tag = None
+            for flow_id, flow in self._flows.items():
+                c_tag = flow.inner_vid or flow.vlan_id or c_tag
+
+            self._c_tag = c_tag
+
             # Now EVC-MAP
             if not self._installed or self._needs_update:
                 log.debug('needs-install-or-update', installed=self._installed, update=self._needs_update)
@@ -493,7 +502,7 @@
     def add_flow(self, flow, evc):
         """
         Add a new flow to an existing EVC-MAP. This can be called to add:
-          o an ACL flow to an existing utility/untagged EVC, or
+          o an ACL flow to an existing utility EVC, or
           o an ACL flow to an existing User Data Flow, or
           o a User Data Flow to an existing ACL flow (and this needs the EVC updated
             as well.
@@ -522,6 +531,7 @@
         self._flows[flow.flow_id] = flow
         self._needs_update = True
 
+        # Are there ACLs to add to any existing (or empty) ACLs
         if len(tmp_map._new_acls) > 0:
             self._new_acls.update(tmp_map._new_acls)        # New ACL flow
             log.debug('add-acl-flows', map=str(self), new=tmp_map._new_acls)
@@ -533,6 +543,7 @@
             self._evc.remove_evc_map(self)
             evc.add_evc_map(self)
             self._evc = evc
+
         return self
 
     @inlineCallbacks
@@ -571,7 +582,7 @@
                     # or Untagged EVC from a user data EVC
                     if self._evc and not self._evc.service_evc and\
                         len(self._flows) > 0 and\
-                        all(f.is_acl_flow for f in self._flows.itervalues()):
+                            all(f.is_acl_flow for f in self._flows.itervalues()):
 
                         self._evc.remove_evc_map(self)
                         first_flow = self._flows.itervalues().next()
@@ -615,7 +626,7 @@
         # Note: When actually installed into the OLT, the .onu_id.gem_port is
         #       appended to the name
         return {'ingress-port': items[1],
-                'flow-id': items[2].split('.')[0]} if len(items) == 3 else dict()
+                'flow-id': items[2].split('.')[0]} if len(items) > 2 else dict()
 
     def add_gem_port(self, gem_port, reflow=False):
         # TODO: Refactor
diff --git a/voltha/adapters/adtran_olt/flow/flow_entry.py b/voltha/adapters/adtran_olt/flow/flow_entry.py
index a0d67fe..02b112e 100644
--- a/voltha/adapters/adtran_olt/flow/flow_entry.py
+++ b/voltha/adapters/adtran_olt/flow/flow_entry.py
@@ -52,7 +52,7 @@
         UPSTREAM = 0          # UNI port to NNI Port
         DOWNSTREAM = 1        # NNI port to UNI Port
         CONTROLLER_UNI = 2    # Trap packet on UNI and send to controller
-        NNI_PON = 3           # NNI port to PON Port (all UNIs) - perhaps multicast?
+        NNI_PON = 3           # NNI port to PON Port (all UNIs) - Utility VLAN & multicast
 
         # The following are not yet supported
         CONTROLLER_NNI = 4    # Trap packet on NNI and send to controller
@@ -585,6 +585,11 @@
         if self.vlan_id == FlowEntry.LEGACY_CONTROL_VLAN and self.eth_type is None and self.pcp == 0:
             return False    # Do not install this flow.  Utility VLAN is in charge
 
+        elif self.flow_direction == FlowEntry.FlowDirection.NNI_PON and \
+                self.vlan_id == self.handler.utility_vlan:
+            # Utility VLAN downstream flow/EVC
+            self._is_acl_flow = True
+
         elif self.vlan_id in self._handler.multicast_vlans:
             #  multicast (ethType = IP)                         # TODO: May need to be an NNI_PON flow
             self._is_multicast = True
@@ -800,7 +805,7 @@
                         if len(gem_ids_with_vid) > 0:
                             gem_ids = gem_ids_with_vid[0]
                             ctag = gem_ids_with_vid[1]
-                            gem_id = gem_ids[0]     # TODO: always grab fist in list
+                            gem_id = gem_ids[0]     # TODO: always grab first in list
                             return flow_entry.in_port, ctag, Onu.gem_id_to_gvid(gem_id), \
                                 evc_map.get_evcmap_name(onu_id, gem_id)
         return None, None, None, None
diff --git a/voltha/adapters/adtran_olt/flow/flow_tables.py b/voltha/adapters/adtran_olt/flow/flow_tables.py
index e90e69b..aa90ee3 100644
--- a/voltha/adapters/adtran_olt/flow/flow_tables.py
+++ b/voltha/adapters/adtran_olt/flow/flow_tables.py
@@ -52,7 +52,8 @@
 
     def add(self, flow):
         assert isinstance(flow, FlowEntry)
-        self._flow_table[flow.flow_id] = flow
+        if flow.flow_id not in self._flow_table:
+            self._flow_table[flow.flow_id] = flow
         return flow
 
     def get(self, item):
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/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..76fbab2 100644
--- a/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py
+++ b/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py
@@ -137,7 +137,7 @@
             resource_type=PONResourceManager.GEMPORT_ID,
             num_of_id=1
         )
-        if gemport_id_list and len(gemport_id_list) == 0:
+        if gemport_id_list is None or len(gemport_id_list) == 0:
             self.log.error("no-gemport-id-available")
             return None
 
@@ -145,44 +145,57 @@
         # allocated for the pon_intf_onu_id tuple
         self.resource_mgr.update_gemport_ids_for_onu(pon_intf_onu_id,
                                                      gemport_id_list)
-
         # We currently use only one gemport
         gemport = gemport_id_list[0]
 
         pon_intf_gemport = (pon_intf, gemport)
+
         # This information is used when packet_indication is received and
         # we need to derive the ONU Id for which the packet arrived based
         # on the pon_intf and gemport available in the packet_indication
         self.kv_store[str(pon_intf_gemport)] = str(onu_id)
-
         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..31fec4b 100644
--- a/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
+++ b/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
@@ -52,21 +52,11 @@
                                        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,
                 start_idx=self.pon_resource_ranges[PONResourceManager.GEMPORT_ID_START_IDX],
-                end_idx=self.pon_resource_ranges[PONResourceManager.GEMPORT_ID_START_IDX])
+                end_idx=self.pon_resource_ranges[PONResourceManager.GEMPORT_ID_END_IDX])
 
     def clear_device_resource_pool(self):
         """
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'],
diff --git a/voltha/adapters/adtran_onu/adtran_onu.py b/voltha/adapters/adtran_onu/adtran_onu.py
index c5288b0..4e0b3c6 100755
--- a/voltha/adapters/adtran_onu/adtran_onu.py
+++ b/voltha/adapters/adtran_onu/adtran_onu.py
@@ -42,13 +42,16 @@
                                                device_handler_class=AdtranOnuHandler,
                                                name='adtran_onu',
                                                vendor='ADTRAN, Inc.',
-                                               version='1.21',
+                                               version='1.22',
                                                device_type='adtran_onu',
                                                vendor_id='ADTN',
                                                accepts_add_remove_flow_updates=False),  # TODO: Support flow-mods
         # Customize OpenOMCI for Adtran ONUs
         self.adtran_omci = deepcopy(OpenOmciAgentDefaults)
 
+        from voltha.extensions.omci.database.mib_db_dict import MibDbVolatileDict
+        self.adtran_omci['mib-synchronizer']['database'] = MibDbVolatileDict
+
         self.adtran_omci['mib-synchronizer']['state-machine'] = AdtnMibSynchronizer
         self.adtran_omci['mib-synchronizer']['tasks']['get-mds'] = AdtnGetMdsTask
         self.adtran_omci['mib-synchronizer']['tasks']['mib-audit'] = AdtnGetMdsTask
diff --git a/voltha/adapters/adtran_onu/adtran_onu_handler.py b/voltha/adapters/adtran_onu/adtran_onu_handler.py
index 045c559..666f94d 100644
--- a/voltha/adapters/adtran_onu/adtran_onu_handler.py
+++ b/voltha/adapters/adtran_onu/adtran_onu_handler.py
@@ -183,18 +183,16 @@
 
     def stop(self):
         assert not self._enabled, 'Stop should only be called if disabled'
-
         self._cancel_deferred()
 
         # Drop registration for adapter messages
         #self.adapter_agent.unregister_for_inter_adapter_messages()
 
         # Heartbeat
-        self._heartbeat.stop()
+        self._heartbeat.enabled = False
 
-        # OMCI Communications
+        # OMCI subscriptions
         self._unsubscribe_to_events()
-        self._openomci.enabled = False
 
         # Port shutdown
         for port in self.uni_ports:
@@ -203,6 +201,9 @@
         if self._pon is not None:
             self._pon.enabled = False
 
+        # OMCI Communications
+        self._openomci.enabled = False
+
     def receive_message(self, msg):
         if self.enabled:
             # TODO: Have OpenOMCI actually receive the messages
@@ -335,8 +336,8 @@
             if flow_entry.flow_id in self._flows:
                 valid_flows.add(flow_entry.flow_id)
 
-            if flow_entry is None or flow_entry.flow_direction not in {FlowEntry.upstream_flow_types,
-                                                                       FlowEntry.downstream_flow_types}:
+            if flow_entry is None or flow_entry.flow_direction not in \
+                    FlowEntry.upstream_flow_types | FlowEntry.downstream_flow_types:
                 continue
 
             is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types
@@ -467,50 +468,51 @@
 
     def disable(self):
         self.log.info('disabling', device_id=self.device_id)
+        try:
+            # Get the latest device reference (If deleted by OLT, it will
+            # throw an exception
+
+            device = self.adapter_agent.get_device(self.device_id)
+
+            # Disable all ports on that device
+            self.adapter_agent.disable_all_ports(self.device_id)
+
+            # Update the device operational status to UNKNOWN
+            device.oper_status = OperStatus.UNKNOWN
+            device.connect_status = ConnectStatus.UNREACHABLE
+            device.reason = 'Disabled'
+            self.adapter_agent.update_device(device)
+
+            # Remove the uni logical port from the OLT, if still present
+            parent_device = self.adapter_agent.get_device(device.parent_id)
+            assert parent_device
+
+            for uni in self.uni_ports:
+                # port_id = 'uni-{}'.format(uni.port_number)
+                port_id = uni.port_id_name()
+                try:
+                    logical_device_id = parent_device.parent_id
+                    assert logical_device_id
+                    port = self.adapter_agent.get_logical_port(logical_device_id,port_id)
+                    self.adapter_agent.delete_logical_port(logical_device_id, port)
+                except KeyError:
+                    self.log.info('logical-port-not-found', device_id=self.device_id,
+                                  portid=port_id)
+
+            # Remove pon port from parent and disable
+            if self._pon is not None:
+                self.adapter_agent.delete_port_reference_from_parent(self.device_id,
+                                                                     self._pon.get_port())
+                self._pon.enabled = False
+
+            # Unregister for proxied message
+            self.adapter_agent.unregister_for_proxied_messages(device.proxy_address)
+
+        except Exception as _e:
+            pass    # This is expected if OLT has deleted the ONU device handler
+
+        # And disable OMCI as well
         self.enabled = False
-
-        # Get the latest device reference
-        device = self.adapter_agent.get_device(self.device_id)
-
-        # Disable all ports on that device
-        self.adapter_agent.disable_all_ports(self.device_id)
-
-        # Update the device operational status to UNKNOWN
-        device.oper_status = OperStatus.UNKNOWN
-        device.connect_status = ConnectStatus.UNREACHABLE
-        device.reason = 'Disabled'
-        self.adapter_agent.update_device(device)
-
-        # Remove the uni logical port from the OLT, if still present
-        parent_device = self.adapter_agent.get_device(device.parent_id)
-        assert parent_device
-
-        for uni in self.uni_ports:
-            # port_id = 'uni-{}'.format(uni.port_number)
-            port_id = uni.port_id_name()
-
-            try:
-                #TODO: there is no logical device if olt disables first
-                logical_device_id = parent_device.parent_id
-                assert logical_device_id
-                port = self.adapter_agent.get_logical_port(logical_device_id,
-                                                           port_id)
-                self.adapter_agent.delete_logical_port(logical_device_id, port)
-            except KeyError:
-                self.log.info('logical-port-not-found', device_id=self.device_id,
-                              portid=port_id)
-
-        # Remove pon port from parent and disable
-        if self._pon is not None:
-            self.adapter_agent.delete_port_reference_from_parent(self.device_id,
-                                                                 self._pon.get_port())
-            self._pon.enabled = False
-
-        # Unregister for proxied message
-        self.adapter_agent.unregister_for_proxied_messages(device.proxy_address)
-
-        # TODO:
-        # 1) Remove all flows from the device? or is it done before we are called
         self.log.info('disabled', device_id=device.id)
 
     def reenable(self):
@@ -568,15 +570,20 @@
     def delete(self):
         self.log.info('deleting', device_id=self.device_id)
 
-        for uni in self._unis.values():
-            uni.stop()
-            uni.delete()
+        try:
+            for uni in self._unis.values():
+                uni.stop()
+                uni.delete()
 
-        self._pon.stop()
-        self._pon.delete()
+            self._pon.stop()
+            self._pon.delete()
+
+        except Exception as _e:
+            pass    # Expected if the OLT deleted us from the device handler
 
         # OpenOMCI cleanup
-        self._openomci.delete()
+        omci, self._openomci = self._openomci, None
+        omci.delete()
 
     def add_uni_ports(self):
         """ Called after in-sync achieved and not in xPON mode"""
diff --git a/voltha/adapters/adtran_onu/flow/flow_entry.py b/voltha/adapters/adtran_onu/flow/flow_entry.py
index 0c37d1d..2051ce8 100644
--- a/voltha/adapters/adtran_onu/flow/flow_entry.py
+++ b/voltha/adapters/adtran_onu/flow/flow_entry.py
@@ -50,6 +50,9 @@
         (FlowDirection.ANI, FlowDirection.UNI): FlowDirection.DOWNSTREAM
     }
 
+    upstream_flow_types = {FlowDirection.UPSTREAM}
+    downstream_flow_types = {FlowDirection.DOWNSTREAM}
+
     # Well known EtherTypes
     class EtherType(IntEnum):
         EAPOL = 0x888E
diff --git a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
index 4b920de..04d9d1f 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 #
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, failure
+from twisted.internet.defer import inlineCallbacks, failure, returnValue
 from voltha.extensions.omci.omci_me import *
 from voltha.extensions.omci.tasks.task import Task
 from voltha.extensions.omci.omci_defs import *
@@ -57,6 +57,7 @@
         self._onu_device = omci_agent.get_device(handler.device_id)
         self._local_deferred = None
         self._flow_entry = flow_entry
+        self._install_by_delete = True
 
         # TODO: Cleanup below that is not needed
         is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types
@@ -71,6 +72,7 @@
         #
         # TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
         #
+        self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
         self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
         # self._hsi_mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
 
@@ -128,16 +130,25 @@
         elif status == RC.InstanceExists:
             return False
 
+        elif status == RC.UnknownInstance and operation == 'delete':
+            return True
+
         raise ServiceInstallFailure('{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
                                     .format(operation, status, error_mask, failed_mask, unsupported_mask))
 
     @inlineCallbacks
     def perform_flow_install(self):
         """
-        Send the commands to configure the flow
+        Send the commands to configure the flow.
+
+        Currently this task uses the pre-installed default TCONT and GEM Port.  This will
+        change when Technology Profiles are supported.
         """
         self.log.info('perform-flow-install', vlan_vid=self._flow_entry.vlan_vid)
 
+        if self._flow_entry.vlan_vid == 0:
+            return
+
         def resources_available():
             # TODO: Rework for non-xpon mode
             return (len(self._handler.uni_ports) > 0 and
@@ -145,33 +156,63 @@
                     len(self._pon.gem_ports))
 
         if self._handler.enabled and resources_available():
+
             omci = self._onu_device.omci_cc
+            brg_id = self._mac_bridge_service_profile_entity_id
+            vlan_vid = self._flow_entry.vlan_vid
+
+            if self._install_by_delete:
+                # Delete any existing flow before adding this new one
+
+                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(brg_id, attributes=None)
+                frame = msg.delete()
+
+                try:
+                    results = yield omci.send(frame)
+                    self.check_status_and_state(results, operation='delete')
+
+                    attributes = dict(
+                        association_type=2,  # Assoc Type, PPTP Ethernet UNI
+                        associated_me_pointer=self._ethernet_uni_entity_id  # Assoc ME, PPTP Entity Id
+                    )
+
+                    frame = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                        self._mac_bridge_service_profile_entity_id,
+                        attributes=attributes
+                    ).create()
+                    results = yield omci.send(frame)
+                    self.check_status_and_state(results, 'flow-recreate-before-set')
+
+                    # TODO: Any of the following needed as well
+
+                    # # Delete bridge ani side vlan filter
+                    # msg = VlanTaggingFilterDataFrame(self._hsi_mac_bridge_port_ani_entity_id)
+                    # frame = msg.delete()
+                    #
+                    # results = yield omci.send(frame)
+                    # self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
+                    #
+                    # # Re-Create bridge ani side vlan filter
+                    # msg = VlanTaggingFilterDataFrame(
+                    #         self._hsi_mac_bridge_port_ani_entity_id,  # Entity ID
+                    #         vlan_tcis=[vlan_vid],             # VLAN IDs
+                    #         forward_operation=0x10
+                    # )
+                    # frame = msg.create()
+                    #
+                    # results = yield omci.send(frame)
+                    # self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
+
+                except Exception as e:
+                    self.log.exception('flow-delete-before-install-failure', e=e)
+                    self.deferred.errback(failure.Failure(e))
+                    returnValue(None)
+
             try:
-                # TODO: make this a member of the onu gem port or the uni port
-                vlan_vid = self._flow_entry.vlan_vid
-
-                # # Delete bridge ani side vlan filter
-                # msg = VlanTaggingFilterDataFrame(self._hsi_mac_bridge_port_ani_entity_id)
-                # frame = msg.delete()
-                #
-                # results = yield omci.send(frame)
-                # self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
-                #
-                # # Re-Create bridge ani side vlan filter
-                # msg = VlanTaggingFilterDataFrame(
-                #         self._hsi_mac_bridge_port_ani_entity_id,  # Entity ID
-                #         vlan_tcis=[vlan_vid],             # VLAN IDs
-                #         forward_operation=0x10
-                # )
-                # frame = msg.create()
-                #
-                # results = yield omci.send(frame)
-                # self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
-
+                # Now set the VLAN Tagging Operation up as we want it
                 # Update uni side extended vlan filter
                 # filter for untagged
                 # probably for eapol
-                # TODO: magic 0x1000 / 4096?
                 # TODO: lots of magic
                 # attributes = dict(
                 #         # This table filters and tags upstream frames
@@ -211,29 +252,55 @@
                 # filter for vlan 0
                 # TODO: lots of magic
 
+                ################################################################################
+                # Update Extended VLAN Tagging Operation Config Data
+                #
+                # Specifies the TPIDs in use and that operations in the downstream direction are
+                # inverse to the operations in the upstream direction
+                # TODO: Downstream mode may need to be modified once we work more on the flow rules
+
                 attributes = dict(
-                        # This table filters and tags upstream frames
-                        received_frame_vlan_tagging_operation_table=
-                        VlanTaggingOperation(
-                                filter_outer_priority=15,   # This entry is not a double-tag rule
-                                filter_outer_vid=4096,      # Do not filter on the outer VID value
-                                filter_outer_tpid_de=0,     # Do not filter on the outer TPID field
-
-                                filter_inner_priority=8,    # Filter on inner vlan
-                                filter_inner_vid=0x0,       # Look for vlan 0
-                                filter_inner_tpid_de=0,     # Do not filter on inner TPID field
-                                filter_ether_type=0,        # Do not filter on EtherType
-
-                                treatment_tags_to_remove=1,   # Remove 1 tags
-                                treatment_outer_priority=15,  # Do not add an outer tag
-                                treatment_outer_vid=0,        # n/a
-                                treatment_outer_tpid_de=0,    # n/a
-
-                                treatment_inner_priority=8,    # Add an inner tag and insert this value as the priority
-                                treatment_inner_vid=vlan_vid,  # use this value as the VID in the inner VLAN tag
-                                treatment_inner_tpid_de=4,     # set TPID to 0x8100
-                        )
+                    input_tpid=0x8100,  # input TPID
+                    output_tpid=0x8100,  # output TPID
+                    downstream_mode=0,  # inverse of upstream
                 )
+
+                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                        self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                        attributes=attributes  # See above
+                )
+                frame = msg.set()
+
+                results = yield omci.send(frame)
+                self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data')
+
+
+                attributes = dict(
+
+
+                    received_frame_vlan_tagging_operation_table=
+                    VlanTaggingOperation(
+                        filter_outer_priority=15,  # This entry is not a double-tag rule
+                        filter_outer_vid=4096,     # Do not filter on the outer VID value
+                        filter_outer_tpid_de=0,    # Do not filter on the outer TPID field
+
+                        filter_inner_priority=15,  # This is a no-tag rule, ignore all other VLAN tag filter fields
+                        filter_inner_vid=0x1000,   # Do not filter on the inner VID
+                        filter_inner_tpid_de=0,    # Do not filter on inner TPID field
+
+                        filter_ether_type=0,         # Do not filter on EtherType
+                        treatment_tags_to_remove=0,  # Remove 0 tags
+
+                        treatment_outer_priority=15,  # Do not add an outer tag
+                        treatment_outer_vid=0,        # n/a
+                        treatment_outer_tpid_de=0,    # n/a
+
+                        treatment_inner_priority=0,    # Add an inner tag and insert this value as the priority
+                        treatment_inner_vid=vlan_vid,  # use this value as the VID in the inner VLAN tag
+                        treatment_inner_tpid_de=4,     # set TPID
+                    )
+                )
+
                 msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
                         self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
                         attributes=attributes  # See above
@@ -242,7 +309,7 @@
 
                 results = yield omci.send(frame)
                 self.check_status_and_state(results,
-                                            'flow-set-ext-vlan-tagging-op-config-data-zero-tagged')
+                                            'flow-set-ext-vlan-tagging-op-config-data-untagged')
                 self.deferred.callback('flow-install-success')
 
             except Exception as e:
diff --git a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
index 2b2853f..0ef7d92 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
@@ -210,7 +210,8 @@
             #  References:
             #            - Nothing
             attributes = {
-                'spanning_tree_ind': False
+                'spanning_tree_ind': False,
+                'learning_ind': True
             }
             frame = MacBridgeServiceProfileFrame(
                 self._mac_bridge_service_profile_entity_id,
diff --git a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
index 209dcd8..11ce30a 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
@@ -157,7 +157,7 @@
         and other characteristics are done once resources (gem-ports, tconts, ...)
         have been defined.
         """
-        self.log.info('perform-service-download')
+        self.log.debug('perform-service-download')
         device = self._handler.adapter_agent.get_device(self.device_id)
 
         def resources_available():
@@ -210,7 +210,7 @@
             #            - ONU created TCONT (created on ONU startup)
 
             tcont_idents = self._onu_device.query_mib(Tcont.class_id)
-            self.log.info('tcont-idents', tcont_idents=tcont_idents)
+            self.log.debug('tcont-idents', tcont_idents=tcont_idents)
 
             for tcont in self._pon.tconts.itervalues():
                 if tcont.entity_id is None:
diff --git a/voltha/adapters/adtran_onu/omci/omci.py b/voltha/adapters/adtran_onu/omci/omci.py
index 6690aba..6e0ed82 100644
--- a/voltha/adapters/adtran_onu/omci/omci.py
+++ b/voltha/adapters/adtran_onu/omci/omci.py
@@ -134,7 +134,7 @@
         self._handler = None
 
         if agent is not None:
-            agent(device_id, cleanup=True)
+            agent.remove_device(device_id, cleanup=True)
 
     @property
     def enabled(self):
diff --git a/voltha/adapters/adtran_onu/onu_tcont.py b/voltha/adapters/adtran_onu/onu_tcont.py
index 1f24b76..dee3fcc 100644
--- a/voltha/adapters/adtran_onu/onu_tcont.py
+++ b/voltha/adapters/adtran_onu/onu_tcont.py
@@ -46,7 +46,7 @@
         return OnuTCont(handler, tcont['alloc-id'], td, name=tcont['name'])
 
     @inlineCallbacks
-    def add_to_hardware(self, omci, tcont_entity_id, prev_alloc_id=OnuTCont.free_tcont_alloc_id):
+    def add_to_hardware(self, omci, tcont_entity_id, prev_alloc_id=free_tcont_alloc_id):
         self.log.debug('add-to-hardware', tcont_entity_id=tcont_entity_id)
 
         if self._entity_id == tcont_entity_id: