ADTRAN: Initial support for Technology Profiles
Full deprecation of xPON CLI/NBI

Change-Id: I06f6de8d8cf003a9ae9c9a2f1644d3a656b1845f
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index 049cd65..3b80acb 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -110,6 +110,7 @@
         self._rest_support = None
         self._initial_enable_complete = False
         self.resource_mgr = None
+        self.tech_profiles = None       # dict():  intf_id -> ResourceMgr.TechProfile
 
         # Northbound and Southbound ports
         self.northbound_ports = {}  # port number -> Port
@@ -1138,7 +1139,7 @@
             current_time = time.time()
             if current_time < timeout:
                 self.startup = reactor.callLater(5, self._finish_reboot, timeout,
-                                                previous_oper_status,
+                                                 previous_oper_status,
                                                  previous_conn_status)
                 returnValue(self.startup)
 
diff --git a/voltha/adapters/adtran_olt/adtran_olt.py b/voltha/adapters/adtran_olt/adtran_olt.py
index 32e271a..21ce715 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.34',
+            version='1.35',
             config=AdapterConfig(log_level=LogLevel.INFO)
         )
         log.debug('adtran_olt.__init__', adapter_agent=adapter_agent)
diff --git a/voltha/adapters/adtran_olt/adtran_olt_handler.py b/voltha/adapters/adtran_olt/adtran_olt_handler.py
index 056e5be..f1228a8 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -32,6 +32,8 @@
 from voltha.protos.common_pb2 import AdminState, OperStatus
 from voltha.protos.device_pb2 import ImageDownload, Image
 from voltha.protos.openflow_13_pb2 import OFPP_MAX
+from common.tech_profile.tech_profile import *
+from voltha.protos.device_pb2 import Port
 
 
 class AdtranOltHandler(AdtranDeviceHandler):
@@ -80,13 +82,16 @@
         self._downloads = {}        # name -> Download obj
         self._pio_exception_map = []
 
+        self.downstream_shapping_supported = True      # 1971320F1-ML-4154 and later
+
         # FIXME:  Remove once we containerize.  Only exists to keep BroadCom OpenOMCI ONU Happy
         #         when it reaches up our rear and tries to yank out a UNI port number
         self.platform_class = None
 
         # To keep broadcom ONU happy
         from voltha.adapters.adtran_olt.resources.adtranolt_platform import adtran_platform
-        self.platform = adtran_platform()
+        self.platform = adtran_platform()           # TODO: Remove once tech-profiles & containerization is done !!!
+
 
     def __del__(self):
         # OLT Specific things here.
@@ -206,6 +211,17 @@
                                                               hash='Not Available')
                                                 device['software-images'].append(image)
 
+                    # Update features based on version
+                    # Format expected to be similar to:  1971320F1-ML-4154
+
+                    running_version = next((image.version for image in device.get('software-images', list())
+                                           if image.is_active), '').split('-')
+                    if len(running_version) > 2:
+                        try:
+                            self.downstream_shapping_supported = int(running_version[-1]) >= 4154
+                        except ValueError:
+                            pass
+
         except Exception as e:
             self.log.exception('dev-info-failure', e=e)
             raise
@@ -213,18 +229,19 @@
         returnValue(device)
 
     def initialize_resource_manager(self):
-        # Initialize the resource manager
+        # Initialize the resource and tech profile managers
         extra_args = '--olt_model {}'.format(self.resource_manager_key)
         self.resource_mgr = AdtranOltResourceMgr(self.device_id,
                                                  self.host_and_port,
                                                  extra_args,
                                                  self.default_resource_mgr_device_info)
+        self._populate_tech_profile_per_pon_port()
 
     @property
     def default_resource_mgr_device_info(self):
         class AdtranOltDevInfo(object):
             def __init__(self, pon_ports):
-                self.technology = "gpon"
+                self.technology = "xgspon"
                 self.onu_id_start = 0
                 self.onu_id_end = platform.MAX_ONUS_PER_PON
                 self.alloc_id_start = platform.MIN_TCONT_ALLOC_ID
@@ -238,6 +255,44 @@
 
         return AdtranOltDevInfo(self.southbound_ports)
 
+    def _populate_tech_profile_per_pon_port(self):
+        self.tech_profiles = {intf_id: self.resource_mgr.resource_managers[intf_id].tech_profile
+                              for intf_id in self.resource_mgr.device_info.intf_ids}
+
+        # Make sure we have as many tech_profiles as there are pon ports on
+        # the device
+        assert len(self.tech_profiles) == self.resource_mgr.device_info.pon_ports
+
+    def get_tp_path(self, intf_id, ofp_port_name):
+        # TODO: Should get Table id form the flow, as of now hardcoded to DEFAULT_TECH_PROFILE_TABLE_ID (64)
+        # 'tp_path' contains the suffix part of the tech_profile_instance path.
+        # The prefix to the 'tp_path' should be set to \
+        # TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX by the ONU adapter.
+        return self.tech_profiles[intf_id].get_tp_path(DEFAULT_TECH_PROFILE_TABLE_ID,
+                                                       ofp_port_name)
+
+    def delete_tech_profile_instance(self, intf_id, onu_id, logical_port):
+        # Remove the TP instance associated with the ONU
+        ofp_port_name = self.get_ofp_port_name(intf_id, onu_id, logical_port)
+        tp_path = self.get_tp_path(intf_id, ofp_port_name)
+        return self.tech_profiles[intf_id].delete_tech_profile_instance(tp_path)
+
+    def get_ofp_port_name(self, pon_id, onu_id, logical_port_number):
+        parent_port_no = self.pon_id_to_port_number(pon_id)
+        child_device = self.adapter_agent.get_child_device(self.device_id,
+                                                           parent_port_no=parent_port_no, onu_id=onu_id)
+        if child_device is None:
+            self.log.error("could-not-find-child-device", parent_port_no=pon_id, onu_id=onu_id)
+            return None, None
+
+        ports = self.adapter_agent.get_ports(child_device.id, Port.ETHERNET_UNI)
+        port = next((port for port in ports if port.port_no == logical_port_number), None)
+        logical_port = self.adapter_agent.get_logical_port(self.logical_device_id,
+                                                           port.label)
+        ofp_port_name = (logical_port.ofp_port.name, logical_port.ofp_port.port_no)
+
+        return ofp_port_name
+
     @inlineCallbacks
     def enumerate_northbound_ports(self, device):
         """
@@ -250,7 +305,7 @@
         from net.rcmd import RCmd
         try:
             # Also get the MAC Address for the OLT
-            command = "ip -o link | grep eth0 | sed -n -e 's/^.*ether //p' | awk '{ print $1 }'"
+            command = "ip link | grep -A1 eth0 | sed -n -e 's/^.*ether //p' | awk '{ print $1 }'"
             rcmd = RCmd(self.ip_address, self.netconf_username, self.netconf_password,
                         command)
             address = yield rcmd.execute()
@@ -392,7 +447,7 @@
             pon_id = pon.get('pon-id')
             assert pon_id is not None, 'PON ID not found'
             if pon['ifIndex'] is None:
-                pon['port_no'] = self._pon_id_to_port_number(pon_id)
+                pon['port_no'] = self.pon_id_to_port_number(pon_id)
             else:
                 pass        # Need to adjust ONU numbering !!!!
 
@@ -622,11 +677,11 @@
 
         # 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)
+        # 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)
+        # TODO: Done in onu delete now -> 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:
@@ -988,7 +1043,7 @@
 
         return pon_id, onu_id
 
-    def _pon_id_to_port_number(self, pon_id):
+    def pon_id_to_port_number(self, pon_id):
         return pon_id + 1 + 4   # Skip over uninitialized ports
 
     def _port_number_to_pon_id(self, port):
@@ -1232,7 +1287,7 @@
         self.adapter_agent.update_device(device)
         return done
 
-    def add_onu_device(self, intf_id, onu_id, serial_number, tconts, gem_ports):
+    def add_onu_device(self, pon_id, onu_id, serial_number):
         onu_device = self.adapter_agent.get_child_device(self.device_id,
                                                          serial_number=serial_number)
         if onu_device is not None:
@@ -1240,84 +1295,51 @@
 
         try:
             from voltha.protos.voltha_pb2 import Device
-
-            # NOTE - channel_id of onu is set to intf_id
+            # NOTE - channel_id of onu is set to pon_id
             proxy_address = Device.ProxyAddress(device_id=self.device_id,
-                                                channel_id=intf_id, onu_id=onu_id,
+                                                channel_id=pon_id, onu_id=onu_id,
                                                 onu_session_id=onu_id)
 
-            self.log.debug("added-onu", port_no=intf_id,
+            self.log.debug("added-onu", port_no=pon_id,
                            onu_id=onu_id, serial_number=serial_number,
                            proxy_address=proxy_address)
 
             self.adapter_agent.add_onu_device(
                 parent_device_id=self.device_id,
-                parent_port_no=intf_id,
+                parent_port_no=pon_id,
                 vendor_id=serial_number[:4],
                 proxy_address=proxy_address,
                 root=True,
                 serial_number=serial_number,
                 admin_state=AdminState.ENABLED,
             )
-            assert serial_number is not None, 'ONU does not have a serial number'
-
-            onu_device = self.adapter_agent.get_child_device(self.device_id,
-                                                             serial_number=serial_number)
-
-            reactor.callLater(0, self._seba_xpon_create, onu_device, intf_id, onu_id, tconts, gem_ports)
-            return onu_device
 
         except Exception as e:
             self.log.exception('onu-activation-failed', e=e)
             return None
 
-    def _seba_xpon_create(self, onu_device, intf_id, onu_id, tconts, gem_ports):
-        # For SEBA, send over tcont and gem_port information until tech profiles are ready
-        # tcont creation (onu)
-        from voltha.registry import registry
-        self.log.info('inject-tcont-gem-data-onu-handler', intf_id=intf_id,
-                      onu_id=onu_id, tconts=tconts, gem_ports=gem_ports)
+    def setup_onu_tech_profile(self, pon_id, onu_id, logical_port_number):
+        # Send ONU Adapter related tech profile information.
+        self.log.debug('add-tech-profile-info')
 
-        onu_adapter_agent = registry('adapter_loader').get_agent(onu_device.adapter)
-        if onu_adapter_agent is None:
-            self.log.error('onu_adapter_agent-could-not-be-retrieved',
-                           onu_device=onu_device)
+        uni_id = self.platform.uni_id_from_uni_port(logical_port_number)
+        parent_port_no = self.pon_id_to_port_number(pon_id)
+        onu_device = self.adapter_agent.get_child_device(self.device_id,
+                                                         onu_id=onu_id,
+                                                         parent_port_no=parent_port_no)
 
-        # In the OpenOLT version, this is where they signal the BroadCom ONU
-        # to enable OMCI. It is a call to the create_interface IAdapter method
-        #
-        # For the Adtran ONU, we are already running OpenOMCI.  On the Adtran ONU, a call
-        # to create_interface vectors into the xpon_create call which should not
-        # recognize the type and raise a value assertion
-        try:
-            onu_adapter_agent.create_interface(onu_device,
-                                               OnuIndication(intf_id, onu_id))
-        except Exception as _e:
-            pass
-        try:
-            # TODO: deprecate the xPON TCONT/TD/GEMPort once we do not have to call into ONU
-            last_alloc_id = None
-            for tcont_dict in tconts:
-                tcont = tcont_dict['object']
-                td = tcont.traffic_descriptor.data if tcont.traffic_descriptor is not None else None
-                onu_adapter_agent.create_tcont(onu_device, tcont.data, traffic_descriptor_data=td)
-                last_alloc_id = tcont.alloc_id
+        ofp_port_name, ofp_port_no = self.get_ofp_port_name(pon_id, onu_id,
+                                                            logical_port_number)
+        if ofp_port_name is None:
+            self.log.error("port-name-not-found")
+            return
 
-            for gem_port_dict in gem_ports:
-                gem_port = gem_port_dict['object']
-                if onu_device.adapter.lower() == 'brcm_openomci_onu':
-                    # BroadCom OpenOMCI ONU adapter uses the tcont alloc_id as the tcont_ref and currently
-                    # they only assign one.
-                    gem_port.data.tcont_ref = str(last_alloc_id)
-                onu_adapter_agent.create_gemport(onu_device, gem_port.data)
-        except Exception as _e:
-            pass
+        tp_path = self.get_tp_path(pon_id, ofp_port_name)
 
+        self.log.debug('Load-tech-profile-request-to-onu-handler', tp_path=tp_path)
 
-class OnuIndication(object):
-    def __init__(self, intf_id, onu_id):
-        self.name = 'Dummy ONU Indication'
-        self.intf_id = intf_id
-        self.onu_id = onu_id
-        self.oper_state = 'up'
-        self.admin_state = 'up'
+        msg = {'proxy_address': onu_device.proxy_address, 'uni_id': uni_id,
+               'event': 'download_tech_profile', 'event_data': tp_path}
+
+        # Send the event message to the ONU adapter
+        self.adapter_agent.publish_inter_adapter_message(onu_device.id, msg)
diff --git a/voltha/adapters/adtran_olt/flow/evc_map.py b/voltha/adapters/adtran_olt/flow/evc_map.py
index dc8cc2d..ee19df9 100644
--- a/voltha/adapters/adtran_olt/flow/evc_map.py
+++ b/voltha/adapters/adtran_olt/flow/evc_map.py
@@ -17,9 +17,11 @@
 import structlog
 from enum import Enum
 from acl import ACL
-from twisted.internet import defer
+from twisted.internet import defer, reactor
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from ncclient.operations.rpc import RPCError
+from voltha.adapters.openolt.protos import openolt_pb2
+
 
 log = structlog.get_logger()
 
@@ -74,9 +76,9 @@
         self._evc = None
         self._new_acls = dict()           # ACL Name -> ACL Object (To be installed into h/w)
         self._existing_acls = dict()      # ACL Name -> ACL Object (Already in H/w)
-        self._gem_ids_and_vid = None      # { key -> onu-id, value -> tuple(sorted GEM Port IDs, onu_vid) }
         self._is_ingress_map = is_ingress_map
         self._pon_id = None
+        self._onu_id = None               # Remains None if associated with a multicast flow
         self._installed = False
         self._needs_update = False
         self._status_message = None
@@ -100,6 +102,12 @@
         self._match_unicast = False
         self._match_igmp = False
 
+        from common.tech_profile.tech_profile import DEFAULT_TECH_PROFILE_TABLE_ID
+        self._tech_profile_id = DEFAULT_TECH_PROFILE_TABLE_ID
+        self._gem_ids_and_vid = None      # { key -> onu-id, value -> tuple(sorted GEM Port IDs, onu_vid) }
+        self._upstream_bandwidth = None
+        self._shaper_name = None
+
         # ACL logic
         self._eth_type = None
         self._ip_protocol = None
@@ -185,8 +193,12 @@
         return self._pon_id     # May be None
 
     @property
-    def onu_ids(self):
-        return self._gem_ids_and_vid.keys()
+    def onu_id(self):
+        return self._onu_id     # May be None if associated with a multicast flow
+
+    # @property
+    # def onu_ids(self):
+    #     return self._gem_ids_and_vid.keys()
 
     @property
     def gem_ids_and_vid(self):
@@ -217,8 +229,7 @@
         xml += '<match-untagged>{}</match-untagged>'.format('true'
                                                             if self._match_untagged
                                                             else 'false')
-        # if self._c_tag is not None:
-        #     xml += '<ctag>{}</ctag>'.format(self._c_tag)
+
         # TODO: The following is not yet supported (and in some cases, not decoded)
         # self._men_priority = EVCMap.PriorityOption.INHERIT_PRIORITY
         # self._men_pri = 0  # If Explicit Priority
@@ -229,11 +240,6 @@
         # self._match_ce_vlan_id = None
         # self._match_untagged = True
         # self._match_destination_mac_address = None
-        # self._eth_type = None
-        # self._ip_protocol = None
-        # self._ipv4_dst = None
-        # self._udp_dst = None
-        # self._udp_src = None
         return xml
 
     def _ingress_install_xml(self, onu_s_gem_ids_and_vid, acl_list, create):
@@ -344,6 +350,15 @@
                 self._installed = True
                 try:
                     self._cancel_deferred()
+
+                    log.info('upstream-bandwidth')
+                    try:
+                        yield self.update_upstream_flow_bandwidth()
+
+                    except Exception as e:
+                        log.exception('upstream-bandwidth-failed', name=self.name, e=e)
+                        raise
+
                     map_xml = self._ingress_install_xml(self._gem_ids_and_vid, work_acls.values(),
                                                         not is_installed) \
                         if self._is_ingress_map else self._egress_install_xml()
@@ -359,7 +374,7 @@
                     else:
                         self._new_acls.update(work_acls)
 
-                except RPCError as rpc_err:             # TODO: Try to catch this before attempting the install
+                except RPCError as rpc_err:
                     if rpc_err.tag == 'data-exists':    # Known race due to bulk-flow operation
                         pass
 
@@ -369,6 +384,15 @@
                     self._new_acls.update(work_acls)
                     raise
 
+                # Install any needed shapers
+                if self._installed:
+                    try:
+                        yield self.update_downstream_flow_bandwidth()
+
+                    except Exception as e:
+                        log.exception('shaper-install-failed', name=self.name, e=e)
+                        raise
+
         returnValue(self._installed and self._valid)
 
     def _ingress_remove_xml(self, onus_gem_ids_and_vid):
@@ -413,12 +437,17 @@
             if len(dl) > 0:
                 defer.gatherResults(dl, consumeErrors=True)
 
+        def _remove_shaper(_):
+            if self._shaper_name is not None:
+                self.update_downstream_flow_bandwidth(remove=True)
+
         map_xml = self._ingress_remove_xml(self._gem_ids_and_vid) if self._is_ingress_map \
             else self._egress_remove_xml()
 
         d = self._handler.netconf_client.edit_config(map_xml)
         d.addCallbacks(_success, _failure)
         d.addBoth(_remove_acls)
+        d.addBoth(_remove_shaper)
         return d
 
     @inlineCallbacks
@@ -628,48 +657,101 @@
         return {'ingress-port': items[1],
                 'flow-id': items[2].split('.')[0]} if len(items) > 2 else dict()
 
-    def add_gem_port(self, gem_port, reflow=False):
-        # TODO: Refactor
-        if self._is_ingress_map:
-            def gem_ports():
-                ports = []
-                for gems_and_vids in self._gem_ids_and_vid.itervalues():
-                    ports.extend(gems_and_vids[0])
-                return ports
+    @inlineCallbacks
+    def update_upstream_flow_bandwidth(self):
+        """
+        Upstream flow bandwidth comes from the flow_entry related to this EVC-MAP
+        and if no bandwidth property is found, allow full bandwidth
+        """
+        # all flows should should be on the same PON
+        flow = self._flows.itervalues().next()
+        is_pon = flow.handler.is_pon_port(flow.in_port)
 
-            before = gem_ports()
-            self._setup_gem_ids()
-            after = gem_ports()
+        if self._is_ingress_map and is_pon:
+            pon_port = flow.handler.get_southbound_port(flow.in_port)
+            if pon_port is None:
+                returnValue('no PON')
 
-            if reflow or len(before) < len(after):
-                self._installed = False
-                return self.install()
+            session = self._handler.rest_client
+            # TODO: Refactor with tech profiles
+            tconts = None               # pon_port.tconts
+            traffic_descriptors = None  # pon_port.traffic_descriptors
 
-        return succeed('nop')
+            if traffic_descriptors is None or tconts is None:
+                returnValue('no TDs on PON')
 
-    def remove_gem_port(self, gem_port):
-        # TODO: Refactor
-        if self._is_ingress_map:
-            def gem_ports():
-                ports = []
-                for gems_and_vids in self._gem_ids_and_vid.itervalues():
-                    ports.extend(gems_and_vids[0])
-                return ports
+            bandwidth = self._upstream_bandwidth or 10000000000
 
-            before = gem_ports()
-            self._setup_gem_ids()
-            after = gem_ports()
+            if self.pon_id is not None and self.onu_id is not None:
+                name = 'tcont-{}-{}-data'.format(self.pon_id, self.onu_id)
+                td = traffic_descriptors.get(name)
+                tcont = tconts.get(name)
 
-            if len(before) > len(after):
-                if len(after) == 0:
-                    return self._remove()
-                else:
-                    self._installed = False
-                    return self.install()
+                if td is not None and tcont is not None:
+                    alloc_id = tcont.alloc_id
+                    td.maximum_bandwidth = bandwidth
+                    try:
+                        results = yield td.add_to_hardware(session)
+                        log.debug('td-modify-results', results=results)
 
-        return succeed('nop')
+                    except Exception as _e:
+                        pass
 
-    def _setup_gem_ids(self):
+    @inlineCallbacks
+    def update_downstream_flow_bandwidth(self, remove=False):
+        """
+        Downstream flow bandwidth is extracted from the related EVC flow_entry
+        bandwidth property. It is written to this EVC-MAP only if it is found
+        """
+        xml = None
+        results = None
+
+        if remove:
+            name, self._shaper_name = self._shaper_name, None
+            if name is not None:
+                xml = self._shaper_remove_xml(name)
+        else:
+            if self._evc is not None and self._evc.flow_entry is not None \
+                    and self._evc.flow_entry.bandwidth is not None:
+                self._shaper_name = self._name
+                xml = self._shaper_install_xml(self._shaper_name,
+                                               self._evc.flow_entry.bandwidth * 1000)  # kbps
+        if xml is not None:
+            try:
+                log.info('downstream-bandwidth', xml=xml, name=self.name, remove=remove)
+                results = yield self._handler.netconf_client.edit_config(xml)
+
+            except RPCError as rpc_err:
+                if rpc_err.tag == 'data-exists':
+                    pass
+
+            except Exception as e:
+                log.exception('downstream-bandwidth', name=self.name, remove=remove, e=e)
+                raise
+
+        returnValue(results)
+
+    def _shaper_install_xml(self, name, bandwidth):
+        xml = '<adtn-shaper:shapers xmlns:adtn-shaper="http://www.adtran.com/ns/yang/adtran-traffic-shapers" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">' + \
+              ' <adtn-shaper:shaper nc:operation="create">'
+        xml += '  <adtn-shaper:name>{}</adtn-shaper:name>'.format(name)
+        xml += '  <adtn-shaper:enabled>true</adtn-shaper:enabled>'
+        xml += '  <adtn-shaper:rate>{}</adtn-shaper:rate>'.format(bandwidth)
+        xml += '  <adtn-shaper-evc-map:evc-map xmlns:adtn-shaper-evc-map="http://www.adtran.com/ns/yang/adtran-traffic-shaper-evc-maps">{}</adtn-shaper-evc-map:evc-map>'.format(self.name)
+        xml += ' </adtn-shaper:shaper>'
+        xml += '</adtn-shaper:shapers>'
+        return xml
+
+    def _shaper_remove_xml(self, name):
+        xml = '<adtn-shaper:shapers xmlns:adtn-shaper="http://www.adtran.com/ns/yang/adtran-traffic-shapers" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">' + \
+              ' <adtn-shaper:shaper nc:operation="delete">'
+        xml += '  <adtn-shaper:name>{}</adtn-shaper:name>'.format(self.name)
+        xml += ' </adtn-shaper:shaper>'
+        xml += '</adtn-shaper:shapers>'
+        return xml
+
+    def _setup_tech_profiles(self):
+        # Set up the TCONT / GEM Ports for this connection (Downstream only of course)
         # all flows should have same GEM port setup
         flow = self._flows.itervalues().next()
         is_pon = flow.handler.is_pon_port(flow.in_port)
@@ -677,11 +759,116 @@
         if self._is_ingress_map and is_pon:
             pon_port = flow.handler.get_southbound_port(flow.in_port)
 
-            if pon_port is not None:
-                self._pon_id = pon_port.pon_id
-                self._gem_ids_and_vid = pon_port.gem_ids(flow.logical_port,
-                                                         flow.vlan_id,
-                                                         flow.is_multicast_flow)
+            if pon_port is None:
+                return
+
+            onu = next((onu for onu in pon_port.onus if onu.logical_port == flow.logical_port), None)
+
+            if onu is None:       # TODO: Add multicast support later (self.onu_id == None)
+                return
+
+            self._pon_id = pon_port.pon_id
+            self._onu_id = onu.onu_id
+
+            # Identify or allocate TCONT and GEM Ports.  If the ONU has been informed of the
+            # GEM PORTs that belong to it, the tech profiles were already set up by a previous
+            # flows
+            onu_gems = onu.gem_ids(self._tech_profile_id)
+
+            if len(onu_gems) > 0:
+                self._gem_ids_and_vid[onu.onu_id] = (onu_gems, flow.vlan_id)
+                return
+
+            uni_id = self._handler.platform.uni_id_from_uni_port(flow.logical_port)
+            pon_profile = self._handler.tech_profiles[self.pon_id]
+            alloc_id = None
+
+            try:
+                (ofp_port_name, ofp_port_no) = self._handler.get_ofp_port_name(self.pon_id,
+                                                                               self.onu_id,
+                                                                               flow.logical_port)
+                if ofp_port_name is None:
+                    log.error("port-name-not-found")
+                    return
+
+                # Check tech profile instance already exists for derived port name
+                tech_profile = pon_profile.get_tech_profile_instance(self._tech_profile_id,
+                                                                     ofp_port_name)
+                log.debug('Get-tech-profile-instance-status',
+                          tech_profile_instance=tech_profile)
+
+                if tech_profile is None:
+                    # create tech profile instance
+                    tech_profile = pon_profile.create_tech_profile_instance(self._tech_profile_id,
+                                                                            ofp_port_name,
+                                                                            self.pon_id)
+                    if tech_profile is None:
+                        raise Exception('Tech-profile-instance-creation-failed')
+                else:
+                    log.debug('Tech-profile-instance-already-exist-for-given port-name',
+                              ofp_port_name=ofp_port_name)
+
+                # upstream scheduler
+                us_scheduler = pon_profile.get_us_scheduler(tech_profile)
+
+                # downstream scheduler
+                ds_scheduler = pon_profile.get_ds_scheduler(tech_profile)
+
+                # create Tcont protobuf
+                pb_tconts = pon_profile.get_tconts(tech_profile, us_scheduler, ds_scheduler)
+
+                # create TCONTs & GEM Ports locally
+                for pb_tcont in pb_tconts:
+                    from ..xpon.olt_tcont import OltTCont
+                    tcont = OltTCont.create(pb_tcont,
+                                            self._tech_profile_id,
+                                            self.pon_id,
+                                            self.onu_id,
+                                            uni_id,
+                                            ofp_port_no)
+                    if tcont is not None:
+                        onu.add_tcont(tcont)
+
+                # Fetch alloc id and gemports from tech profile instance
+                alloc_id = tech_profile.us_scheduler.alloc_id
+
+                onu_gems = [gem.gemport_id for gem in tech_profile.upstream_gem_port_attribute_list]
+
+                for gem in tech_profile.upstream_gem_port_attribute_list:
+                    from ..xpon.olt_gem_port import OltGemPort
+                    gem_port = OltGemPort.create(self._handler,
+                                                 gem,
+                                                 tech_profile.us_scheduler.alloc_id,
+                                                 self._tech_profile_id,
+                                                 self.pon_id,
+                                                 self.onu_id,
+                                                 uni_id,
+                                                 ofp_port_no)
+                    if gem_port is not None:
+                        onu.add_gem_port(gem_port)
+                #
+                #
+
+                self._gem_ids_and_vid = {onu.onu_id: (onu_gems, flow.vlan_id)}
+
+                # Send technology profile information to ONU
+                reactor.callLater(0, self._handler.setup_onu_tech_profile, self._pon_id,
+                                  self.onu_id, flow.logical_port)
+
+            except BaseException as e:
+                log.exception(exception=e)
+
+            # Update the allocated alloc_id and gem_port_id for the ONU/UNI to KV store
+            pon_intf_onu_id = (self.pon_id, self.onu_id, uni_id)
+            resource_manager = self._handler.resource_mgr.resource_managers[self.pon_id]
+
+            resource_manager.update_alloc_ids_for_onu(pon_intf_onu_id, list([alloc_id]))
+            resource_manager.update_gemport_ids_for_onu(pon_intf_onu_id, onu_gems)
+
+            self._handler.resource_mgr.update_gemports_ponport_to_onu_map_on_kv_store(onu_gems,
+                                                                                      self.pon_id,
+                                                                                      self.onu_id,
+                                                                                      uni_id)
 
     def _decode(self, evc):
         from evc import EVC
@@ -701,6 +888,9 @@
         is_pon = flow.handler.is_pon_port(flow.in_port)
         is_uni = flow.handler.is_uni_port(flow.in_port)
 
+        if flow.bandwidth is not None:
+            self._upstream_bandwidth = flow.bandwidth * 1000000
+
         if is_pon or is_uni:
             # Preserve CE VLAN tag only if utility VLAN/EVC
             self._uni_port = flow.handler.get_port_name(flow.in_port)
@@ -722,7 +912,8 @@
 
         # If no match of VLAN this may be for untagged traffic or upstream and needs to
         # match the gem-port vid
-        self._setup_gem_ids()
+
+        self._setup_tech_profiles()
 
         # self._match_untagged = flow.vlan_id is None and flow.inner_vid is None
         self._c_tag = flow.inner_vid or flow.vlan_id
diff --git a/voltha/adapters/adtran_olt/flow/flow_entry.py b/voltha/adapters/adtran_olt/flow/flow_entry.py
index 04aa9eb..a28367a 100644
--- a/voltha/adapters/adtran_olt/flow/flow_entry.py
+++ b/voltha/adapters/adtran_olt/flow/flow_entry.py
@@ -89,6 +89,7 @@
         self._logical_port = None    # Currently ONU VID is logical port if not doing xPON
         self._is_multicast = False
         self._is_acl_flow = False
+        self._bandwidth = None
 
         # A value used to locate possible related flow entries
         self.signature = None
@@ -141,6 +142,11 @@
         return self.handler.device_id
 
     @property
+    def bandwidth(self):
+        """ Bandwidth in Mbps (if any) """
+        return self._bandwidth
+
+    @property
     def flow_direction(self):
         return self._flow_direction
 
@@ -483,19 +489,22 @@
                     # Downstream flow
                     log.debug('*** field.type == METADATA', value=field.table_metadata)
 
-                    if 0xFFFFFFFF >= field.table_metadata > OFPVID_PRESENT + 4095:
-                        # Default flows for old-style controller flows
-                        self.inner_vid = None
-
-                    elif field.table_metadata > 0xFFFFFFFF:
+                    if field.table_metadata > 4095:
                         # ONOS v1.13.5 or later. c-vid in upper 32-bits
-                        self.inner_vid = field.table_metadata >> 32
+                        vid = field.table_metadata & 0x0FFF
+                        if vid > 0:
+                            self.inner_vid = vid        # CTag is never '0'
 
-                    log.debug('*** field.type == METADATA', value=field.table_metadata,
-                              inner_vid=self.inner_vid)
+                    elif field.table_metadata > 0:
+                        # Pre-ONOS v1.13.5 (vid without the 4096 offset)
+                        self.inner_vid = field.table_metadata
+
                 else:
                     # Upstream flow
                     pass   # Not used upstream at this time
+
+                log.debug('*** field.type == METADATA', value=field.table_metadata,
+                          inner_vid=self.inner_vid)
             else:
                 log.warn('unsupported-selection-field', type=field.type)
                 self._status_message = 'Unsupported field.type={}'.format(field.type)
@@ -672,7 +681,7 @@
             sig_table = self._handler.downstream_flows.get(self.signature)
             flow_table = sig_table.flows if sig_table is not None else None
 
-        if flow_table is None or flow_id not in flow_table:
+        if flow_table is None or flow_id not in flow_table.keys():
             returnValue('NOP')
 
         # Remove from flow table and clean up flow table if empty
diff --git a/voltha/adapters/adtran_olt/onu.py b/voltha/adapters/adtran_olt/onu.py
index a8eb44c..a1c3314 100644
--- a/voltha/adapters/adtran_olt/onu.py
+++ b/voltha/adapters/adtran_olt/onu.py
@@ -18,6 +18,7 @@
 import structlog
 from twisted.internet import reactor, defer
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from common.tech_profile.tech_profile import DEFAULT_TECH_PROFILE_TABLE_ID
 
 from adtran_olt_handler import AdtranOltHandler
 from net.adtran_rest import RestInvalidResponseCode
@@ -58,9 +59,8 @@
         self._resync_flows = False
         self._sync_deferred = None     # For sync of ONT config to hardware
 
-        # Resources
-        self._gem_ports = {}  # gem-id -> GemPort
-        self._tconts = {}  # alloc-id -> TCont
+        self._gem_ports = {}                        # gem-id -> GemPort
+        self._tconts = {}                           # alloc-id -> TCont
         self._uni_ports = onu_info['uni-ports']
 
         # Provisionable items
@@ -276,15 +276,13 @@
                 pass
 
     @inlineCallbacks
-    def create(self, tconts, gem_ports, reflow=False):
+    def create(self, reflow=False):
         """
         Create (or reflow) this ONU to hardware
