Update VLAN support to match POC-3 requirements

Change-Id: I556edb4b55d8cbf7ce112f9d867641ec7eaeb489
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index 5422a07..e8c8af6 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -144,7 +144,7 @@
         # registered (via xPON API/CLI) before they are activated.
 
         self._autoactivate = False
-        self.max_nni_ports = 1  # TODO: This is a VOLTHA imposed limit in 'low_decomposer.py
+        self.max_nni_ports = 1  # TODO: This is a VOLTHA imposed limit in 'flow_decomposer.py
                                 # and logical_device_agent.py
         # OMCI ZMQ Channel
         self.zmq_port = DEFAULT_ZEROMQ_OMCI_TCP_PORT
@@ -152,7 +152,7 @@
         # Heartbeat support
         self.heartbeat_count = 0
         self.heartbeat_miss = 0
-        self.heartbeat_interval = 5  # TODO: Decrease before release or any scale testing
+        self.heartbeat_interval = 2  # TODO: Decrease before release or any scale testing
         self.heartbeat_failed_limit = 3
         self.heartbeat_timeout = 5
         self.heartbeat = None
diff --git a/voltha/adapters/adtran_olt/adtran_olt.py b/voltha/adapters/adtran_olt/adtran_olt.py
index 23ba5eb..be75f2e 100644
--- a/voltha/adapters/adtran_olt/adtran_olt.py
+++ b/voltha/adapters/adtran_olt/adtran_olt.py
@@ -51,7 +51,7 @@
         self.descriptor = Adapter(
             id=self.name,
             vendor='Adtran, Inc.',
-            version='0.5',
+            version='0.7',
             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 524648b..4eb2a63 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -32,7 +32,7 @@
     OntaniConfig, VOntaniConfig, VEnetConfig
 
 FIXED_ONU = True  # Enhanced ONU support
-
+ATT_NETWORK = True  # Use AT&T cVlan scheme
 
 class AdtranOltHandler(AdtranDeviceHandler):
     """
@@ -516,6 +516,11 @@
 
     def get_channel_id(self, pon_id, onu_id):
         from pon_port import PonPort
+        if ATT_NETWORK:
+            if FIXED_ONU:
+                return onu_id + 2
+            return 1 + onu_id + (pon_id * 120)
+
         if FIXED_ONU:
             return self._onu_offset(onu_id)
         return self._onu_offset(onu_id) + (pon_id * PonPort.MAX_ONUS_SUPPORTED)
@@ -528,6 +533,11 @@
 
     def _channel_id_to_pon_id(self, channel_id, onu_id):
         from pon_port import PonPort
+        if ATT_NETWORK:
+            if FIXED_ONU:
+                return channel_id - onu_id - 2
+            return (channel_id - onu_id - 1) / 120
+
         if FIXED_ONU:
             return channel_id - self._onu_offset(onu_id)
         return (channel_id - self._onu_offset(onu_id)) / PonPort.MAX_ONUS_SUPPORTED
@@ -627,27 +637,13 @@
             return self._v_enets
         return None
 
-    def create_interface(self, data):
-        """
-        Create XPON interfaces
-        :param data: (xpon config info)
-        """
+    def _data_to_dict(self, data):
         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)
-
-            if name in items:
-                raise KeyError("Channel group '{}' already exists".format(name))
-
-            items[name] = {
+            return 'channel-group', {
                 'name': name,
                 'enabled': interface.enabled,
                 'system-id': inst_data.system_id,
@@ -655,23 +651,18 @@
             }
 
         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
                 return {
                     SERIAL_NUMBER: 'serial-number',
                     LOID: 'loid',
-                    REGISTRATION_ID: 'registation-id',
+                    REGISTRATION_ID: 'registration-id',
                     OMCI: 'omci',
-                    DOT1X: 'don1x'
+                    DOT1X: 'dot1x'
                 }.get(value, 'unknown')
 
-            items[name] = {
+            return 'channel-partition', {
                 'name': name,
                 'enabled': interface.enabled,
                 'authentication-method': _auth_method_enum_to_string(inst_data.authentication_method),
@@ -682,12 +673,7 @@
             }
 
         elif isinstance(data, ChannelpairConfig):
-            self.log.debug('create_interface-channel-pair', interface=interface, data=inst_data)
-
-            if name in items:
-                raise KeyError("Channel pair '{}' already exists".format(name))
-
-            items[name] = {
+            return 'channel-pair', {
                 'name': name,
                 'enabled': interface.enabled,
                 'channel-group': inst_data.channelgroup_ref,
@@ -696,12 +682,7 @@
             }
 
         elif isinstance(data, ChannelterminationConfig):
-            self.log.debug('create_interface-channel-termination', interface=interface, data=inst_data)
-
-            if name in items:
-                raise KeyError("Channel termination '{}' already exists".format(name))
-
-            items[name] = {
+            return 'channel-termination', {
                 'name': name,
                 'enabled': interface.enabled,
                 'xgs-ponid': inst_data.xgs_ponid,
@@ -709,15 +690,9 @@
                 'channel-pair': inst_data.channelpair_ref,
                 'ber-calc-period': inst_data.ber_calc_period
             }
-            self.on_channel_termination_config(name, 'create')
 
         elif isinstance(data, OntaniConfig):
-            self.log.debug('create_interface-ont-ani', interface=interface, data=inst_data)
-
-            if name in items:
-                raise KeyError("ONT ANI '{}' already exists".format(name))
-
-            items[name] = {
+            return 'ont-ani', {
                 'name': name,
                 'enabled': interface.enabled,
                 'upstream-fec': inst_data.upstream_fec_indicator,
@@ -725,12 +700,7 @@
             }
 
         elif isinstance(data, VOntaniConfig):
-            self.log.debug('create_interface-v-ont-ani', interface=interface, data=inst_data)
-
-            if name in items:
-                raise KeyError("vONT ANI '{}' already exists".format(name))
-
-            items[name] = {
+            return 'vOnt-ani', {
                 'name': name,
                 'enabled': interface.enabled,
                 'onu-id': inst_data.onu_id,
@@ -741,12 +711,7 @@
             }
 
         elif isinstance(data, VEnetConfig):
-            self.log.debug('create_interface-v-enet', interface=interface, data=inst_data)
-
-            if name in items:
-                raise KeyError("vENET '{}' already exists".format(name))
-
-            items[name] = {
+            return 'vEnet', {
                 'name': name,
                 'enabled': interface.enabled,
                 'v-ont-ani': inst_data.v_ontani_ref
@@ -755,6 +720,33 @@
         else:
             raise NotImplementedError('Unknown data type')
 
+    def create_interface(self, data):
+        """
+        Create XPON interfaces
+        :param data: (xpon config info)
+        """
+        self.log.debug('create-interface', interface=data.interface, inst_data=data.data)
+
+        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
+
+        item_type, new_item = self._data_to_dict(data)
+        self.log.debug('new-item', item_type=item_type, item=new_item)
+
+        if name in items:
+            raise KeyError("{} '{}' already exists".format(item_type, name))
+
+        items[name] = new_item
+
+        if isinstance(data, ChannelterminationConfig):
+            self._on_channel_termination_create(name)
+
     def update_interface(self, data):
         """
         Update XPON interfaces
@@ -769,10 +761,46 @@
         if items is None:
             raise ValueError('Unknown data type: {}'.format(type(data)))
 
-        if name not in items:
+        existing_item = items.get(name)
+        if existing_item is None:
             raise KeyError("'{}' not found. Type: {}".format(name, type(data)))
 
+        item_type, update_item = self._data_to_dict(data)
+        self.log.debug('update-item', item_type=item_type, item=update_item)
+
+        # TODO: Calculate the difference
+        diffs = {}
+
+        if len(diffs) == 0:
+            self.log.debug('update-item-no-diffs')
+
         self._cached_xpon_pon_info = {}     # Clear cached data
+
+        # Act on changed items
+        if isinstance(data, ChannelgroupConfig):
+            pass
+
+        elif isinstance(data, ChannelpartitionConfig):
+            pass
+
+        elif isinstance(data, ChannelpairConfig):
+            pass
+
+        elif isinstance(data, ChannelterminationConfig):
+            pass
+
+        elif isinstance(data, OntaniConfig):
+            pass
+
+        elif isinstance(data, VOntaniConfig):
+            pass
+
+        elif isinstance(data, VEnetConfig):
+            pass
+
+        else:
+            raise NotImplementedError('Unknown data type')
+
         raise NotImplementedError('TODO: not yet supported')
 
     def delete_interface(self, data):
@@ -781,23 +809,42 @@
         :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)
