blob: 1eb0a13f13c46b65de40cc57e2e32d686dfb9c6a [file] [log] [blame]
# 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 voltha.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.
if parent.xpon_support:
self._enabled = not parent.xpon_support
self._admin_state = AdminState.DISABLED
else:
self._enabled = not parent.xpon_support
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
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.sync_deferred = reactor.callLater(self.sync_tick,
self.sync_hardware)
self.deferred = self.finish_stop()
yield self.deferred
except Exception as e:
self.log.exception('stop-failed', e=e)
raise
returnValue('Stopped')
def finish_stop(self):
pass # Add to your derived class if needed
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