-        :param tconts: (dict) Current TCONT information
-        :param gem_ports: (dict) Current GEM Port configuration information
         :param reflow: (boolean) Flag, if True, indicating if this is a reflow ONU
                                  information after an unmanaged OLT hardware reboot
         """
-        self.log.debug('create', tconts=tconts, gem_ports=gem_ports, reflow=reflow)
+        self.log.debug('create', reflow=reflow)
         self._cancel_deferred()
 
         data = json.dumps({'onu-id': self._onu_id,
@@ -317,26 +315,9 @@
                     self.log.warn('onu-exists-check', pon_id=self.pon_id, onu_id=self.onu_id,
                                   serial_number=self.serial_number)
 
-        # Now set up all TConts & GEM-ports
-        for tcont in tconts:
-            try:
-                _results = yield self.add_tcont(tcont['object'], reflow=reflow)
-
-            except Exception as e:
-                self.log.exception('add-tcont', tcont=tcont, e=e)
-                first_sync = 2    # Expedite first hw-sync
-
-        for gem_port in gem_ports:
-            try:
-                _results = yield self.add_gem_port(gem_port['object'], reflow=reflow)
-
-            except Exception as e:
-                self.log.exception('add-gem-port', gem_port=gem_port, reflow=reflow, e=e)
-                first_sync = 2    # Expedite first hw-sync
-
         self._sync_deferred = reactor.callLater(first_sync, self._sync_hardware)
-        # Recalculate PON upstream FEC
 
+        # Recalculate PON upstream FEC
         self.pon.upstream_fec_enable = self.pon.any_upstream_fec_enabled
         returnValue('created')
 
@@ -388,6 +369,10 @@
         except Exception as e:
             self.log.exception('onu-delete', e=e)
 
+        # Release resource manager resources for this ONU
+        pon_intf_id_onu_id = (self.pon_id, self.onu_id)
+        self._olt.resource_mgr.free_pon_resources_for_onu(pon_intf_id_onu_id)
+
         returnValue('deleted')
 
     def start(self):
@@ -404,10 +389,7 @@
         self._cancel_deferred()
         self._sync_deferred = reactor.callLater(0, self._sync_hardware)
 
-        tconts, self._tconts = self._tconts, {}
-        gem_ports, self._gem_ports = self._gem_ports, {}
-
-        return self.create(tconts, gem_ports)
+        return self.create()
 
     def _sync_hardware(self):
         from codec.olt_config import OltConfig
@@ -646,24 +628,6 @@
 
         except Exception as e:
             self.log.exception('tcont', tcont=tcont, reflow=reflow, e=e)
-            # May occur with xPON provisioning, use hw-resync to recover
-            results = 'resync needed'
-
-        returnValue(results)
-
-    @inlineCallbacks
-    def update_tcont_td(self, alloc_id, new_td):
-        tcont = self._tconts.get(alloc_id)
-
-        if tcont is None:
-            returnValue('not-found')
-
-        tcont.traffic_descriptor = new_td
-        try:
-            results = yield tcont.add_to_hardware(self.olt.rest_client)
-        except Exception as e:
-            self.log.exception('tcont', tcont=tcont, e=e)
-            # May occur with xPON provisioning, use hw-resync to recover
             results = 'resync needed'
 
         returnValue(results)
@@ -693,10 +657,12 @@
     def gem_port(self, gem_id):
         return self._gem_ports.get(gem_id)
 
-    def gem_ids(self):
+    def gem_ids(self, tech_profile_id):
         """Get all GEM Port IDs used by this ONU"""
+        assert tech_profile_id >= DEFAULT_TECH_PROFILE_TABLE_ID
         return sorted([gem_id for gem_id, gem in self._gem_ports.items()
-                       if not gem.multicast])
+                       if not gem.multicast and
+                       tech_profile_id == gem.tech_profile_id])
 
     @inlineCallbacks
     def add_gem_port(self, gem_port, reflow=False):
@@ -724,28 +690,15 @@
 
         try:
             results = yield gem_port.add_to_hardware(self.olt.rest_client)
-            # May need to update flow tables/evc-maps
-            if gem_port.alloc_id in self._tconts:
-                from flow.flow_entry import FlowEntry
-                # GEM-IDs are a sorted list (ascending). First gemport handles downstream traffic
-                # from flow.flow_entry import FlowEntry
-                evc_maps = FlowEntry.find_evc_map_flows(self)
-
-                for evc_map in evc_maps:
-                    evc_map.add_gem_port(gem_port, reflow=reflow)
 
         except Exception as e:
             self.log.exception('gem-port', gem_port=gem_port, reflow=reflow, e=e)
-            # This can happen with xPON if the ONU has been provisioned, but the PON Discovery
-            # has not occurred for the ONU. Rely on hw sync to recover
             results = 'resync needed'
 
         returnValue(results)
 
     @inlineCallbacks
     def remove_gem_id(self, gem_id):
-        from flow.flow_entry import FlowEntry
-
         gem_port = self._gem_ports.get(gem_id)
 
         if gem_port is None:
@@ -753,14 +706,6 @@
 
         del self._gem_ports[gem_id]
         try:
-
-            if gem_port.alloc_id in self._tconts:
-                # May need to update flow tables/evc-maps
-                # GEM-IDs are a sorted list (ascending). First gemport handles downstream traffic
-                evc_maps = FlowEntry.find_evc_map_flows(self)
-                for evc_map in evc_maps:
-                    evc_map.remove_gem_port(gem_port)
-
             yield gem_port.remove_from_hardware(self.olt.rest_client)
 
         except RestInvalidResponseCode as e:
@@ -771,13 +716,6 @@
             self.log.exception('gem-port-delete', e=ex)
             raise
 
-        for evc_map in FlowEntry.find_evc_map_flows(self):
-            try:
-                evc_map.remove_gem_port(gem_port)
-
-            except Exception as ex:
-                self.log.exception('evc-map-gem-remove', e=ex)
-
         returnValue('done')
 
     @staticmethod
diff --git a/voltha/adapters/adtran_olt/pon_port.py b/voltha/adapters/adtran_olt/pon_port.py
index 6a76fd9..c9e18c8 100644
--- a/voltha/adapters/adtran_olt/pon_port.py
+++ b/voltha/adapters/adtran_olt/pon_port.py
@@ -69,7 +69,7 @@
         self._discovered_onus = []  # List of serial numbers
         self._discovery_deferred = None     # Specifically for ONU discovery
 
-        self._onus = {}                     # serial_number-base64 -> ONU  (allowed list)
+        self._onus = {}                     # serial_number-base64 -> ONU
         self._onu_by_id = {}                # onu-id -> ONU
         self._mcast_gem_ports = {}          # VLAN -> GemPort
 
@@ -87,13 +87,6 @@
         # Statistics
         self.tx_bip_errors = 0
 
-        # xPON Configuration (TODO: Move Tcont/GEMPort to ONU eventually)
-        #                     TODO: TD may be system-wide, wait for Service Profiles
-        self._v_ont_anis = {}             # Name -> dict
-        self._ont_anis = {}               # Name -> dict
-        self._tconts = {}                 # Name -> dict
-        self._gem_ports = {}              # Name -> dict
-
     def __str__(self):
         return "PonPort-{}: Admin: {}, Oper: {}, OLT: {}".format(self._label,
                                                                  self._admin_state,
@@ -473,9 +466,10 @@
                 if logical_port is None or (logical_port == vlan and logical_port in self.olt.multicast_vlans):
                     gem_ids[vlan] = ([gem_port.gem_id], None)
         else:
-            for onu_id, onu in self._onu_by_id.iteritems():
-                if logical_port is None or logical_port == onu.logical_port:
-                    gem_ids[onu_id] = (onu.gem_ids(), flow_vlan)
+            raise NotImplemented('TODO: This is deprecated')
+            # for onu_id, onu in self._onu_by_id.iteritems():
+            #     if logical_port is None or logical_port == onu.logical_port:
+            #         gem_ids[onu_id] = (onu.gem_ids(), flow_vlan)
         return gem_ids
 
     def _get_pon_config(self):
@@ -627,11 +621,9 @@
 
         # Get new/missing from the discovered ONU leaf.  Stale ONUs from previous
         # configs are now cleaned up during h/w re-sync/reflow.
-
         new, rediscovered_onus = self._process_status_onu_discovered_list(status.discovered_onu)
 
         # Process newly discovered ONU list and rediscovered ONUs
-
         for serial_number in new | rediscovered_onus:
             reactor.callLater(0, self.add_onu, serial_number, status)
 
@@ -725,41 +717,24 @@
     def _get_onu_info(self, serial_number):
         """
         Parse through available xPON information for ONU configuration settings