+        self.log.debug('delete-interface', name=name, data=data)
 
-        if item in items:
+        if item is not None:
+            self._cached_xpon_pon_info = {}     # Clear cached data
             del items[name]
-            pass    # TODO Do something....
+
+            if isinstance(data, ChannelgroupConfig):
+                pass
+
+            elif isinstance(data, ChannelpartitionConfig):
+                pass
+
+            elif isinstance(data, ChannelpairConfig):
+                pass
+
+            elif isinstance(data, ChannelterminationConfig):
+                self._on_channel_termination_delete(name)
+
+            elif isinstance(data, OntaniConfig):
+                pass
+
+            elif isinstance(data, VOntaniConfig):
+                pass
+
+            elif isinstance(data, VEnetConfig):
+                pass
+
+            else:
+                raise NotImplementedError('Unknown data type')
+
             raise NotImplementedError('TODO: not yet supported')
 
-    def on_channel_termination_config(self, name, operation, pon_type='xgs-ponid'):
-        supported_operations = ['create']
-
-        assert operation in supported_operations, \
-            'Unsupported channel-term operation: {}'.format(operation)
+    def _on_channel_termination_create(self, name, pon_type='xgs-ponid'):
         assert name in self._channel_terminations, \
             '{} is not a channel-termination'.format(name)
         ct = self._channel_terminations[name]
