ADTRAN ONU: Move user-data flow create to OpenOMCI Task

Change-Id: I18b2c03fc9edc5458acb3e943e49087bc9ffec91
diff --git a/voltha/adapters/adtran_olt/README.md b/voltha/adapters/adtran_olt/README.md
index e2aaeaf..9e109a7 100644
--- a/voltha/adapters/adtran_olt/README.md
+++ b/voltha/adapters/adtran_olt/README.md
@@ -4,18 +4,19 @@
 extension of the existing **preprovision_olt** command and these are placed after
 entering two dashes '_--_'.  The full syntax to use is.
 
-| Short | Long             | Default | Notes |
-| :---: | :--------------: | :-----: | ----- |
-|  -u   | --nc_username    | ''      | NETCONF Username |
-|  -p   | --nc_password    | ''      | NETCONF Password |
-|  -t   | --nc_port        | 830     | NETCONF TCP Port |
-|  -U   | --rc_username    | ''      | REST Username |
-|  -P   | --rc_password    | ''      | REST Password |
-|  -T   | --rc_port        | 8081    | REST TCP Port |
-|  -z   | --zmq_port       | 5656    | ZeroMQ OMCI Proxy Port |
-|  -M   | --multicast_vlan | 4000    | Multicast VLANs (comma-delimeted) |
-|  -v   | --untagged_vlan  | 4092    | VLAN wrapper for untagged ONU frames |
-|  -Z   | --pio_port       | 5657    | PIO Service ZeroMQ Port |
+| Short | Long               | Default    | Notes |
+| :---: | :----------------: | :--------: | ----- |
+|  -u   | --nc_username      | ''         | NETCONF Username |
+|  -p   | --nc_password      | ''         | NETCONF Password |
+|  -t   | --nc_port          | 830        | NETCONF TCP Port |
+|  -U   | --rc_username      | ''         | REST Username |
+|  -P   | --rc_password      | ''         | REST Password |
+|  -T   | --rc_port          | 8081       | REST TCP Port |
+|  -z   | --zmq_port         | 5656       | ZeroMQ OMCI Proxy Port |
+|  -M   | --multicast_vlan   | 4000       | Multicast VLANs (comma-delimited) |
+|  -v   | --untagged_vlan    | 4092       | VLAN wrapper for untagged ONU frames |
+|  -Z   | --pio_port         | 5657       | PIO Service ZeroMQ Port |
+|  -X   | --xpon_enable      | False      | Support BBF WT-386 xPON CLI/NBI provisioning |
 
 For example, if your Adtran OLT is address 10.17.174.193 with the default TCP ports and
 NETCONF credentials of admin/admin and REST credentials of ADMIN/ADMIN, the command line
@@ -42,13 +43,18 @@
     preprovision_olt -t adtran_olt --host_and_port 10.17.174.193:830
 ```
 
-Currently the Adtran Device Adapter supports xPON provisioning and to enable PON ports, or activate ONUs, you
-must use the appropriate commands. In the VOLTHA v2.0 release (Q4 2018?), the xPON provisioning will be removed
-from VOLTHA and replaced with Technology Profiles.
 
-## REST Based Pre-Provisioning
+## xPON Provisioning Support
+
+Currently the Adtran Device Adapter supports xPON provisioning to enable PON ports, or activate ONUs, you
+must use the appropriate commands. In the VOLTHA v2.0 release (Q4 2018?), the xPON provisioning will be removed
+from VOLTHA and replaced with Technology Profiles. _By default, this provisioning is now disabled and you should
+use the '-X' extra-arguments provisioning command switch if you wish to use it_.
+
+### REST Based xPON Pre-Provisioning
 In addition to CLI provisioning, the Adtran OLT Device Adapter can also be provisioned though the
-VOLTHA Northbound REST API. 
+VOLTHA Northbound REST API. The following examples show curl commands when running with the **_Consul_**
+key-value store. Similar curl commands can be used when **_etcd_** is used as the key value store
 
 ```bash
 VOLTHA_IP=localhost
@@ -94,14 +100,14 @@
 Besides specifying the "ipv4_address" leaf, you can alternatively use the "host_and_port" leaf to
 provide the IP Host address and the NetCONF port as in "10.17.174.228:830"
 
-## Enabling the Pre-Provisioned OLT
+### Enabling the Pre-Provisioned OLT
 To enable the OLT, you need the retrieve the OLT Device ID and issue a POST request to the proper URL as in:
 ```bash
 DEVICE_ID=$(jq .id /tmp/adtn-olt.json | sed 's/"//g')
 
 curl -k -s -X POST https://${VOLTHA_IP}:${REST_PORT}/api/v1/local/devices/${DEVICE_ID}/enable
 ```