+
         :param serial_number: (string) Decoded (not base64) serial number string
         :return: (dict) onu config data or None on lookup failure
         """
         try:
             if self.activation_method == "autodiscovery":
                 # if self.authentication_method == 'serial-number':
-                raise NotImplemented('TODO: Not supported at this time')
+                raise NotImplemented('autodiscovery: Not supported at this time')
 
             elif self.activation_method == "autoactivate":
-                # TODO: Currently a somewhat copy of the xPON way to do things
-                #       update here with Technology profile info when it becomes available
-                activate_onu, tconts, gem_ports = self.get_device_profile_info(serial_number)
+                onu_id = self.get_next_onu_id
+                enabled = True
+                channel_speed = 10000000000
+                upstream_fec_enabled = True
 
-                try:
-                    # TODO: (SEBA) All of this can be greatly simplified once we fully
-                    #       deprecate xPON support
-                    vont_ani = next(info for _, info in self._v_ont_anis.items()
-                                    if info.get('expected-serial-number') == serial_number)
-
-                    onu_id = vont_ani['onu-id']
-                    enabled = vont_ani['enabled']
-                    channel_speed = vont_ani['upstream-channel-speed']
-                    upstream_fec_enabled = self._ont_anis[vont_ani['name']]['upstream-fec']
-
-                except StopIteration:
-                    # Can happen if vont-ani or ont-ani has not yet been configured
-                    self.log.error('no-vont-or-ont-autoactivate')
-                    return None, False
-
-                except Exception as e:
-                    self.log.exception('autoactivate', e=e)
-                    raise
             else:
-                self.log.debug('unsupported-activation-method', method=self.activation_method)
-                return None, False
+                self.log.error('unsupported-activation-method', method=self.activation_method)
+                return None
 
             onu_info = {
                 'device-id': self.olt.device_id,
@@ -770,30 +745,26 @@
                 'upstream-channel-speed': channel_speed,
                 'upstream-fec': upstream_fec_enabled,
                 'password': Onu.DEFAULT_PASSWORD,
-                't-conts': tconts,
-                'gem-ports': gem_ports,
             }
-            intf_id = platform.intf_id_to_port_no(self._pon_id, Port.PON_OLT)
+            pon_id = self.olt.pon_id_to_port_number(self._pon_id)
 
-            # TODO: Currently only one ONU port and it is hardcoded to port 0
-            onu_info['uni-ports'] = [platform.mk_uni_port_num(intf_id, onu_id)]
-
-            # Hold off ONU activation until at least one GEM Port is defined.
-            self.log.debug('onu-info-tech-profiles', gem_ports=gem_ports)
+            # TODO: Currently only one  UNI port and it is hardcoded to port 0
+            onu_info['uni-ports'] = [platform.mk_uni_port_num(pon_id, onu_id)]
 
             # return onu_info
-            return onu_info, activate_onu
+            return onu_info
 
         except Exception as e:
             self.log.exception('get-onu-info-tech-profiles', e=e)
-            return None, False
+            return None
 
     @inlineCallbacks
     def add_onu(self, serial_number_64, status):
         """
         Add an ONU to the PON
 
-        TODO:  This needs major refactoring after xPON is deprecated to be more maintainable
+        :param serial_number_64: (str) base-64 encoded serial number
+        :param status: (dict) OLT PON status. Used to detect if ONU is already provisioned
         """
         serial_number = Onu.serial_number_to_string(serial_number_64)
         self.log.info('add-onu', serial_number=serial_number,
@@ -804,76 +775,53 @@
         if serial_number_64 in self._onus:
             returnValue('wait-for-fpga')
 
-        onu_info, activate_onu = self._get_onu_info(serial_number)
+        if serial_number_64 in status.onus:
+            # Handles fast entry into this task before FPGA can clear results of ONU delete
+            returnValue('sticky-onu')
 
-        if activate_onu:
-            alarm = OnuDiscoveryAlarm(self.olt.alarms, self.pon_id, serial_number)
-            reactor.callLater(0, alarm.raise_alarm)
+        # At our limit?   TODO: Retrieve from device resource manager if available
+        if len(self._onus) >= self.MAX_ONUS_SUPPORTED:
+            self.log.warning('max-onus-provisioned', count=len(self._onus))
+            returnValue('max-onus-reached')
 
-        if serial_number_64 not in status.onus or onu_info['onu-id'] in self._active_los_alarms:
-            onu = None
-            onu_id = onu_info['onu-id']
+        onu_info = self._get_onu_info(serial_number)
+        onu_id = onu_info['onu-id']
 
-            if serial_number_64 in self._onus and onu_id in self._onu_by_id:
-                # Handles fast entry into this task before FPGA can set/clear results
-                returnValue('sticky-onu')
+        if onu_id is None:
+            self.log.warning('no-onu-ids-available', serial_number=serial_number,
+                             serial_number_64=serial_number_64)
+            returnValue('no-ids-available')
 
-            elif (serial_number_64 in self._onus and onu_id not in self._onu_by_id) or \
-                    (serial_number_64 not in self._onus and onu_id in self._onu_by_id):
-                # May be here due to unmanaged power-cycle on OLT or fiber bounced for a
-                # previously activated ONU.
-                #
-                # TODO: Track when the ONU was discovered, and if > some maximum amount
-                #       place the ONU (with serial number & ONU ID) on a wait list and
-                #       use that to recover the ONU ID should it show up within a
-                #       reasonable amount of time.  Periodically groom the wait list and
-                #       delete state ONUs so we can reclaim the ONU ID.
-                #
-                returnValue('waiting-for-fpga')    # non-XPON mode will not
+        # TODO: Is the best before or after creation in parent device?
+        alarm = OnuDiscoveryAlarm(self.olt.alarms, self.pon_id, serial_number)
+        reactor.callLater(0, alarm.raise_alarm)
 
-            elif len(self._onus) >= self.MAX_ONUS_SUPPORTED:
-                self.log.warning('max-onus-provisioned', count=len(self._onus))
-                returnValue('max-onus-reached')
+        # Have the core create the ONU device
+        self._parent.add_onu_device(self._port_no, onu_id, serial_number)
 
-            else:
-                # TODO: Make use of upstream_channel_speed variable
-                onu = Onu(onu_info)
-                self._onus[serial_number_64] = onu
-                self._onu_by_id[onu.onu_id] = onu
+        onu = Onu(onu_info)
+        self._onus[serial_number_64] = onu
+        self._onu_by_id[onu.onu_id] = onu
 
-            if onu is not None:
-                tconts = onu_info.pop('t-conts')
-                gem_ports = onu_info.pop('gem-ports')
+        try:
+            # Add Multicast to PON on a per-ONU basis
+            #
+            # for id_or_vid, gem_port in gem_ports.iteritems():
+            #     try:
+            #         if gem_port.multicast:
+            #             self.log.debug('id-or-vid', id_or_vid=id_or_vid)
+            #             vid = self.olt.multicast_vlans[0] if len(self.olt.multicast_vlans) else None
+            #             if vid is not None:
+            #                 self.add_mcast_gem_port(gem_port, vid)
+            #
+            #     except Exception as e:
+            #         self.log.exception('id-or-vid', e=e)
 
-                if activate_onu:
-                    _onu_device = self._parent.add_onu_device(self._port_no,     # PON ID
-                                                              onu.onu_id,        # ONU ID
-                                                              serial_number,
-                                                              tconts,
-                                                              gem_ports)
-                try:
-                    # Add Multicast to PON on a per-ONU basis until xPON multicast support
-                    # is ready
-                    # In xPON/BBF, mcast gems tie back to the channel-pair
-                    # MCAST VLAN IDs stored as a negative value
+            _results = yield onu.create()
 
-                    # for id_or_vid, gem_port in gem_ports.iteritems():  # TODO: Deprecate this when BBF ready
-                    #     try:
-                    #         if gem_port.multicast:
-                    #             self.log.debug('id-or-vid', id_or_vid=id_or_vid)
-                    #             vid = self.olt.multicast_vlans[0] if len(self.olt.multicast_vlans) else None
-                    #             if vid is not None:
-                    #                 self.add_mcast_gem_port(gem_port, vid)
-                    #
-                    #     except Exception as e:
-                    #         self.log.exception('id-or-vid', e=e)
-
-                    # TODO: Need to clean up TCont and GEM-Port on ONU delete in non-xPON mode
-                    _results = yield onu.create(tconts, gem_ports)
-
-                except Exception as e:
-                    self.log.exception('add-onu', serial_number=serial_number_64, e=e)
-                    # allowable exception.  H/w re-sync will recover any issues
+        except Exception as e:
+            self.log.warning('add-onu', serial_number=serial_number_64, e=e)
+            # allowable exception.  H/w re-sync will recover/fix any issues
 
     @property
     def get_next_onu_id(self):
@@ -910,29 +858,12 @@
 
         if onu is not None:
             try:
-                # Remove from xPON config    (TODO: Deprecate this by refactoring ONU add steps)
-                name = 'customer-{}-{}'.format(self.pon_id, onu_id)
-                self._v_ont_anis.pop(name, None)
-                self._ont_anis.pop(name, None)
-
-                tcont_name = 'tcont-{}-{}-data'.format(self.pon_id, onu_id)
-                self._tconts.pop(tcont_name, None)
-
-                gem_ids = {gem_port.gem_id for gem_port in onu.gem_ports}
-                for gem_id in gem_ids:
-                    gem_port_name = 'gem-{}-{}-{}'.format(self.pon_id, onu_id, gem_id)
-                    self._gem_ports.pop(gem_port_name, None)
-
-            except Exception as e:
-                self.log.exception('onu-delete-cleanup', serial_number=onu.serial_number, e=e)
-
-            try:
-                # Remove from hardware
-                onu.delete()
+                proxy_address = onu.proxy_address
+                onu.delete()                            # Remove from hardware
 
                 # And removal from VOLTHA adapter agent
                 if not hw_only:
-                    self._parent.delete_child_device(onu.proxy_address)
+                    self._parent.delete_child_device(proxy_address)
 
             except Exception as e:
                 self.log.exception('onu-delete', serial_number=onu.serial_number, e=e)
@@ -961,122 +892,3 @@
         assert len(self.olt.multicast_vlans) == 1, 'Only support 1 MCAST VLAN until BBF Support'
 
         self._mcast_gem_ports[vlan] = mcast_gem
-
-    #  ===========================================================================
-    #
-    #  Some xPON methods that need refactoring
-
-    @property
-    def tconts(self):
-        return self._tconts
-
-    @property
-    def gem_ports(self):
-        return self._gem_ports
-
-    def get_device_profile_info(self, serial_number):
-        vont_ani_info = next((info for _, info in self._v_ont_anis.items()
-                             if info.get('expected-serial-number') == serial_number), None)
-        # New ONU?
-        activate_onu = vont_ani_info is None
-
-        tconts = list()
-        gem_ports = list()
-
-        if activate_onu:
-            onu_id = self.get_next_onu_id
-            name = 'customer-{}-{}'.format(self.pon_id, onu_id)
-            vont_ani_info = {
-                'name'                    : name,
-                'enabled'                 : True,
-                'description'             : '',
-                'pon-id'                  : self.pon_id,
-                'onu-id'                  : onu_id,
-                'expected-serial-number'  : serial_number,
-                'expected-registration-id': '',                         # TODO: How about this?
-                'upstream-channel-speed'  : 10000000000,
-            }
-            ont_ani_info = {
-                'name':             name,
-                'description':      '',
-                'enabled':          True,
-                'upstream-fec':     True,
-                'mgnt-gemport-aes': False
-            }
-            assert name not in self._v_ont_anis
-            assert name not in self._ont_anis
-            self._v_ont_anis[name] = vont_ani_info
-            self._ont_anis[name] = ont_ani_info
-
-            tcont, tc, td = self.create_xpon_tcont(onu_id)
-            from xpon.olt_tcont import OltTCont
-            tc['object'] = OltTCont.create(tc,
-                                           OltTrafficDescriptor.create(tc['td-ref']),
-                                           self.pon_id, onu_id)
-            self._tconts[tcont.name] = tc['object']
-            tconts.append(tc)
-
-            # Now create the User-Data GEM-Ports
-            num_priorities = 1                          # TODO: Pull from tech-profile later
-            for index in range(0, num_priorities):
-                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, tcont.alloc_id, self.pon_id, onu_id)
-                self._gem_ports[gem_port.name] = gp['object']
-                gem_ports.append(gp)
-
-        return activate_onu, tconts, gem_ports
-
-    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.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 = {
-            'name': gem_port.name,
-            'gemport-id': gem_port.gemport_id,
-            'tcont-ref': gem_port.tcont_ref,
-            'encryption': False,
-            'traffic-class': 0,
-            'data': gem_port
-        }
-        return gem_port, gp
-
-    def create_xpon_tcont(self, onu_id):
-        """ Create the xPON TCONT Config data """
-        tcont = TcontsConfigData()
-        tcont.name = 'tcont-{}-{}-data'.format(self.pon_id, onu_id)
-        pon_intf_onu_id = (self.pon_id, onu_id)
-        tcont.alloc_id = self._parent.resource_mgr.get_alloc_id(pon_intf_onu_id)
-        # TODO: Add release of alloc_id on ONU delete and/or TCONT delete
-
-        traffic_desc = TrafficDescriptorProfileData(name='BestEffort',
-                                                    fixed_bandwidth=0,
-                                                    assured_bandwidth=0,
-                                                    maximum_bandwidth=10000000000,
-                                                    priority=0,
-                                                    weight=0,
-                                                    additional_bw_eligibility_indicator=0)
-        tc = {
-            'name': tcont.name,
-            'alloc-id': tcont.alloc_id,
-            'pon-id': self.pon_id,
-            'onu-id': onu_id,
-            'td-ref': {  # TODO: This should be the TD Name and the TD installed in xpon cache
-                'name': traffic_desc.name,
-                'fixed-bandwidth': traffic_desc.fixed_bandwidth,
-                'assured-bandwidth': traffic_desc.assured_bandwidth,
-                'maximum-bandwidth': traffic_desc.maximum_bandwidth,
-                'priority': traffic_desc.priority,
-                'weight': traffic_desc.weight,
-                'additional-bw-eligibility-indicator': 0,
-                'data': traffic_desc
-            },
-            'data': tcont
-        }
-        return tcont, tc, traffic_desc
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 76fbab2..c6ce41b 100644
--- a/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py
+++ b/voltha/adapters/adtran_olt/resources/adtran_olt_resource_manager.py
@@ -58,6 +58,10 @@
             self.device_id, self.args.backend,
             host, port
         )
+        # Tech profiles uses this resource manager to retrieve information on a per-interface
+        # basis
+        self.resource_managers = {intf_id: self.resource_mgr for intf_id in device_info.intf_ids}
+
         # Flag to indicate whether information fetched from device should
         # be used to initialize PON Resource Ranges
         self.use_device_info = False
@@ -71,7 +75,8 @@
     def get_onu_id(self, pon_intf_id):
         onu_id = self.resource_mgr.get_resource_id(pon_intf_id,
                                                    PONResourceManager.ONU_ID,
-                                                   1)
+                                                   onu_id=None,
+                                                   num_of_id=1)
         if onu_id is not None:
             pon_intf_onu_id = (pon_intf_id, onu_id)
             self.resource_mgr.init_resource_map(pon_intf_onu_id)
@@ -97,12 +102,10 @@
             # ONU.
             return alloc_id_list[0]
 
-        alloc_id_list = self.resource_mgr.get_resource_id(
-            pon_intf_id=pon_intf,
-            onu_id=onu_id,
-            resource_type=PONResourceManager.ALLOC_ID,
-            num_of_id=1
-        )
+        alloc_id_list = self.resource_mgr.get_resource_id(pon_intf,
+                                                          PONResourceManager.ALLOC_ID,
+                                                          onu_id=onu_id,
+                                                          num_of_id=1)
         if alloc_id_list and len(alloc_id_list) == 0:
             self.log.error("no-alloc-id-available")
             return None
@@ -118,43 +121,37 @@
 
         return alloc_id
 
-    def get_gemport_id(self, pon_intf_onu_id):
+    def get_gemport_id(self, pon_intf_onu_id, num_of_id=1):
+        # TODO: Remove this if never used
         # Derive the pon_intf and onu_id from the pon_intf_onu_id tuple
         pon_intf = pon_intf_onu_id[0]
         onu_id = pon_intf_onu_id[1]
+        uni_id = pon_intf_onu_id[2]
+        assert False, 'unused function'
 
-        gemport_id_list = self.resource_mgr.get_current_gemport_ids_for_onu(
+        gemport_id_list = self.resource_managers[pon_intf].get_current_gemport_ids_for_onu(
             pon_intf_onu_id)
-
         if gemport_id_list and len(gemport_id_list) > 0:
-            # Since we support only one gemport_id for the ONU at the moment,
-            # return the first gemport_id in the list, if available, for that
-            # ONU.
-            return gemport_id_list[0]
+            return gemport_id_list
 
-        gemport_id_list = self.resource_mgr.get_resource_id(
+        gemport_id_list = self.resource_mgrs[pon_intf].get_resource_id(
             pon_intf_id=pon_intf,
             resource_type=PONResourceManager.GEMPORT_ID,
-            num_of_id=1
+            num_of_id=num_of_id
         )
-        if gemport_id_list is None or len(gemport_id_list) == 0:
+
+        if gemport_id_list and len(gemport_id_list) == 0:
             self.log.error("no-gemport-id-available")
             return None
 
         # update the resource map on KV store with the list of gemport_id
         # 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]
+        self.resource_managers[pon_intf].update_gemport_ids_for_onu(pon_intf_onu_id,
+                                                                    gemport_id_list)
 
-        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
+        self.update_gemports_ponport_to_onu_map_on_kv_store(gemport_id_list,
+                                                            pon_intf, onu_id, uni_id)
+        return gemport_id_list
 
     def free_pon_resources_for_onu(self, pon_intf_id_onu_id):
         """ Typically called on ONU delete """
@@ -222,3 +219,69 @@
         # After we have initialized resource ranges, initialize the
         # resource pools accordingly.
         self.resource_mgr.init_device_resource_pool()
+
+    def get_current_gemport_ids_for_onu(self, pon_intf_onu_id):
+        pon_intf_id = pon_intf_onu_id[0]
+        return self.resource_managers[pon_intf_id].get_current_gemport_ids_for_onu(pon_intf_onu_id)
+
+    def get_current_alloc_ids_for_onu(self, pon_intf_onu_id):
+        pon_intf_id = pon_intf_onu_id[0]
+        alloc_ids = self.resource_managers[pon_intf_id].get_current_alloc_ids_for_onu(pon_intf_onu_id)
+        if alloc_ids is None:
+            return None
+        # We support only one tcont at the moment
+        return alloc_ids[0]
+
+    def update_gemports_ponport_to_onu_map_on_kv_store(self, gemport_list, pon_port, onu_id, uni_id):
+        for gemport in gemport_list:
+            pon_intf_gemport = (pon_port, 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)] = ' '.join(map(str, (onu_id, uni_id)))
+
+    def get_onu_uni_from_ponport_gemport(self, pon_port, gemport):
+        pon_intf_gemport = (pon_port, gemport)
+        return tuple(map(int, self.kv_store[str(pon_intf_gemport)].split(' ')))
+
+    def get_flow_id(self, pon_intf_id, onu_id, uni_id, flow_store_cookie, flow_category=None):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
+        try:
+            flow_ids = self.resource_managers[pon_intf_id]. \
+                get_current_flow_ids_for_onu(pon_intf_onu_id)
+            if flow_ids is not None:
+                for flow_id in flow_ids:
+                    flows = self.get_flow_id_info(pon_intf_id, onu_id, uni_id, flow_id)
+                    assert (isinstance(flows, list))
+                    for flow in flows:
+
+                        if flow_category is not None and \
+                                'flow_category' in flow and \
+                                flow['flow_category'] == flow_category:
+                            return flow_id
+                        if flow['flow_store_cookie'] == flow_store_cookie:
+                            return flow_id
+        except Exception as e:
+            self.log.error("error-retrieving-flow-info", e=e)
+
+        flow_id = self.resource_managers[pon_intf_id].get_resource_id(
+            pon_intf_onu_id[0], PONResourceManager.FLOW_ID)
+        if flow_id is not None:
+            self.resource_managers[pon_intf_id].update_flow_id_for_onu(
+                pon_intf_onu_id, flow_id
+            )
+
+        return flow_id
+
+    def get_flow_id_info(self, pon_intf_id, onu_id, uni_id, flow_id):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
+        return self.resource_managers[pon_intf_id].get_flow_id_info(pon_intf_onu_id, flow_id)
+
+    def get_current_flow_ids_for_uni(self, pon_intf_id, onu_id, uni_id):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
+        return self.resource_managers[pon_intf_id].get_current_flow_ids_for_onu(pon_intf_onu_id)
+
+    def update_flow_id_info_for_uni(self, pon_intf_id, onu_id, uni_id, flow_id, flow_data):
+        pon_intf_onu_id = (pon_intf_id, onu_id, uni_id)
+        return self.resource_managers[pon_intf_id].update_flow_id_info_for_onu(
+            pon_intf_onu_id, flow_id, flow_data)
\ No newline at end of file
diff --git a/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py b/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
index 31fec4b..db56616 100644
--- a/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
+++ b/voltha/adapters/adtran_olt/resources/adtran_resource_manager.py
@@ -23,6 +23,7 @@
 from bitstring import BitArray
 import json
 from common.pon_resource_manager.resource_manager import PONResourceManager
+from adtran_tech_profile import AdtnTechProfile
 import adtranolt_platform as platform
 
 
@@ -153,30 +154,43 @@
         :param pon_intf_id: OLT PON interface id
         :param resource_type: String to identify type of resource
         :param num_of_id: required number of ids
-        :param onu_id: ONU ID if unique per ONU
+        :param onu_id: ONU ID if unique per ONU  (Used for Alloc IDs)
         :return list/int/None: list, int or None if resource type is
                                alloc_id/gemport_id, onu_id or invalid type
                                respectively
         """
         result = None
 