@@ -833,15 +880,27 @@
         # TODO support FEC, and MCAST AES settings
         # TODO Support setting of line rate
 
-        if operation == 'create':
-            pon_port.xpon_name = name
-            pon_port.discovery_tick = polling_period
-            pon_port.authentication_method = authentication_method
-            pon_port.deployment_range = deployment_range * 1000     # pon-agent uses meters
-            pon_port.downstream_fec_enable = downstream_fec
-            # TODO: pon_port.mcast_aes = mcast_aes
+        pon_port.xpon_name = name
+        pon_port.discovery_tick = polling_period
+        pon_port.authentication_method = authentication_method
+        pon_port.deployment_range = deployment_range * 1000     # pon-agent uses meters
+        pon_port.downstream_fec_enable = downstream_fec
+        # TODO: pon_port.mcast_aes = mcast_aes
 
-            pon_port.admin_state = AdminState.ENABLED if enabled else AdminState.DISABLED
+        pon_port.admin_state = AdminState.ENABLED if enabled else AdminState.DISABLED
+
+    def _on_channel_termination_delete(self, name, pon_type='xgs-ponid'):
+        assert name in self._channel_terminations, \
+            '{} is not a channel-termination'.format(name)
+        ct = self._channel_terminations[name]
+
+        # Look up the southbound PON port
+        pon_id = ct[pon_type]
+        pon_port = self.southbound_ports.get(pon_id, None)
+        if pon_port is None:
+            raise ValueError('Unknown PON port. PON-ID: {}'.format(pon_id))
+
+        pon_port.enabled = False
 
     def create_tcont(self, tcont_data, traffic_descriptor_data):
         """
diff --git a/voltha/adapters/adtran_olt/pon_port.py b/voltha/adapters/adtran_olt/pon_port.py
index 275236e..0505057 100644
--- a/voltha/adapters/adtran_olt/pon_port.py
+++ b/voltha/adapters/adtran_olt/pon_port.py
@@ -53,7 +53,7 @@
     _SUPPORTED_ACTIVATION_METHODS = ['autodiscovery', 'autoactivate']
     _SUPPORTED_AUTHENTICATION_METHODS = ['serial-number']
 
-    def __init__(self, pon_index, port_no, parent, label=None):
+    def __init__(self, pon_index, port_no, parent):
         # TODO: Weed out those properties supported by common 'Port' object (future)
         self.log = structlog.get_logger(device_id=parent.device_id, pon_id=pon_index)
 
@@ -61,9 +61,9 @@
         self._pon_id = pon_index
         self._port_no = port_no
         self._name = 'xpon 0/{}'.format(pon_index+1)
-        self._label = label or 'PON-{}'.format(pon_index)
+        self._label = 'pon-{}'.format(pon_index)
         self._port = None
-        self._no_onu_discover_tick = 5.0  # TODO: Decrease to 1 or 2 later
+        self._no_onu_discover_tick = 5.0
         self._discovery_tick = 20.0
         self._discovered_onus = []  # List of serial numbers
         self._sync_tick = 20.0
@@ -140,6 +140,7 @@
 
     @xpon_name.setter
     def xpon_name(self, value):
+        assert '/' not in value, "xPON names cannot have embedded forward slashes '/'"
         self._xpon_name = value
 
     @property
@@ -728,6 +729,9 @@
             self._active_los_alarms.add(onu_id)
             los_alarm(True, onu_id)
 
+        # TODO: A method to update the AdapterAgent's child device state (operStatus)
+        #       would be useful here
+
     def _process_status_onu_discovered_list(self, discovered_onus):
         """
         Look for new ONUs
@@ -879,18 +883,24 @@
         :param onu: 
         :return: 
         """
-        olt = self.olt
-        adapter = self.adapter_agent
-        channel_id = onu.onu_vid
+        # Only call older 'child_device_detected' if not using xPON to configure the system
 
-        proxy = Device.ProxyAddress(device_id=olt.device_id, channel_id=channel_id)
+        if self.activation_method == "autoactivate":
+            olt = self.olt
+            adapter = self.adapter_agent
+            channel_id = onu.onu_vid
 
-        adapter.child_device_detected(parent_device_id=olt.device_id,
-                                      parent_port_no=self._port_no,
-                                      child_device_type=onu.vendor_id,
-                                      proxy_address=proxy,
-                                      admin_state=AdminState.ENABLED,
-                                      vlan=channel_id)
+            proxy = Device.ProxyAddress(device_id=olt.device_id,
+                                        channel_id=channel_id,
+                                        onu_id=onu.onu_id,
+                                        onu_session_id=onu.onu_id)
+
+            adapter.child_device_detected(parent_device_id=olt.device_id,
+                                          parent_port_no=self._port_no,
+                                          child_device_type=onu.vendor_id,
+                                          proxy_address=proxy,
+                                          admin_state=AdminState.ENABLED,
+                                          vlan=channel_id)
 
     def get_next_onu_id(self):
         used_ids = [onu.onu_id for onu in self._onus.itervalues()]