VOL-618: ADTRAN ONU support for OMCI remote reboot
Change-Id: Icd332bcddf5f1224d5ba4050efaf1b2bcff602d9
diff --git a/voltha/adapters/adtran_onu/adtran_onu.py b/voltha/adapters/adtran_onu/adtran_onu.py
index c0d9ae9..b062aa0 100755
--- a/voltha/adapters/adtran_onu/adtran_onu.py
+++ b/voltha/adapters/adtran_onu/adtran_onu.py
@@ -36,7 +36,7 @@
device_handler_class=AdtranOnuHandler,
name='adtran_onu',
vendor='Adtran, Inc.',
- version='0.4',
+ version='0.5',
device_type='adtran_onu',
vendor_id='ADTN')
diff --git a/voltha/adapters/adtran_onu/adtran_onu_handler.py b/voltha/adapters/adtran_onu/adtran_onu_handler.py
index 2231dd4..dea1e78 100644
--- a/voltha/adapters/adtran_onu/adtran_onu_handler.py
+++ b/voltha/adapters/adtran_onu/adtran_onu_handler.py
@@ -39,7 +39,8 @@
_ = third_party
_MAXIMUM_PORT = 128 # PON and UNI ports
-
+_ONU_REBOOT_MIN = 60
+_ONU_REBOOT_RETRY = 10
class AdtranOnuHandler(AdtranXPON):
def __init__(self, adapter, device_id):
@@ -544,41 +545,88 @@
@inlineCallbacks
def reboot(self):
- from common.utils.asleep import asleep
self.log.info('rebooting', device_id=self.device_id)
self._cancel_deferred()
- # Drop registration for adapter messages
- self.adapter_agent.unregister_for_inter_adapter_messages()
+ reregister = True
+ try:
+ # Drop registration for adapter messages
+ self.adapter_agent.unregister_for_inter_adapter_messages()
+
+ except KeyError:
+ reregister = False
# Update the operational status to ACTIVATING and connect status to
# UNREACHABLE
device = self.adapter_agent.get_device(self.device_id)
+
previous_oper_status = device.oper_status
previous_conn_status = device.connect_status
+
device.oper_status = OperStatus.ACTIVATING
device.connect_status = ConnectStatus.UNREACHABLE
- device.reason = 'Rebooting'
-
+ device.reason = 'Attempting reboot'
self.adapter_agent.update_device(device)
- # Sleep 10 secs, simulating a reboot
# TODO: send alert and clear alert after the reboot
- yield asleep(10) # TODO: Need to reboot for real
- # Register for adapter messages
- self.adapter_agent.register_for_inter_adapter_messages()
+ if not self.is_mock:
+ from twisted.internet.defer import TimeoutError
+
+ try:
+ ######################################################
+ # MIB Reset - For ADTRAN ONU, we do not get a response
+ # back (because we are rebooting)
+ pass
+ yield self.omci.send_reboot(timeout=0.1)
+
+ except TimeoutError:
+ # This is expected
+ returnValue('reboot-in-progress')
+
+ except Exception as e:
+ self.log.exception('send-reboot', e=e)
+ raise
+
+ # Reboot in progress. A reboot may take up to 3 min 30 seconds
+ # Go ahead and pause less than that and start to look
+ # for it being alive
+
+ device.reason = 'reboot in progress'
+ self.adapter_agent.update_device(device)
+
+ self._deferred = reactor.callLater(_ONU_REBOOT_MIN,
+ self._finish_reboot,
+ previous_oper_status,
+ previous_conn_status,
+ reregister)
+
+ @inlineCallbacks
+ def _finish_reboot(self, previous_oper_status, previous_conn_status,
+ reregister):
+ from common.utils.asleep import asleep
+
+ if not self.is_mock:
+ # TODO: Do a simple poll and call this again if we timeout
+ # _ONU_REBOOT_RETRY
+ yield asleep(180) # 3 minutes ...
# Change the operational status back to its previous state. With a
# real OLT the operational state should be the state the device is
# after a reboot.
# Get the latest device reference
device = self.adapter_agent.get_device(self.device_id)
+
device.oper_status = previous_oper_status
device.connect_status = previous_conn_status
device.reason = ''
self.adapter_agent.update_device(device)
- self.log.info('rebooted', device_id=self.device_id)
+
+ if reregister:
+ self.adapter_agent.register_for_inter_adapter_messages()
+
+ self.log.info('reboot-complete', device_id=self.device_id)
+
def self_test_device(self, device):
"""
@@ -699,17 +747,38 @@
def delete(self):
self.log.info('deleting', device_id=self.device_id)
- # A delete request may be received when an OLT is disabled
-
- self.enabled = False
-
- # TODO: Need to implement this
- # 1) Remove all flows from the device
-
- self.log.info('deleted', device_id=self.device_id)
-
- # Drop device ID
- self.device_id = None
+ #
+ # handling needed here
+ # self.enabled = False
+ #
+ # # TODO: Need to implement this
+ # # 1) Remove all flows from the device
+ #
+ # self.log.info('deleted', device_id=self.device_id)
+ #
+ # # Drop device ID
+ # self.device_id = None @inlineCallbacks
+ # def delete_v_ont_ani(self, data):
+ # self.log.info('deleting-v_ont_ani')
+ #
+ # device = self.adapter_agent.get_device(self.device_id)
+ # # construct message
+ # # MIB Reset - OntData - 0
+ # if device.connect_status != ConnectStatus.REACHABLE:
+ # self.log.error('device-unreachable')
+ # returnValue(None)
+ #
+ # self.send_mib_reset()
+ # yield self.wait_for_response()
+ # self.proxy_address = device.proxy_address
+ # self.adapter_agent.unregister_for_proxied_messages(device.proxy_address)
+ #
+ # ports = self.adapter_agent.get_ports(self.device_id, Port.PON_ONU)
+ # if ports is not None:
+ # for port in ports:
+ # if port.label == 'PON port':
+ # self.adapter_agent.delete_port(self.device_id, port)
+ # break
def _check_for_mock_config(self, data):
# Check for MOCK configuration
@@ -799,8 +868,7 @@
return update
def on_vont_ani_delete(self, vont_ani):
- # TODO: Is this ever called or is the iAdapter 'delete' called first?
- return None # Implement in your OLT, if needed
+ return self.delete()
def on_venet_create(self, venet):
self.log.info('venet-create', venet=venet)
diff --git a/voltha/adapters/adtran_onu/omci/me_frame.py b/voltha/adapters/adtran_onu/omci/me_frame.py
index bac4c33..1808d04 100644
--- a/voltha/adapters/adtran_onu/omci/me_frame.py
+++ b/voltha/adapters/adtran_onu/omci/me_frame.py
@@ -51,7 +51,7 @@
@property
def entity_class_name(self):
- return self._class.__class__.__name__
+ return self._class.__name__
@property
def entity_id(self):
@@ -64,23 +64,34 @@
@staticmethod
def check_type(param, types):
if not isinstance(param, types):
- raise TypeError("param '{}' should be a {}".format(param, types))
+ raise TypeError("Parameter '{}' should be a {}".format(param, types))
def _check_operation(self, operation):
allowed = self.entity_class.mandatory_operations | self.entity_class.optional_operations
- assert operation in allowed, "{} not allowed for '{}'".format(str(operation).split('.')[1],
+ assert operation in allowed, "{} not allowed for '{}'".format(operation.name,
self.entity_class_name)
def _check_attributes(self, attributes, access):
- for attribute in attributes:
- index = self.entity_class.attribute_name_to_index_map.get(attribute)
+ keys = attributes.keys() if isinstance(attributes, dict) else attributes
+ for attr_name in keys:
# Bad attribute name (invalid or spelling error)?
- assert index is not None, "Attribute '{}' is not valid for '{}'".format(attribute,
- self.entity_class_name)
+ index = self.entity_class.attribute_name_to_index_map.get(attr_name)
+ if index is None:
+ raise KeyError("Attribute '{}' is not valid for '{}'".
+ format(attr_name, self.entity_class_name))
# Invalid access?
- # TODO: Add read-only access to EntityClass _access set. Currently it is protected
- assert access in self.entity_class.attributes[index]._access,\
- "No '{} access for attribute '{}".format(str(access).split('.')[1], attribute)
+ assert access in self.entity_class.attributes[index].access, \
+ "Access '{}' for attribute '{}' is not valid for '{}'".format(access.name,
+ attr_name,
+ self.entity_class_name)
+
+ if access.value in [AA.W.value, AA.SBC.value] and isinstance(attributes, dict):
+ for attr_name, value in attributes.iteritems():
+ index = self.entity_class.attribute_name_to_index_map.get(attr_name)
+ attribute = self.entity_class.attributes[index]
+ if not attribute.valid(value):
+ raise ValueError("Invalid value '{}' for attribute '{}' of '{}".
+ format(value, attr_name, self.entity_class_name))
@staticmethod
def _attr_to_data(attributes):
@@ -138,7 +149,7 @@
assert len(data) > 0, 'No attributes supplied'
self._check_operation(OP.Create)
- self._check_attributes(data.keys(), AA.Writable)
+ self._check_attributes(data, AA.Writable)
return OmciFrame(
transaction_id=None,
@@ -175,7 +186,7 @@
assert len(data) > 0, 'No attributes supplied'
self._check_operation(OP.Set)
- self._check_attributes(data.keys(), AA.Writable)
+ self._check_attributes(data, AA.Writable)
return OmciFrame(
transaction_id=None,
@@ -210,3 +221,71 @@
entity_id=getattr(self, 'entity_id'),
attributes_mask=self.entity_class.mask_for(*mask_set)
))
+
+ def reboot(self):
+ """
+ Create a Reboot request from for this ME
+ :return: (OmciFrame) OMCI Frame
+ """
+ self._check_operation(OP.Reboot)
+
+ return OmciFrame(
+ transaction_id=None,
+ message_type=OmciReboot.message_id,
+ omci_message=OmciReboot(
+ entity_class=getattr(self.entity_class, 'class_id'),
+ entity_id=getattr(self, 'entity_id')
+ ))
+
+ def mib_reset(self):
+ """
+ Create a MIB Reset request from for this ME
+ :return: (OmciFrame) OMCI Frame
+ """
+ self._check_operation(OP.MibReset)
+
+ return OmciFrame(
+ transaction_id=None,
+ message_type=OmciMibReset.message_id,
+ omci_message=OmciMibReset(
+ entity_class=getattr(self.entity_class, 'class_id'),
+ entity_id=getattr(self, 'entity_id')
+ ))
+
+ def mib_upload(self):
+ """
+ Create a MIB Upload request from for this ME
+ :return: (OmciFrame) OMCI Frame
+ """
+ self._check_operation(OP.MibUpload)
+
+ return OmciFrame(
+ transaction_id=None,
+ message_type=OmciMibUpload.message_id,
+ omci_message=OmciMibUpload(
+ entity_class=getattr(self.entity_class, 'class_id'),
+ entity_id=getattr(self, 'entity_id')
+ ))
+
+ def mib_upload_next(self):
+ """
+ Create a MIB Upload Next request from for this ME
+ :return: (OmciFrame) OMCI Frame
+ """
+ assert hasattr(self, 'data'), 'data required for Set actions'
+ data = getattr(self, 'data')
+ MEFrame.check_type(data, dict)
+ assert len(data) > 0, 'No attributes supplied'
+ assert 'mib_data_sync' in data, "'mib_data_sync' not in attributes list"
+
+ self._check_operation(OP.MibUploadNext)
+ self._check_attributes(data, AA.Writable)
+
+ return OmciFrame(
+ transaction_id=None,
+ message_type=OmciMibUploadNext.message_id,
+ omci_message=OmciMibUploadNext(
+ entity_class=getattr(self.entity_class, 'class_id'),
+ entity_id=getattr(self, 'entity_id'),
+ command_sequence_number=data['mib_data_sync']
+ ))
diff --git a/voltha/adapters/adtran_onu/omci/omci_cc.py b/voltha/adapters/adtran_onu/omci/omci_cc.py
index 9ee5a64..413a055 100644
--- a/voltha/adapters/adtran_onu/omci/omci_cc.py
+++ b/voltha/adapters/adtran_onu/omci/omci_cc.py
@@ -26,6 +26,7 @@
from common.frameio.frameio import hexify
from voltha.extensions.omci.omci import *
from omci_entities import add_onu_me_entities
+from omci_me import OntGFrame, OntDataFrame
_ = third_party
@@ -167,6 +168,9 @@
Attempt to retrieve and remove an ONU Alarm Message from the ONU
autonomous message queue.
+ TODO: We may want to deprecate this, see TODO comment around line 399 in
+ the _request_success() method below
+
:return: a Deferred which fires with the next Alarm Frame available in
the queue.
"""
@@ -178,6 +182,9 @@
Attempt to retrieve and remove an ONU Attribute Value Change (AVC)
Message from the ONU autonomous message queue.
+ TODO: We may want to deprecate this, see TODO comment around line 399 in
+ the _request_success() method below
+
:return: a Deferred which fires with the next AVC Frame available in
the queue.
"""
@@ -189,6 +196,9 @@
Attempt to retrieve and remove an ONU Test Results Message from the
ONU autonomous message queue.
+ TODO: We may want to deprecate this, see TODO comment around line 399 in
+ the _request_success() method below
+
:return: a Deferred which fires with the next Test Results Frame is
available in the queue.
"""
@@ -278,7 +288,7 @@
self._consecutive_errors = 0
except KeyError as e:
- # TODO: Investigate. Probably an unknown/unsupported ME
+ # Unknown, Unsupported, or vendor-specific ME. Key is the unknown classID
# TODO: Can we create a temporary one to hold it so upload does not always fail on new ME's?
self.log.exception('frame-decode-key-error', msg=hexlify(msg), e=e)
return
@@ -384,6 +394,13 @@
# TODO: Here we could update the MIB database if we did a set/create/delete
# or perhaps a verify if a GET. Also could increment mib counter
#
+ # TODO: A better way to perform this in VOLTHA v1.3 would be to provide
+ # a pub/sub capability for external users/tasks to monitor responses
+ # that could optionally take a filter. This would allow a MIB-Sync
+ # task to easily watch all AVC notifications as well as Set/Create/Delete
+ # operations and keep them serialized. It may also be a better/easier
+ # way to handle things if we containerize OpenOMCI.
+ #
try:
if isinstance(rx_frame.omci_message, OmciGetResponse):
pass # TODO: Implement MIB check or remove
@@ -454,42 +471,34 @@
return d
###################################################################################
- # TODO: The following three need to be ported to the new OMCI_CC and ME_Frame style
- # or perhaps made into static methods in the base ME_Frame class.
+ # MIB Action shortcuts
- def send_mib_reset(self, entity_id=0, timeout=DEFAULT_OMCI_TIMEOUT):
+ def send_mib_reset(self, timeout=DEFAULT_OMCI_TIMEOUT):
+ """
+ Perform a MIB Reset
+ """
self.log.debug('send-mib-reset')
- frame = OmciFrame(
- transaction_id=self._get_tx_tid(),
- message_type=OmciMibReset.message_id,
- omci_message=OmciMibReset(
- entity_class=OntData.class_id,
- entity_id=entity_id
- )
- )
+
+ frame = OntDataFrame().mib_reset()
return self.send(frame, timeout)
def send_mib_upload(self, timeout=DEFAULT_OMCI_TIMEOUT):
self.log.debug('send-mib-upload')
- frame = OmciFrame(
- transaction_id=self._get_tx_tid(),
- message_type=OmciMibUpload.message_id,
- omci_message=OmciMibUpload(
- entity_class=OntData.class_id,
- entity_id=0
- )
- )
+
+ frame = OntDataFrame().mib_upload()
return self.send(frame, timeout)
def send_mib_upload_next(self, seq_no, timeout=DEFAULT_OMCI_TIMEOUT):
self.log.debug('send-mib-upload-next')
- frame = OmciFrame(
- transaction_id=self._get_tx_tid(),
- message_type=OmciMibUploadNext.message_id,
- omci_message=OmciMibUploadNext(
- entity_class=OntData.class_id,
- entity_id=0,
- command_sequence_number=seq_no
- )
- )
+
+ frame = OntDataFrame(seq_no).mib_upload_next()
+ return self.send(frame, timeout)
+
+ def send_reboot(self, timeout=DEFAULT_OMCI_TIMEOUT):
+ """
+ Send an ONU Device reboot request (ONU-G ME).
+ """
+ self.log.debug('send-mib-reboot')
+
+ frame = OntGFrame().reboot()
return self.send(frame, timeout)
diff --git a/voltha/adapters/adtran_onu/omci/omci_entities.py b/voltha/adapters/adtran_onu/omci/omci_entities.py
index f38f55b..62c8516 100644
--- a/voltha/adapters/adtran_onu/omci/omci_entities.py
+++ b/voltha/adapters/adtran_onu/omci/omci_entities.py
@@ -70,7 +70,7 @@
mandatory_operations = {OP.Get}
-class FlexibleConfigurationStatusPortal(EntityClass):
+class FCPortalOrSraStat(EntityClass):
class_id = 65420
attributes = [
ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
@@ -78,10 +78,9 @@
mandatory_operations = {OP.Get, OP.Set, OP.Create, OP.Delete}
-class ONU3G(EntityClass):
+class Onu3gOrInvStat2(EntityClass):
class_id = 65422
attributes = [
- # TODO: Fix access for all attributes below
ECA(ShortField("managed_entity_id", None), {AA.R, AA.SBC}),
]
mandatory_operations = {OP.Set, OP.Get, OP.Create, OP.Delete}
diff --git a/voltha/adapters/adtran_onu/omci/omci_me.py b/voltha/adapters/adtran_onu/omci/omci_me.py
index c3716cf..6b26ad5 100644
--- a/voltha/adapters/adtran_onu/omci/omci_me.py
+++ b/voltha/adapters/adtran_onu/omci/omci_me.py
@@ -494,7 +494,7 @@
"""
This managed entity represents the ONU as equipment.
"""
- def __init__(self, attributes):
+ def __init__(self, attributes=None):
"""
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
@@ -509,7 +509,7 @@
"""
This managed entity contains additional attributes associated with a PON ONU.
"""
- def __init__(self, attributes):
+ def __init__(self, attributes=None):
"""
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
@@ -643,3 +643,22 @@
super(VlanTaggingFilterDataFrame, self).__init__(VlanTaggingFilterData,
entity_id,
data)
+
+
+class OntDataFrame(MEFrame):
+ """
+ This managed entity models the MIB itself
+ """
+ def __init__(self, mib_data_sync=None):
+ """
+ :param mib_data_sync: (int) This attribute is used to check the alignment
+ of the MIB of the ONU with the corresponding MIB
+ in the OLT. (0..255)
+ """
+ self.check_type(mib_data_sync, (int, type(None)))
+ if mib_data_sync is not None and not 0 <= mib_data_sync <= 255:
+ raise ValueError('mib_data_sync should be 0..255')
+
+ data = {'mib_data_sync': mib_data_sync} if mib_data_sync is not None else None
+
+ super(OntDataFrame, self).__init__(OntData, 0, data)
diff --git a/voltha/adapters/adtran_onu/pon_port.py b/voltha/adapters/adtran_onu/pon_port.py
index 4b6c044..b9c82ba 100644
--- a/voltha/adapters/adtran_onu/pon_port.py
+++ b/voltha/adapters/adtran_onu/pon_port.py
@@ -338,6 +338,8 @@
returnValue('not-enabled')
device = None
+ seq_no = 0
+ number_of_commands = 0
omci = self._handler.omci
if self._handler.is_mock:
@@ -354,7 +356,6 @@
results = yield omci.send_mib_reset()
status = results.fields['omci_message'].fields['success_code']
assert status == 0, 'Unexpected MIB reset response status: {}'.format(status)
-
# TODO: On a real system, need to flush the external MIB database
# TODO: Also would need to watch for any AVC being handled between the MIB reset and the DB flush
self.mib_data_store = dict()
@@ -380,7 +381,6 @@
pass
# Successful if here
-
device.reason = 'MIB Synchronization Complete'
self._handler.adapter_agent.update_device(device)
@@ -389,7 +389,7 @@
self.log.info('mib-synchronized')
except TimeoutError as e:
- self.log.warn('mib-upload', e=e)
+ self.log.warn('mib-upload', e=e, seq_no=seq_no, number_of_commands=number_of_commands)
if device is not None:
device.reason = 'mib-upload-failure: Response Timeout'