+        if num_of_id < 1:
+            self._log.error("invalid-num-of-resources-requested")
+            return result
+
         path = self._get_path(pon_intf_id, resource_type)
         if path is None:
             return result
 
         try:
             resource = self._get_resource(path, onu_id)
-            if resource is None:
-                raise Exception("get-resource-failed")
-
-            if resource_type == PONResourceManager.ONU_ID:
+            if resource is not None and \
+                    (resource_type == PONResourceManager.ONU_ID or
+                     resource_type == PONResourceManager.FLOW_ID):
                 result = self._generate_next_id(resource)
 
-            elif resource_type == PONResourceManager.GEMPORT_ID:
-                result = [self._generate_next_id(resource) for _ in range(num_of_id)]
+            elif resource is not None and \
+                    resource_type == PONResourceManager.GEMPORT_ID:
+                if num_of_id == 1:
+                    result = self._generate_next_id(resource)
+                else:
+                    result = [self._generate_next_id(resource) for _ in range(num_of_id)]
 
-            elif resource_type == PONResourceManager.ALLOC_ID:
-                result = [self._generate_next_id(resource, onu_id) for _ in range(num_of_id)]
+            elif resource is not None and \
+                    resource_type == PONResourceManager.ALLOC_ID:
+                if num_of_id == 1:
+                    result = self._generate_next_id(resource, onu_id)
+                else:
+                    result = [self._generate_next_id(resource, onu_id) for _ in range(num_of_id)]
+            else:
+                raise Exception("get-resource-failed")
 
             self._log.debug("Get-" + resource_type + "-success", result=result,
                             path=path)
@@ -208,7 +222,7 @@
         try:
             resource = self._get_resource(path, onu_id=onu_id)
             if resource is None:
-                raise Exception("get-resource-failed")
+                raise Exception("get-resource-for-free-failed")
 
             if resource_type == PONResourceManager.ONU_ID:
                 self._release_id(resource, release_content)
@@ -221,7 +235,7 @@
                 for content in release_content:
                     self._release_id(resource, content, onu_id)
             else:
-                raise Exception("get-resource-failed")
+                raise Exception("get-resource-for-free-failed")
 
             self._log.debug("Free-" + resource_type + "-success", path=path)
 
diff --git a/voltha/adapters/adtran_olt/resources/adtranolt_platform.py b/voltha/adapters/adtran_olt/resources/adtranolt_platform.py
index de0f317..7a6abe5 100644
--- a/voltha/adapters/adtran_olt/resources/adtranolt_platform.py
+++ b/voltha/adapters/adtran_olt/resources/adtranolt_platform.py
@@ -33,21 +33,6 @@
 Encoding of identifiers
 =======================
 
-GEM port ID
-
-    GEM port id is unique per PON port and ranges 
-
-     9            3 2    0
-    +--------------+------+
-    |     onu id   | GEM  |
-    |              | idx  |
-    +--------------+------+
-
-    GEM port id range (0..1023) is reserved, by standard
-    Minimum GEM Port on Adtran OLT is 2176 (0x880)
-    onu id = 7 bits = 128 ONUs per PON
-    GEM index = 3 bits = 8 GEM ports per ONU
-
 Alloc ID
 
     Uniquely identifies a T-CONT
@@ -110,9 +95,6 @@
 MIN_TCONT_ALLOC_ID = 1024                   # 1024..16383
 MAX_TCONT_ALLOC_ID = 16383
 
-# MIN_GEM_PORT_ID = 1023                    # 1023..65534
-# MAX_GEM_PORT_ID = 65534
-
 MIN_GEM_PORT_ID = 2176                      # 2176..4222
 MAX_GEM_PORT_ID = MIN_GEM_PORT_ID + 2046
 
@@ -125,18 +107,25 @@
     def __init__(self):
         pass
 
-    def mk_uni_port_num(self, intf_id, onu_id):
-        return intf_id << 11 | onu_id << 4
+    def mk_uni_port_num(self, intf_id, onu_id, uni_id=0):
+        return intf_id << 11 | onu_id << 4 | uni_id
+
+    def uni_id_from_uni_port(self, uni_port):
+        return uni_port & 0xF
 
 
-def mk_uni_port_num(intf_id, onu_id):
+def mk_uni_port_num(intf_id, onu_id, uni_id=0):
     """
     Create a unique virtual UNI port number based up on PON and ONU ID
     :param intf_id:
     :param onu_id: (int) ONU ID (0..max)
     :return: (int) UNI Port number
     """
-    return intf_id << 11 | onu_id << 4
+    return intf_id << 11 | onu_id << 4 | uni_id
+
+
+def uni_id_from_uni_port(uni_port):
+    return uni_port & 0xF
 
 
 def intf_id_from_uni_port_num(port_num):
@@ -163,33 +152,6 @@
     return alloc_id
 
 
-# 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):
-    if intf_type is Port.ETHERNET_NNI:
-        # OpenOLT starts at 128.  We start at 1 (one-to-one mapping)
-        return intf_id
-    elif intf_type is Port.PON_OLT:
-        # OpenOLT sets bit 29 + intf_id. We start at 5 for now for PON 0
-        # return 0x2 << 28 | intf_id
-        return intf_id + 5              # see _pon_id_to_port_number
-    else:
-        raise Exception('Invalid port type')
-
-
 def intf_id_from_nni_port_num(port_num):
     # OpenOLT starts at 128.  We start at 1 (one-to-one mapping)
     # return port_num - 128
diff --git a/voltha/adapters/adtran_olt/xpon/adtran_xpon.py b/voltha/adapters/adtran_olt/xpon/adtran_xpon.py
deleted file mode 100644
index 30e5919..0000000
--- a/voltha/adapters/adtran_olt/xpon/adtran_xpon.py
+++ /dev/null
@@ -1,292 +0,0 @@
-# Copyright 2017-present Adtran, Inc.
-#
-# 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.
-
-import structlog
-from traffic_descriptor import TrafficDescriptor
-from voltha.protos.bbf_fiber_tcont_body_pb2 import TcontsConfigData
-from voltha.protos.bbf_fiber_traffic_descriptor_profile_body_pb2 import TrafficDescriptorProfileData
-from voltha.protos.bbf_fiber_gemport_body_pb2 import GemportsConfigData
-
-log = structlog.get_logger()
-
-
-class AdtranXPON(object):
-    """
-    Class to abstract common OLT and ONU xPON operations
-    """
-    def __init__(self, **kwargs):
-        # xPON config dictionaries
-        self._v_ont_anis = {}             # Name -> dict
-        self._ont_anis = {}               # Name -> dict
-        self._tconts = {}                 # Name -> dict
-        self._traffic_descriptors = {}    # Name -> dict
-        self._gem_ports = {}              # Name -> dict
-
-    @property
-    def tconts(self):
-        return self._tconts
-
-    @property
-    def traffic_descriptors(self):
-        return self._traffic_descriptors
-
-    @property
-    def gem_ports(self):
-        return self._gem_ports
-
-    def _get_xpon_collection(self, data):
-        """
-        Get the collection for the object type and handler routines
-        :param data: xPON object          TODO: These three are still needed for the ONU until
-                                                xPON is deprecated as the OLT calls into the ONU
-                                                to start it up and passes these three ProtoBuf
-                                                messages.
-        """
-        if isinstance(data, TcontsConfigData):
-            return self.tconts, \
-                   self.on_tcont_create,\
-                   self.on_tcont_modify, \
-                   self.on_tcont_delete
-
-        elif isinstance(data, TrafficDescriptorProfileData):
-            return self.traffic_descriptors, \
-                   self.on_td_create,\
-                   self.on_td_modify, \
-                   self.on_td_delete
-
-        elif isinstance(data, GemportsConfigData):
-            return self.gem_ports, \
-                   self.on_gemport_create,\
-                   self.on_gemport_modify, \
-                   self.on_gemport_delete
-
-        return None, None, None, None
-
-    def _data_to_dict(self, data, td=None):
-        if isinstance(data, TcontsConfigData):
-            return 'TCONT', {
-                'name': data.name,
-                'alloc-id': data.alloc_id,
-                'vont-ani': data.interface_reference,
-                'td-ref': td['name'],
-                'data': data
-            }
-        elif isinstance(data, TrafficDescriptorProfileData):
-            additional = TrafficDescriptor.AdditionalBwEligibility.from_value(
-                data.additional_bw_eligibility_indicator)
-
-            return 'Traffic-Desc', {
-                'name': data.name,
-                'fixed-bandwidth': data.fixed_bandwidth,
-                'assured-bandwidth': data.assured_bandwidth,
-                'maximum-bandwidth': data.maximum_bandwidth,
-                'priority': data.priority,
-                'weight': data.weight,
-                'additional-bw-eligibility-indicator': additional,
-                'data': data
-            }
-        elif isinstance(data, GemportsConfigData):
-            return 'GEMPort', {
-                'name': data.name,
-                'gemport-id': data.gemport_id,
-                'tcont-ref': data.tcont_ref,
-                'encryption': data.aes_indicator,
-                'traffic-class': data.traffic_class,
-                'venet-ref': data.itf_ref,                # vENET
-                'data': data
-            }
-        return None
-
-    def create_tcont(self, tcont_data, td_data):
-        """
-        Create TCONT information
-        :param tcont_data:
-        :param td_data:
-        """
-        log.debug('create-tcont', tcont=tcont_data, td=td_data)
-
-        # Handle TD first, then TCONT
-        if td_data is not None:
-            try:
-                self.xpon_create(td_data)
-
-            except Exception as e:
-                log.exception('td-create', td=td_data)
-
-        try:
-            td = self.traffic_descriptors.get(td_data.name) if td_data is not None else None
-            self.xpon_create(tcont_data, td=td)
-
-        except Exception as e:
-            log.exception('tcont-create', tcont=tcont_data)
-
-    def update_tcont(self, tcont_data, traffic_descriptor_data):
-        """
-        Update TCONT information
-        :param tcont_data:
-        :param traffic_descriptor_data:
-        """
-        log.debug('update-tcont', tcont=tcont_data, td=traffic_descriptor_data)
-
-        # Handle TD first, then TCONT. The TD may be new
-        try:
-            items, _, _, _ = self._get_xpon_collection(traffic_descriptor_data)
-            existing_item = items.get(traffic_descriptor_data.name)
-            if existing_item is None:
-                self.xpon_create(traffic_descriptor_data)
-            else:
-                self.xpon_update(traffic_descriptor_data)
-
-        except Exception as e:
-            log.exception('td-update', td=traffic_descriptor_data)
-
-        try:
-            self.xpon_update(tcont_data)
-
-        except Exception as e:
-            log.exception('tcont-update', tcont=tcont_data)
-
-    def remove_tcont(self, tcont_data, traffic_descriptor_data):
-        """
-        Remove TCONT information
-        :param tcont_data:
-        :param traffic_descriptor_data:
-        """
-        log.debug('remove-tcont', tcont=tcont_data, td=traffic_descriptor_data)
-
-        # Handle TCONT first when removing, then TD
-        try:
-            self.xpon_remove(traffic_descriptor_data)
-        except Exception as e:
-            log.exception('td-update', td=traffic_descriptor_data)
-
-        try:
-            self.xpon_remove(tcont_data)
-        except Exception as e:
-            log.exception('tcont-update', tcont=tcont_data)
-
-    def xpon_create(self, data, td=None):
-        log.debug('xpon-create', data=data)
-        name = data.name
-        items, create_method, update_method, _ = self._get_xpon_collection(data)
-
-        if items is None:
-            from voltha.adapters.adtran_olt.adtran_olt_handler import OnuIndication
-            if isinstance(data, OnuIndication):   # Ignore this
-                return
-            raise ValueError('Unknown data type: {}'.format(type(data)))
-
-        item_type, new_item = self._data_to_dict(data, td=td)
-
-        if name in items:
-            # Treat like an update. It will update collection if needed
-            return self.xpon_update(data, td=td)
-
-        log.debug('new-item', item_type=item_type, item=new_item)
-        items[name] = new_item
-
-        if create_method is not None:
-            try:
-                new_item = create_method(new_item)
-            except Exception as e:
-                log.exception('xpon-create', item=new_item, e=e)
-
-        if new_item is not None:
-            items[name] = new_item
-        else:
-            del items[name]
-
-    def xpon_update(self, data, td=None):
-        log.debug('xpon-update', data=data)
-        name = data.name
-        items, create, update_method, delete = self._get_xpon_collection(data)
-
-        if items is None:
-            raise ValueError('Unknown data type: {}'.format(type(data)))
-
-        existing_item = items.get(name)
-        if existing_item is None:
-            raise KeyError("'{}' not found. Type: {}".format(name, type(data)))
-
-        item_type, update_item = self._data_to_dict(data, td=td)
-        log.debug('update-item', item_type=item_type, item=update_item)
-
-        def _dict_diff(lhs, rhs):
-            """
-            Compare the values of two dictionaries and return the items in 'rhs'
-            that are different than 'lhs. The RHS dictionary keys can be a subset of the
-            LHS dictionary, or the RHS dictionary keys can contain new values.
-
-            :param lhs: (dict) Original dictionary values
-            :param rhs: (dict) New dictionary values to compare to the original (lhs) dict
-            :return: (dict) Dictionary with differences from the RHS dictionary
-            """
-            lhs_keys = {k for k in lhs.keys() if k not in ['object', 'data']}
-            rhs_keys = {k for k in rhs.keys() if k not in ['object', 'data']}
-            assert len(lhs_keys) == len(lhs_keys & rhs_keys), 'Dictionary Keys do not match'
-            return {k: v for k, v in rhs.items() if k not in lhs or lhs[k] != rhs[k]}
-
-        # Calculate the difference
-        diffs = _dict_diff(existing_item, update_item)
-
-        if len(diffs) == 0:
-            log.debug('update-item-no-diffs')
-            return
-
-        items[name] = update_item
-
-        # Act on any changed items
-        if update_method is not None:
-            try:
-                update_item = update_method(existing_item, update_item, diffs)
-            except Exception as e:
-                log.exception('xpon-update', existing=existing_item,
-                              update=update_item, diffs=diffs,
-                              e=e)
-
-        if update_item is not None:
-            items[name] = update_item
-        else:
-            del items[name]
-
-    def xpon_remove(self, data):
-        log.debug('xpon_remove', data=data)
-        raise NotImplementedError("xPON support has been disabled")
-
-    def on_tcont_create(self, tcont):
-        return tcont   # Implement in your OLT, if needed
-
-    def on_tcont_modify(self, tcont, update, diffs):
-        return update   # Implement in your OLT, if needed
-
-    def on_tcont_delete(self, tcont):
-        return None   # Implement in your OLT, if needed
-
-    def on_td_create(self, traffic_desc):
-        return traffic_desc   # Implement in your OLT, if needed
-
-    def on_td_modify(self, traffic_desc, update, diffs):
-        return update   # Implement in your OLT, if needed
-
-    def on_td_delete(self, traffic_desc):
-        return None   # Implement in your OLT, if needed
-
-    def on_gemport_create(self, gem_port):
-        return gem_port   # Implement in your OLT, if needed
-
-    def on_gemport_modify(self, gem_port, update, diffs):
-        return update   # Implement in your OLT, if needed
-
-    def on_gemport_delete(self, gem_port):
-        return None   # Implement in your OLT, if needed
diff --git a/voltha/adapters/adtran_olt/xpon/gem_port.py b/voltha/adapters/adtran_olt/xpon/gem_port.py
index 50149b5..d14b4f2 100644
--- a/voltha/adapters/adtran_olt/xpon/gem_port.py
+++ b/voltha/adapters/adtran_olt/xpon/gem_port.py
@@ -17,23 +17,25 @@
     """
     Class to wrap TCont capabilities
     """
