Dual-tag support for individual ONU gemport selection

Change-Id: I71b158c9a683a6b79df5a2b2719aff2ca7bfb1f3
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index 26c411b..8f6ed6c 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -627,13 +627,8 @@
             self.startup = port.start()
             results = yield self.startup
 
-        if reconciling:
-            start_downlinks = device.admin_state == AdminState.ENABLED
-        else:
-            start_downlinks = self.autoactivate
-
         for port in self.southbound_ports.itervalues():
-            self.startup = port.start() if start_downlinks else port.stop()
+            self.startup = port.start() if port.admin_state == AdminState.ENABLED else port.stop()
             results = yield self.startup
 
         returnValue(results)
diff --git a/voltha/adapters/adtran_olt/adtran_olt.py b/voltha/adapters/adtran_olt/adtran_olt.py
index e4f70a4..1b66359 100644
--- a/voltha/adapters/adtran_olt/adtran_olt.py
+++ b/voltha/adapters/adtran_olt/adtran_olt.py
@@ -50,8 +50,8 @@
         self.config = config
         self.descriptor = Adapter(
             id=self.name,
-            vendor='Adtran Inc.',
-            version='0.3',
+            vendor='Adtran, Inc.',
+            version='0.4',
             config=AdapterConfig(log_level=LogLevel.INFO)
         )
         log.debug('adtran_olt.__init__', adapter_agent=adapter_agent)
@@ -200,25 +200,73 @@
         return device
 
     def download_image(self, device, request):
+        """
+        This is called to request downloading a specified image into
+        the standby partition of a device based on a NBI call.
+        This call is expected to be non-blocking.
+        :param device: A Voltha.Device object.
+                       A Voltha.ImageDownload object.
+        :return: (Deferred) Shall be fired to acknowledge the download.
+        """
         log.info('image_download', device=device, request=request)
         raise NotImplementedError()
 
     def get_image_download_status(self, device, request):
+        """
+        This is called to inquire about a requested image download
+        status based on a NBI call.
+        The adapter is expected to update the DownloadImage DB object
+        with the query result
+        :param device: A Voltha.Device object.
+                       A Voltha.ImageDownload object.
+        :return: (Deferred) Shall be fired to acknowledge
+        """
         log.info('get_image_download', device=device, request=request)
         raise NotImplementedError()
 
     def cancel_image_download(self, device, request):
+        """
+        This is called to cancel a requested image download
+        based on a NBI call.  The admin state of the device will not
+        change after the download.
+        :param device: A Voltha.Device object.
+                       A Voltha.ImageDownload object.
+        :return: (Deferred) Shall be fired to acknowledge
+        """
         log.info('cancel_image_download', device=device)
         raise NotImplementedError()
 
     def activate_image_update(self, device, request):
-        log.info('activate_image_update', device=device, \
-                request=request)
+        """
+        This is called to activate a downloaded image from
+        a standby partition into active partition.
+        Depending on the device implementation, this call
+        may or may not cause device reboot.
+        If no reboot, then a reboot is required to make the
+        activated image running on device
+        This call is expected to be non-blocking.
+        :param device: A Voltha.Device object.
+                       A Voltha.ImageDownload object.
+        :return: (Deferred) OperationResponse object.
+        """
+        log.info('activate_image_update', device=device, request=request)
         raise NotImplementedError()
 
     def revert_image_update(self, device, request):
-        log.info('revert_image_update', device=device, \
-                request=request)
+        """
+        This is called to deactivate the specified image at
+        active partition, and revert to previous image at
+        standby partition.
+        Depending on the device implementation, this call
+        may or may not cause device reboot.
+        If no reboot, then a reboot is required to make the
+        previous image running on device
+        This call is expected to be non-blocking.
+        :param device: A Voltha.Device object.
+                       A Voltha.ImageDownload object.
+        :return: (Deferred) OperationResponse object.
+        """
+        log.info('revert_image_update', device=device, request=request)
         raise NotImplementedError()
 
     def self_test_device(self, device):
@@ -393,7 +441,7 @@
         """
         log.info('create-interface', data=data)
         handler = self.devices_handlers[device.id]
-        handler.create_interface(device, data)
+        handler.create_interface(data)
 
     def update_interface(self, device, data):
         """
@@ -401,7 +449,8 @@
         in the devices
         """
         log.info('update-interface', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.update_interface(data)
 
     def remove_interface(self, device, data):
         """
@@ -409,9 +458,10 @@
         in the devices
         """
         log.info('remove-interface', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.remove_interface(data)
 
-    def receive_onu_detect_state(self, device_id, state):
+    def receive_onu_detect_state(self, proxy_address, state):
         """
         Receive onu detect state in ONU adapter
         :param proxy_address: ONU device address
@@ -421,52 +471,142 @@
         raise NotImplementedError()
 
     def create_tcont(self, device, tcont_data, traffic_descriptor_data):