-### Other REST APIs
+#### Other REST APIs
 To list out any devices, you can use the following command:
 
 ```bash
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index d506fa2..60522b1 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -52,7 +52,7 @@
 _DEFAULT_NETCONF_PORT = 830
 
 _STARTUP_RETRY_TIMEOUT = 5       # 5 seconds delay after activate failed before we
-_DEFAULT_XPON_SUPPORTED = True   # LOOK for the keywords 'xpon_support', SEBA
+_DEFAULT_XPON_SUPPORTED = False  # LOOK for the keywords 'xpon_support', SEBA
                                  # for areas to clean up once xPON is deprecated
 
 
@@ -278,7 +278,7 @@
                             default='{}'.format(DEFAULT_UTILITY_VLAN),
                             help='VLAN for Untagged Frames from ONUs')
         parser.add_argument('--xpon_enable', '-X', action='store_true',
-                            default=not _DEFAULT_XPON_SUPPORTED,
+                            default=_DEFAULT_XPON_SUPPORTED,
                             help='enable xPON (BBF WT-385) provisioning support')
         try:
             args = parser.parse_args(shlex.split(device.extra_args))
diff --git a/voltha/adapters/adtran_olt/adtran_olt.py b/voltha/adapters/adtran_olt/adtran_olt.py
index 19e8511..d906508 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='0.21',
+            version='1.22',
             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 bf4c654..788cb70 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -1309,7 +1309,7 @@
                 root=True,
                 serial_number=serial_number,
                 admin_state=AdminState.ENABLED,
-                vlan=self.get_onu_vid(onu_id)         # TODO: a hack, need a decent flow decomposer
+                # vlan=self.get_onu_vid(onu_id)         # TODO: a hack, need a decent flow decomposer
             )
             assert serial_number is not None, 'ONU does not have a serial number'
 
diff --git a/voltha/adapters/adtran_onu/adtran_onu.py b/voltha/adapters/adtran_onu/adtran_onu.py
index 450273f..ba1384f 100755
--- a/voltha/adapters/adtran_onu/adtran_onu.py
+++ b/voltha/adapters/adtran_onu/adtran_onu.py
@@ -40,7 +40,7 @@
                                                device_handler_class=AdtranOnuHandler,
                                                name='adtran_onu',
                                                vendor='Adtran Inc.',
-                                               version='0.15',
+                                               version='1.16',
                                                device_type='adtran_onu',
                                                vendor_id='ADTN',
                                                accepts_add_remove_flow_updates=False),  # TODO: Support flow-mods
diff --git a/voltha/adapters/adtran_onu/adtran_onu_handler.py b/voltha/adapters/adtran_onu/adtran_onu_handler.py
index 693d9bd..86b9991 100644
--- a/voltha/adapters/adtran_onu/adtran_onu_handler.py
+++ b/voltha/adapters/adtran_onu/adtran_onu_handler.py
@@ -34,12 +34,14 @@
 from voltha.extensions.omci.omci_me import *
 
 import voltha.adapters.adtran_olt.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
 
 _ = 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
-BRDCM_DEFAULT_VLAN = 4091
 
 
 class AdtranOnuHandler(AdtranXPON):
@@ -73,6 +75,14 @@
         self._deferred = None
         self._event_deferred = None
 
+        # Flow entries
+        self._flows = dict()
+
+        # OMCI resources
+        # TODO: Some of these could be dynamically chosen
+        self.vlan_tcis_1 = 0x900
+        self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1
+
         # Assume no XPON support unless we get an vont-ani/ont-ani/venet create
         self.xpon_support = False    # xPON no longer available
 
@@ -144,9 +154,8 @@
         assert isinstance(port_no_or_name, int), 'Invalid parameter type'
         return self._unis.get(port_no_or_name)
 
-    @property
-    def pon_port(self):
-        return self._pon
+    def pon_port(self, port_no=None):
+        return self._pon if port_no is None or port_no == self._pon.port_number else None
 
     @property
     def pon_ports(self):
@@ -331,141 +340,31 @@
 
     @inlineCallbacks
     def update_flow_table(self, flows):
-        #
-        # We need to proxy through the OLT to get to the ONU
-        # Configuration from here should be using OMCI
-        #
+        if len(flows) == 0:
+            returnValue('nop')  # TODO:  Do we need to delete all flows if empty?
+
         self.log.debug('bulk-flow-update', flows=flows)
-
-        import voltha.core.flow_decomposer as fd
-        from voltha.protos.openflow_13_pb2 import OFPXMC_OPENFLOW_BASIC
-
-        def is_downstream(port):
-            return port == self._pon_port_number
-
-        def is_upstream(port):
-            return not is_downstream(port)
+        valid_flows = set()
 
         for flow in flows:
-            match = {
-                'in_port': None,
-                # 'etype': None,            # These remaining key-value
-                # 'proto': None,            # pairs are set if found in the
-                # 'vlan_vid': None,         # flow information
-                # 'inner_vid': None,
-                # 'vlan_pcp': None,
-                # 'udp_dst': None,
-                # 'udp_src': None,
-                # 'ipv4_dst': None,
-                # 'ipv4_src': None,
-            }
-            actions = {
-                'out_port': None,
-                # 'push_tpid': None,        # These remaining key-value
-                # 'pop_vlan': None,  (bool) # pairs are set if found in the
-                # 'set_vlan_vid': None,     # flow information
-            }
-            self.log.debug('bulk-flow-update', flow=flow)
-            try:
-                _out_port = fd.get_out_port(flow)  # may be None
-                self.log.debug('out-port', out_port=_out_port)
+            # Decode it
+            flow_entry = FlowEntry.create(flow, self)
 
-                for field in fd.get_ofb_fields(flow):
-                    if field.type == fd.IN_PORT:
-                        assert match['in_port'] is None, \
-                            'Only a single input port is supported'
-                        match['in_port'] = field.port
-                        self.log.debug('field-type-in-port', in_port=field.port)
+            # Already handled?
+            if flow_entry.flow_id in self._flows:
+                valid_flows.add(flow_entry.flow_id)
 
-                    elif field.type == fd.ETH_TYPE:
-                        match['etype'] = field.eth_type
-                        self.log.debug('field-type-eth-type', eth_type=field.eth_type)
-
-                    elif field.type == fd.IP_PROTO:
-                        match['proto'] = field.ip_proto
-                        self.log.debug('field-type-ip-proto', ip_proto=field.ip_proto)
-
-                    elif field.type == fd.VLAN_VID:
-                        match['vlan_vid'] = field.vlan_vid & 0xfff
-                        self.log.debug('field-type-vlan-vid', vlan=field.vlan_vid & 0xfff)
-
-                    elif field.type == fd.VLAN_PCP:
-                        match['vlan_pcp'] = field.vlan_pcp
-                        self.log.debug('field-type-vlan-pcp', pcp=field.vlan_pcp)
-
-                    elif field.type == fd.UDP_DST:
-                        match['udp_dst'] = field.udp_dst
-                        self.log.debug('field-type-udp-dst', udp_dst=field.udp_dst)
-
-                    elif field.type == fd.UDP_SRC:
-                        match['udp_src'] = field.udp_src
-                        self.log.debug('field-type-udp-src', udp_src=field.udp_src)
-
-                    elif field.type == fd.IPV4_DST:
-                        match['ipv4_dst'] = field.ipv4_dst
-                        self.log.debug('field-type-ipv4-dst', ipv4_dst=field.ipv4_dst)
-
-                    elif field.type == fd.IPV4_SRC:
-                        match['ipv4_src'] = field.ipv4_src
-                        self.log.debug('field-type-ipv4-src', ipv4_dst=field.ipv4_src)
-
-                    elif field.type == fd.METADATA:
-                        match['inner_vid'] = field.table_metadata
-                        self.log.debug('field-type-metadata', metadata=field.table_metadata)
-
-                    else:
-                        raise NotImplementedError('field.type={}'.format(field.type))
-
-                for action in fd.get_actions(flow):
-                    if action.type == fd.OUTPUT:
-                        actions['out_port'] = action.output.port
-                        self.log.debug('action-type-output', output=action.output.port)
-
-                    elif action.type == fd.POP_VLAN:
-                        actions['pop_vlan'] = True
-                        self.log.debug('action-type-pop-vlan')
-
-                    elif action.type == fd.PUSH_VLAN:
-                        actions['push_tpid'] = action.push.ethertype
-                        self.log.debug('action-type-push-vlan',
-                                       push_tpid=action.push.ethertype)
-
-                        if action.push.ethertype != 0x8100:
-                            self.log.error('unsupported-tpid',
-                                           ethertype=action.push.ethertype,
-                                           in_port=match['in_port'])
-
-                    elif action.type == fd.SET_FIELD:
-                        _field = action.set_field.field.ofb_field
-                        self.log.debug('action-type-set-field', field=_field)
-                        assert (action.set_field.field.oxm_class == OFPXMC_OPENFLOW_BASIC)
-
-                        if _field.type == fd.VLAN_VID:
-                            actions['set_vlan_vid'] = _field.vlan_vid & 0xfff
-                            self.log.debug('set-field-type-vlan-vid', actions['set_vlan_vid'])
-                        else:
-                            self.log.error('unsupported-action-set-field-type',
-                                           field_type=_field.type,
-                                           in_port=match['in_port'])
-                    else:
-                        self.log.error('unsupported-action-type', action_type=action.type,
-                                       in_port=match['in_port'])
-
-                assert match['in_port'] is not None, 'No input port specified'
-                assert actions['out_port'] is not None, 'No output port specified'
-
-                _is_upstream = is_upstream(match['in_port'])
-
-            except Exception as e:
-                self.log.exception('failed-to-decode-flow', e=e)
+            if flow_entry is None or flow_entry.flow_direction not in {FlowEntry.FlowDirection.UPSTREAM,
+                                                                       FlowEntry.FlowDirection.DOWNSTREAM}:
                 continue
 
+            is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+
             # Ignore untagged upstream etherType flows. These are trapped at the
             # OLT and the default flows during initial OMCI service download will
             # send them to the Default VLAN (4091) port for us
             #
-            if _is_upstream and match.get('vlan_vid') is None \
-                    and match.get('etype') is not None:
+            if is_upstream and flow_entry.vlan_vid is None and flow_entry.etype is not None:
                 continue
 
             # Also ignore upstream untagged/priority tag that sets priority tag
@@ -473,14 +372,50 @@
             # priority tag data will be at a higher level.  Also should ignore the
             # corresponding priority-tagged to priority-tagged flow as well.
 
-            if (match.get('vlan_vid') == 0 and action.get('set_vlan_vid') == 0) or \
-                    (match.get('vlan_vid') is None and action.get('set_vlan_vid') == 0
-                     and not _is_upstream):
+            if (flow_entry.vlan_vid == 0 and flow_entry.set_vlan_vid == 0) or \
+                    (flow_entry.vlan_vid is None and flow_entry.set_vlan_vid == 0
+                     and not is_upstream):
                 continue
 
-            from omci.adtn_install_flow import AdtnInstallFlowTask
-            task = AdtnInstallFlowTask(self.openomci, self, match, action, _is_upstream)
-            self.openomci.onu_omci_device.task_runner.queue_task(task)
+            # Is it the first user-data flow downstream with a non-zero/non-None VID
+            # to match on?  If so, use as the device VLAN
+            # TODO: When multicast is supported, skip the multicast VLAN here?
+
+            if not is_upstream and flow_entry.vlan_vid:
+                uni = self.uni_port(flow_entry.out_port)
+                if uni is not None:
+                    uni.subscriber_vlan = flow_entry.vlan_vid
+
+            # Add it to hardware
+            try:
+                def failed(_reason, fid):
+                    del self._flows[fid]
+
+                task = AdtnInstallFlowTask(self.openomci.omci_agent, self, flow_entry)
+                d = self.openomci.onu_omci_device.task_runner.queue_task(task)
+                d.addErrback(failed, flow_entry.flow_id)
+
+                valid_flows.add(flow_entry.flow_id)
+                self._flows[flow_entry.flow_id] = flow_entry
+
+            except Exception as e:
+                self.log.exception('flow-add', e=e, flow=flow_entry)
+
+        # Now check for flows that were missing in the bulk update
+        deleted_flows = set(self._flows.keys()) - valid_flows
+
+        for flow_id in deleted_flows:
+            try:
+                del_flow = self._flows[flow_id]
+
+                task = AdtnRemoveFlowTask(self.openomci.omci_agent, self, del_flow)
+                self.openomci.onu_omci_device.task_runner.queue_task(task)
+                # TODO: Change to success/failure callback checks later
+                # d.addCallback(success, flow_entry.flow_id)
+                del self._flows[flow_id]
+
+            except Exception as e:
+                self.log.exception('flow-remove', e=e, flow=self._flows[flow_id])
 
     @inlineCallbacks
     def reboot(self):
@@ -857,7 +792,7 @@
 
         device = self.adapter_agent.get_device(self.device_id)
         subscriber_vlan = device.vlan
-        untagged_vlan = BRDCM_DEFAULT_VLAN          # TODO: Need a better way to define this
+        untagged_vlan = OMCI.DEFAULT_UNTAGGED_VLAN
 
         for entity_id, pptp in pptp_entities.items():
             intf_id = self.proxy_address.channel_id
diff --git a/voltha/adapters/adtran_onu/flow/__init__.py b/voltha/adapters/adtran_onu/flow/__init__.py
new file mode 100644
index 0000000..b0fb0b2
--- /dev/null
+++ b/voltha/adapters/adtran_onu/flow/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
diff --git a/voltha/adapters/adtran_onu/flow/flow_entry.py b/voltha/adapters/adtran_onu/flow/flow_entry.py
new file mode 100644
index 0000000..7a90daf
--- /dev/null
+++ b/voltha/adapters/adtran_onu/flow/flow_entry.py
@@ -0,0 +1,270 @@
+# 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.
+
+from enum import IntEnum
+import voltha.core.flow_decomposer as fd
+from voltha.core.flow_decomposer import *
+from voltha.protos.openflow_13_pb2 import OFPP_MAX
+
+log = structlog.get_logger()
+
+# IP Protocol numbers
+_supported_ip_protocols = [
+    1,          # ICMP
+    2,          # IGMP
+    6,          # TCP
+    17,         # UDP
+]
+
+
+class FlowEntry(object):
+    """
+    Provide a class that wraps the flow rule and also provides state/status for a FlowEntry.
+
+    When a new flow is sent, it is first decoded to check for any potential errors. If None are
+    found, the entry is created and it is analyzed to see if it can be combined to with any other flows
+    to create or modify an existing EVC.
+
+    Note: Since only E-LINE is supported, modification of an existing EVC is not performed.
+    """
+    class FlowDirection(IntEnum):
+        UPSTREAM = 0          # UNI port to ANI Port
+        DOWNSTREAM = 1        # ANI port to UNI Port
+        ANI = 2               # ANI port to ANI Port
+        UNI = 3               # UNI port to UNI Port
+        OTHER = 4             # Unable to determine
+
+    _flow_dir_map = {
+        (FlowDirection.UNI, FlowDirection.ANI): FlowDirection.UPSTREAM,
+        (FlowDirection.ANI, FlowDirection.UNI): FlowDirection.DOWNSTREAM
+    }
+    # Well known EtherTypes
+    class EtherType(IntEnum):
+        EAPOL = 0x888E
+        IPv4 = 0x0800
+        IPv6 = 0x86DD
+        ARP = 0x0806
+        LLDP = 0x88CC
+
+    # Well known IP Protocols
+    class IpProtocol(IntEnum):
+        IGMP = 2
+        UDP = 17
+
+    def __init__(self, flow, handler):
+        self._flow = flow               # TODO: Drop this reference once debugging done
+        self._handler = handler
+        self.flow_id = flow.id
+        self._flow_direction = FlowEntry.FlowDirection.OTHER
+        self._is_multicast = False
+        self.tech_profile_id = None
+
+        # Selection properties
+        self.in_port = None
+        self.vlan_vid = None
+        self.vlan_pcp = None
+        self.etype = None
+        self.proto = None
+        self.ipv4_dst = None
+        self.udp_dst = None         # UDP Port #
+        self.udp_src = None         # UDP Port #
+        self.inner_vid = None
+
+        # Actions
+        self.out_port = None
+        self.pop_vlan = False
+        self.push_vlan_tpid = None
+        self.set_vlan_vid = None
+        self._name = self.create_flow_name()
+
+    def __str__(self):
+        return 'flow_entry: {}, in: {}, out: {}, vid: {}, inner:{}, eth: {}, IP: {}'.format(
+            self.name, self.in_port, self.out_port, self.vlan_vid, self.inner_vid,
+            self.etype, self.proto)
+
+    def __repr__(self):
+        return str(self)
+
+    @property
+    def name(self):
+        return self._name    # TODO: Is a name really needed in production?
+
+    def create_flow_name(self):
+        return 'flow-{}-{}'.format(self.device_id, self.flow_id)
+
+    @property
+    def handler(self):
+        return self._handler
+
+    @property
+    def device_id(self):
+        return self.handler.device_id
+
+    @property
+    def flow_direction(self):
+        return self._flow_direction
+
+    @property
+    def is_multicast_flow(self):
+        return self._is_multicast
+
+    @staticmethod
+    def create(flow, handler):
+        """
+        Create the appropriate FlowEntry wrapper for the flow.  This method returns a two
+        results.
+
+        The first result is the flow entry that was created. This could be a match to an
+        existing flow since it is a bulk update.  None is returned only if no match to
+        an existing entry is found and decode failed (unsupported field)
+
+        :param flow:   (Flow) Flow entry passed to VOLTHA adapter
+        :param handler: (DeviceHandler) handler for the device
+        :return: (FlowEntry) Created flow entry, None on decode failure
+        """
+        # Exit early if it already exists
+        try:
+            flow_entry = FlowEntry(flow, handler)
+
+            if not flow_entry.decode(flow):
+                return None
+
+            # TODO: Do we want to do the OMCI here ?
+
+            return flow_entry
+
+        except Exception as e:
+            log.exception('flow-entry-processing', e=e)
+            return None
+
+    def decode(self, flow):
+        """
+        Examine flow rules and extract appropriate settings
+        """
+        log.debug('start-decode')
+        status = self._decode_traffic_selector(flow) and self._decode_traffic_treatment(flow)
+
+        if status:
+            ani_ports = [pon.port_number for pon in self._handler.pon_ports]
+            uni_ports = [uni.port_number for uni in self._handler.uni_ports]
+
+            # Determine direction of the flow
+            def port_type(port_number):
+                if port_number in ani_ports:
+                    return FlowEntry.FlowDirection.ANI
+
+                elif port_number in uni_ports:
+                    return FlowEntry.FlowDirection.UNI
+
+                return FlowEntry.FlowDirection.OTHER
+
+            self._flow_direction = FlowEntry._flow_dir_map.get((port_type(self.in_port),
+                                                                port_type(self.out_port)),
+                                                               FlowEntry.FlowDirection.OTHER)
+        return status
+
+    def _decode_traffic_selector(self, flow):
+        """
+        Extract traffic selection settings
+        """
+        self.in_port = fd.get_in_port(flow)
+
+        if self.in_port > OFPP_MAX:
+            log.warn('logical-input-ports-not-supported')
+            return False
+
+        for field in fd.get_ofb_fields(flow):
+            if field.type == IN_PORT:
+                assert self.in_port == field.port, 'Multiple Input Ports found in flow rule'
+
+            elif field.type == VLAN_VID:
+                self.vlan_vid = field.vlan_vid & 0xfff
+                log.debug('*** field.type == VLAN_VID', value=field.vlan_vid, vlan_id=self.vlan_vid)
+                self._is_multicast = False  # TODO: self.vlan_id in self._handler.multicast_vlans
+
+            elif field.type == VLAN_PCP:
+                log.debug('*** field.type == VLAN_PCP', value=field.vlan_pcp)
+                self.vlan_pcp = field.vlan_pcp
+
+            elif field.type == ETH_TYPE:
+                log.debug('*** field.type == ETH_TYPE', value=field.eth_type)
+                self.etype = field.eth_type
+
+            elif field.type == IP_PROTO:
+                log.debug('*** field.type == IP_PROTO', value=field.ip_proto)
+                self.proto = field.ip_proto
+
+                if self.proto not in _supported_ip_protocols:
+                    log.error('Unsupported IP Protocol', ip_proto=self.proto)
+                    return False
+
+            elif field.type == IPV4_DST:
+                log.debug('*** field.type == IPV4_DST', value=field.ipv4_dst)
+                self.ipv4_dst = field.ipv4_dst
+
+            elif field.type == UDP_DST:
+                log.debug('*** field.type == UDP_DST', value=field.udp_dst)
+                self.udp_dst = field.udp_dst
+
+            elif field.type == UDP_SRC:
+                log.debug('*** field.type == UDP_SRC', value=field.udp_src)
+                self.udp_src = field.udp_src
+
+            elif field.type == METADATA:
+                log.debug('*** field.type == METADATA', value=field.table_metadata)
+                self.inner_vid = field.table_metadata
+                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)
+                return False
+
+        return True
+
+    def _decode_traffic_treatment(self, flow):
+        self.out_port = fd.get_out_port(flow)
+
+        if self.out_port > OFPP_MAX:
+            log.warn('logical-output-ports-not-supported')
+            return False
+
+        for act in fd.get_actions(flow):
+            if act.type == fd.OUTPUT:
+                assert self.out_port == act.output.port, 'Multiple Output Ports found in flow rule'
+                pass           # Handled earlier
+
+            elif act.type == POP_VLAN:
+                log.debug('*** action.type == POP_VLAN')
+                self.pop_vlan = True
+
+            elif act.type == PUSH_VLAN:
+                log.debug('*** action.type == PUSH_VLAN', value=act.push)
+                tpid = act.push.ethertype
+                self.push_tpid = tpid
+                assert tpid == 0x8100, 'Only TPID 0x8100 is currently supported'
+
+            elif act.type == SET_FIELD:
+                log.debug('*** action.type == SET_FIELD', value=act.set_field.field)
+                assert (act.set_field.field.oxm_class == ofp.OFPXMC_OPENFLOW_BASIC)
+                field = act.set_field.field.ofb_field
+                if field.type == VLAN_VID:
+                    self.set_vlan_vid = field.vlan_vid & 0xfff
+
+            else:
+                log.warn('unsupported-action', action=act)
+                self._status_message = 'Unsupported action.type={}'.format(act.type)
+                return False
+
+        return True
diff --git a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
index f3e2cee..b5fcb40 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
@@ -14,95 +14,68 @@
 # limitations under the License.
 #
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, TimeoutError, failure
+from twisted.internet.defer import inlineCallbacks, 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.adtran_onu.flow.flow_entry import FlowEntry
 
 OP = EntityOperations
 RC = ReasonCodes
 
 
-class ServiceDownloadFailure(Exception):
+class ServiceInstallFailure(Exception):
     """
-    This error is raised by default when the download fails
-    """
-
-
-class ServiceResourcesFailure(Exception):
-    """
-    This error is raised by when one or more resources required is not available
+    This error is raised by default when the flow-install fails
     """
 
 
 class AdtnInstallFlowTask(Task):
     """
-    OpenOMCI MIB Download Example - Service specific
-
-    This task takes the legacy OMCI 'script' for provisioning the Adtran ONU
-    and converts it to run as a Task on the OpenOMCI Task runner.  This is
-    in order to begin to decompose service instantiation in preparation for
-    Technology Profile work.
-
-    Once technology profiles are ready, some of this task may hang around or
-    be moved into OpenOMCI if there are any very common settings/configs to do
-    for any profile that may be provided in the v2.0 release
+    OpenOMCI MIB Flow Install Task
 
     Currently, the only service tech profiles expected by v2.0 will be for AT&T
     residential data service and DT residential data service.
     """
     task_priority = Task.DEFAULT_PRIORITY + 10
-    default_tpid = 0x8100
-    default_gem_payload = 1518
-    BRDCM_DEFAULT_VLAN = 4091
-
     name = "ADTRAN MIB Install Flow Task"
 
-    def __init__(self, omci_agent, handler, match, action, is_upstream):
+    def __init__(self, omci_agent, handler, flow_entry):
         """
         Class initialization
 
         :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
         :param handler: (AdtranOnuHandler) ONU Handler
-        :param match: (dict) Flow match rules
-        :param action: (dict) Flow action rules
-        :param is_upstream: (bool) True if upstream flow is being installed
+        :param flow_entry: (FlowEntry) Flow to install
         """
         super(AdtnInstallFlowTask, self).__init__(AdtnInstallFlowTask.name,
                                                   omci_agent,
                                                   handler.device_id,
-                                                  priority=AdtnInstallFlowTask.task_priority)
+                                                  priority=AdtnInstallFlowTask.task_priority,
+                                                  exclusive=False)
         self._handler = handler
         self._onu_device = omci_agent.get_device(handler.device_id)
         self._local_deferred = None
-
-        self._match = match
-        self._action = action
+        self._flow_entry = flow_entry
 
         # TODO: Cleanup below that is not needed
-        self._vlan_tcis_1 = 0x900
-        self._input_tpid = AdtnInstallFlowTask.default_tpid
-        self._output_tpid = AdtnInstallFlowTask.default_tpid
+        is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+        uni_port = flow_entry.in_port if is_upstream else flow_entry.out_port
+        pon_port = flow_entry.out_port if is_upstream else flow_entry.in_port
 
-        if self._handler.xpon_support:
-            device = self._handler.adapter_agent.get_device(self.device_id)
-            self._cvid = device.vlan
-        else:
-            # TODO: TCIS below is just a test, may need 0x900...as in the xPON mode
-            self._vlan_tcis_1 = AdtnInstallFlowTask.BRDCM_DEFAULT_VLAN
-            self._cvid = AdtnInstallFlowTask.BRDCM_DEFAULT_VLAN
+        self._uni = handler.uni_port(uni_port)
+        self._pon = handler.pon_port(pon_port)
 
         # Entity IDs. IDs with values can probably be most anything for most ONUs,
         #             IDs set to None are discovered/set
         #
         # TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
         #
-        self._ieee_mapper_service_profile_entity_id = 0x100
-        self._gal_enet_profile_entity_id = 0x100
+        self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
+        # self._hsi_mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
 
         # Next to are specific
-        self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
-        self._vlan_config_entity_id = self._vlan_tcis_1
+        self._mac_bridge_service_profile_entity_id = handler.mac_bridge_service_profile_entity_id
 
     def cancel_deferred(self):
         super(AdtnInstallFlowTask, self).cancel_deferred()
@@ -155,15 +128,15 @@
         elif status == RC.InstanceExists:
             return False
 
-        raise ServiceDownloadFailure('{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
-                                     .format(operation, status, error_mask, failed_mask, unsupported_mask))
+        raise ServiceInstallFailure('{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
+                                    .format(operation, status, error_mask, failed_mask, unsupported_mask))
 
     @inlineCallbacks
     def perform_flow_install(self):
         """
         Send the commands to configure the flow
         """
-        self.log.info('perform-service-download')
+        self.log.info('perform-flow-install', vlan_vid=self._flow_entry.vlan_vid)
 
         if self._handler.xpon_support:
             self.deferred.callback('flow-install-nop')  # xPON mode does not need this
@@ -171,105 +144,101 @@
         def resources_available():
             # TODO: Rework for non-xpon mode
             return (len(self._handler.uni_ports) > 0 and
-                    len(self._handler.pon_port.tconts) and
-                    len(self._handler.pon_port.gem_ports) and
-                    self._action.get('set_vlan_vid') is not None)
+                    len(self._pon.tconts) and
+                    len(self._pon.gem_ports))
 
         if self._handler.enabled and resources_available():
             omci = self._onu_device.omci_cc
             try:
                 # TODO: make this a member of the onu gem port or the uni port
-                _mac_bridge_service_profile_entity_id = 0x201
-                _mac_bridge_port_ani_entity_id = 0x2102  # TODO: can we just use the entity id from the anis list?
-                _set_vlan_vid = self._action['set_vlan_vid']
+                vlan_vid = self._flow_entry.vlan_vid
 
-                # Delete bridge ani side vlan filter
-                msg = VlanTaggingFilterDataFrame(_mac_bridge_port_ani_entity_id)
-                frame = msg.delete()
-
-                results = yield omci.send(frame)
-                self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
-
-                # Re-Create bridge ani side vlan filter
-                msg = VlanTaggingFilterDataFrame(
-                        _mac_bridge_port_ani_entity_id,  # Entity ID
-                        vlan_tcis=[_set_vlan_vid],  # VLAN IDs
-                        forward_operation=0x10
-                )
-                frame = msg.create()
-
-                results = yield omci.send(frame)
-                self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
+                # # Delete bridge ani side vlan filter
+                # msg = VlanTaggingFilterDataFrame(self._hsi_mac_bridge_port_ani_entity_id)
+                # frame = msg.delete()
+                #
+                # results = yield omci.send(frame)
+                # self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
+                #
+                # # Re-Create bridge ani side vlan filter
+                # msg = VlanTaggingFilterDataFrame(
+                #         self._hsi_mac_bridge_port_ani_entity_id,  # Entity ID
+                #         vlan_tcis=[vlan_vid],             # VLAN IDs
+                #         forward_operation=0x10
+                # )
+                # frame = msg.create()
+                #
+                # results = yield omci.send(frame)
+                # self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
 
                 # Update uni side extended vlan filter
                 # filter for untagged
                 # probably for eapol
                 # TODO: magic 0x1000 / 4096?
                 # TODO: lots of magic
-                attributes = dict(
-                        received_frame_vlan_tagging_operation_table=
-                        VlanTaggingOperation(
-                                filter_outer_priority=15,
-                                filter_outer_vid=4096,
-                                filter_outer_tpid_de=0,
-
-                                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=_set_vlan_vid,
-                                treatment_inner_tpid_de=4
-                        )
-                )
-                # TODO: Move this to a task
-                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                        _mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                        attributes=attributes  # See above
-                )
-                frame = msg.set()
-
-                results = yield omci.send(frame)
-                self.check_status_and_state(results,
-                                            'flow-set-ext-vlan-tagging-op-config-data-untagged')
+                # attributes = dict(
+                #         # This table filters and tags upstream frames
+                #         received_frame_vlan_tagging_operation_table=
+                #         VlanTaggingOperation(
+                #                 filter_outer_priority=15,     # This entry is not a double-tag rule (ignore out tag rules)
+                #                 filter_outer_vid=4096,        # Do not filter on the outer VID value
+                #                 filter_outer_tpid_de=0,       # Do not filter on the outer TPID field
+                #
+                #                 filter_inner_priority=15,     # This is a no-tag rule, ignore all other VLAN tag filter fields
+                #                 filter_inner_vid=4096,        # Do not filter on the inner VID
+                #                 filter_inner_tpid_de=0,       # Do not filter on inner TPID field
+                #                 filter_ether_type=0,          # Do not filter on EtherType
+                #
+                #                 treatment_tags_to_remove=0,   # Remove 0 tags
+                #
+                #                 treatment_outer_priority=15,  # Do not add an outer tag
+                #                 treatment_outer_vid=0,        # n/a
+                #                 treatment_outer_tpid_de=0,    # n/a
+                #
+                #                 treatment_inner_priority=0,    # Add an inner tag and insert this value as the priority
+                #                 treatment_inner_vid=vlan_vid,  # Push this tag onto the frame
+                #                 treatment_inner_tpid_de=4      # set TPID
+                #         )
+                # )
+                # msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                #         self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                #         attributes=attributes  # See above
+                # )
+                # frame = msg.set()
+                #
+                # results = yield omci.send(frame)
+                # self.check_status_and_state(results,
+                #                             'flow-set-ext-vlan-tagging-op-config-data-untagged')
 
                 # Update uni side extended vlan filter
                 # filter for vlan 0
                 # TODO: lots of magic
+
                 attributes = dict(
+                        # This table filters and tags upstream frames
                         received_frame_vlan_tagging_operation_table=
                         VlanTaggingOperation(
-                                filter_outer_priority=15,
-                                # This entry is not a double-tag rule
-                                filter_outer_vid=4096,  # Do not filter on the outer VID value
-                                filter_outer_tpid_de=0,
-                                # Do not filter on the outer TPID field
+                                filter_outer_priority=15,   # This entry is not a double-tag rule
+                                filter_outer_vid=4096,      # Do not filter on the outer VID value
+                                filter_outer_tpid_de=0,     # Do not filter on the outer TPID field
 
-                                filter_inner_priority=8,  # Filter on inner vlan
-                                filter_inner_vid=0x0,  # Look for vlan 0
-                                filter_inner_tpid_de=0,  # Do not filter on inner TPID field
-                                filter_ether_type=0,  # Do not filter on EtherType
+                                filter_inner_priority=8,    # Filter on inner vlan
+                                filter_inner_vid=0x0,       # Look for vlan 0
+                                filter_inner_tpid_de=0,     # Do not filter on inner TPID field
+                                filter_ether_type=0,        # Do not filter on EtherType
 
-                                treatment_tags_to_remove=1,
-                                treatment_outer_priority=15,
-                                treatment_outer_vid=0,
-                                treatment_outer_tpid_de=0,
+                                treatment_tags_to_remove=1,   # Remove 1 tags
+                                treatment_outer_priority=15,  # Do not add an outer tag
+                                treatment_outer_vid=0,        # n/a
+                                treatment_outer_tpid_de=0,    # n/a
 
-                                treatment_inner_priority=8,
-                                # Add an inner tag and insert this value as the priority
-                                treatment_inner_vid=_set_vlan_vid,
-                                # use this value as the VID in the inner VLAN tag
-                                treatment_inner_tpid_de=4,  # set TPID
+                                treatment_inner_priority=8,    # Add an inner tag and insert this value as the priority
+                                treatment_inner_vid=vlan_vid,  # use this value as the VID in the inner VLAN tag
+                                treatment_inner_tpid_de=4,     # set TPID to 0x8100
                         )
                 )
                 msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                        _mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                        self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
                         attributes=attributes  # See above
                 )
                 frame = msg.set()
@@ -286,24 +255,5 @@
 
         else:
             # TODO: Provide better error reason, what was missing...
-            e = ServiceResourcesFailure('Required resources are not available')
+            e = ServiceInstallFailure('Required resources are not available')
             self.deferred.errback(failure.Failure(e))
-
-    def check_status_and_state(self, result, operation=''):
-        from voltha.extensions.omci.omci_defs import ReasonCodes
-        self.log.debug('function-entry')
-        omci_msg = result.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:", operation, omci_msg=omci_msg, status=status,
-                       error_mask=error_mask,
-                       failed_mask=failed_mask, unsupported_mask=unsupported_mask)
-
-        if status == ReasonCodes.Success:
-            return True
-
-        elif status == ReasonCodes.InstanceExists:
-            return False
diff --git a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
index 6c853e1..136e2e2 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
@@ -55,7 +55,7 @@
     default_tpid = 0x8100
     default_gem_payload = 1518
 
-    name = "ADTRAN MIB Download Example Task"
+    name = "ADTRAN MIB Download Task"
 
     def __init__(self, omci_agent, handler):
         """
@@ -67,7 +67,8 @@
         super(AdtnMibDownloadTask, self).__init__(AdtnMibDownloadTask.name,
                                                   omci_agent,
                                                   handler.device_id,
-                                                  priority=AdtnMibDownloadTask.task_priority)
+                                                  priority=AdtnMibDownloadTask.task_priority,
+                                                  exclusive=True)
         self._handler = handler
         self._onu_device = omci_agent.get_device(handler.device_id)
         self._local_deferred = None
@@ -79,21 +80,22 @@
         self._pon_port_num = 0
         self._uni_port_num = 0  # TODO Both port numbers are the same, is this correct?  See MacBridgePortConfigurationDataFrame
 
-        self._vlan_tcis_1 = 0x900
+        self._pon = handler.pon_port()
+        self._vlan_tcis_1 = self._handler.vlan_tcis_1
 
         # Entity IDs. IDs with values can probably be most anything for most ONUs,
         #             IDs set to None are discovered/set
         #
         # TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
         #
-        self._mac_bridge_service_profile_entity_id = 0x100
-        self._ieee_mapper_service_profile_entity_id = 0x100
-        self._mac_bridge_port_ani_entity_id = 0x100
+        self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
+        self._mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
         self._gal_enet_profile_entity_id = 0x100
 
-        # Next to are specific
+        # Next to are specific     TODO: UNI lookups here or uni specific install !!!
         self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
-        self._vlan_config_entity_id = self._vlan_tcis_1
+        self._mac_bridge_service_profile_entity_id = \
+            self._handler.mac_bridge_service_profile_entity_id
 
     def cancel_deferred(self):
         super(AdtnMibDownloadTask, self).cancel_deferred()
@@ -172,17 +174,17 @@
                 # Lock the UNI ports to prevent any alarms during initial configuration
                 # of the ONU
                 self.strobe_watchdog()
-                yield self.enable_unis(self._handler.uni_ports, True)
+                # yield self.enable_unis(self._handler.uni_ports, True)
 
                 # Provision the initial bridge configuration
                 yield self.perform_initial_bridge_setup()
 
-                # If here, we are done
+                # If here, we are done with the generic MIB download
                 device = self._handler.adapter_agent.get_device(self.device_id)
 
                 device.reason = 'Initial OMCI Download Complete'
                 self._handler.adapter_agent.update_device(device)
-                self.deferred.callback('TODO: What should we return to the caller?')
+                self.deferred.callback('MIB Download - success')
 
             except TimeoutError as e:
                 self.deferred.errback(failure.Failure(e))
@@ -207,7 +209,6 @@
             #            - MAC Bridge Port Configuration Data (PON & UNI)
             #  References:
             #            - Nothing
-
             attributes = {
                 'spanning_tree_ind': False
             }
@@ -251,15 +252,15 @@
             frame = MacBridgePortConfigurationDataFrame(
                 self._mac_bridge_port_ani_entity_id,                    # Entity ID
                 bridge_id_pointer=self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
-                # TODO: The PORT number for this port and the UNI port are the same. Is this correct?
+                # TODO: The PORT number for this port and the UNI port are the same. Correct?
                 port_num=self._pon_port_num,                            # Port ID
                 tp_type=3,                                              # TP Type (IEEE 802.1p mapper service)
                 tp_pointer=self._ieee_mapper_service_profile_entity_id  # TP ID, 8021p mapper ID
             ).create()
             results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'create-mac-bridge-port-configuration-data-part-1')
+            self.check_status_and_state(results, 'create-mac-bridge-port-config-data-part-1')
 
-            ################################################################################
+            #############################################################
             # VLAN Tagging Filter config
             #
             #  EntityID will be referenced by:
@@ -269,15 +270,15 @@
             #
             # Set anything, this request will not be used when using Extended Vlan
 
-            frame = VlanTaggingFilterDataFrame(
-                self._mac_bridge_port_ani_entity_id,  # Entity ID
-                vlan_tcis=[self._vlan_tcis_1],        # VLAN IDs
-                forward_operation=0x10
-            ).create()
-            results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'create-vlan-tagging-filter-data')
+            # frame = VlanTaggingFilterDataFrame(
+            #     self._mac_bridge_port_ani_entity_id,  # Entity ID
+            #     vlan_tcis=[self._vlan_tcis_1],        # VLAN IDs
+            #     forward_operation=0x10
+            # ).create()
+            # results = yield omci_cc.send(frame)
+            # self.check_status_and_state(results, 'create-vlan-tagging-filter-data')
 
-            ########################################################################################
+            #############################################################
             # Create GalEthernetProfile - Once per ONU/PON interface
             #
             #  EntityID will be referenced by:
@@ -292,9 +293,9 @@
             results = yield omci_cc.send(frame)
             self.check_status_and_state(results, 'create-gal-ethernet-profile')
 
-            ################################################################################
-            # UNI Specific                                                                 #
-            ################################################################################
+            ##################################################
+            # UNI Specific                                   #
+            ##################################################
             # MAC Bridge Port config
             # This configuration is for Ethernet UNI
             #
@@ -312,10 +313,10 @@
                 tp_pointer=self._ethernet_uni_entity_id  # TP ID, 8021p mapper Id
             ).create()
             results = yield omci_cc.send(frame)
-            self.check_status_and_state(results, 'create-mac-bridge-port-configuration-data-part-2')
+            self.check_status_and_state(results, 'create-mac-bridge-port-config-data-part-2')
 
-        except TimeoutError as e:
-            self.log.warn('rx-timeout-1', frame=frame)
+        except TimeoutError as _e:
+            self.log.warn('rx-timeout-download', frame=hexlify(frame))
             raise
 
         except Exception as e:
@@ -336,7 +337,7 @@
         frame = None
 
         for uni in unis:
-            ################################################################################
+            ##################################################################
             #  Lock/Unlock UNI  -  0 to Unlock, 1 to lock
             #
             #  EntityID is referenced by:
@@ -351,7 +352,7 @@
                 self.check_status_and_state(results, 'set-pptp-ethernet-uni-lock-restore')
 
             except TimeoutError:
-                self.log.warn('rx-timeout', frame=frame)
+                self.log.warn('rx-timeout-uni-enable', frame=hexlify(frame))
                 raise
 
             except Exception as e:
diff --git a/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py b/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py
new file mode 100644
index 0000000..9ca1c19
--- /dev/null
+++ b/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py
@@ -0,0 +1,226 @@
+#
+# 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.
+#
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, 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.adtran_onu.flow.flow_entry import FlowEntry
+from voltha.adapters.adtran_onu.omci.omci import OMCI
+
+OP = EntityOperations
+RC = ReasonCodes
+
+
+class ServiceRemovalFailure(Exception):
+    """
+    This error is raised by default when the flow-install fails
+    """
+
+
+class AdtnRemoveFlowTask(Task):
+    """
+    OpenOMCI MIB Flow Remove Task
+
+    Currently, the only service tech profiles expected by v2.0 will be for AT&T
+    residential data service and DT residential data service.
+    """
+    task_priority = Task.DEFAULT_PRIORITY + 10
+    default_tpid = 0x8100                           # TODO: Locate to a better location
+
+    name = "ADTRAN MIB Install Flow Task"
+
+    def __init__(self, omci_agent, handler, flow_entry):
+        """
+        Class initialization
+
+        :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+        :param handler: (AdtranOnuHandler) ONU Handler
+        :param flow_entry: (FlowEntry) Flow to install
+        """
+        super(AdtnRemoveFlowTask, self).__init__(AdtnRemoveFlowTask.name,
+                                                 omci_agent,
+                                                 handler.device_id,
+                                                 priority=AdtnRemoveFlowTask.task_priority,
+                                                 exclusive=False)
+        self._handler = handler
+        self._onu_device = omci_agent.get_device(handler.device_id)
+        self._local_deferred = None
+        self._flow_entry = flow_entry
+
+        # TODO: Cleanup below that is not needed
+        # self._vlan_tcis_1 = 0x900
+        # self._input_tpid = AdtnRemoveFlowTask.default_tpid
+        # self._output_tpid = AdtnRemoveFlowTask.default_tpid
+
+        is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+        # uni_port = flow_entry.in_port if is_upstream else flow_entry.out_port
+        pon_port = flow_entry.out_port if is_upstream else flow_entry.in_port
+
+        # self._uni = handler.uni_port(uni_port)
+        self._pon = handler.pon_port(pon_port)
+
+        self._vid = OMCI.DEFAULT_UNTAGGED_VLAN
+
+        # Entity IDs. IDs with values can probably be most anything for most ONUs,
+        #             IDs set to None are discovered/set
+        #
+        # TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
+        #
+        self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
+        self._mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
+
+        # Next to are specific
+        self._mac_bridge_service_profile_entity_id = handler.mac_bridge_service_profile_entity_id
+
+    def cancel_deferred(self):
+        super(AdtnRemoveFlowTask, 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 flow installation
+        """
+        super(AdtnRemoveFlowTask, self).start()
+        self._local_deferred = reactor.callLater(0, self.perform_flow_removal)
+
+    def stop(self):
+        """
+        Shutdown flow install task
+        """
+        self.log.debug('stopping')
+
+        self.cancel_deferred()
+        super(AdtnRemoveFlowTask, 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)
+        """
+        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(operation, 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 ServiceRemovalFailure(
+            '{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
+            .format(operation, status, error_mask, failed_mask, unsupported_mask))
+
+    @inlineCallbacks
+    def perform_flow_removal(self):
+        """
+        Send the commands to configure the flow
+        """
+        self.log.info('perform-flow-removall')
+
+        if self._handler.xpon_support:
+            self.deferred.callback('flow-removal-nop')  # xPON mode does not need this
+
+        def resources_available():
+            # TODO: Rework for non-xpon mode
+            return (len(self._handler.uni_ports) > 0 and
+                    len(self._pon.tconts) and
+                    len(self._pon.gem_ports))
+
+        if self._handler.enabled and resources_available():
+            omci = self._onu_device.omci_cc
+            try:
+                # TODO: make this a member of the onu gem port or the uni port
+                set_vlan_vid = self._flow_entry.set_vlan_vid
+
+                # # Delete bridge ani side vlan filter
+                # msg = VlanTaggingFilterDataFrame(self._mac_bridge_port_ani_entity_id)
+                # frame = msg.delete()
+                #
+                # results = yield omci.send(frame)
+                # self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
+                #
+                # # Re-Create bridge ani side vlan filter
+                # msg = VlanTaggingFilterDataFrame(
+                #         self._mac_bridge_port_ani_entity_id,  # Entity ID
+                #         vlan_tcis=[self._vlan_tcis_1],  # VLAN IDs
+                #         forward_operation=0x10
+                # )
+                # frame = msg.create()
+                # results = yield omci.send(frame)
+                # self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
+
+                # Update uni side extended vlan filter
+                attributes = dict(
+                        received_frame_vlan_tagging_operation_table=
+                        VlanTaggingOperation(
+                                filter_outer_priority=15,    # This entry is not a double-tag rule
+                                filter_outer_vid=4096,       # Do not filter on the outer VID value
+                                filter_outer_tpid_de=0,      # Do not filter on the outer TPID field
+
+                                filter_inner_priority=15,    # This is a no-tag rule, ignore all other VLAN tag filter fields
+                                filter_inner_vid=0x1000,     # Do not filter on the inner VID
+                                filter_inner_tpid_de=0,      # Do not filter on inner TPID field
+
+                                filter_ether_type=0,         # Do not filter on EtherType
+                                treatment_tags_to_remove=0,  # Remove 0 tags
+
+                                treatment_outer_priority=15,  # Do not add an outer tag
+                                treatment_outer_vid=0,        # n/a
+                                treatment_outer_tpid_de=0,    # n/a
+
+                                treatment_inner_priority=0,     # Add an inner tag and insert this value as the priority
+                                treatment_inner_vid=self._vid,  # use this value as the VID in the inner VLAN tag
+                                treatment_inner_tpid_de=4,      # set TPID
+                        )
+                )
+                msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+                        self._mac_bridge_service_profile_entity_id,  # Bridge Entity ID
+                        attributes=attributes  # See above
+                )
+                frame = msg.set()
+                results = yield omci.send(frame)
+                self.check_status_and_state(results,
+                                            'flow-set-ext-vlan-tagging-op-config-data-untagged')
+
+                self.deferred.callback('flow-remove-success')
+
+            except Exception as e:
+                # TODO: Better context info for this exception output...
+                self.log.exception('failed-to-remove-flow', e=e)
+                self.deferred.errback(failure.Failure(e))
+
+        else:
+            # TODO: Provide better error reason, what was missing...
+            e = ServiceRemovalFailure('Required resources are not available')
+            self.deferred.errback(failure.Failure(e))
diff --git a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
index e62cb6e..6dc510a 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
@@ -18,6 +18,7 @@
 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.adtran_onu.omci.omci import OMCI
 
 OP = EntityOperations
 RC = ReasonCodes
@@ -52,11 +53,8 @@
     residential data service and DT residential data service.
     """
     task_priority = Task.DEFAULT_PRIORITY + 10
-    default_tpid = 0x8100
-    default_gem_payload = 1518
-    BRDCM_DEFAULT_VLAN = 4091
-
-    name = "ADTRAN MIB Download Example Task"
+    default_tpid = 0x8100                       # TODO: Move to a better location
+    name = "ADTRAN Service Download Task"
 
     def __init__(self, omci_agent, handler):
         """
@@ -68,34 +66,36 @@
         super(AdtnServiceDownloadTask, self).__init__(AdtnServiceDownloadTask.name,
                                                       omci_agent,
                                                       handler.device_id,
-                                                      priority=AdtnServiceDownloadTask.task_priority)
+                                                      priority=AdtnServiceDownloadTask.task_priority,
+                                                      exclusive=True)
         self._handler = handler
         self._onu_device = omci_agent.get_device(handler.device_id)
         self._local_deferred = None
+        self._pon = handler.pon_port()
 
-        self._vlan_tcis_1 = 0x900
+        # self._vlan_tcis_1 = self._handler.vlan_tcis_1
         self._input_tpid = AdtnServiceDownloadTask.default_tpid
         self._output_tpid = AdtnServiceDownloadTask.default_tpid
 
         if self._handler.xpon_support:
             device = self._handler.adapter_agent.get_device(self.device_id)
-            self._cvid = device.vlan
+            self._vid = device.vlan
         else:
             # TODO: TCIS below is just a test, may need 0x900...as in the xPON mode
-            self._vlan_tcis_1 = AdtnServiceDownloadTask.BRDCM_DEFAULT_VLAN
-            self._cvid = AdtnServiceDownloadTask.BRDCM_DEFAULT_VLAN
+            # self._vlan_tcis_1 = OMCI.DEFAULT_UNTAGGED_VLAN
+            self._vid = OMCI.DEFAULT_UNTAGGED_VLAN
 
         # Entity IDs. IDs with values can probably be most anything for most ONUs,
         #             IDs set to None are discovered/set
         #
         # TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
         #
-        self._ieee_mapper_service_profile_entity_id = 0x100
+        self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
         self._gal_enet_profile_entity_id = 0x100
 
         # Next to are specific
         self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
-        self._vlan_config_entity_id = self._vlan_tcis_1
+        self._mac_bridge_service_profile_entity_id = self._handler.mac_bridge_service_profile_entity_id
 
     def cancel_deferred(self):
         super(AdtnServiceDownloadTask, self).cancel_deferred()
@@ -160,7 +160,6 @@
         have been defined.
         """
         self.log.info('perform-service-download')
-
         device = self._handler.adapter_agent.get_device(self.device_id)
 
         def resources_available():
@@ -168,12 +167,12 @@
             if self._handler.xpon_support:
                 return (device.vlan > 0 and
                         len(self._handler.uni_ports) > 0 and
-                        len(self._handler.pon_port.tconts) and
-                        len(self._handler.pon_port.gem_ports))
+                        len(self._pon.tconts) and
+                        len(self._pon.gem_ports))
             else:
                 return (len(self._handler.uni_ports) > 0 and
-                        len(self._handler.pon_port.tconts) and
-                        len(self._handler.pon_port.gem_ports))
+                        len(self._pon.tconts) and
+                        len(self._pon.gem_ports))
 
         if self._handler.enabled and resources_available():
             device.reason = 'Performing Service OMCI Download'
@@ -191,14 +190,12 @@
 
                 # If here, we are done
                 device = self._handler.adapter_agent.get_device(self.device_id)
-
                 device.reason = ''
                 self._handler.adapter_agent.update_device(device)
                 self.deferred.callback('service-download-success')
 
             except TimeoutError as e:
                 self.deferred.errback(failure.Failure(e))
-
         else:
             # TODO: Provide better error reason, what was missing...
             e = ServiceResourcesFailure('Required resources are not available')
@@ -221,7 +218,7 @@
             tcont_idents = self._onu_device.query_mib(Tcont.class_id)
             self.log.debug('tcont-idents', tcont_idents=tcont_idents)
 
-            for tcont in self._handler.pon_port.tconts.itervalues():
+            for tcont in self._pon.tconts.itervalues():
                 free_entity_id = next((k for k, v in tcont_idents.items()
                                        if isinstance(k, int) and
                                        v.get('attributes', {}).get('alloc_id', 0) == 0xFFFF), None)
@@ -258,7 +255,7 @@
             #              - GalEthernetProfile
             #
 
-            for gem_port in self._handler.pon_port.gem_ports.itervalues():
+            for gem_port in self._pon.gem_ports.itervalues():
                 tcont = gem_port.tcont
                 if tcont is None:
                     self.log.error('unknown-tcont-reference', gem_id=gem_port.gem_id)
@@ -278,7 +275,7 @@
             #            - Gem Interworking TPs are set here
             #
             # TODO: All p-bits currently go to the one and only GEMPORT ID for now
-            gem_ports = self._handler.pon_port.gem_ports
+            gem_ports = self._pon.gem_ports
             gem_entity_ids = [gem_port.entity_id for _, gem_port in gem_ports.items()] \
                 if len(gem_ports) else [OmciNullPointer]
 
@@ -305,7 +302,7 @@
             )
 
             frame = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._vlan_config_entity_id,
+                self._mac_bridge_service_profile_entity_id,
                 attributes=attributes
             ).create()
             results = yield omci_cc.send(frame)
@@ -324,7 +321,7 @@
                 downstream_mode=0,              # inverse of upstream
             )
             frame = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._vlan_config_entity_id,
+                self._mac_bridge_service_profile_entity_id,
                 attributes=attributes
             ).set()
             results = yield omci_cc.send(frame)
@@ -355,56 +352,21 @@
                     treatment_outer_tpid_de=0,    # n/a
 
                     treatment_inner_priority=0,      # Add an inner tag and insert this value as the priority
-                    treatment_inner_vid=self._cvid,  # use this value as the VID in the inner VLAN tag
+                    treatment_inner_vid=self._vid,   # use this value as the VID in the inner VLAN tag
                     treatment_inner_tpid_de=4,       # set TPID
                 )
             )
             frame = ExtendedVlanTaggingOperationConfigurationDataFrame(
-                self._vlan_config_entity_id,  # Entity ID
-                attributes=attributes         # See above
+                self._mac_bridge_service_profile_entity_id,  # Entity ID
+                attributes=attributes                        # See above
             ).set()
             results = yield omci_cc.send(frame)
             self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data-untagged')
 
-            ################################################################################
-            ################################################################################
-            ################################################################################
-            # BP: This is for AT&T RG's                #
-            #   TODO: CB: NOTE: TRY THIS ONCE OTHER SEQUENCES WORK
-            #
-            # Set AR - ExtendedVlanTaggingOperationConfigData
-            #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid
-            # results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(
-            #                                 0x900,  # Entity ID
-            #                                 8,      # Filter Inner Priority, do not filter on Inner Priority
-            #                                 0,    # Filter Inner VID, this will be 0 in CORD
-            #                                 0,      # Filter Inner TPID DE
-            #                                 1,      # Treatment tags, number of tags to remove
-            #                                 8,      # Treatment inner priority, copy Inner Priority
-            #                                 2)   # Treatment inner VID, this will be 2 in CORD
-            # Set AR - ExtendedVlanTaggingOperationConfigData
-            #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid
-            # results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(
-            #                                 0x200,  # Entity ID
-            #                                 8,      # Filter Inner Priority
-            #                                 0,      # Filter Inner VID
-            #                                 0,      # Filter Inner TPID DE
-            #                                 1,      # Treatment tags to remove
-            #                                 8,      # Treatment inner priority
-            #                                 cvid)   # Treatment inner VID
-            # Set AR - ExtendedVlanTaggingOperationConfigData
-            #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to untagged pkts - c-vid
-            # results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_untagged(
-            #                                0x100,   # Entity ID            BP: Oldvalue 0x202
-            #                                0x1000,  # Filter Inner VID     BP: Oldvalue 0x1000
-            #                                cvid)    # Treatment inner VID  BP: cvid
-            # success = results.fields['omci_message'].fields['success_code'] == 0
-            # error_mask = results.fields['omci_message'].fields['parameter_error_attributes_mask']
-
             ###############################################################################
 
         except TimeoutError as e:
-            self.log.warn('rx-timeout-2', frame=frame)
+            self.log.warn('rx-timeout-download', frame=hexlify(frame))
             raise
 
         except Exception as e:
@@ -440,7 +402,7 @@
                 self.check_status_and_state(results, 'set-pptp-ethernet-uni-lock-restore')
 
             except TimeoutError:
-                self.log.warn('rx-timeout', frame=frame)
+                self.log.warn('rx-timeout-unis', frame=hexlify(frame))
                 raise
 
             except Exception as e:
diff --git a/voltha/adapters/adtran_onu/omci/omci.py b/voltha/adapters/adtran_onu/omci/omci.py
index 9e1ba37..15244b8 100644
--- a/voltha/adapters/adtran_onu/omci/omci.py
+++ b/voltha/adapters/adtran_onu/omci/omci.py
@@ -11,7 +11,6 @@
 # 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 twisted.internet.defer import inlineCallbacks, returnValue, TimeoutError
 from twisted.internet import reactor
@@ -33,6 +32,8 @@
     """
     OpenOMCI Support
     """
+    DEFAULT_UNTAGGED_VLAN = 4091      # To be equivalent to BroadCom Defaults
+
     def __init__(self, handler, omci_agent):
         self.log = structlog.get_logger(device_id=handler.device_id)
         self._handler = handler
@@ -43,7 +44,6 @@
         self._resync_deferred = None    # For TCont/GEM use
         self._bridge_initialized = False
         self._in_sync_reached = False
-
         self._omcc_version = OMCCVersion.Unknown
         self._total_tcont_count = 0                    # From ANI-G ME
         self._qos_flexibility = 0                      # From ONT2_G ME
@@ -334,23 +334,26 @@
                 if self._mib_downloaded:
                     self._service_downloaded = True
                 else:
-                    # Now try the services
+                    # Now try the services (HSI, ...) specific download
                     self._mib_downloaded = True
-                    reactor.callLater(0, self.capabilities_handler)
+                    reactor.callLater(0, self.capabilities_handler, None, None)
 
                 self._mib_download_task = None
 
-            def failure(_reason):
+            def failure(reason):
+                self.log.error('mib-download-failure', reason=reason)
                 self._mib_download_task = None
-                # TODO: Handle failure, retry for now?
                 self._mib_download_deferred = reactor.callLater(_STARTUP_RETRY_WAIT,
-                                                                self.capabilities_handler)
+                                                                self.capabilities_handler,
+                                                                None, None)
             if not self._mib_downloaded:
-                self._mib_download_task = AdtnMibDownloadTask(self.omci_agent, self._handler)
+                self._mib_download_task = AdtnMibDownloadTask(self.omci_agent,
+                                                              self._handler)
             else:
-                self._mib_download_task = AdtnServiceDownloadTask(self.omci_agent, self._handler)
-
-            self._mib_download_deferred = self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
+                self._mib_download_task = AdtnServiceDownloadTask(self.omci_agent,
+                                                                  self._handler)
+            self._mib_download_deferred = \
+                self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
             self._mib_download_deferred.addCallbacks(success, failure)
 
     def onu_is_reachable(self, _topic, msg):
diff --git a/voltha/adapters/adtran_onu/pon_port.py b/voltha/adapters/adtran_onu/pon_port.py
index 49b2235..4962f4e 100644
--- a/voltha/adapters/adtran_onu/pon_port.py
+++ b/voltha/adapters/adtran_onu/pon_port.py
@@ -41,6 +41,11 @@
         self._gem_ports = {}                           # gem-id -> GemPort
         self._tconts = {}                              # alloc-id -> TCont
 
+        # OMCI resources
+        # TODO: These could be dynamically chosen (can be most any value)
+        self.hsi_8021p_mapper_entity_id = 0x100
+        self.hsi_mac_bridge_port_ani_entity_id = 0x100
+
     def __str__(self):
         return "PonPort"      # TODO: Encode current state
 
diff --git a/voltha/adapters/adtran_onu/uni_port.py b/voltha/adapters/adtran_onu/uni_port.py
index 0c53b2b..2259058 100644
--- a/voltha/adapters/adtran_onu/uni_port.py
+++ b/voltha/adapters/adtran_onu/uni_port.py
@@ -20,12 +20,11 @@
 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
+from omci.omci import OMCI
 
 
 class UniPort(object):
     """Wraps southbound-port(s) support for ONU"""
-    DEFAULT_UNTAGGED_VLAN = 4091        # TODO: BroadCom Default.  Need a better way to define this
-
     def __init__(self, handler, name, port_no, ofp_port_no, subscriber_vlan=None,
                  untagged_vlan=None):
         self.log = structlog.get_logger(device_id=handler.device_id,
@@ -44,6 +43,7 @@
 
         self._admin_state = AdminState.ENABLED
         self._oper_status = OperStatus.ACTIVE
+
         # TODO Add state, stats, alarm reference, ...
         pass
 
@@ -144,6 +144,12 @@
         """
         return self._subscriber_vlan
 
+    @subscriber_vlan.setter
+    def subscriber_vlan(self, value):
+        if value:
+            if self._subscriber_vlan is None or self._subscriber_vlan != value:
+                self._subscriber_vlan = value
+
     @property
     def logical_port_number(self):
         """
@@ -178,7 +184,7 @@
             # port number.  UNI-1,  UNI 1, and UNI 3-2-1 are the same
             port_no = int(venet_info['name'].replace(' ', '-').split('-')[-1:][0])
             subscriber_vlan = port_no
-            untagged_vlan = UniPort.DEFAULT_UNTAGGED_VLAN
+            untagged_vlan = OMCI.DEFAULT_UNTAGGED_VLAN
             try:
                 # Subscriber VLAN and Untagged vlan are comma separated
                 parts = venet_info['description'].split(',')