-    def __init__(self, gem_id, alloc_id,
+    def __init__(self, gem_id, alloc_id, tech_profile_id,
                  encryption=False,
                  omci_transport=False,
                  multicast=False,
                  tcont_ref=None,
                  traffic_class=None,
-                 name=None,
-                 handler=None):
-        self.name = name
+                 handler=None,
+                 is_mock=False):
+
         self.gem_id = gem_id
         self._alloc_id = alloc_id
+        self.tech_profile_id = tech_profile_id
         self.tcont_ref = tcont_ref
         self.traffic_class = traffic_class
         self._encryption = encryption
         self._omci_transport = omci_transport
         self.multicast = multicast
         self._handler = handler
+        self._is_mock = is_mock
 
         # TODO: Make this a base class and derive OLT and ONU specific classes from it
         #       The primary thing to change is the PON ID is OLT specific and the add/remove
@@ -42,6 +44,8 @@
         self._onu_id = None
         self._intf_id = None
 
+        self.tech_profile_id = None     # TODO: Make property and clean up object once tech profiles fully supported
+
         # Statistics
         self.rx_packets = 0
         self.rx_bytes = 0
@@ -49,9 +53,7 @@
         self.tx_bytes = 0
 
     def __str__(self):
-        return "GemPort: {}, alloc-id: {}, gem-id: {}".format(self.name,
-                                                              self.alloc_id,
-                                                              self.gem_id)
+        return "GemPort: alloc-id: {}, gem-id: {}".format(self.alloc_id,self.gem_id)
 
     @property
     def pon_id(self):
@@ -97,6 +99,10 @@
         return tcont_item.get('object') if tcont_item is not None else None
 
     @property
+    def encryption(self):
+        return self._encryption
+
+    @property
     def omci_transport(self):
         return self._omci_transport
 
diff --git a/voltha/adapters/adtran_olt/xpon/olt_gem_port.py b/voltha/adapters/adtran_olt/xpon/olt_gem_port.py
index d4abe27..3d2210d 100644
--- a/voltha/adapters/adtran_olt/xpon/olt_gem_port.py
+++ b/voltha/adapters/adtran_olt/xpon/olt_gem_port.py
@@ -1,4 +1,4 @@
-
+#
 # Copyright 2017-present Adtran, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,6 +18,7 @@
 
 from gem_port import GemPort
 from twisted.internet.defer import inlineCallbacks, returnValue
+from ..adtran_olt_handler import AdtranOltHandler
 
 log = structlog.get_logger()
 
@@ -26,44 +27,39 @@
     """
     Adtran OLT specific implementation
     """
-    def __init__(self, gem_id, alloc_id, pon_id, onu_id,
+    def __init__(self, gem_id, alloc_id, tech_profile_id, pon_id, onu_id,
                  encryption=False,
                  omci_transport=False,
                  multicast=False,
                  tcont_ref=None,
                  traffic_class=None,
-                 name=None,
                  handler=None,
-                 is_mock=False,
-                 pb_data=None):
-        super(OltGemPort, self).__init__(gem_id, alloc_id,
+                 is_mock=False):
+        super(OltGemPort, self).__init__(gem_id, alloc_id, tech_profile_id,
                                          encryption=encryption,
                                          omci_transport=omci_transport,
                                          multicast=multicast,
                                          tcont_ref=tcont_ref,
                                          traffic_class=traffic_class,
-                                         name=name,
-                                         handler=handler)
-        self._is_mock = is_mock
+                                         handler=handler,
+                                         is_mock=is_mock)
         self._timestamp = None
         self.pon_id = pon_id
         self.onu_id = onu_id
-        self.data = pb_data     # Needed for non-xPON mode
+
+    def __str__(self):
+        return "GemPort: {}/{}, alloc-id: {}, gem-id: {}".format(self.pon_id, self.onu_id,
+                                                                 self.alloc_id, self.gem_id)
 
     @staticmethod
-    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'],
+    def create(handler, gem, alloc_id, tech_profile_id, pon_id, onu_id, _uni_id, _ofp_port_no):
+        return OltGemPort(gem.gemport_id,
                           alloc_id,
+                          tech_profile_id,
                           pon_id, onu_id,
-                          encryption=gem_port['encryption'],  # aes_indicator,
-                          tcont_ref=gem_port['tcont-ref'],
-                          name=gem_port['name'],
-                          traffic_class=gem_port['traffic-class'],
+                          encryption=gem.aes_encryption.lower() == 'true',
                           handler=handler,
-                          multicast=mcast,
-                          pb_data=gem_port['data'])
+                          multicast=False)
 
     @property
     def timestamp(self):
@@ -87,7 +83,8 @@
 
     @inlineCallbacks
     def add_to_hardware(self, session, operation='POST'):
-        from ..adtran_olt_handler import AdtranOltHandler
+        if self._is_mock:
+            returnValue('mock')
 
         uri = AdtranOltHandler.GPON_GEM_CONFIG_LIST_URI.format(self.pon_id, self.onu_id)
         data = json.dumps(self.to_dict())
@@ -106,7 +103,8 @@
                 raise
 
     def remove_from_hardware(self, session):
-        from ..adtran_olt_handler import AdtranOltHandler
+        if self._is_mock:
+            returnValue('mock')
 
         uri = AdtranOltHandler.GPON_GEM_CONFIG_URI.format(self.pon_id, self.onu_id, self.gem_id)
         name = 'gem-port-delete-{}-{}: {}'.format(self.pon_id, self.onu_id, self.gem_id)
diff --git a/voltha/adapters/adtran_olt/xpon/olt_tcont.py b/voltha/adapters/adtran_olt/xpon/olt_tcont.py
index 804c8c2..d678bc3 100644
--- a/voltha/adapters/adtran_olt/xpon/olt_tcont.py
+++ b/voltha/adapters/adtran_olt/xpon/olt_tcont.py
@@ -14,8 +14,11 @@
 
 import structlog
 import json
-from twisted.internet.defer import  inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue
 from tcont import TCont
+from voltha.adapters.openolt.protos import openolt_pb2
+from olt_traffic_descriptor import OltTrafficDescriptor
+from ..adtran_olt_handler import AdtranOltHandler
 
 log = structlog.get_logger()
 
@@ -24,29 +27,25 @@
     """
     Adtran OLT specific implementation
     """
-    def __init__(self, alloc_id, traffic_descriptor, pon_id, onu_id,
-                 name=None, is_mock=False,
-                 pb_data=None):
-        super(OltTCont, self).__init__(alloc_id, traffic_descriptor, name=name)
-        self._is_mock = is_mock
+    def __init__(self, alloc_id, tech_profile_id, traffic_descriptor, pon_id, onu_id, is_mock=False):
+        super(OltTCont, self).__init__(alloc_id, tech_profile_id, traffic_descriptor, is_mock=is_mock)
         self.pon_id = pon_id
         self.onu_id = onu_id
-        self.data = pb_data             # Needed for non-xPON mode
+
+    def __str__(self):
+        return "TCont: {}/{}, alloc-id: {}".format(self.pon_id, self.onu_id, self.alloc_id)
 
     @staticmethod
-    def create(tcont, td, pon_id, onu_id):
-        from olt_traffic_descriptor import OltTrafficDescriptor
+    def create(tcont, pon_id, onu_id, tech_profile_id, uni_id, ofp_port_no):
+        # Only valid information in the upstream tcont of a tech profile
+        if tcont.direction != openolt_pb2.UPSTREAM:
+            return None
 
-        assert isinstance(tcont, dict), 'TCONT should be a dictionary'
-        assert isinstance(td, OltTrafficDescriptor), 'Invalid Traffic Descriptor data type'
-
-        return OltTCont(tcont['alloc-id'], td, pon_id, onu_id,
-                        name=tcont['name'],
-                        pb_data=tcont['data'])
+        td = OltTrafficDescriptor.create(tcont, pon_id, onu_id, uni_id, ofp_port_no)
+        return OltTCont(tcont.alloc_id, tech_profile_id, td, pon_id, onu_id)
 
     @inlineCallbacks
     def add_to_hardware(self, session):
-        from ..adtran_olt_handler import AdtranOltHandler
         if self._is_mock:
             returnValue('mock')
 
@@ -63,9 +62,8 @@
 
         if self.traffic_descriptor is not None:
             try:
-                results = yield self.traffic_descriptor.add_to_hardware(session,
-                                                                        self.pon_id, self.onu_id,
-                                                                        self.alloc_id)
+                results = yield self.traffic_descriptor.add_to_hardware(session)
+
             except Exception as e:
                 log.exception('traffic-descriptor', tcont=self,
                               td=self.traffic_descriptor, e=e)
@@ -74,12 +72,10 @@
         returnValue(results)
 
     def remove_from_hardware(self, session):
-        from ..adtran_olt_handler import AdtranOltHandler
+        if self._is_mock:
+            returnValue('mock')
 
-        pon_id = self.pon_id
-        onu_id = self.onu_id        # TODO: Cleanup parameters
-
-        uri = AdtranOltHandler.GPON_TCONT_CONFIG_URI.format(pon_id, onu_id, self.alloc_id)
+        uri = AdtranOltHandler.GPON_TCONT_CONFIG_URI.format(self.pon_id, self.onu_id, self.alloc_id)
         name = 'tcont-delete-{}-{}: {}'.format(self.pon_id, self.onu_id, self.alloc_id)
         return session.request('DELETE', uri, name=name)
 
diff --git a/voltha/adapters/adtran_olt/xpon/olt_traffic_descriptor.py b/voltha/adapters/adtran_olt/xpon/olt_traffic_descriptor.py
index 635e5e1..f3c0713 100644
--- a/voltha/adapters/adtran_olt/xpon/olt_traffic_descriptor.py
+++ b/voltha/adapters/adtran_olt/xpon/olt_traffic_descriptor.py
@@ -16,6 +16,8 @@
 import json
 from traffic_descriptor import TrafficDescriptor
 from twisted.internet.defer import inlineCallbacks, returnValue
+from ..adtran_olt_handler import AdtranOltHandler
+from voltha.adapters.openolt.protos import openolt_pb2
 
 log = structlog.get_logger()
 
@@ -24,53 +26,56 @@
     """
     Adtran ONU specific implementation
     """
-    def __init__(self, fixed, assured, maximum,
+    def __init__(self, pon_id, onu_id, alloc_id, fixed, assured, maximum,
                  additional=TrafficDescriptor.AdditionalBwEligibility.DEFAULT,
                  best_effort=None,
-                 name=None,
-                 is_mock=False,
-                 pb_data=None):
+                 is_mock=False):
         super(OltTrafficDescriptor, self).__init__(fixed, assured, maximum,
                                                    additional=additional,
-                                                   best_effort=best_effort,
-                                                   name=name)
+                                                   best_effort=best_effort)
+        self.pon_id = pon_id
+        self.onu_id = onu_id
+        self.alloc_id = alloc_id
         self._is_mock = is_mock
-        self.data = pb_data
 
     @staticmethod
-    def create(traffic_disc):
-        from best_effort import BestEffort
+    def create(tcont, pon_id, onu_id, _uni_id, _ofp_port_no):
+        alloc_id = tcont.alloc_id
+        shaping_info = tcont.traffic_shaping_info
+        fixed = shaping_info.cir
+        assured = 0
+        maximum = shaping_info.pir
 
-        assert isinstance(traffic_disc, dict), 'Traffic Descriptor should be a dictionary'
+        best_effort = None
+        # if shaping_info.add_bw_ind == openolt_pb2.InferredAdditionBWIndication_Assured:
+        #     pass
+        #               TODO: Support additional BW decode
+        # elif shaping_info.add_bw_ind == openolt_pb2.InferredAdditionBWIndication_BestEffort:
+        #     pass
+        # additional = TrafficDescriptor.AdditionalBwEligibility.from_value(
+        #     traffic_disc['additional-bw-eligibility-indicator'])
+        #
+        # if additional == TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING:
+        #     best_effort = BestEffort(traffic_disc['maximum-bandwidth'],
+        #                              traffic_disc['priority'],
+        #                              traffic_disc['weight'])
+        # else:
+        #     best_effort = None
 
-        additional = TrafficDescriptor.AdditionalBwEligibility.from_value(
-            traffic_disc['additional-bw-eligibility-indicator'])
-
-        if additional == TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING:
-            best_effort = BestEffort(traffic_disc['maximum-bandwidth'],
-                                     traffic_disc['priority'],
-                                     traffic_disc['weight'])
-        else:
-            best_effort = None
-
-        return OltTrafficDescriptor(traffic_disc['fixed-bandwidth'],
-                                    traffic_disc['assured-bandwidth'],
-                                    traffic_disc['maximum-bandwidth'],
-                                    name=traffic_disc['name'],
-                                    best_effort=best_effort,
-                                    additional=additional,
-                                    pb_data=traffic_disc['data'])
+        return OltTrafficDescriptor(pon_id, onu_id, alloc_id,
+                                    fixed, assured, maximum, best_effort=best_effort)
 
     @inlineCallbacks
-    def add_to_hardware(self, session, pon_id, onu_id, alloc_id):
-        from ..adtran_olt_handler import AdtranOltHandler
-
+    def add_to_hardware(self, session):
+        # TODO: Traffic descriptors are no longer shared, save pon and onu ID to base class
         if self._is_mock:
             returnValue('mock')
 
-        uri = AdtranOltHandler.GPON_TCONT_CONFIG_URI.format(pon_id, onu_id, alloc_id)
+        uri = AdtranOltHandler.GPON_TCONT_CONFIG_URI.format(self.pon_id,
+                                                            self.onu_id,
+                                                            self.alloc_id)
         data = json.dumps({'traffic-descriptor': self.to_dict()})
-        name = 'tcont-td-{}-{}: {}'.format(pon_id, onu_id, alloc_id)
+        name = 'tcont-td-{}-{}: {}'.format(self.pon_id, self.onu_id, self.alloc_id)
         try:
             results = yield session.request('PATCH', uri, data=data, name=name)
 
@@ -78,16 +83,17 @@
             log.exception('traffic-descriptor', td=self, e=e)
             raise
 
-        if self.additional_bandwidth_eligibility == \
-                TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING:
-            if self.best_effort is None:
-                raise ValueError('TCONT is best-effort but does not define best effort sharing')
-
-            try:
-                results = yield self.best_effort.add_to_hardware(session, pon_id, onu_id, alloc_id)
-
-            except Exception as e:
-                log.exception('best-effort', best_effort=self.best_effort, e=e)
-                raise
+        # TODO: Add support for best-effort sharing
+        # if self.additional_bandwidth_eligibility == \
+        #         TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING:
+        #     if self.best_effort is None:
+        #         raise ValueError('TCONT is best-effort but does not define best effort sharing')
+        #
+        #     try:
+        #         results = yield self.best_effort.add_to_hardware(session)
+        #
+        #     except Exception as e:
+        #         log.exception('best-effort', best_effort=self.best_effort, e=e)
+        #         raise
 
         returnValue(results)
diff --git a/voltha/adapters/adtran_olt/xpon/tcont.py b/voltha/adapters/adtran_olt/xpon/tcont.py
index fd65adf..b5e8eee 100644
--- a/voltha/adapters/adtran_olt/xpon/tcont.py
+++ b/voltha/adapters/adtran_olt/xpon/tcont.py
@@ -17,13 +17,14 @@
     """
     Class to wrap TCont capabilities
     """
-    def __init__(self, alloc_id, traffic_descriptor, name=None):
+    def __init__(self, alloc_id, tech_profile_id, traffic_descriptor, is_mock=False):
         self.alloc_id = alloc_id
         self.traffic_descriptor = traffic_descriptor
-        self.name = name
+        self._is_mock = is_mock
+        self.tech_profile_id = tech_profile_id
 
         # TODO: Make this a base class and derive OLT and ONU specific classes from it
         #       The primary thing difference is the add/remove from hardware methods
 
     def __str__(self):
-        return "TCont: {}, alloc-id: {}".format(self.name, self.alloc_id)
+        return "TCont: alloc-id: {}".format(self.alloc_id)
diff --git a/voltha/adapters/adtran_olt/xpon/traffic_descriptor.py b/voltha/adapters/adtran_olt/xpon/traffic_descriptor.py
index 96b7ce5..230605b 100644
--- a/voltha/adapters/adtran_olt/xpon/traffic_descriptor.py
+++ b/voltha/adapters/adtran_olt/xpon/traffic_descriptor.py
@@ -48,9 +48,7 @@
 
     def __init__(self, fixed, assured, maximum,
                  additional=AdditionalBwEligibility.DEFAULT,
-                 best_effort=None,
-                 name=None):
-        self.name = name
+                 best_effort=None):
         self.fixed_bandwidth = fixed       # bps
         self.assured_bandwidth = assured   # bps
         self.maximum_bandwidth = maximum   # bps