+        """
+        API to create tcont object in the devices
+        :param device: device id
+        :tcont_data: tcont data object
+        :traffic_descriptor_data: traffic descriptor data object
+        :return: None
+        """
         log.info('create-tcont', tcont_data=tcont_data,
                  traffic_descriptor_data=traffic_descriptor_data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.create_tcont(tcont_data, traffic_descriptor_data)
 
     def update_tcont(self, device, tcont_data, traffic_descriptor_data):
+        """
+        API to update tcont object in the devices
+        :param device: device id
+        :tcont_data: tcont data object
+        :traffic_descriptor_data: traffic descriptor data object
+        :return: None
+        """
         log.info('update-tcont', tcont_data=tcont_data,
                  traffic_descriptor_data=traffic_descriptor_data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.update_tcont(tcont_data, traffic_descriptor_data)
 
     def remove_tcont(self, device, tcont_data, traffic_descriptor_data):
+        """
+        API to delete tcont object in the devices
+        :param device: device id
+        :tcont_data: tcont data object
+        :traffic_descriptor_data: traffic descriptor data object
+        :return: None
+        """
         log.info('remove-tcont', tcont_data=tcont_data,
                  traffic_descriptor_data=traffic_descriptor_data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.remove_tcont(tcont_data, traffic_descriptor_data)
 
     def create_gemport(self, device, data):
+        """
+        API to create gemport object in the devices
+        :param device: device id
+        :data: gemport data object
+        :return: None
+        """
         log.info('create-gemport', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.create_gemport(data)
 
     def update_gemport(self, device, data):
+        """
+        API to update gemport object in the devices
+        :param device: device id
+        :data: gemport data object
+        :return: None
+        """
         log.info('update-gemport', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.update_gemport(data)
 
     def remove_gemport(self, device, data):
+        """
+        API to delete gemport object in the devices
+        :param device: device id
+        :data: gemport data object
+        :return: None
+        """
         log.info('remove-gemport', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.remove_gemport(data)
 
     def create_multicast_gemport(self, device, data):
+        """
+        API to create multicast gemport object in the devices
+        :param device: device id
+        :data: multicast gemport data object
+        :return: None
+        """
         log.info('create-mcast-gemport', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.create_multicast_gemport(data)
 
     def update_multicast_gemport(self, device, data):
+        """
+        API to update  multicast gemport object in the devices
+        :param device: device id
+        :data: multicast gemport data object
+        :return: None
+        """
         log.info('update-mcast-gemport', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.update_multicast_gemport(data)
 
     def remove_multicast_gemport(self, device, data):
+        """
+        API to delete multicast gemport object in the devices
+        :param device: device id
+        :data: multicast gemport data object
+        :return: None
+        """
         log.info('remove-mcast-gemport', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.remove_multicast_gemport(data)
 
     def create_multicast_distribution_set(self, device, data):
+        """
+        API to create multicast distribution rule to specify
+        the multicast VLANs that ride on the multicast gemport
+        :param device: device id
+        :data: multicast distribution data object
+        :return: None
+        """
         log.info('create-mcast-distribution-set', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.create_multicast_distribution_set(data)
 
     def update_multicast_distribution_set(self, device, data):
+        """
+        API to update multicast distribution rule to specify
+        the multicast VLANs that ride on the multicast gemport
+        :param device: device id
+        :data: multicast distribution data object
+        :return: None
+        """
         log.info('update-mcast-distribution-set', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.create_multicast_distribution_set(data)
 
     def remove_multicast_distribution_set(self, device, data):
+        """
+        API to delete multicast distribution rule to specify
+        the multicast VLANs that ride on the multicast gemport
+        :param device: device id
+        :data: multicast distribution data object
+        :return: None
+        """
         log.info('remove-mcast-distribution-set', data=data)
-        raise NotImplementedError()
+        handler = self.devices_handlers[device.id]
+        handler.create_multicast_distribution_set(data)
diff --git a/voltha/adapters/adtran_olt/adtran_olt_handler.py b/voltha/adapters/adtran_olt/adtran_olt_handler.py
index fd90bf2..a4cba1c 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -19,6 +19,8 @@
 from twisted.internet.defer import returnValue, inlineCallbacks, succeed
 
 from adtran_device_handler import AdtranDeviceHandler
+from tcont import TCont, TrafficDescriptor, BestEffort
+from gem_port import GemPort
 from codec.olt_state import OltState
 from flow.flow_entry import FlowEntry
 from net.adtran_zmq import AdtranZmqClient
@@ -71,13 +73,17 @@
 
         # xPON config dictionaries
 
-        self._channel_groups = {}         #  Name -> dict
-        self._channel_partitions = {}     #  Name -> dict
-        self._channel_pairs = {}          #  Name -> dict
-        self._channel_terminations = {}   #  Name -> dict
-        self._v_ont_anis = {}             #  Name -> dict
-        self._ont_anis = {}               #  Name -> dict
-        self._v_enets = {}                #  Name -> dict
+        self._channel_groups = {}         # Name -> dict
+        self._channel_partitions = {}     # Name -> dict
+        self._channel_pairs = {}          # Name -> dict
+        self._channel_terminations = {}   # Name -> dict
+        self._v_ont_anis = {}             # Name -> dict
+        self._ont_anis = {}               # Name -> dict
+        self._v_enets = {}                # Name -> dict
+        self._tconts = {}                 # Name -> dict
+        self._traffic_descriptors = {}    # Name -> dict
+        self._gem_ports = {}              # Name -> dict
+        self._cached_xpon_pon_info = {}   # PON-id -> dict
 
     def __del__(self):
         # OLT Specific things here.
@@ -242,7 +248,7 @@
                                                     admin_state=admin_state)
 
             # TODO: For now, limit number of PON ports to make debugging easier
-            if self.autoactivate and len(self.southbound_ports) >= self.max_pon_ports:
+            if len(self.southbound_ports) >= self.max_pon_ports:
                 break
 
         self.num_southbound_ports = len(self.southbound_ports)
@@ -446,7 +452,7 @@
 
         # Now drop all flows from this device that were not in this bulk update
         try:
-            FlowEntry.drop_missing_flows(device.id, valid_flows)
+            yield FlowEntry.drop_missing_flows(device.id, valid_flows)
 
         except Exception as e:
             self.log.exception('bulk-flow-update-remove', e=e)
@@ -533,42 +539,87 @@
         :param pon_id: (int) PON Identifier
         :return: (dict) reduced xPON information for the specific PON port
         """
-        terminations = {key: val for key, val in self._channel_terminations.iteritems()
-                        if val[pon_id_type] == pon_id}
+        if pon_id not in self._cached_xpon_pon_info:
 
-        pair_names = set([term['channel-pair'] for term in terminations.itervalues()])
+            terminations = {key: val for key, val in self._channel_terminations.iteritems()
+                            if val[pon_id_type] == pon_id}
 
-        pairs = {key: val for key, val in self._channel_pairs.iteritems()
-                 if key in pair_names}
+            pair_names = set([term['channel-pair'] for term in terminations.itervalues()])
+            pairs = {key: val for key, val in self._channel_pairs.iteritems()
+                     if key in pair_names}
 
-        partition_names = set([pair['channel-partition'] for pair in pairs.itervalues()])
+            partition_names = set([pair['channel-partition'] for pair in pairs.itervalues()])
+            partitions = {key: val for key, val in self._channel_partitions.iteritems()
+                          if key in partition_names}
 
-        partitions = {key: val for key, val in self._channel_partitions.iteritems()
-                      if key in partition_names}
+            v_ont_anis = {key: val for key, val in self._v_ont_anis.iteritems()
+                          if val['preferred-channel-pair'] in pair_names}
+            v_ont_ani_names = set(v_ont_anis.keys())
 
-        v_ont_anis = {key: val for key, val in self._v_ont_anis.iteritems()
-                      if val['preferred-channel-pair'] in pair_names}
+            group_names = set(pair['channel-group'] for pair in pairs.itervalues())
+            groups = {key: val for key, val in self._channel_groups.iteritems()
+                      if key in group_names}
 
-        return {
-            'channel-terminations': terminations,
-            'channel-pairs': pairs,
-            'channel-partitions': partitions,
-            'v_ont_anis': v_ont_anis
-        }
+            venets = {key: val for key, val in self._v_enets.iteritems()
+                      if val['v-ont-ani'] in v_ont_ani_names}
 
-    def create_interface(self, device, data):
+            tconts = {key: val for key, val in self._tconts.iteritems()
+                      if val.vont_ani in v_ont_ani_names}
+            tcont_names = set(tconts.keys())
+
+            gem_ports = {key: val for key, val in self._gem_ports.iteritems()
+                         if val.tconf_ref in tcont_names}
+
+            self._cached_xpon_pon_info[pon_id] = {
+                'channel-terminations': terminations,
+                'channel-pairs': pairs,
+                'channel-partitions': partitions,
+                'channel-groups': groups,
+                'v-ont-anis': v_ont_anis,
+                'v-enets': venets,
+                'tconts': tconts,
+                'gem-ports': gem_ports
+            }
+        return self._cached_xpon_pon_info[pon_id]
+
+    def _get_xpon_collection(self, data):
+        if isinstance(data, ChannelgroupConfig):
+            return self._channel_groups
+        elif isinstance(data, ChannelpartitionConfig):
+            return self._channel_partitions
+        elif isinstance(data, ChannelpairConfig):
+            return self._channel_pairs
+        elif isinstance(data, ChannelterminationConfig):
+            return self._channel_terminations
+        elif isinstance(data, OntaniConfig):
+            return self._ont_anis
+        elif isinstance(data, VOntaniConfig):
+            return self._v_ont_anis
+        elif isinstance(data, VEnetConfig):
+            return self._v_enets
+        return None
+
+    def create_interface(self, data):
         """
         Create XPON interfaces
-        :param device: (Device)
-        :param data: (ChannelgroupConfig) Channel Group configuration
+        :param data: (xpon config info)
         """
         name = data.name
         interface = data.interface
         inst_data = data.data
 
+        items = self._get_xpon_collection(data)
+
+        if items is not None and name not in items:
+            self._cached_xpon_pon_info = {}     # Clear cached data
+
         if isinstance(data, ChannelgroupConfig):
             self.log.debug('create_interface-channel-group', interface=interface, data=inst_data)
-            self._channel_groups[name] = {
+
+            if name in items:
+                raise KeyError("Channel group '{}' already exists".format(name))
+
+            items[name] = {
                 'name': name,
                 'enabled': interface.enabled,
                 'system-id': inst_data.system_id,
@@ -578,6 +629,9 @@
         elif isinstance(data, ChannelpartitionConfig):
             self.log.debug('create_interface-channel-partition', interface=interface, data=inst_data)
 
+            if name in items:
+                raise KeyError("Channel partition '{}' already exists".format(name))
+
             def _auth_method_enum_to_string(value):
                 from voltha.protos.bbf_fiber_types_pb2 import SERIAL_NUMBER, LOID, \
                     REGISTRATION_ID, OMCI, DOT1X
@@ -589,7 +643,7 @@
                     DOT1X: 'don1x'
                 }.get(value, 'unknown')
 
-            self._channel_partitions[name] = {
+            items[name] = {
                 'name': name,
                 'enabled': interface.enabled,
                 'authentication-method': _auth_method_enum_to_string(inst_data.authentication_method),
@@ -601,7 +655,11 @@
 
         elif isinstance(data, ChannelpairConfig):
             self.log.debug('create_interface-channel-pair', interface=interface, data=inst_data)
-            self._channel_pairs[name] = {
+
+            if name in items:
+                raise KeyError("Channel pair '{}' already exists".format(name))
+
+            items[name] = {
                 'name': name,
                 'enabled': interface.enabled,
                 'channel-group': inst_data.channelgroup_ref,
@@ -611,7 +669,11 @@
 
         elif isinstance(data, ChannelterminationConfig):
             self.log.debug('create_interface-channel-termination', interface=interface, data=inst_data)
-            self._channel_terminations[name] = {
+
+            if name in items:
+                raise KeyError("Channel termination '{}' already exists".format(name))
+
+            items[name] = {
                 'name': name,
                 'enabled': interface.enabled,
                 'xgs-ponid': inst_data.xgs_ponid,
@@ -623,7 +685,11 @@
 
         elif isinstance(data, OntaniConfig):
             self.log.debug('create_interface-ont-ani', interface=interface, data=inst_data)
-            self._ont_anis[name] = {
+
+            if name in items:
+                raise KeyError("ONT ANI '{}' already exists".format(name))
+
+            items[name] = {
                 'name': name,
                 'enabled': interface.enabled,
                 'upstream-fec': inst_data.upstream_fec_indicator,
@@ -632,7 +698,11 @@
 
         elif isinstance(data, VOntaniConfig):
             self.log.debug('create_interface-v-ont-ani', interface=interface, data=inst_data)
-            self._v_ont_anis[name] = {
+
+            if name in items:
+                raise KeyError("vONT ANI '{}' already exists".format(name))
+
+            items[name] = {
                 'name': name,
                 'enabled': interface.enabled,
                 'onu-id': inst_data.onu_id,
@@ -644,7 +714,11 @@
 
         elif isinstance(data, VEnetConfig):
             self.log.debug('create_interface-v-enet', interface=interface, data=inst_data)
-            self._v_enets[name] = {
+
+            if name in items:
+                raise KeyError("vENET '{}' already exists".format(name))
+
+            items[name] = {
                 'name': name,
                 'enabled': interface.enabled,
                 'v-ont-ani': inst_data.v_ontani_ref
@@ -653,6 +727,46 @@
         else:
             raise NotImplementedError('Unknown data type')
 
+    def update_interface(self, data):
+        """
+        Update XPON interfaces
+        :param data: (xpon config info)
+        """
+        name = data.name
+        interface = data.interface
+        inst_data = data.data
+
+        items = self._get_xpon_collection(data)
+
+        if items is None:
+            raise ValueError('Unknown data type: {}'.format(type(data)))
+
+        if name not in items:
+            raise KeyError("'{}' not found. Type: {}".format(name, type(data)))
+
+        self._cached_xpon_pon_info = {}     # Clear cached data
+        raise NotImplementedError('TODO: not yet supported')
+
+    def delete_interface(self, data):
+        """
+        Deleete XPON interfaces
+        :param data: (xpon config info)
+        """
+        name = data.name
+        interface = data.interface
+        inst_data = data.data
+        self._cached_xpon_pon_info = {}     # Clear cached data
+
+        items = self._get_xpon_collection(data)
+        item = items.get(name)
+
+        if item in items:
+            del items[name]
+            self._cached_xpon_pon_info = {}     # Clear cached data
+
+            pass    # TODO Do something....
+            raise NotImplementedError('TODO: not yet supported')
+
     def on_channel_termination_config(self, name, operation, pon_type='xgs-ponid'):
         supported_operations = ['create']
 
@@ -692,11 +806,187 @@
             pon_port.xpon_name = name
             pon_port.discovery_tick = polling_period
             pon_port.authentication_method = authentication_method
-            # pon_port.deployment_range = deployment_range
-            # pon_port.fec_enable = downstream_fec
-            # pon_port.mcast_aes = mcast_aes
+            # TODO: pon_port.deployment_range = deployment_range
+            # TODO: pon_port.fec_enable = downstream_fec
+            # TODO: pon_port.mcast_aes = mcast_aes
 
-            if enabled:
-                pon_port.start()
-            else:
-                pon_port.stop()
+            pon_port.admin_state = AdminState.ENABLED if enabled else AdminState.DISABLED
+
+    def create_tcont(self, tcont_data, traffic_descriptor_data):
+        """
+        Create TCONT information
+        :param tcont_data:
+        :param traffic_descriptor_data:
+        """
+        traffic_descriptor = TrafficDescriptor.create(traffic_descriptor_data)
+        tcont = TCont.create(tcont_data, traffic_descriptor)
+
+        if tcont.name in self._tconts:
+            raise KeyError("TCONT '{}' already exists".format(tcont.name))
+
+        if traffic_descriptor.name in self._traffic_descriptors:
+            raise KeyError("Traffic Descriptor '{}' already exists".format(traffic_descriptor.name))
+
+        self._cached_xpon_pon_info = {}     # Clear cached data
+
+        self._tconts[tcont.name] = tcont
+        self._traffic_descriptors[traffic_descriptor.name] = traffic_descriptor
+
+    def update_tcont(self, tcont_data, traffic_descriptor_data):
+        """
+        Update TCONT information
+        :param tcont_data:
+        :param traffic_descriptor_data:
+        """
+        if tcont_data.name not in self._tconts:
+            raise KeyError("TCONT '{}' does not exists".format(tcont_data.name))
+
+        if traffic_descriptor_data.name not in self._traffic_descriptors:
+            raise KeyError("Traffic Descriptor '{}' does not exists".
+                           format(traffic_descriptor_data.name))
+
+        self._cached_xpon_pon_info = {}     # Clear cached data
+
+        traffic_descriptor = TrafficDescriptor.create(traffic_descriptor_data)
+        tcont = TCont.create(tcont_data, traffic_descriptor)
+        #
+        pass
+        raise NotImplementedError('TODO: Not yet supported')
+
+    def remove_tcont(self, tcont_data, traffic_descriptor_data):
+        """
+        Remove TCONT information
+        :param tcont_data:
+        :param traffic_descriptor_data:
+        """
+        tcont = self._tconts.get(tcont_data.name)
+        traffic_descriptor = self._traffic_descriptors.get(traffic_descriptor_data.name)
+
+        if traffic_descriptor is not None:
+            del self._traffic_descriptors[traffic_descriptor_data.name]
+
+            self._cached_xpon_pon_info = {}     # Clear cached data
+            pass         # Perform any needed operations
+            # raise NotImplementedError('TODO: Not yet supported')
+
+        if tcont is not None:
+            del self._tconts[tcont_data.name]
+
+            self._cached_xpon_pon_info = {}     # Clear cached data
+            pass         # Perform any needed operations
+            raise NotImplementedError('TODO: Not yet supported')
+
+    def create_gemport(self, data):
+        """
+        Create GEM Port
+        :param data:
+        """
+        gem_port = GemPort.create(data)
+
+        if gem_port.name in self._gem_ports:
+            raise KeyError("GEM Port '{}' already exists".format(gem_port.name))
+
+        self._cached_xpon_pon_info = {}  # Clear cached data
+        self._gem_ports[gem_port.name] = gem_port
+
+        # TODO: On GEM Port changes, may need to add ONU Flow(s)
+
+    def update_gemport(self, data):
+        """
+        Update GEM Port
+        :param data:
+        """
+        if data.name not in self._gem_ports:
+            raise KeyError("GEM Port '{}' does not exists".format(data.name))
+
+        self._cached_xpon_pon_info = {}  # Clear cached data
+        gem_port = GemPort.create(data)
+        #
+        # TODO: On GEM Port changes, may need to add/delete/modify ONU Flow(s)
+        pass
+        raise NotImplementedError('TODO: Not yet supported')
+
+    def delete_gemport(self, data):
+        """
+        Delete GEM Port
+        :param data:
+        """
+        gem_port = self._gem_ports.get(data.name)
+
+        if gem_port is not None:
+            del self._gem_ports[data.name]
+
+            self._cached_xpon_pon_info = {}     # Clear cached data
+            #
+            # TODO: On GEM Port changes, may need to delete ONU Flow(s)
+            pass         # Perform any needed operations
+            raise NotImplementedError('TODO: Not yet supported')
+
+    def create_multicast_gemport(self, data):
+        """
+        API to create multicast gemport object in the devices
+        :data: multicast gemport data object
+        :return: None
+        """
+        #
+        #
+        #
+        raise NotImplementedError('TODO: Not yet supported')
+
+    def update_multicast_gemport(self, data):
+        """
+        API to update  multicast gemport object in the devices
+        :data: multicast gemport data object
+        :return: None
+        """
+        #
+        #
+        #
+        raise NotImplementedError('TODO: Not yet supported')
+
+    def remove_multicast_gemport(self, data):
+        """
+        API to delete multicast gemport object in the devices
+        :data: multicast gemport data object
+        :return: None
+        """
+        #
+        #
+        #
+        raise NotImplementedError('TODO: Not yet supported')
+
+    def create_multicast_distribution_set(self, data):
+        """
+        API to create multicast distribution rule to specify
+        the multicast VLANs that ride on the multicast gemport
+        :data: multicast distribution data object
+        :return: None
+        """
+        #
+        #
+        #
+        raise NotImplementedError('TODO: Not yet supported')
+
+    def update_multicast_distribution_set(self, data):
+        """
+        API to update multicast distribution rule to specify
+        the multicast VLANs that ride on the multicast gemport
+        :data: multicast distribution data object
+        :return: None
+        """
+        #
+        #
+        #
+        raise NotImplementedError('TODO: Not yet supported')
+
+    def remove_multicast_distribution_set(self, data):
+        """
+        API to delete multicast distribution rule to specify
+        the multicast VLANs that ride on the multicast gemport
+        :data: multicast distribution data object
+        :return: None
+        """
+        #
+        #
+        #
+        raise NotImplementedError('TODO: Not yet supported')
diff --git a/voltha/adapters/adtran_olt/flow/evc.py b/voltha/adapters/adtran_olt/flow/evc.py
index acea0ee..ee31788 100644
--- a/voltha/adapters/adtran_olt/flow/evc.py
+++ b/voltha/adapters/adtran_olt/flow/evc.py
@@ -194,7 +194,7 @@
 
     def cancel_defers(self):
         d, self._install_deferred = self._install_deferred, None
-        if d is not None:
+        if d is not None and not d.called:
             try:
                 d.cancel()
             except:
diff --git a/voltha/adapters/adtran_olt/flow/evc_map.py b/voltha/adapters/adtran_olt/flow/evc_map.py
index e3a2753..6f54e57 100644
--- a/voltha/adapters/adtran_olt/flow/evc_map.py
+++ b/voltha/adapters/adtran_olt/flow/evc_map.py
@@ -55,10 +55,20 @@
         EXPLICIT_PRIORITY = 1
         DEFAULT = INHERIT_PRIORITY
 
+        @staticmethod
+        def xml(value):
+            if value is None:
+                value = EVCMap.PriorityOption.DEFAULT
+            if value == EVCMap.PriorityOption.INHERIT_PRIORITY:
+                return '<inherit-pri/>'
+            elif value == EVCMap.PriorityOption.EXPLICIT_PRIORITY:
+                return '<explicit-pri/>'
+            raise ValueError('Invalid PriorityOption enumeration')
+
     def __init__(self, flow, evc, is_ingress_map):
         self._flow = flow
         self._evc = evc
-        self._gem_ids = None
+        self._gem_ids_and_vid = None
         self._is_ingress_map = is_ingress_map
         self._installed = False
         self._status_message = None
@@ -66,18 +76,19 @@
         self._name = None
         self._enabled = True
         self._uni_port = None
-        self._evc_connection = EVCMap.EvcConnection.NO_EVC_CONNECTION
+        self._evc_connection = EVCMap.EvcConnection.DEFAULT
         self._evc_name = None
+        self._is_pon_port = None
 
-        self._men_priority = EVCMap.PriorityOption.INHERIT_PRIORITY
+        self._men_priority = EVCMap.PriorityOption.DEFAULT
         self._men_pri = 0  # If Explicit Priority
 
         self._c_tag = None
-        self._men_ctag_priority = EVCMap.PriorityOption.INHERIT_PRIORITY
+        self._men_ctag_priority = EVCMap.PriorityOption.DEFAULT
         self._men_ctag_pri = 0  # If Explicit Priority
 
         self._match_ce_vlan_id = None
-        self._match_untagged = True
+        self._match_untagged = False
         self._match_destination_mac_address = None
         self._match_l2cp = False
         self._match_broadcast = False
@@ -167,12 +178,12 @@
                 else:
                     xml += EVCMap.EvcConnection.xml(self._evc_connection)
 
-                # if self._match_untagged:
-                #    xml += '<match-untagged>True</match-untagged>'
-                if self._c_tag is not None:
-                    xml += '<ctag>{}</ctag>'.format(self._c_tag)
-
-                # TODO: The following is not yet supported
+                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
                 #
@@ -192,11 +203,21 @@
             def _ingress_xml():
                 from ..onu import Onu
                 xml = '<evc-maps xmlns="http://www.adtran.com/ns/yang/adtran-evc-maps">'
-                for onu_id, gem_ids in self._gem_ids.iteritems():
-                    for gem_id in gem_ids:
+                for onu_id, gem_ids_and_vid in self._gem_ids_and_vid.iteritems():
+                    first_gem_id = True
+                    vid = gem_ids_and_vid[1]
+
+                    for gem_id in gem_ids_and_vid[0]:
                         xml += '<evc-map>'
                         xml += '<name>{}.{}.{}</name>'.format(self.name, onu_id, gem_id)
                         xml += '<ce-vlan-id>{}</ce-vlan-id>'.format(Onu.gem_id_to_gvid(gem_id))
+
+                        if first_gem_id and vid is not None:
+                            first_gem_id = False
+                            xml += '<network-ingress-filter>'
+                            xml += '<men-ctag>{}</men-ctag>'.format(vid)    # Added in august 2017 model
+                            xml += '</network-ingress-filter>'
+
                         xml += _common_xml()
                         xml += '</evc-map>'
                 xml += '</evc-maps>'
@@ -219,6 +240,9 @@
                 self._installed = results.ok
                 self.status = '' if results.ok else results.error
 
+                if self._pon_port is not None:
+                    self._pon_port.add_pon_evc_map(self)
+
             except Exception as e:
                 log.exception('install', name=self.name, e=e)
                 raise
@@ -235,8 +259,8 @@
             xml = '<evc-maps xmlns="http://www.adtran.com/ns/yang/adtran-evc-maps"' + \
                   ' xc:operation = "delete">'
 
-            for onu_id, gem_ids in self._gem_ids.iteritems():
-                for gem_id in gem_ids:
+            for onu_id, gem_ids_and_vid in self._gem_ids_and_vid.iteritems():
+                for gem_id in gem_ids_and_vid[0]:
                     xml += '<evc-map>'
                     xml += '<name>{}.{}.{}</name>'.format(self.name, onu_id, gem_id)
                     xml += '</evc-map>'
@@ -255,6 +279,9 @@
         def _failure(results):
             log.error('remove-failed', results=results)
 
+        if self._pon_port is not None:
+            self._pon_port.remove_pon_evc_map(self)
+
         # TODO: create generator of XML once we have MANY to install at once
         map_xml = _ingress_xml() if self._is_ingress_map else _egress_xml()
         d = self._flow.handler.netconf_client.edit_config(map_xml, lock_timeout=30)
@@ -279,6 +306,50 @@
         self._evc = None
         returnValue('Done')
 
+    def add_onu(self, onu):
+        """
+        Add an ONU to a pon-wide EVC Map
+
+        :param onu: (Onu) ONU to add
+        :return: (defeered)
+        """
+        if self._pon_port is not None:
+            gem_ids = onu.gem_ids(True)
+            vid = onu.onu_vid
+            pass    # TODO: Implement this
+
+    def remove_onu(self, onu):
+        """
+        Remove an ONU to a pon-wide EVC Map
+
+        :param onu: (Onu) ONU to add
+        :return: (defeered)
+        """
+        if self._pon_port is not None:
+            gem_ids = onu.gem_ids(True)
+            vid = onu.onu_vid
+            pass    # TODO: Implement this
+
+    def add_gem_id(self, onu, gem_id):
+        """
+        Add a GEM ID to and existing EVC_MAP
+
+        :param onu: (Onu) ONU
+        :param gem_id: (Int) GEM ID
+        :return: (defeered)
+        """
+        pass    # TODO: Implement this
+
+    def remove_gem_id(self, onu, gem_id):
+        """
+        Remove a GEM ID from and existing EVC_MAP
+
+        :param onu: (Onu) ONU
+        :param gem_id: (Int) GEM ID
+        :return: (defeered)
+        """
+        pass    # TODO: Implement this
+
     def _decode(self):
         from evc import EVC
         from flow_entry import FlowEntry
@@ -298,7 +369,7 @@
         is_uni = flow.handler.is_uni_port(flow.in_port)
 
         if is_pon or is_uni:
-            self._uni_port = self._flow.handler.get_port_name(flow.in_port)
+            self._uni_port = flow.handler.get_port_name(flow.in_port)
             self._evc.ce_vlan_preservation = False
         else:
             self._status_message = 'EVC-MAPS without UNI or PON ports are not supported'
@@ -323,15 +394,16 @@
             pon_port = flow.handler.get_southbound_port(flow.in_port)
 
             if pon_port is not None:
-                self._gem_ids = pon_port.gem_ids(self._flow.onu_vid, self._needs_acl_support)
+                if flow.onu_vid is None:
+                    self._pon_port = pon_port   # EVC Map is for all ONUs on port
+
+                self._gem_ids_and_vid = pon_port.gem_ids(flow.onu_vid, self._needs_acl_support)
+
                 # TODO: Only EAPOL ACL support for the first demo
                 if self._needs_acl_support and self._eth_type != FlowEntry.EtherType.EAPOL.value:
-                    self._gem_ids = dict()
+                    self._gem_ids_and_vid = dict()
 
-        # if flow.vlan_id is None and flow.inner_vid is None:
-        #     self._match_untagged = True
-        # else:
-        #     self._match_untagged = False
+        # self._match_untagged = flow.vlan_id is None and flow.inner_vid is None
         self._c_tag = flow.inner_vid
 
         # If a push of a single VLAN is present with a POP of the VLAN in the EVC's
diff --git a/voltha/adapters/adtran_olt/flow/flow_entry.py b/voltha/adapters/adtran_olt/flow/flow_entry.py
index 0cf59ba..b5bb36b 100644
--- a/voltha/adapters/adtran_olt/flow/flow_entry.py
+++ b/voltha/adapters/adtran_olt/flow/flow_entry.py
@@ -420,7 +420,7 @@
 
         self.evc_map = None
         self.evc = None
-        returnValue(succeed('Done'))
+        returnValue('Done')
 
     ######################################################
     # Bulk operations
diff --git a/voltha/adapters/adtran_olt/gem_port.py b/voltha/adapters/adtran_olt/gem_port.py
index 292890c..0be0d35 100644
--- a/voltha/adapters/adtran_olt/gem_port.py
+++ b/voltha/adapters/adtran_olt/gem_port.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import structlog
+from voltha.protos.bbf_fiber_gemport_body_pb2 import GemportsConfigData
 
 log = structlog.get_logger()
 
@@ -25,11 +26,19 @@
                  encryption=False,
                  omci_transport=False,
                  multicast=False,
+                 tcont_ref=None,
+                 ident=None,
+                 traffic_class=None,
+                 intf_ref=None,
                  exception=False,        # TODO: Debug only, remove in production
                  name=None):
         self.name = name
         self.gem_id = gem_id
-        self.alloc_id = alloc_id
+        self._alloc_id = alloc_id
+        self.tconf_ref = tcont_ref
+        self.intf_ref = intf_ref
+        self.traffic_class = traffic_class
+        self.id = ident
         self.encryption = encryption
         self.omci_transport = omci_transport
         self.multicast = multicast
@@ -40,6 +49,27 @@
                                                               self.alloc_id,
                                                               self.gem_id)
 
+    @staticmethod
+    def create(data):
+        assert isinstance(data, GemportsConfigData)
+
+        return GemPort(data.gemport_id, None,
+                       encryption=data.aes_indicator,
+                       tcont_ref=data.tcont_ref,
+                       ident=data.id,
+                       name=data.name,
+                       traffic_class=data.traffic_class,
+                       intf_ref=data.itf_ref)            # v_enet
+
+    @property
+    def alloc_id(self):
+        if self._alloc_id is None:
+            #
+            # TODO: Resolve this (needs to be OLT handler)
+            #
+            pass
+        return self._alloc_id
+
     def to_dict(self):
         return {
             'port-id': self.gem_id,
diff --git a/voltha/adapters/adtran_olt/onu.py b/voltha/adapters/adtran_olt/onu.py
index 35d60ec..bff3682 100644
--- a/voltha/adapters/adtran_olt/onu.py
+++ b/voltha/adapters/adtran_olt/onu.py
@@ -28,7 +28,7 @@
     'PMC?': 'pmcs_onu',       # TODO: Get actual VSSN for this vendor
     'PSMO': 'ponsim_onu',
     'SIM?': 'simulated_onu',  # TODO: Get actual VSSN for this vendor
-    'TBT?': 'tibit_onu',      # TODO: Get actual VSSN for this vendor
+    'TBIT': 'tibit_onu',
 }
 
 
@@ -64,6 +64,7 @@
         self._pon = onu_info['pon']
         self._name = '{}@{}'.format(self._pon.name, self._onu_id)
         self._xpon_name = onu_info['xpon-name']
+        # TODO: Change to OrderedDict sorted by ascending gem-id
         self._gem_ports = {}                           # gem-id -> GemPort
         self._tconts = {}                              # alloc-id -> TCont
         self._onu_vid = onu_info['onu-vid']
diff --git a/voltha/adapters/adtran_olt/pon_port.py b/voltha/adapters/adtran_olt/pon_port.py
index 47bf18f..1ab4a7b 100644
--- a/voltha/adapters/adtran_olt/pon_port.py
+++ b/voltha/adapters/adtran_olt/pon_port.py
@@ -66,6 +66,7 @@
         self._onus = {}         # serial_number-base64 -> ONU  (allowed list)
         self._onu_by_id = {}    # onu-id -> ONU
         self._next_onu_id = Onu.MIN_ONU_ID
+        self._pon_evc_map = {}  # evc-map name -> EVC Map
 
         self._admin_state = AdminState.DISABLED
         self._oper_status = OperStatus.DISCOVERED
@@ -136,6 +137,19 @@
         return self._state
 
     @property
+    def admin_state(self):
+        return self._admin_state
+
+    @admin_state.setter
+    def admin_state(self, value):
+        if self._admin_state != value:
+            self._admin_state = value
+            if self._admin_state == AdminState.ENABLED:
+                self.start()
+            else:
+                self.stop()
+
+    @property
     def adapter_agent(self):
         return self.olt.adapter_agent
 
@@ -151,7 +165,7 @@
         if self.discovery_tick != value:
             self._discovery_tick = value / 10
 
-            if self._discovery_deferred is not None:
+            if self._discovery_deferred is not None and not self._discovery_deferred.called:
                 self._discovery_deferred.cancel()
                 self._discovery_deferred = None
 
@@ -430,12 +444,12 @@
         :param onu_vid: (int) ONU VLAN ID if customer ONU specific. None if for all ONUs
                               on PON
         :param exception_gems: (boolean) Select from special purpose ACL GEM-Portas
-        :return: (dict) key -> onu-id, value -> frozenset of GEM Port IDs
+        :return: (dict) key -> onu-id, value -> tuple(frozenset of GEM Port IDs, onu_vid)
         """
         gem_ids = {}
         for onu_id, onu in self._onu_by_id.iteritems():
             if onu_vid is None or onu_vid == onu.onu_vid:
-                gem_ids[onu_id] = onu.gem_ids(exception_gems)
+                gem_ids[onu_id] = (onu.gem_ids(exception_gems), onu.onu_vid)
         return gem_ids
 
     def get_pon_config(self):
@@ -540,7 +554,7 @@
 
     def _process_status_onu_discovered_list(self, discovered_onus):
         """
-        Look for new or missing ONUs
+        Look for new ONUs
         
         :param discovered_onus: (frozenset) Set of ONUs currently discovered
         """
@@ -555,9 +569,9 @@
         my_onus = frozenset(self._onus.keys())
 
         new_onus = discovered_onus - my_onus
-        missing_onus = my_onus - discovered_onus
+        # TODO: Remove later if not needed -> missing_onus = my_onus - discovered_onus
 
-        return new_onus, missing_onus
+        return new_onus, None # , missing_onus
 
     def _get_onu_info(self, serial_number):
         """
@@ -572,21 +586,30 @@
                 onu_id = self.get_next_onu_id()
                 enabled = True
                 channel_speed = 0
+                tconts = get_tconts(serial_number, onu_id)
+                gem_ports = get_gem_ports(serial_number, onu_id)
 
             elif self.activation_method == "autodiscovery":
                 if self.authentication_method == 'serial-number':
                     gpon_info = self.olt.get_xpon_info(self.pon_id)
 
                     try:
-                        vont_info = next(info for _, info in gpon_info['v_ont_anis'].items()
+                        vont_info = next(info for _, info in gpon_info['v-ont-anis'].items()
                                          if info.get('expected-serial-number') == serial_number)
 
                         onu_id = vont_info['onu-id']
                         enabled = vont_info['enabled']
                         channel_speed = vont_info['upstream-channel-speed']
 
+                        tconts = {key: val for key, val in gpon_info['tconts'].iteritems()
+                                  if val.vont_ani == vont_info['name']}
+                        tcont_names = set(tconts.keys())
+
+                        gem_ports = {key: val for key, val in gpon_info['gem-ports'].iteritems()
+                                     if val.tconf_ref in tcont_names}
+
                     except StopIteration:
-                        return None
+                        return None     # Can happen if vont-ani has not yet been configured
                 else:
                     return None
             else:
@@ -600,11 +623,13 @@
                 'enabled': enabled,
                 'upstream-channel-speed': channel_speed,
                 'password': Onu.DEFAULT_PASSWORD,
-                't-conts': get_tconts(self.pon_id, serial_number, onu_id),
-                'gem-ports': get_gem_ports(self.pon_id, serial_number, onu_id),
+                't-conts': tconts,
+                'gem-ports': gem_ports,
                 'onu-vid': self.olt.get_channel_id(self._pon_id, onu_id)
             }
-            return onu_info
+            # Hold off ONU activation until at least one GEM Port is defined.
+
+            return onu_info if len(gem_ports) > 0 else None
 
         except Exception as e:
             self.log.exception('get-onu-info', e=e)
@@ -640,6 +665,14 @@
                         yield onu.create(tconts, gem_ports)
                         self.activate_onu(onu)
 
+                        if len(self._pon_evc_map) > 0:
+                            # Add gem-id's to maps
+                            dl = []
+                            for evc_map in self._pon_evc_map.itervalues():
+                                dl = evc_map.add_onu(onu)
+
+                            yield defer.gatherResults(dl)
+
                     except Exception as e:
                         del self._onus[serial_number]
                         del self._onu_by_id[onu.onu_id]
@@ -677,19 +710,52 @@
             if onu_id not in used_ids:
                 return onu_id
 
+    @inlineCallbacks
     def delete_onu(self, onu_id):
         uri = AdtranOltHandler.GPON_ONU_CONFIG_URI.format(self._pon_id, onu_id)
         name = 'pon-delete-onu-{}-{}'.format(self._pon_id, onu_id)
 
+        onu = self._onu_by_id.get(onu_id)
+
         # Remove from any local dictionary
         if onu_id in self._onu_by_id:
             del self._onu_by_id[onu_id]
         for sn in [onu.serial_numbers for onu in self._onus.itervalues() if onu.onu_id == onu_id]:
             del self._onus[sn]
+        try:
+            yield self._parent.rest_client.request('DELETE', uri, name=name)
+
+        except Exception as e:
+            self.log.exception('onu', serial_number=onu.serial_number, e=e)
+
+        if onu is not None and len(self._pon_evc_map) > 0:
+            # Drop gem-id's from any existing maps
+            dl = []
+            for evc_map in self._pon_evc_map.itervalues():
+                dl = evc_map.remove_onu(onu)
+            try:
+                yield defer.gatherResults(dl)
+
+            except Exception as e:
+                self.log.exception('maps', serial_number=onu.serial_number, e=e)
 
         # TODO: Need removal from VOLTHA child_device method
 
-        return self._parent.rest_client.request('DELETE', uri, name=name)
+    def add_pon_evc_map(self, evc_map):
+        """
+        Add an EVC MAP that covers all ONUs on a PON (typically control exception flows)
+        :param evc_map: (EVCMap) EVC Map
+        """
+        assert evc_map.name not in self._pon_evc_map
+        self._pon_evc_map[evc_map.name] = evc_map
+
+    def remove_pon_evc_map(self, evc_map):
+        """
+        Remove an EVC MAP that covers all ONUs on a PON (typically control exception flows)
+        :param evc_map: (EVCMap) EVC Map
+        """
+        if evc_map.name in self._pon_evc_map:
+            del self._pon_evc_map[evc_map.name]
 
     @inlineCallbacks
     def channel_partition(self, name, partition=0, xpon_system=0, operation=None):
diff --git a/voltha/adapters/adtran_olt/tcont.py b/voltha/adapters/adtran_olt/tcont.py
index 9b78f24..3dc4da0 100644
--- a/voltha/adapters/adtran_olt/tcont.py
+++ b/voltha/adapters/adtran_olt/tcont.py
@@ -14,6 +14,8 @@
 
 import structlog
 from enum import Enum
+from voltha.protos.bbf_fiber_tcont_body_pb2 import TcontsConfigData
+from voltha.protos.bbf_fiber_traffic_descriptor_profile_body_pb2 import TrafficDescriptorProfileData
 
 log = structlog.get_logger()
 
@@ -22,24 +24,35 @@
     """
     Class to wrap TCont capabilities
     """
-    def __init__(self, alloc_id, traffic_descriptor, best_effort=None, name=None):
+    def __init__(self, alloc_id, traffic_descriptor, best_effort=None,
+                 name=None, ident=None, vont_ani=None):
         self.alloc_id = alloc_id
         self.traffic_descriptor = traffic_descriptor
         self.best_effort = best_effort
         self.name = name
+        self.id = ident
+        self.vont_ani = vont_ani        # (string) reference
 
     def __str__(self):
         return "TCont: {}, alloc-id: {}".format(self.name,self.alloc_id)
 
+    @staticmethod
+    def create(data, td):
+        assert isinstance(data, TcontsConfigData)
+        assert isinstance(td, TrafficDescriptor)
+
+        return TCont(data.alloc_id, td, best_effort=td.best_effort,
+                     name=data.name, ident=data.id, vont_ani=data.interface_reference)
+
 
 class TrafficDescriptor(object):
     """
     Class to wrap the uplink traffic descriptor.
     """
     class AdditionalBwEligibility(Enum):
-        NON_ASSURED_SHARING = 1
-        BEST_EFFORT_SHARING = 2
-        NONE = 3
+        NONE = 0
+        BEST_EFFORT_SHARING = 1
+        NON_ASSURED_SHARING = 2             # Should match xpon.py values
         DEFAULT = NONE
 
         @staticmethod
@@ -50,11 +63,26 @@
                 TrafficDescriptor.AdditionalBwEligibility.NONE: "none"
             }.get(value, "unknown")
 
+        @staticmethod
+        def from_value(value):
+            """
+            Matches both Adtran and xPON values
+            :param value:
+            :return:
+            """
+            return {
+                0: TrafficDescriptor.AdditionalBwEligibility.NONE,
+                1: TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING,
+                2: TrafficDescriptor.AdditionalBwEligibility.NON_ASSURED_SHARING,
+            }.get(value, TrafficDescriptor.AdditionalBwEligibility.DEFAULT)
+
     def __init__(self, fixed, assured, maximum,
                  additional=AdditionalBwEligibility.DEFAULT,
                  best_effort=None,
-                 name=None):
+                 name=None,
+                 ident=None):
         self.name = name
+        self.id = ident
         self.fixed_bandwidth = fixed       # bps
         self.assured_bandwidth = assured   # bps
         self.maximum_bandwidth = maximum   # bps
@@ -69,6 +97,27 @@
                                                         self.assured_bandwidth,
                                                         self.maximum_bandwidth)
 
+    @staticmethod
+    def create(data):
+        assert isinstance(data, TrafficDescriptorProfileData)
+
+        additional = TrafficDescriptor.AdditionalBwEligibility.from_value(
+            data.additional_bw_eligibility_indicator)
+
+        if additional == TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING:
+            best_effort = BestEffort(data.maximum_bandwidth,
+                                     data.priority,
+                                     data.weight)
+        else:
+            best_effort = None
+
+        return TrafficDescriptor(data.fixed_bandwidth, data.assured_bandwidth,
+                                 data.maximum_bandwidth,
+                                 name=data.name,
+                                 ident=data.id,
+                                 best_effort=best_effort,
+                                 additional=additional)
+
     def to_dict(self):
         val = {
             'fixed-bandwidth': self.fixed_bandwidth,
@@ -99,8 +148,3 @@
             'weight': self.weight
         }
         return val
-
-
-
-
-