VOL-1397: Adtran-OLT - Initial containerization commit
 - Need to move VERSION to base directory

Change-Id: I9d62d0607a011ce642e379fd92b35ec48b300070
diff --git a/adapters/adtran_common/port.py b/adapters/adtran_common/port.py
new file mode 100644
index 0000000..0fc49dc
--- /dev/null
+++ b/adapters/adtran_common/port.py
@@ -0,0 +1,251 @@
+# Copyright 2017-present Adtran, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import structlog
+from enum import Enum
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+from pyvoltha.protos.common_pb2 import OperStatus, AdminState
+
+
+class AdtnPort(object):
+    """
+    A class similar to the 'Port' class in the VOLTHA
+    """
+    class State(Enum):
+        INITIAL = 0  # Created and initialization in progress
+        RUNNING = 1  # PON port contacted, ONU discovery active
+        STOPPED = 2  # Disabled
+        DELETING = 3  # Cleanup
+
+    def __init__(self, parent, **kwargs):
+        assert parent, 'parent is None'
+        assert 'port_no' in kwargs, 'Port number not found'
+
+        self.log = structlog.get_logger(device_id=parent.device_id)
+
+        self._parent = parent
+        self._port_no = kwargs.get('port_no')
+
+        # Set the following in your derived class.  These names are used in
+        # various ways.  Typically, the physical port name will be used during
+        # device handler conversations with the hardware (REST, NETCONF, ...)
+        # while the logical port name is what the outside world (ONOS, SEBA, ...)
+        # uses.  All ports have a physical port name, but only ports exposed through
+        # VOLTHA as a logical port will have a logical port name
+
+        self._physical_port_name = None
+        self._logical_port_name = None
+        self._label = None
+        self._port = None
+
+        self.sync_tick = 20.0
+        self.sync_deferred = None  # For sync of PON config to hardware
+
+        # TODO: Deprecate 'enabled' and use admin_state instead may want initial to always be
+        # disabled and then in derived classes, set it in the 'reset' method called on startup.
+        self._enabled = True
+        self._admin_state = AdminState.ENABLED
+
+        self._oper_status = OperStatus.DISCOVERED
+        self._state = AdtnPort.State.INITIAL
+
+        self.deferred = None  # General purpose
+
+        # Statistics
+        self.rx_packets = 0
+        self.rx_bytes = 0
+        self.tx_packets = 0
+        self.tx_bytes = 0
+        self.timestamp = 0      # UTC when KPI items last updated
+
+    def __del__(self):
+        self.stop()
+
+    def get_port(self):
+        """
+        Get the VOLTHA PORT object for this port
+        :return: VOLTHA Port object
+        """
+        raise NotImplementedError('Add to your derived class')
+
+    @property
+    def port_no(self):
+        return self._port_no
+
+    @property
+    def intf_id(self):
+        return self.port_no
+
+    @property
+    def physical_port_name(self):
+        return self._physical_port_name
+
+    @property
+    def logical_port_name(self):
+        return self._logical_port_name
+
+    @property                           # For backwards compatibility
+    def name(self):
+        return self._logical_port_name
+
+    @property
+    def state(self):
+        return self._state
+
+    @state.setter
+    def state(self, value):
+        self._state = value
+
+    @property
+    def olt(self):
+        return self._parent
+
+    @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 enabled(self):
+        return self._admin_state == AdminState.ENABLED
+
+    @enabled.setter
+    def enabled(self, value):
+        assert isinstance(value, bool), 'enabled is a boolean'
+        self.admin_state = AdminState.ENABLED if value else AdminState.DISABLED
+
+    @property
+    def oper_status(self):
+        return self._oper_status
+
+    @property
+    def adapter_agent(self):
+        return self.olt.adapter_agent
+
+    def get_logical_port(self):
+        """
+        Get the VOLTHA logical port for this port. For PON ports, a logical port
+        is not currently created, so always return None
+
+        :return: VOLTHA logical port or None if not supported
+        """
+        return None
+
+    def cancel_deferred(self):
+        d1, self.deferred = self.deferred, None
+        d2, self.sync_deferred = self.sync_deferred, None
+
+        for d in [d1, d2]:
+            try:
+                if d is not None and not d.called:
+                    d.cancel()
+            except Exception:
+                pass
+
+    def _update_adapter_agent(self):
+        raise NotImplementedError('Add to your derived class')
+
+    def start(self):
+        """
+        Start/enable this PON and start ONU discover
+        """
+        if self.state == AdtnPort.State.RUNNING:
+            return succeed('Running')
+
+        self.log.info('start-port')
+
+        self.cancel_deferred()
+        self.state = AdtnPort.State.INITIAL
+        self._oper_status = OperStatus.ACTIVATING
+        self._enabled = True
+
+        # Do the rest of the startup in an async method
+        self.deferred = reactor.callLater(0.5, self.finish_startup)
+        self._update_adapter_agent()
+
+        return succeed('Scheduled')
+
+    def finish_startup(self):
+        if self.state == AdtnPort.State.INITIAL:
+            self.log.debug('final-startup')
+
+            # If here, initial settings were successfully written to hardware
+
+            self._enabled = True
+            self._admin_state = AdminState.ENABLED
+            self._oper_status = OperStatus.ACTIVE  # TODO: is this correct, how do we tell GRPC
+            self.state = AdtnPort.State.RUNNING
+
+            self.sync_deferred = reactor.callLater(self.sync_tick,
+                                                   self.sync_hardware)
+            self._update_adapter_agent()
+
+    @inlineCallbacks
+    def stop(self):
+        if self.state == AdtnPort.State.STOPPED:
+            self.log.debug('already stopped')
+            returnValue('Stopped')
+
+        self.log.info('stopping')
+        try:
+            self.cancel_deferred()
+            self._enabled = False
+            self._admin_state = AdminState.DISABLED
+            self._oper_status = OperStatus.UNKNOWN
+            self._update_adapter_agent()
+
+            self.state = AdtnPort.State.STOPPED
+
+            self.deferred = self.finish_stop()
+            yield self.deferred
+
+        except Exception as e:
+            self.log.exception('stop-failed', e=e)
+
+        returnValue('Stopped')
+
+    @inlineCallbacks
+    def finish_stop(self):
+        pass   # Add to your derived class if needed
+        returnValue(None)
+
+    def restart(self):
+        if self.state == AdtnPort.State.RUNNING or self.state == AdtnPort.State.STOPPED:
+            start_it = (self.state == AdtnPort.State.RUNNING)
+            self.state = AdtnPort.State.INITIAL
+            return self.start() if start_it else self.stop()
+        return succeed('nop')
+
+    def delete(self):
+        """
+        Parent device is being deleted. Do not change any config but
+        stop all polling
+        """
+        self.log.info('Deleting')
+        self.state = AdtnPort.State.DELETING
+        self.cancel_deferred()
+
+    def sync_hardware(self):
+        raise NotImplementedError('Add to your derived class')
+
+# TODO: Continue to consolidate port functionality