@@ -60,10 +58,9 @@
             else None
 
     def __str__(self):
-        return "TrafficDescriptor: {}, {}/{}/{}".format(self.name,
-                                                        self.fixed_bandwidth,
-                                                        self.assured_bandwidth,
-                                                        self.maximum_bandwidth)
+        return "TrafficDescriptor: {}/{}/{}".format(self.fixed_bandwidth,
+                                                    self.assured_bandwidth,
+                                                    self.maximum_bandwidth)
 
     def to_dict(self):
         val = {
diff --git a/voltha/adapters/adtran_onu/adtran_onu.py b/voltha/adapters/adtran_onu/adtran_onu.py
index ecec76f..d8ae1b9 100755
--- a/voltha/adapters/adtran_onu/adtran_onu.py
+++ b/voltha/adapters/adtran_onu/adtran_onu.py
@@ -42,7 +42,7 @@
                                                device_handler_class=AdtranOnuHandler,
                                                name='adtran_onu',
                                                vendor='ADTRAN, Inc.',
-                                               version='1.23',
+                                               version='1.24',
                                                device_type='adtran_onu',
                                                vendor_id='ADTN',
                                                accepts_add_remove_flow_updates=False),  # TODO: Support flow-mods
@@ -117,16 +117,14 @@
         self.log.info('receive_inter_adapter_message', msg=msg)
         proxy_address = msg['proxy_address']
         assert proxy_address is not None
-
         # Device_id from the proxy_address is the olt device id. We need to
         # get the onu device id using the port number in the proxy_address
-
         device = self.adapter_agent.get_child_device_with_proxy_address(proxy_address)
-
         if device is not None:
-            handler = self.devices_handlers.get(device.id)
-            if handler is not None:
-                handler.rx_inter_adapter_message(msg)
+            handler = self.devices_handlers[device.id]
+            handler.event_messages.put(msg)
+        else:
+            self.log.error("device-not-found")
 
     def abandon_device(self, device):
         raise NotImplementedError('TODO: Not currently supported')
diff --git a/voltha/adapters/adtran_onu/adtran_onu_handler.py b/voltha/adapters/adtran_onu/adtran_onu_handler.py
index 040a617..f9ce52f 100644
--- a/voltha/adapters/adtran_onu/adtran_onu_handler.py
+++ b/voltha/adapters/adtran_onu/adtran_onu_handler.py
@@ -13,11 +13,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-from voltha.adapters.adtran_olt.xpon.adtran_xpon import AdtranXPON
+import ast
 from pon_port import PonPort
 from uni_port import UniPort
 from heartbeat import HeartBeat
 from omci.omci import OMCI
+from onu_traffic_descriptor import OnuTrafficDescriptor
+from onu_tcont import OnuTCont
+from onu_gem_port import OnuGemPort
 
 from voltha.extensions.alarms.adapter_alarms import AdapterAlarms
 from voltha.extensions.kpi.onu.onu_pm_metrics import OnuPmMetrics
@@ -32,19 +35,25 @@
 from voltha.protos.common_pb2 import OperStatus, ConnectStatus
 from common.utils.indexpool import IndexPool
 from voltha.extensions.omci.omci_me import *
+from common.tech_profile.tech_profile import TechProfile
+from voltha.core.config.config_backend import ConsulStore
+from voltha.core.config.config_backend import EtcdStore
 
 import voltha.adapters.adtran_olt.resources.adtranolt_platform as platform
 from voltha.adapters.adtran_onu.flow.flow_entry import FlowEntry
 from omci.adtn_install_flow import AdtnInstallFlowTask
 from omci.adtn_remove_flow import AdtnRemoveFlowTask
+from omci.adtn_tp_service_specific_task import AdtnTpServiceSpecificTask
+from common.tech_profile.tech_profile import DEFAULT_TECH_PROFILE_TABLE_ID
 
 _ = third_party
 _MAXIMUM_PORT = 17        # Only one PON and UNI port at this time
 _ONU_REBOOT_MIN = 90      # IBONT 602 takes about 3 minutes
 _ONU_REBOOT_RETRY = 10
+_STARTUP_RETRY_WAIT = 20
 
 
-class AdtranOnuHandler(AdtranXPON):
+class AdtranOnuHandler(object):
     def __init__(self, adapter, device_id):
         kwargs = dict()
         super(AdtranOnuHandler, self).__init__(**kwargs)
@@ -58,14 +67,11 @@
         self.pm_metrics = None
         self.alarms = None
         self._mgmt_gemport_aes = False
-        self._upstream_channel_speed = 0
 
         self._openomci = OMCI(self, adapter.omci_agent)
         self._in_sync_subscription = None
 
-        self._onu_port_number = 0
         self._pon_port_number = 1
-        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)
 
         self._unis = dict()         # Port # -> UniPort
         self._pon = PonPort.create(self, self._pon_port_number)
@@ -81,6 +87,30 @@
         self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1
         self.gal_enet_profile_entity_id = 0     # Was 0x100, but ONU seems to overwrite and use zero
 
+        # Technology profile related values
+        self.incoming_messages = DeferredQueue()
+        self.event_messages = DeferredQueue()
+        self._tp_service_specific_task = dict()
+        self._tech_profile_download_done = dict()
+        self._upstream_channel_speed = 0                # TODO: Deprecate
+
+        # Initialize KV store client
+        self.args = registry('main').get_args()
+        if self.args.backend == 'etcd':
+            host, port = self.args.etcd.split(':', 1)
+            self.kv_client = EtcdStore(host, port,
+                                       TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX)
+        elif self.args.backend == 'consul':
+            host, port = self.args.consul.split(':', 1)
+            self.kv_client = ConsulStore(host, port,
+                                         TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX)
+        else:
+            self.log.error('Invalid-backend')
+            raise Exception("Invalid-backend-for-kv-store")
+
+        # Handle received ONU event messages
+        reactor.callLater(0, self.handle_onu_events)
+
     def __str__(self):
         return "AdtranOnuHandler: {}".format(self.device_id)
 
@@ -153,19 +183,12 @@
     def pon_ports(self):
         return [self._pon]
 
-    @property
-    def _next_port_number(self):
-        return self._port_number_pool.get_next()
-
-    def _release_port_number(self, number):
-        self._port_number_pool.release(number)
-
     def start(self):
         assert self._enabled, 'Start should only be called if enabled'
         self._cancel_deferred()
 
         # Register for adapter messages
-        #self.adapter_agent.register_for_inter_adapter_messages()
+        self.adapter_agent.register_for_inter_adapter_messages()
 
         # OpenOMCI Startup
         self._subscribe_to_events()
@@ -183,11 +206,10 @@
 
     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()
+        self.adapter_agent.unregister_for_inter_adapter_messages()
 
         # Heartbeat
         self._heartbeat.enabled = False
@@ -221,7 +243,7 @@
             self.adapter_agent.register_for_proxied_messages(device.proxy_address)
 
             # initialize device info
-            device.root = True
+            device.root = False
             device.vendor = 'Adtran Inc.'
             device.model = 'n/a'
             device.hardware_version = 'n/a'
@@ -296,7 +318,7 @@
         self.adapter_agent.register_for_proxied_messages(device.proxy_address)
 
         # Register for adapter messages
-        #self.adapter_agent.register_for_inter_adapter_messages()
+        self.adapter_agent.register_for_inter_adapter_messages()
 
         # Set the connection status to REACHABLE
         device.connect_status = ConnectStatus.REACHABLE
@@ -314,6 +336,179 @@
 
         self.log.info('reconciling-ONU-device-ends')
 
+    @inlineCallbacks
+    def handle_onu_events(self):
+        # TODO: Add 'shutdown' message to exit loop
+        event_msg = yield self.event_messages.get()
+        try:
+            if event_msg['event'] == 'download_tech_profile':
+                tp_path = event_msg['event_data']
+                uni_id = event_msg['uni_id']
+                self.load_and_configure_tech_profile(uni_id, tp_path)
+
+        except Exception as e:
+            self.log.error("exception-handling-onu-event", e=e)
+
+        # Handle next event
+        reactor.callLater(0, self.handle_onu_events)
+
+    def _tp_path_to_tp_id(self, tp_path):
+        parts = tp_path.split('/')
+        if len(parts) > 2:
+            try:
+                return int(tp_path[1])
+            except ValueError:
+                return DEFAULT_TECH_PROFILE_TABLE_ID
+
+    def _create_tcont(self, uni_id, us_scheduler, tech_profile_id):
+        """
+        Decode Upstream Scheduler and create appropriate TCONT structures
+
+        :param uni_id: (int) UNI ID on the PON
+        :param us_scheduler: (Scheduler) Upstream Scheduler with TCONT information
+        :param tech_profile_id: (int) Tech Profile ID
+
+        :return (OnuTCont) Created TCONT
+        """
+        self.log.debug('create-tcont', us_scheduler=us_scheduler, profile_id=tech_profile_id)
+
+        q_sched_policy = {
+            'strictpriority': 1,        # Per TCONT (ME #262) values
+            'wrr': 2
+        }.get(us_scheduler.get('q_sched_policy', 'none').lower(), 0)
+
+        tcont_data = {
+            'tech-profile-id': tech_profile_id,
+            'uni-id': uni_id,
+            'alloc-id': us_scheduler['alloc_id'],
+            'q-sched-policy': q_sched_policy
+        }
+        # TODO: Support TD if shaping on ONU is to be performed
+        td = OnuTrafficDescriptor(0, 0, 0)
+        tcont = OnuTCont.create(self, tcont_data, td)
+        self._pon.add_tcont(tcont)
+        return tcont
+
+    # Called when there is an olt up indication, providing the gem port id chosen by the olt handler
+    def _create_gemports(self, upstream_ports, downstream_ports, tcont, uni_id, tech_profile_id):
+        """
+        Create GEM Ports for a specifc tech profile
+
+        The routine will attempt to combine upstream and downstream GEM Ports into bidirectional
+        ports where possible
+
+        :param upstream_ports: (list of IGemPortAttribute) Upstream GEM Port attributes
+        :param downstream_ports: (list of IGemPortAttribute) Downstream GEM Port attributes
+        :param tcont: (OnuTCont) Associated TCONT
+        :param uni_id: (int) UNI Instance ID
+        :param tech_profile_id: (int) Tech Profile ID
+        """
+        self.log.debug('create-gemports', upstream=upstream_ports,
+                       downstream_ports=downstream_ports,
+                       tcont=tcont, tech_id=tech_profile_id)
+        # Convert GEM Port lists to dicts with GEM ID as they key
+        upstream = {gem['gemport_id']: gem for gem in upstream_ports}
+        downstream = {gem['gemport_id']: gem for gem in downstream_ports}
+
+        upstream_ids = set(upstream.keys())
+        downstream_ids = set(downstream.keys())
+        bidirectional_ids = upstream_ids & downstream_ids
+
+        gem_port_types = {     # Keys are the 'direction' attribute value, value is list of GEM attributes
+            1: [upstream[gid] for gid in upstream_ids - bidirectional_ids],
+            2: [downstream[gid] for gid in downstream_ids - bidirectional_ids],
+            3: [upstream[gid] for gid in bidirectional_ids]
+        }
+        for direction, gem_list in gem_port_types.items():
+            for gem in gem_list:
+                gem_data = {
+                    'gemport-id': gem['gemport_id'],
+                    'direction': direction,
+                    'encryption': gem['aes_encryption'].lower() == 'true',
+                    'discard-policy': gem['discard_policy'],
+                    'max-q-size': gem['max_q_size'],
+                    'pbit-map': gem['pbit_map'],
+                    'priority-q': gem['priority_q'],
+                    'scheduling_policy': gem['scheduling_policy'],
+                    'weight': gem['weight'],
+                    'uni-id': uni_id,
+                    'discard_config': {
+                        'max-probability': gem['discard_config']['max_probability'],
+                        'max-threshold': gem['discard_config']['max_threshold'],
+                        'min-threshold': gem['discard_config']['min_threshold'],
+                    },
+                }
+                gem_port = OnuGemPort.create(self, gem_data,
+                                             tcont.alloc_id,
+                                             tech_profile_id,
+                                             self._pon.next_gem_entity_id)
+                self._pon.add_gem_port(gem_port)
+
+    def _do_tech_profile_configuration(self, uni_id, tp, tech_profile_id):
+        us_scheduler = tp['us_scheduler']
+        tcont = self._create_tcont(uni_id, us_scheduler, tech_profile_id)
+
+        upstream = tp['upstream_gem_port_attribute_list']
+        downstream = tp['downstream_gem_port_attribute_list']
+        self._create_gemports(upstream, downstream, tcont, uni_id, tech_profile_id)
+
+    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)
+
+        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]:
+            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)
+
+                tech_profile_id = self._tp_path_to_tp_id(tp_path)
+                self._do_tech_profile_configuration(uni_id, tp, tech_profile_id)
+
+                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')
+                tp_task = AdtnTpServiceSpecificTask(self.openomci.omci_agent, self, uni_id)
+
+                self._tp_service_specific_task[uni_id][tp_path] = tp_task
+                # self._deferred = self.openomci.onu_omci_device.task_runner.queue_task(tp_task)
+                # 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")
+
     def update_pm_config(self, device, pm_config):
         # TODO: This has not been tested
         self.log.info('update_pm_config', pm_config=pm_config)
@@ -393,13 +588,13 @@
         self._cancel_deferred()
 
         reregister = False
-        # try:
-        #     # Drop registration for adapter messages
-        #     reregister = True
-        #     self.adapter_agent.unregister_for_inter_adapter_messages()
-        #
-        # except KeyError:
-        #     reregister = False
+        try:
+            # Drop registration for adapter messages
+            reregister = True
+            self.adapter_agent.unregister_for_inter_adapter_messages()
+
+        except KeyError:
+            reregister = False
 
         # Update the operational status to ACTIVATING and connect status to
         # UNREACHABLE
@@ -548,9 +743,10 @@
             assert self.logical_device_id, 'Invalid logical device ID'
 
             # reestablish logical ports for each UNI
+            multi_uni = len(self.uni_ports) > 1
             for uni in self.uni_ports:
                 self.adapter_agent.add_port(device.id, uni.get_port())
-                uni.add_logical_port(uni.logical_port_number)
+                uni.add_logical_port(uni.logical_port_number, multi_uni)
 
             device = self.adapter_agent.get_device(device.id)
             device.oper_status = OperStatus.ACTIVE
@@ -592,23 +788,21 @@
         pptp_entities = self.openomci.onu_omci_device.configuration.pptp_entities
         device = self.adapter_agent.get_device(self.device_id)
 
+        multi_uni = len(pptp_entities) > 1
+        uni_id = 0
+
         for entity_id, pptp in pptp_entities.items():
             intf_id = self.proxy_address.channel_id
             onu_id = self.proxy_address.onu_id
-            uni_no_start = platform.mk_uni_port_num(intf_id, onu_id)
-
-            working_port = self._next_port_number
-            uni_no = uni_no_start + working_port        # OpenFlow port number
+            uni_no = platform.mk_uni_port_num(intf_id, onu_id, uni_id=uni_id)
             uni_name = "uni-{}".format(uni_no)
-            mac_bridge_port_num = working_port + 1
-            self.log.debug('live-port-number-ready', uni_no=uni_no, uni_name=uni_name)
+            mac_bridge_port_num = uni_id + 1
 
             uni_port = UniPort.create(self, uni_name, uni_no, uni_name)
             uni_port.entity_id = entity_id
             uni_port.enabled = True
             uni_port.mac_bridge_port_num = mac_bridge_port_num
-            uni_port.add_logical_port(uni_port.port_number)
-
+            uni_port.add_logical_port(uni_port.port_number, multi_uni)
             self.log.debug("created-uni-port", uni=uni_port)
 
             self.adapter_agent.add_port(device.id, uni_port.get_port())
@@ -637,128 +831,7 @@
                                                                 pon_port)
             self.adapter_agent.update_device(device)
             uni_port.enabled = True
-            # TODO: only one uni/pptp for now. flow bug in openolt
-
-    def on_tcont_create(self, tcont):
-        from onu_tcont import OnuTCont
-
-        self.log.info('create-tcont')
-
-        td = self.traffic_descriptors.get(tcont.get('td-ref'))
-        traffic_descriptor = td['object'] if td is not None else None
-        tcont['object'] = OnuTCont.create(self, tcont, traffic_descriptor)
-
-        if self._pon is not None:
-            self._pon.add_tcont(tcont['object'])
-
-        return tcont
-
-    def on_tcont_modify(self, tcont, update, diffs):
-        valid_keys = ['td-ref']  # Modify of these keys supported
-
-        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
-        if invalid_key is not None:
-            raise KeyError("TCONT leaf '{}' is read-only or write-once".format(invalid_key))
-
-        tc = tcont.get('object')
-        assert tc is not None, 'TCONT not found'
-
-        update['object'] = tc
-
-        if self._pon is not None:
-            keys = [k for k in diffs.keys() if k in valid_keys]
-
-            for k in keys:
-                if k == 'td-ref':
-                    td = self.traffic_descriptors.get(update['td-ref'])
-                    if td is not None:
-                        self._pon.update_tcont_td(tcont['alloc-id'], td)
-
-        return update
-
-    def on_tcont_delete(self, tcont):
-        if self._pon is not None:
-            self._pon.remove_tcont(tcont['alloc-id'])
-
-        return None
-
-    def on_td_create(self, traffic_disc):
-        from onu_traffic_descriptor import OnuTrafficDescriptor
-
-        traffic_disc['object'] = OnuTrafficDescriptor.create(traffic_disc)
-        return traffic_disc
-
-    def on_td_modify(self, traffic_disc, update, diffs):
-        from onu_traffic_descriptor import OnuTrafficDescriptor
-
-        valid_keys = ['fixed-bandwidth',
-                      'assured-bandwidth',
-                      'maximum-bandwidth',
-                      'priority',
-                      'weight',
-                      'additional-bw-eligibility-indicator']
-        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
-        if invalid_key is not None:
-            raise KeyError("traffic-descriptor leaf '{}' is read-only or write-once".format(invalid_key))
-
-        # New traffic descriptor
-        update['object'] = OnuTrafficDescriptor.create(update)
-
-        td_name = traffic_disc['name']
-        tconts = {key: val for key, val in self.tconts.iteritems()
-                  if val['td-ref'] == td_name and td_name is not None}
-
-        for tcont in tconts.itervalues():
-            if self._pon is not None:
-                self._pon.update_tcont_td(tcont['alloc-id'], update['object'])
-
-        return update
-
-    def on_td_delete(self, traffic_desc):
-        # TD may be used by more than one TCONT. Only delete if the last one
-
-        td_name = traffic_desc['name']
-        num_tconts = len([val for val in self.tconts.itervalues()
-                          if val['td-ref'] == td_name and td_name is not None])
-
-        return None if num_tconts <= 1 else traffic_desc
-
-    def on_gemport_create(self, gem_port):
-        from onu_gem_port import OnuGemPort
-        assert self._pon is not None, 'No PON port'
-
-        gem_port['object'] = OnuGemPort.create(self, gem_port,
-                                               self._pon.next_gem_entity_id)
-        self._pon.add_gem_port(gem_port['object'])
-        return gem_port
-
-    def on_gemport_modify(self, gem_port, update, diffs):
-        valid_keys = ['encryption',
-                      'traffic-class']  # Modify of these keys supported
-
-        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
-        if invalid_key is not None:
-            raise KeyError("GEM Port leaf '{}' is read-only or write-once".format(invalid_key))
-
-        port = gem_port.get('object')
-        assert port is not None, 'GemPort not found'
-
-        keys = [k for k in diffs.keys() if k in valid_keys]
-        update['object'] = port
-
-        for k in keys:
-            if k == 'encryption':
-                port.encryption = update[k]
-            elif k == 'traffic-class':
-                pass                    # TODO: Implement
-
-        return update
-
-    def on_gemport_delete(self, gem_port):
-        if self._pon is not None:
-            self._pon.remove_gem_id(gem_port['gemport-id'])
-
-        return None
+            uni_id += 1
 
     def rx_inter_adapter_message(self, msg):
         raise NotImplemented('Not currently supported')
diff --git a/voltha/adapters/adtran_onu/omci/adtn_tp_service_specific_task.py b/voltha/adapters/adtran_onu/omci/adtn_tp_service_specific_task.py
new file mode 100644
index 0000000..7dd375a
--- /dev/null
+++ b/voltha/adapters/adtran_onu/omci/adtn_tp_service_specific_task.py
@@ -0,0 +1,464 @@
+#
+# Copyright 2018 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.
+
+import structlog
+from common.frameio.frameio import hexify
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, TimeoutError, failure
+from voltha.extensions.omci.omci_me import *
+from voltha.extensions.omci.tasks.task import Task
+from voltha.extensions.omci.omci_defs import *
+from voltha.adapters.brcm_openomci_onu.uni_port import *
+from voltha.adapters.brcm_openomci_onu.pon_port \
+    import BRDCM_DEFAULT_VLAN, TASK_PRIORITY, DEFAULT_TPID, DEFAULT_GEM_PAYLOAD
+
+OP = EntityOperations
+RC = ReasonCodes
+
+
+class TechProfileDownloadFailure(Exception):
+    """
+    This error is raised by default when the download fails
+    """
+
+
+class TechProfileResourcesFailure(Exception):
+    """
+    This error is raised by when one or more resources required is not available
+    """
+
+
+class AdtnTpServiceSpecificTask(Task):
+    """
+    Adtran OpenOMCI Tech-Profile Download Task
+    """
+    name = "Adtran Tech-Profile Download Task"
+
+    def __init__(self, omci_agent, handler, uni_id):
+        """
+        Class initialization
+
+        :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+        :param device_id: (str) ONU Device ID
+        """
+        log = structlog.get_logger(device_id=handler.device_id, uni_id=uni_id)
+        log.debug('function-entry')
+
+        super(AdtnTpServiceSpecificTask, self).__init__(AdtnTpServiceSpecificTask.name,
+                                                        omci_agent,
+                                                        handler.device_id,
+                                                        priority=TASK_PRIORITY,
+                                                        exclusive=False)
+        self.log = log
+
+        self._onu_device = omci_agent.get_device(handler.device_id)
+        self._local_deferred = None
+
+        pon_port = handler.pon_port()
+        self._uni_port = handler.uni_ports[uni_id]
+        assert self._uni_port.uni_id == uni_id
+
+        # Port numbers
+        self._input_tpid = DEFAULT_TPID
+        self._output_tpid = DEFAULT_TPID
+
+        self._vlan_tcis_1 = BRDCM_DEFAULT_VLAN
+        self._cvid = BRDCM_DEFAULT_VLAN
+        self._vlan_config_entity_id = self._vlan_tcis_1
+        self._max_gem_payload = DEFAULT_GEM_PAYLOAD
+
+        # Entity IDs. IDs with values can probably be most anything for most ONUs,
+        #             IDs set to None are discovered/set
+
+        self._mac_bridge_service_profile_entity_id = handler.mac_bridge_service_profile_entity_id
+        self._ieee_mapper_service_profile_entity_id = pon_port.ieee_mapper_service_profile_entity_id
+        self._mac_bridge_port_ani_entity_id = pon_port.mac_bridge_port_ani_entity_id
+        self._gal_enet_profile_entity_id = handler.gal_enet_profile_entity_id
+
+        # Extract the current set of TCONT and GEM Ports from the Handler's pon_port that are
+        # relevant to this task's UNI. It won't change. But, the underlying pon_port may change
+        # due to additional tasks on different UNIs. So, it we cannot use the pon_port affter
+        # this initializer
+        self._tconts = [tcont for tcont in pon_port.tconts.itervalues()
+                        if tcont.uni_id == self._uni_port.uni_id]
+
+        self._gem_ports = [gem_port for gem_port in pon_port.gem_ports.itervalues()
+                           if gem_port.uni_id != self._uni_port.uni_id]
+
+        self.tcont_me_to_queue_map = dict()
+        self.uni_port_to_queue_map = dict()
+
+    def cancel_deferred(self):
+        self.log.debug('function-entry')
+        super(AdtnTpServiceSpecificTask, self).cancel_deferred()
+
+        d, self._local_deferred = self._local_deferred, None
+        try:
+            if d is not None and not d.called:
+                d.cancel()
+        except:
+            pass
+
+    def start(self):
+        """
+        Start the Tech-Profile Download
+        """
+        self.log.debug('function-entry')
+        super(AdtnTpServiceSpecificTask, self).start()
+        self._local_deferred = reactor.callLater(0, self.perform_service_specific_steps)
+
+    def stop(self):
+        """
+        Shutdown Tech-Profile download tasks
+        """
+        self.log.debug('function-entry')
+        self.log.debug('stopping')
+
+        self.cancel_deferred()
+        super(AdtnTpServiceSpecificTask, self).stop()
+
+    def check_status_and_state(self, results, operation=''):
+        """
+        Check the results of an OMCI response.  An exception is thrown
+        if the task was cancelled or an error was detected.
+
+        :param results: (OmciFrame) OMCI Response frame
+        :param operation: (str) what operation was being performed
+        :return: True if successful, False if the entity existed (already created)
+        """
+        self.log.debug('function-entry')
+
+        omci_msg = results.fields['omci_message'].fields
+        status = omci_msg['success_code']
+        error_mask = omci_msg.get('parameter_error_attributes_mask', 'n/a')
+        failed_mask = omci_msg.get('failed_attributes_mask', 'n/a')
+        unsupported_mask = omci_msg.get('unsupported_attributes_mask', 'n/a')
+
+        self.log.debug("OMCI Result: %s", operation, omci_msg=omci_msg, status=status, error_mask=error_mask,
+                       failed_mask=failed_mask, unsupported_mask=unsupported_mask)
+
+        if status == RC.Success:
+            self.strobe_watchdog()
+            return True
+
+        elif status == RC.InstanceExists:
+            return False
+
+        raise TechProfileDownloadFailure(
+            '{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
+            .format(operation, status, error_mask, failed_mask, unsupported_mask))
+
+    @inlineCallbacks
+    def perform_service_specific_steps(self):
+        self.log.debug('function-entry')
+
+        omci_cc = self._onu_device.omci_cc
+
+        try:
+            ################################################################################
+            # TCONTS
+            #
+            #  EntityID will be referenced by:
+            #            - GemPortNetworkCtp
+            #  References:
+            #            - ONU created TCONT (created on ONU startup)
+
+            tcont_idents = self._onu_device.query_mib(Tcont.class_id)
+            self.log.debug('tcont-idents', tcont_idents=tcont_idents)
+
+            for tcont in self._tconts:
+                free_entity_id = None
+                for k, v in tcont_idents.items():
+                    alloc_check = v.get('attributes', {}).get('alloc_id', 0)
+                    # Some onu report both to indicate an available tcont
+                    if alloc_check == 0xFF or alloc_check == 0xFFFF:
+                        free_entity_id = k
+                        break
+                    else:
+                        free_entity_id = None
+
+                self.log.debug('tcont-loop', free_entity_id=free_entity_id, alloc_id=tcont.alloc_id)
+
+                if free_entity_id is None:
+                    self.log.error('no-available-tconts')
+                    break
+
+                # TODO: Need to restore on failure.  Need to check status/results
+                results = yield tcont.add_to_hardware(omci_cc, free_entity_id)
+                self.check_status_and_state(results, 'create-tcont')
+
+            ################################################################################
+            # GEMS  (GemPortNetworkCtp and GemInterworkingTp)
+            #
+            #  For both of these MEs, the entity_id is the GEM Port ID. The entity id of the
+            #  GemInterworkingTp ME could be different since it has an attribute to specify
+            #  the GemPortNetworkCtp entity id.
+            #
+            #        for the GemPortNetworkCtp ME
+            #
+            #  GemPortNetworkCtp
+            #    EntityID will be referenced by:
+            #              - GemInterworkingTp
+            #    References:
+            #              - TCONT
+            #              - Hardcoded upstream TM Entity ID
+            #              - (Possibly in Future) Upstream Traffic descriptor profile pointer
+            #
+            #  GemInterworkingTp
+            #    EntityID will be referenced by:
+            #              - Ieee8021pMapperServiceProfile
+            #    References:
+            #              - GemPortNetworkCtp
+            #              - Ieee8021pMapperServiceProfile
+            #              - GalEthernetProfile
+            #
+
+            onu_g = self._onu_device.query_mib(OntG.class_id)
+            # If the traffic management option attribute in the ONU-G ME is 0
+            # (priority controlled) or 2 (priority and rate controlled), this
+            # pointer specifies the priority queue ME serving this GEM port
+            # network CTP. If the traffic management option attribute is 1
+            # (rate controlled), this attribute redundantly points to the
+            # T-CONT serving this GEM port network CTP.
+            traffic_mgmt_opt = \
+                onu_g.get('attributes', {}).get('traffic_management_options', 0)
+            self.log.debug("traffic-mgmt-option", traffic_mgmt_opt=traffic_mgmt_opt)
+
+            prior_q = self._onu_device.query_mib(PriorityQueueG.class_id)
+            for k, v in prior_q.items():
+                self.log.debug("prior-q", k=k, v=v)
+
+                try:
+                    _ = iter(v)
+                except TypeError:
+                    continue
+
+                if 'instance_id' in v:
+                    related_port = v['attributes']['related_port']
+                    if v['instance_id'] & 0b1000000000000000:
+                        tcont_me = (related_port & 0xffff0000) >> 16
+                        if tcont_me not in self.tcont_me_to_queue_map:
+                            self.log.debug("prior-q-related-port-and-tcont-me",
+                                            related_port=related_port,
+                                            tcont_me=tcont_me)
+                            self.tcont_me_to_queue_map[tcont_me] = list()
+
+                        self.tcont_me_to_queue_map[tcont_me].append(k)
+                    else:
+                        uni_port = (related_port & 0xffff0000) >> 16
+                        if uni_port ==  self._uni_port.entity_id:
+                            if uni_port not in self.uni_port_to_queue_map:
+                                self.log.debug("prior-q-related-port-and-uni-port-me",
+                                                related_port=related_port,
+                                                uni_port_me=uni_port)
+                                self.uni_port_to_queue_map[uni_port] = list()
+
+                            self.uni_port_to_queue_map[uni_port].append(k)
+
+
+            self.log.debug("ul-prior-q", ul_prior_q=self.tcont_me_to_queue_map)
+            self.log.debug("dl-prior-q", dl_prior_q=self.uni_port_to_queue_map)
+
+            for gem_port in self._gem_ports:
+                # TODO: Traffic descriptor will be available after meter bands are available
+                tcont = gem_port.tcont
+                if tcont is None:
+                    self.log.error('unknown-tcont-reference', gem_id=gem_port.gem_id)
+                    continue
+
+                ul_prior_q_entity_id = None
+                dl_prior_q_entity_id = None
+                if gem_port.direction == "upstream" or \
+                        gem_port.direction == "bi-directional":
+
+                    # Sort the priority queue list in order of priority.
+                    # 0 is highest priority and 0x0fff is lowest.
+                    self.tcont_me_to_queue_map[tcont.entity_id].sort()
+                    self.uni_port_to_queue_map[self._uni_port.entity_id].sort()
+                    # Get the priority queue associated with p-bit that is
+                    # mapped to the gem port.
+                    # p-bit-7 is highest priority and p-bit-0 is lowest
+                    # Gem port associated with p-bit-7 should be mapped to
+                    # highest priority queue and gem port associated with p-bit-0
+                    # should be mapped to lowest priority queue.
+                    # The self.tcont_me_to_queue_map and self.uni_port_to_queue_map
+                    # have priority queue entities ordered in descending order
+                    # of priority
+                    for i, p in enumerate(gem_port.pbit_map):
+                        if p == '1':
+                            ul_prior_q_entity_id = \
+                                self.tcont_me_to_queue_map[tcont.entity_id][i]
+                            dl_prior_q_entity_id = \
+                                self.uni_port_to_queue_map[self._uni_port.entity_id][i]
+                            break
+
+                    assert ul_prior_q_entity_id is not None and \
+                           dl_prior_q_entity_id is not None
+
+                    # TODO: Need to restore on failure.  Need to check status/results
+                    results = yield gem_port.add_to_hardware(omci_cc,
+                                                   tcont.entity_id,
+                                                   self._ieee_mapper_service_profile_entity_id +
+                                                             self._uni_port.mac_bridge_port_num,
+                                                   self._gal_enet_profile_entity_id,
+                                                   ul_prior_q_entity_id, dl_prior_q_entity_id)
+                    self.check_status_and_state(results, 'create-gem-port')
+                elif gem_port.direction == "downstream":
+                    # Downstream is inverse of upstream
+                    # TODO: could also be a case of multicast. Not supported for now
+                    pass
+
+            ################################################################################
+            # Update the IEEE 802.1p Mapper Service Profile config
+            #
+            #  EntityID was created prior to this call. This is a set
+            #
+            #  References:
+            #            - Gem Interwork TPs are set here
+            #
+
+            gem_entity_ids = [OmciNullPointer] * 8
+            for gem_port in self._gem_ports:
+                self.log.debug("tp-gem-port", entity_id=gem_port.entity_id, uni_id=gem_port.uni_id)
+
+                if gem_port.direction == "upstream" or \
+                        gem_port.direction == "bi-directional":
+                    for i, p in enumerate(gem_port.pbit_map):
+                        if p == '1':
+                            gem_entity_ids[i] = gem_port.entity_id
+                elif gem_port.direction == "downstream":
+                    # Downstream gem port p-bit mapper is inverse of upstream
+                    # TODO: Could also be a case of multicast. Not supported for now
+                    pass
+
+            msg = Ieee8021pMapperServiceProfileFrame(
+                self._ieee_mapper_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # 802.1p mapper Service Mapper Profile ID
+                interwork_tp_pointers=gem_entity_ids  # Interworking TP IDs
+            )
+            frame = msg.set()
+            self.log.debug('openomci-msg', omci_msg=msg)
+            results = yield omci_cc.send(frame)
+            self.check_status_and_state(results, 'set-8021p-mapper-service-profile-ul')
+
+            ################################################################################
+            # Create Extended VLAN Tagging Operation config (PON-side)
+            #
+            #  EntityID relates to the VLAN TCIS
+            #  References:
+            #            - VLAN TCIS from previously created VLAN Tagging filter data
+            #            - PPTP Ethernet or VEIP UNI
+            #
+
+            # TODO: do this for all uni/ports...
+            # TODO: magic.  static variable for assoc_type
+
+            # default to PPTP
+            if self._uni_port.type is UniType.VEIP:
+                association_type = 10
+            elif self._uni_port.type is UniType.PPTP:
+                association_type = 2
+            else:
+                association_type = 2
+
+            attributes = dict(
+                association_type=association_type,                  # Assoc Type, PPTP/VEIP Ethernet UNI
+                associated_me_pointer=self._uni_port.entity_id,      # Assoc ME, PPTP/VEIP Entity Id
+
+                # See VOL-1311 - Need to set table during create to avoid exception
+                # trying to read back table during post-create-read-missing-attributes
+                # But, because this is a R/W attribute. Some ONU may not accept the
+                # value during create. It is repeated again in a set below.
+                input_tpid=self._input_tpid,  # input TPID
+                output_tpid=self._output_tpid,  # output TPID
+            )
+
+            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                self._mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
+                attributes=attributes
+            )
+
+            frame = msg.create()
+            self.log.debug('openomci-msg', omci_msg=msg)
+            results = yield omci_cc.send(frame)
+            self.check_status_and_state(results, 'create-extended-vlan-tagging-operation-configuration-data')
+
+            attributes = dict(
+                # Specifies the TPIDs in use and that operations in the downstream direction are
+                # inverse to the operations in the upstream direction
+                input_tpid=self._input_tpid,    # input TPID
+                output_tpid=self._output_tpid,  # output TPID
+                downstream_mode=0,              # inverse of upstream
+            )
+
+            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                self._mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
+                attributes=attributes
+            )
+
+            frame = msg.set()
+            self.log.debug('openomci-msg', omci_msg=msg)
+            results = yield omci_cc.send(frame)
+            self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data')
+
+            attributes = dict(
+                # parameters: Entity Id ( 0x900), Filter Inner Vlan Id(0x1000-4096,do not filter on Inner vid,
+                #             Treatment Inner Vlan Id : 2
+
+                # Update uni side extended vlan filter
+                # filter for untagged
+                # probably for eapol
+                # TODO: lots of magic
+                # TODO: magic 0x1000 / 4096?
+                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,
+                    filter_inner_vid=4096,
+                    filter_inner_tpid_de=0,
+                    filter_ether_type=0,
+
+                    treatment_tags_to_remove=0,
+                    treatment_outer_priority=15,
+                    treatment_outer_vid=0,
+                    treatment_outer_tpid_de=0,
+
+                    treatment_inner_priority=0,
+                    treatment_inner_vid=self._cvid,
+                    treatment_inner_tpid_de=4,
+                )
+            )
+
+            msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                self._mac_bridge_service_profile_entity_id + self._uni_port.mac_bridge_port_num,  # Bridge Entity ID
+                attributes=attributes
+            )
+
+            frame = msg.set()
+            self.log.debug('openomci-msg', omci_msg=msg)
+            results = yield omci_cc.send(frame)
+            self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data-table')
+
+            self.deferred.callback("tech-profile-download-success")
+
+        except TimeoutError as e:
+            self.log.warn('rx-timeout-2', e=e)
+            self.deferred.errback(failure.Failure(e))
+
+        except Exception as e:
+            self.log.exception('omci-setup-2', e=e)
+            self.deferred.errback(failure.Failure(e))
diff --git a/voltha/adapters/adtran_onu/omci/omci.py b/voltha/adapters/adtran_onu/omci/omci.py
index 6e0ed82..a9d44c0 100644
--- a/voltha/adapters/adtran_onu/omci/omci.py
+++ b/voltha/adapters/adtran_onu/omci/omci.py
@@ -41,7 +41,6 @@
         self._enabled = False
         self._connected = False
         self._deferred = None
-        self._resync_deferred = None    # For TCont/GEM use
         self._bridge_initialized = False
         self._in_sync_reached = False
         self._omcc_version = OMCCVersion.Unknown
@@ -107,24 +106,15 @@
 
     def _cancel_deferred(self):
         d1, self._deferred = self._deferred, None
-        d2, self._resync_deferred = self._resync_deferred, None
-        d3, self._mib_download_deferred = self._mib_download_deferred, None
+        d2, self._mib_download_deferred = self._mib_download_deferred, None
 
-        for d in [d1, d2, d3]:
+        for d in [d1, d2]:
             try:
                 if d is not None and not d.called:
                     d.cancel()
             except:
                 pass
 
-    def _cancel_resync_deferred(self):
-        d, self._resync_deferred = self._resync_deferred, None
-        try:
-            if d is not None and not d.called:
-                d.cancel()
-        except:
-            pass
-
     def delete(self):
         self.enabled = False
 
@@ -247,11 +237,6 @@
             self.log.exception('device-info-load', e=e)
             self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self._mib_in_sync)
 
-    def gem_or_tcont_added(self):
-        if self._in_sync_reached:
-            self._cancel_resync_deferred()
-            # TODO: Need to configure service here
-
     def _subscribe_to_events(self):
         from voltha.extensions.omci.onu_device_entry import OnuDeviceEvents, \
             OnuDeviceEntry
diff --git a/voltha/adapters/adtran_onu/omci/omci_entities.py b/voltha/adapters/adtran_onu/omci/omci_entities.py
index e982ee7..654078d 100644
--- a/voltha/adapters/adtran_onu/omci/omci_entities.py
+++ b/voltha/adapters/adtran_onu/omci/omci_entities.py
@@ -15,9 +15,13 @@
 """ Adtran vendor-specific OMCI Entities"""
 
 import inspect
-
 import sys
-from scapy.fields import ShortField, IntField, ByteField, StrFixedLenField
+import json
+from binascii import hexlify
+from bitstring import BitArray
+from scapy.fields import ByteField, ShortField,  BitField
+from scapy.fields import IntField, StrFixedLenField, FieldListField, PacketLenField
+from scapy.packet import Packet
 from voltha.extensions.omci.omci_entities import EntityClassAttribute, \
     AttributeAccess, EntityOperations, EntityClass
 
@@ -128,6 +132,118 @@
     mandatory_operations = {OP.Get, OP.Set, OP.GetNext}
 
 
+class AdtnVlanTaggingOperation(Packet):
+    name = "VlanTaggingOperation"
+    fields_desc = [
+        BitField("filter_outer_priority", 0, 4),
+        BitField("filter_outer_vid", 0, 13),
+        BitField("filter_outer_tpid_de", 0, 3),
+        BitField("pad1", 0, 12),
+
+        BitField("filter_inner_priority", 0, 4),
+        BitField("filter_inner_vid", 0, 13),
+        BitField("filter_inner_tpid_de", 0, 3),
+        BitField("pad2", 0, 8),
+        BitField("filter_ether_type", 0, 4),
+
+        BitField("treatment_tags_to_remove", 0, 2),
+        BitField("pad3", 0, 10),
+        BitField("treatment_outer_priority", 0, 4),
+        BitField("treatment_outer_vid", 0, 13),
+        BitField("treatment_outer_tpid_de", 0, 3),
+
+        BitField("pad4", 0, 12),
+        BitField("treatment_inner_priority", 0, 4),
+        BitField("treatment_inner_vid", 0, 13),
+        BitField("treatment_inner_tpid_de", 0, 3),
+    ]
+
+    def to_json(self):
+        return json.dumps(self.fields, separators=(',', ':'))
+
+    @staticmethod
+    def json_from_value(value):
+        bits = BitArray(hex=hexlify(value))
+        temp = AdtnVlanTaggingOperation(
+            filter_outer_priority=bits[0:4].uint,         # 4  <-size
+            filter_outer_vid=bits[4:17].uint,             # 13
+            filter_outer_tpid_de=bits[17:20].uint,        # 3
+                                                          # pad 12
+            filter_inner_priority=bits[32:36].uint,       # 4
+            filter_inner_vid=bits[36:49].uint,            # 13
+            filter_inner_tpid_de=bits[49:52].uint,        # 3
+                                                          # pad 8
+            filter_ether_type=bits[60:64].uint,           # 4
+            treatment_tags_to_remove=bits[64:66].uint,    # 2
+                                                          # pad 10
+            treatment_outer_priority=bits[76:80].uint,    # 4
+            treatment_outer_vid=bits[80:93].uint,         # 13
+            treatment_outer_tpid_de=bits[93:96].uint,     # 3
+                                                          # pad 12
+            treatment_inner_priority=bits[108:112].uint,  # 4
+            treatment_inner_vid=bits[112:125].uint,       # 13
+            treatment_inner_tpid_de=bits[125:128].uint,   # 3
+        )
+        return json.dumps(temp.fields, separators=(',', ':'))
+
+    def index(self):
+        return '{:02}'.format(self.fields.get('filter_outer_priority',0)) + \
+               '{:03}'.format(self.fields.get('filter_outer_vid',0)) + \
+               '{:01}'.format(self.fields.get('filter_outer_tpid_de',0)) + \
+               '{:03}'.format(self.fields.get('filter_inner_priority',0)) + \
+               '{:04}'.format(self.fields.get('filter_inner_vid',0)) + \
+               '{:01}'.format(self.fields.get('filter_inner_tpid_de',0)) + \
+               '{:02}'.format(self.fields.get('filter_ether_type',0))
+
+    def is_delete(self):
+        return self.fields.get('treatment_tags_to_remove',0) == 0x3 and \
+            self.fields.get('pad3',0) == 0x3ff and \
+            self.fields.get('treatment_outer_priority',0) == 0xf and \
+            self.fields.get('treatment_outer_vid',0) == 0x1fff and \
+            self.fields.get('treatment_outer_tpid_de',0) == 0x7 and \
+            self.fields.get('pad4',0) == 0xfff and \
+            self.fields.get('treatment_inner_priority',0) == 0xf and \
+            self.fields.get('treatment_inner_vid',0) == 0x1fff and \
+            self.fields.get('treatment_inner_tpid_de',0) == 0x7
+
+    def delete(self):
+        self.fields['treatment_tags_to_remove'] = 0x3
+        self.fields['pad3'] = 0x3ff
+        self.fields['treatment_outer_priority'] = 0xf
+        self.fields['treatment_outer_vid'] = 0x1fff
+        self.fields['treatment_outer_tpid_de'] = 0x7
+        self.fields['pad4'] = 0xfff
+        self.fields['treatment_inner_priority'] = 0xf
+        self.fields['treatment_inner_vid'] = 0x1fff
+        self.fields['treatment_inner_tpid_de'] = 0x7
+        return self
+
+
+class AdtnExtendedVlanTaggingOperationConfigurationData(EntityClass):
+    class_id = 171
+    attributes = [
+        ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
+        ECA(ByteField("association_type", None), {AA.R, AA.W, AA.SBC},
+            range_check=lambda x: 0 <= x <= 11),
+        ECA(ShortField("received_vlan_tagging_operation_table_max_size", None),
+            {AA.R}),
+        ECA(ShortField("input_tpid", None), {AA.R, AA.W}),
+        ECA(ShortField("output_tpid", None), {AA.R, AA.W}),
+        ECA(ByteField("downstream_mode", None), {AA.R, AA.W},
+            range_check=lambda x: 0 <= x <= 8),
+        ECA(StrFixedLenField("received_frame_vlan_tagging_operation_table",
+                             AdtnVlanTaggingOperation, 16), {AA.R, AA.W}),
+        ECA(ShortField("associated_me_pointer", None), {AA.R, AA.W, AA.SBC}),
+        ECA(FieldListField("dscp_to_p_bit_mapping", None,
+                           BitField('',  0, size=3), count_from=lambda _: 64),
+            {AA.R, AA.W}),
+    ]
+    mandatory_operations = {OP.Create, OP.Delete, OP.Set, OP.Get, OP.GetNext}
+    optional_operations = {OP.SetTable}
+
+
+
+
 #################################################################################
 # entity class lookup table from entity_class values
 _onu_entity_classes_name_map = dict(
diff --git a/voltha/adapters/adtran_onu/onu_gem_port.py b/voltha/adapters/adtran_onu/onu_gem_port.py
index d2c3dad..cb5ae45 100644
--- a/voltha/adapters/adtran_onu/onu_gem_port.py
+++ b/voltha/adapters/adtran_onu/onu_gem_port.py
@@ -24,25 +24,25 @@
     """
     Adtran ONU specific implementation
     """
-    def __init__(self, gem_id, alloc_id, entity_id,
-                 encryption=False,
-                 omci_transport=False,
+    def __init__(self, gem_data, alloc_id, tech_profile_id, entity_id,
                  multicast=False,
-                 tcont_ref=None,
                  traffic_class=None,
-                 name=None,
-                 handler=None):
-        super(OnuGemPort, self).__init__(gem_id, alloc_id,
+                 handler=None,
+                 is_mock=False):
+        gem_id = gem_data['gemport-id']
+        encryption = gem_data['encryption']
+        super(OnuGemPort, self).__init__(gem_id, alloc_id, tech_profile_id,
                                          encryption=encryption,
-                                         omci_transport=omci_transport,
+                                         omci_transport=False,
                                          multicast=multicast,
-                                         tcont_ref=tcont_ref,
                                          traffic_class=traffic_class,
-                                         name=name,
-                                         handler=handler)
+                                         handler=handler,
+                                         is_mock=is_mock)
+        self._gem_data = gem_data
         self._entity_id = entity_id
         self._tcont_entity_id = None
         self._interworking = False
+        self.uni_id = gem_data['uni-id']
         self.log = structlog.get_logger(device_id=handler.device_id, gem_id=gem_id)
 
     @property
@@ -50,29 +50,18 @@
         return self._entity_id
 
     @property
-    def encryption(self):
-        return self._encryption
-
-    @property
     def in_hardware(self):
         return self._tcont_entity_id is not None and self._interworking
 
-    @encryption.setter
-    def encryption(self, value):
-        assert isinstance(value, bool), 'encryption is a boolean'
-
-        if self._encryption != value:
-            self._encryption = value
-
     @staticmethod
-    def create(handler, gem_port, entity_id):
-        return OnuGemPort(gem_port['gemport-id'],
-                          None,
+    def create(handler, gem_data, alloc_id, tech_profile_id, entity_id):
+        # TODO: Only a minimal amount of info from the 'gem_port' dictionary
+        #       is currently used to create the GEM ports.
+
+        return OnuGemPort(gem_data,
+                          alloc_id,
+                          tech_profile_id,
                           entity_id,
-                          encryption=gem_port['encryption'],  # aes_indicator,
-                          tcont_ref=gem_port['tcont-ref'],
-                          name=gem_port['name'],
-                          traffic_class=gem_port['traffic-class'],
                           handler=handler)
 
     @inlineCallbacks
@@ -80,6 +69,8 @@
                         tcont_entity_id,
                         ieee_mapper_service_profile_entity_id,
                         gal_enet_profile_entity_id):
+        if self._is_mock:
+            returnValue('mock')
 
         self.log.debug('add-to-hardware', gem_id=self.gem_id,
                        gem_entity_id=self.entity_id,
@@ -154,6 +145,9 @@
 
     @inlineCallbacks
     def remove_from_hardware(self, omci):
+        if self._is_mock:
+            returnValue('mock')
+
         self.log.debug('remove-from-hardware',  gem_id=self.gem_id)
 
         results = None
diff --git a/voltha/adapters/adtran_onu/onu_tcont.py b/voltha/adapters/adtran_onu/onu_tcont.py
index dee3fcc..3031ca8 100644
--- a/voltha/adapters/adtran_onu/onu_tcont.py
+++ b/voltha/adapters/adtran_onu/onu_tcont.py
@@ -28,11 +28,14 @@
     free_tcont_alloc_id = 0xFFFF
     free_gpon_tcont_alloc_id = 0xFF     # SFU may use this to indicate a free TCONT
 
-    def __init__(self, handler, alloc_id, traffic_descriptor, name=None):
-        super(OnuTCont, self).__init__(alloc_id, traffic_descriptor, name=name)
-        self._handler = handler
-        self._entity_id = None
+    def __init__(self, handler, alloc_id, sched_policy, tech_profile_id, uni_id, traffic_descriptor, is_mock=False):
+        super(OnuTCont, self).__init__(alloc_id, tech_profile_id, traffic_descriptor, is_mock=is_mock)
         self.log = structlog.get_logger(device_id=handler.device_id, alloc_id=alloc_id)
+
+        self._handler = handler
+        self.sched_policy = sched_policy
+        self.uni_id = uni_id
+        self._entity_id = None
         self._free_alloc_id = OnuTCont.free_tcont_alloc_id
 
     @property
@@ -43,11 +46,18 @@
     def create(handler, tcont, td):
         assert isinstance(tcont, dict), 'TCONT should be a dictionary'
         assert isinstance(td, TrafficDescriptor), 'Invalid Traffic Descriptor data type'
-        return OnuTCont(handler, tcont['alloc-id'], td, name=tcont['name'])
+        return OnuTCont(handler,
+                        tcont['alloc-id'],
+                        tcont['q-sched-policy'],
+                        tcont['tech-profile-id'],
+                        tcont['uni-id'],
+                        td)
 
     @inlineCallbacks
     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._is_mock:
+            returnValue('mock')
 
         if self._entity_id == tcont_entity_id:
             returnValue('Already set')
@@ -56,6 +66,9 @@
             raise KeyError('TCONT already assigned: {}'.format(self.entity_id))
 
         try:
+            # TODO: Look up ONU2-G QoS flexibility attribute and only set this
+            #       if it can be supported
+
             self._free_alloc_id = prev_alloc_id
             frame = TcontFrame(tcont_entity_id, self.alloc_id).set()
             results = yield omci.send(frame)
@@ -80,6 +93,8 @@
     @inlineCallbacks
     def remove_from_hardware(self, omci):
         self.log.debug('remove-from-hardware', tcont_entity_id=self.entity_id)
+        if self._is_mock:
+            returnValue('mock')
         try:
             frame = TcontFrame(self.entity_id, self._free_alloc_id).set()
             results = yield omci.send(frame)
diff --git a/voltha/adapters/adtran_onu/onu_traffic_descriptor.py b/voltha/adapters/adtran_onu/onu_traffic_descriptor.py
index 894959b..c09b0a6 100644
--- a/voltha/adapters/adtran_onu/onu_traffic_descriptor.py
+++ b/voltha/adapters/adtran_onu/onu_traffic_descriptor.py
@@ -23,12 +23,10 @@
     """
     def __init__(self, fixed, assured, maximum,
                  additional=TrafficDescriptor.AdditionalBwEligibility.DEFAULT,
-                 best_effort=None,
-                 name=None):
+                 best_effort=None):
         super(OnuTrafficDescriptor, self).__init__(fixed, assured, maximum,
                                                    additional=additional,
-                                                   best_effort=best_effort,
-                                                   name=name)
+                                                   best_effort=best_effort)
 
     @staticmethod
     def create(traffic_disc):
@@ -47,7 +45,6 @@
         return OnuTrafficDescriptor(traffic_disc['fixed-bandwidth'],
                                     traffic_disc['assured-bandwidth'],
                                     traffic_disc['maximum-bandwidth'],
-                                    name=traffic_disc['name'],
                                     best_effort=best_effort,
                                     additional=additional)
 
diff --git a/voltha/adapters/adtran_onu/pon_port.py b/voltha/adapters/adtran_onu/pon_port.py
index d82eff9..4f00c0d 100644
--- a/voltha/adapters/adtran_onu/pon_port.py
+++ b/voltha/adapters/adtran_onu/pon_port.py
@@ -184,27 +184,6 @@
         self.log.info('add', tcont=tcont, reflow=reflow)
         self._tconts[tcont.alloc_id] = tcont
 
-        # TODO: Refactor once xPON goes away
-        self._handler.openomci.gem_or_tcont_added()
-
-    def update_tcont_td(self, alloc_id, new_td):
-        tcont = self._tconts.get(alloc_id)
-
-        if tcont is None:
-            return  # not-found
-
-        tcont.traffic_descriptor = new_td
-
-        # TODO: Not yet implemented
-        #TODO: How does this affect ONU tcont settings?
-        #try:
-        #    results = yield tcont.add_to_hardware(self._handler.omci)
-        #except Exception as e:
-        #    self.log.exception('tcont', tcont=tcont, e=e)
-        #    # May occur with xPON provisioning, use hw-resync to recover
-        #    results = 'resync needed'
-        # returnValue(results)
-
     @inlineCallbacks
     def remove_tcont(self, alloc_id):
         tcont = self._tconts.get(alloc_id)
@@ -246,9 +225,6 @@
         self.log.info('add', gem_port=gem_port, reflow=reflow)
         self._gem_ports[gem_port.gem_id] = gem_port
 
-        # TODO: Refactor once xPON goes away
-        self._handler.openomci.gem_or_tcont_added()
-
     @inlineCallbacks
     def remove_gem_id(self, gem_id):
         """
diff --git a/voltha/adapters/adtran_onu/uni_port.py b/voltha/adapters/adtran_onu/uni_port.py
index 0f2c10f..5194282 100644
--- a/voltha/adapters/adtran_onu/uni_port.py
+++ b/voltha/adapters/adtran_onu/uni_port.py
@@ -20,6 +20,7 @@
 from voltha.protos.logical_device_pb2 import LogicalPort
 from voltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER
 from voltha.protos.openflow_13_pb2 import ofp_port
+import voltha.adapters.adtran_olt.resources.adtranolt_platform as platform
 
 
 class UniPort(object):
@@ -29,6 +30,7 @@
         self._enabled = False
         self._handler = handler
         self._name = name
+        self.uni_id = platform.uni_id_from_uni_port(port_no)
         self._port = None
         self._port_number = port_no
         self._ofp_port_no = ofp_port_no         # Set at by creator (vENET create)
@@ -162,16 +164,16 @@
         """
         if self._port is None:
             self._port = Port(port_no=self.port_number,
-                              label='vEth-{}'.format(self.port_number),
+                              label=self.port_id_name(),
                               type=Port.ETHERNET_UNI,
                               admin_state=self._admin_state,
                               oper_status=self._oper_status)
         return self._port
 
     def port_id_name(self):
-        return 'uni-{}'.format(self._logical_port_number)
+        return 'uni-{}'.format(self._port_number)
 
-    def add_logical_port(self, openflow_port_no,
+    def add_logical_port(self, openflow_port_no, multi_uni_naming,
                          capabilities=OFPPF_10GB_FD | OFPPF_FIBER,
                          speed=OFPPF_10GB_FD):
 
@@ -202,7 +204,7 @@
                                           device.parent_port_no & 0xff,
                                           (port_no >> 8) & 0xff,
                                           port_no & 0xff)),
-                name=device.serial_number,
+                name=device.serial_number + ['', '-' + str(self._mac_bridge_port_num)][multi_uni_naming],
                 config=0,
                 state=OFPPS_LIVE,
                 curr=capabilities,