Adtran ONU: Updated support for MIB Sync and refactored OMCI service delivery task
Change-Id: I915a46db0d7eca3d8b1d9fa5cf57efbed8f2b6ef
diff --git a/voltha/adapters/adtran_olt/flow/flow_entry.py b/voltha/adapters/adtran_olt/flow/flow_entry.py
index 6d5ea37..fa4fbed 100644
--- a/voltha/adapters/adtran_olt/flow/flow_entry.py
+++ b/voltha/adapters/adtran_olt/flow/flow_entry.py
@@ -19,7 +19,7 @@
from utility_evc import UtilityEVC
import voltha.core.flow_decomposer as fd
from voltha.core.flow_decomposer import *
-from voltha.protos.openflow_13_pb2 import OFPP_MAX
+from voltha.protos.openflow_13_pb2 import OFPP_MAX, OFPP_CONTROLLER
from twisted.internet.defer import returnValue, inlineCallbacks, gatherResults
log = structlog.get_logger()
@@ -55,19 +55,30 @@
Note: Since only E-LINE is supported, modification of an existing EVC is not performed.
"""
+ class PortType(IntEnum):
+ NNI = 0 # NNI Port
+ UNI = 1 # UNI Port
+ PON = 2 # PON Port (all UNIs on PON)
+ CONTROLLER = 3 # Controller port (packet in/out)
+
class FlowDirection(IntEnum):
UPSTREAM = 0 # UNI port to NNI Port
DOWNSTREAM = 1 # NNI port to UNI Port
- NNI = 2 # NNI port to NNI Port
- UNI = 3 # UNI port to UNI Port
- OTHER = 4 # Unable to determine
+ CONTROLLER_UNI = 2 # Trap packet on UNI and send to controller
+ NNI_PON = 3 # NNI port to PON Port (all UNIs) - perhaps multicast?
- _flow_dir_map = {
- (FlowDirection.UNI, FlowDirection.NNI): FlowDirection.UPSTREAM,
- (FlowDirection.NNI, FlowDirection.UNI): FlowDirection.DOWNSTREAM,
- (FlowDirection.UNI, FlowDirection.UNI): FlowDirection.UNI,
- (FlowDirection.NNI, FlowDirection.NNI): FlowDirection.NNI,
- }
+ # The following are not yet supported
+ CONTROLLER_NNI = 4 # Trap packet on NNI and send to controller
+ CONTROLLER_PON = 5 # Trap packet on all UNIs of a PON and send to controller
+ NNI_NNI = 6 # NNI port to NNI Port
+ UNI_UNI = 7 # UNI port to UNI Port
+ OTHER = 9 # Unable to determine
+ NNI = 10 # Deprecate in v2.0
+ UNI = 11 # Deprecate in v2.0
+
+ upstream_flow_types = {FlowDirection.UPSTREAM, FlowDirection.CONTROLLER_UNI}
+ downstream_flow_types = {FlowDirection.DOWNSTREAM, FlowDirection.NNI_PON}
+
LEGACY_CONTROL_VLAN = 4000
# Well known EtherTypes
@@ -76,6 +87,7 @@
IPv4 = 0x0800
IPv6 = 0x86DD
ARP = 0x0806
+ LLDP = 0x88CC
# Well known IP Protocols
class IpProtocol(IntEnum):
diff --git a/voltha/adapters/adtran_onu/adtran_onu.py b/voltha/adapters/adtran_onu/adtran_onu.py
index 58f635e..53addc2 100755
--- a/voltha/adapters/adtran_onu/adtran_onu.py
+++ b/voltha/adapters/adtran_onu/adtran_onu.py
@@ -27,6 +27,8 @@
from omci.adtn_capabilities_task import AdtnCapabilitiesTask
from omci.adtn_get_mds_task import AdtnGetMdsTask
from omci.adtn_mib_sync import AdtnMibSynchronizer
+from omci.adtn_mib_resync_task import AdtnMibResyncTask
+from omci.adtn_mib_reconcile_task import AdtnMibReconcileTask
from copy import deepcopy
_ = third_party
@@ -40,7 +42,7 @@
device_handler_class=AdtranOnuHandler,
name='adtran_onu',
vendor='Adtran Inc.',
- version='1.18',
+ version='1.19',
device_type='adtran_onu',
vendor_id='ADTN',
accepts_add_remove_flow_updates=False), # TODO: Support flow-mods
@@ -50,8 +52,9 @@
self.adtran_omci['mib-synchronizer']['state-machine'] = AdtnMibSynchronizer
self.adtran_omci['mib-synchronizer']['tasks']['get-mds'] = AdtnGetMdsTask
self.adtran_omci['mib-synchronizer']['tasks']['mib-audit'] = AdtnGetMdsTask
+ self.adtran_omci['mib-synchronizer']['tasks']['mib-resync'] = AdtnMibResyncTask
+ self.adtran_omci['mib-synchronizer']['tasks']['mib-reconcile'] = AdtnMibReconcileTask
self.adtran_omci['omci-capabilities']['tasks']['get-capabilities'] = AdtnCapabilitiesTask
-
# TODO: Continue to customize adtran_omci here as needed
self._omci_agent = OpenOMCIAgent(self.adapter_agent.core,
@@ -133,7 +136,7 @@
def receive_proxied_message(self, proxy_address, msg):
self.log.debug('receive-proxied-message', proxy_address=proxy_address,
- device_id=proxy_address.device_id, msg=binascii.hexlify(msg))
+ device_id=proxy_address.device_id, msg=binascii.hexlify(msg))
# Device_id from the proxy_address is the olt device id. We need to
# get the onu device id using the port number in the proxy_address
device = self.adapter_agent.get_child_device_with_proxy_address(proxy_address)
diff --git a/voltha/adapters/adtran_onu/adtran_onu_handler.py b/voltha/adapters/adtran_onu/adtran_onu_handler.py
index 86b9991..4d3b5db 100644
--- a/voltha/adapters/adtran_onu/adtran_onu_handler.py
+++ b/voltha/adapters/adtran_onu/adtran_onu_handler.py
@@ -82,6 +82,7 @@
# TODO: Some of these could be dynamically chosen
self.vlan_tcis_1 = 0x900
self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1
+ self.gal_enet_profile_entity_id = 0 # Was 0x100, but ONU seems to overwrite and use zero
# Assume no XPON support unless we get an vont-ani/ont-ani/venet create
self.xpon_support = False # xPON no longer available
@@ -354,11 +355,11 @@
if flow_entry.flow_id in self._flows:
valid_flows.add(flow_entry.flow_id)
- if flow_entry is None or flow_entry.flow_direction not in {FlowEntry.FlowDirection.UPSTREAM,
- FlowEntry.FlowDirection.DOWNSTREAM}:
+ if flow_entry is None or flow_entry.flow_direction not in {FlowEntry.upstream_flow_types,
+ FlowEntry.downstream_flow_types}:
continue
- is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+ is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types
# Ignore untagged upstream etherType flows. These are trapped at the
# OLT and the default flows during initial OMCI service download will
@@ -772,7 +773,6 @@
# If the PON has already synchronized, add the logical port now
# since we know we have been activated
-
if self._pon is not None and self.openomci.connected:
uni_port.add_logical_port(ofp_port_no, subscriber_vlan=subscriber_vlan)
diff --git a/voltha/adapters/adtran_onu/omci/adtn_capabilities_task.py b/voltha/adapters/adtran_onu/omci/adtn_capabilities_task.py
index 6be8fd7..6dbed03 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_capabilities_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_capabilities_task.py
@@ -35,14 +35,18 @@
'supported-message-types': {set of supported message types}
}
"""
+ name = "Adtran ONU Capabilities Task"
+
def __init__(self, omci_agent, device_id):
"""
Class initialization
- :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+ :param omci_agent: (OpenOMCIAgent) OMCI Adapter agent
:param device_id: (str) ONU Device ID
"""
super(AdtnCapabilitiesTask, self).__init__(omci_agent, device_id)
+
+ self.name = AdtnCapabilitiesTask.name
self._omci_managed = False # TODO: Look up capabilities/model number
@property
diff --git a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
index b5fcb40..5f155f1 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
@@ -44,7 +44,7 @@
"""
Class initialization
- :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+ :param omci_agent: (OpenOMCIAgent) OMCI Adapter agent
:param handler: (AdtranOnuHandler) ONU Handler
:param flow_entry: (FlowEntry) Flow to install
"""
@@ -59,7 +59,7 @@
self._flow_entry = flow_entry
# TODO: Cleanup below that is not needed
- is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+ is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types
uni_port = flow_entry.in_port if is_upstream else flow_entry.out_port
pon_port = flow_entry.out_port if is_upstream else flow_entry.in_port
diff --git a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
index 136e2e2..5b852a1 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
@@ -61,8 +61,8 @@
"""
Class initialization
- :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
- :param device_id: (str) ONU Device ID
+ :param omci_agent: (OpenOMCIAgent) OMCI Adapter agent
+ :param handler: (OnuHandler) ONU Device Handler
"""
super(AdtnMibDownloadTask, self).__init__(AdtnMibDownloadTask.name,
omci_agent,
@@ -90,7 +90,7 @@
#
self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
self._mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
- self._gal_enet_profile_entity_id = 0x100
+ self._gal_enet_profile_entity_id = self._handler.gal_enet_profile_entity_id
# Next to are specific TODO: UNI lookups here or uni specific install !!!
self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
diff --git a/voltha/adapters/adtran_onu/omci/adtn_mib_reconcile_task.py b/voltha/adapters/adtran_onu/omci/adtn_mib_reconcile_task.py
new file mode 100644
index 0000000..872cedb
--- /dev/null
+++ b/voltha/adapters/adtran_onu/omci/adtn_mib_reconcile_task.py
@@ -0,0 +1,184 @@
+#
+# Copyright 2018 the original author or authors.
+#
+# 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.
+#
+from twisted.internet.defer import returnValue
+from voltha.extensions.omci.omci_defs import *
+from voltha.extensions.omci.omci_entities import Ieee8021pMapperServiceProfile
+from voltha.extensions.omci.tasks.mib_reconcile_task import MibReconcileTask
+from voltha.extensions.omci.database.mib_db_api import ATTRIBUTES_KEY
+from twisted.internet.defer import inlineCallbacks
+from voltha.extensions.omci.omci_defs import ReasonCodes, EntityOperations
+from voltha.extensions.omci.omci_me import MEFrame
+
+OP = EntityOperations
+RC = ReasonCodes
+AA = AttributeAccess
+
+
+class AdtnMibReconcileTask(MibReconcileTask):
+ """
+ Adtran ONU OpenOMCI MIB Reconcile Task
+
+ For some crazy reason, the ADTRAN ONU does not report the IEEE802.1p Mapper ME
+ in the ONU upload even though it does exists. This results in an 'instance
+ exists' error when trying to create it on the ONU
+ """
+ name = "Adtran MIB Reconcile Task"
+
+ def __init__(self, omci_agent, device_id, diffs):
+ super(AdtnMibReconcileTask, self).__init__(omci_agent, device_id, diffs)
+
+ self.name = AdtnMibReconcileTask.name
+ self._me_130_okay = False # Set true once bug is fixed (auto detect)
+ self._omci_managed = False # Set once ONU Data tracking of MIB-Data-Sync supported
+
+ @inlineCallbacks
+ def fix_olt_only(self, olt, onu_db, olt_db):
+ """
+ Fix ME's that were only found on the OLT. For OLT only MEs there are
+ the following things that will be checked.
+
+ o ME's that do not have an OpenOMCI class decoder. These are stored
+ as binary blobs in the MIB database. Since the OLT will never
+ create these (all are learned from ONU), it is assumed the ONU
+ has removed them for some purpose. So delete them from the OLT
+ database.
+
+ o For ME's that are created by the ONU (no create/delete access), the
+ MEs 'may' not be on the ONU because of a reboot or an OLT created
+ ME was deleted and the ONU gratuitously removes it. So delete them
+ from the OLT database.
+
+ o For ME's that are created by the OLT/OpenOMCI, delete them from the
+ ONU
+
+ :param olt: (list(int,int)) List of tuples where (class_id, inst_id)
+ :param onu_db: (dict) ONU Database snapshot at time of audit
+ :param olt_db: (dict) OLT Database snapshot at time of audit
+
+ :return: (int, int) successes, failures
+ """
+ # Has IEEE 802.1p reporting Bug fixed?
+
+ if self._me_130_okay or Ieee8021pMapperServiceProfile.class_id in onu_db:
+ self._me_130_okay = True
+ returnValue(super(AdtnMibReconcileTask, self).fix_olt_only(olt, onu_db, olt_db))
+
+ ############################
+ # Base class handles all but ME 130
+ local_mes = {Ieee8021pMapperServiceProfile.class_id}
+ not_manual = [(cid, eid) for cid, eid in olt if cid not in local_mes]
+
+ results = yield super(AdtnMibReconcileTask, self).fix_olt_only(not_manual,
+ onu_db,
+ olt_db)
+ successes = results[0]
+ failures = results[1]
+
+ # If IEEE 802.1p mapper needs to be checked, do it manually as the IBONT 602
+ # manipulates it during MEF EVC/EVC-Map creation
+ for cid in local_mes:
+ class_entry = olt_db.get(cid, None)
+
+ if class_entry is not None:
+ entries = {k: v for k, v in class_entry.items() if isinstance(k, int)}
+ for eid, instance in entries.items():
+ try:
+ self.strobe_watchdog()
+ results = yield self.manual_verification(cid, eid, instance[ATTRIBUTES_KEY])
+ successes += results[0]
+ failures += results[1]
+
+ except Exception as _e:
+ failures += 1
+
+ returnValue((successes, failures))
+
+ @inlineCallbacks
+ def update_mib_data_sync(self):
+ """ IBONT version does not support MDS"""
+ if self._omci_managed:
+ results = yield super(AdtnMibReconcileTask, self).update_mib_data_sync()
+ returnValue(results)
+
+ returnValue((1, 0))
+
+ @inlineCallbacks
+ def manual_verification(self, cid, eid, attributes):
+ # Trim off read-only attributes from ones passed in
+
+ me_map = self._device.me_map
+ ro_set = {AA.R}
+ ro_attrs = {attr.field.name for attr in me_map[cid].attributes
+ if attr.access == ro_set}
+ attributes = {k: v for k, v in attributes.items() if k not in ro_attrs}
+ attributes_to_fix = dict()
+
+ try:
+ while len(attributes):
+ frame = MEFrame(me_map[cid], eid, attributes).get()
+ self.strobe_watchdog()
+ results = yield self._device.omci_cc.send(frame)
+ omci_message = results.fields['omci_message'].fields
+ status = omci_message['success_code']
+
+ if status == RC.UnknownEntity.value:
+ self.strobe_watchdog()
+ results = yield self.create_instance(me_map[cid], eid, attributes)
+ returnValue((results[0], results[1]))
+
+ if status != RC.Success.value:
+ self.log.error('manual-check-get-failed', cid=cid, eid=eid,
+ attributes=attributes, status=status)
+ returnValue((1, 0))
+
+ onu_attr = {k: v for k, v in omci_message['data'].items()}
+ attributes_to_fix.update({k: v for k, v in onu_attr.items()
+ if k in attributes and v != attributes[k]})
+ attributes = {k: v for k, v in attributes if k not in onu_attr.keys()}
+
+ if len(attributes_to_fix) > 0:
+ try:
+ frame = MEFrame(me_map[cid], eid, attributes_to_fix).set()
+ self.strobe_watchdog()
+ yield self._device.omci_cc.send(frame)
+ returnValue((1, 0))
+
+ except Exception as _e:
+ returnValue((0, 1))
+
+ except Exception as e:
+ self.log.exception('manual-check-failed', e=e, cid=cid, eid=eid)
+ raise
+
+ @inlineCallbacks
+ def create_instance(self, cid, eid, attributes):
+ try:
+ me_map = self._device.me_map
+ frame = MEFrame(me_map[cid], eid, attributes).create()
+
+ self.strobe_watchdog()
+ results = yield self._device.omci_cc.send(frame)
+ status = results.fields['omci_message'].fields['success_code']
+ if status == RC.Success.value or status == RC.InstanceExists.value:
+ returnValue((1, 0))
+
+ self.log.error('manual-check-create-failed', cid=cid, eid=eid,
+ attributes=attributes, status=status)
+ returnValue((0, 1))
+
+ except Exception as e:
+ self.log.exception('manual-check-failed', e=e, cid=cid, eid=eid)
+ raise
diff --git a/voltha/adapters/adtran_onu/omci/adtn_mib_resync_task.py b/voltha/adapters/adtran_onu/omci/adtn_mib_resync_task.py
new file mode 100644
index 0000000..500ee02
--- /dev/null
+++ b/voltha/adapters/adtran_onu/omci/adtn_mib_resync_task.py
@@ -0,0 +1,67 @@
+#
+# Copyright 2017 the original author or authors.
+#
+# 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.
+#
+from voltha.extensions.omci.tasks.mib_resync_task import MibResyncTask
+from voltha.extensions.omci.omci_entities import GalEthernetProfile, GemPortNetworkCtp, \
+ Ieee8021pMapperServiceProfile
+
+
+class AdtnMibResyncTask(MibResyncTask):
+ """
+ ADTRAN MIB resynchronization Task
+
+ The ADTRAN IBONT 602 does not report the current value of the GAL Ethernet
+ Payload size, it is always 0.
+
+ Also, the MEF EVC/EVC-MAP code monitors GEM Port CTP ME
+ """
+ def __init__(self, omci_agent, device_id):
+ """
+ Class initialization
+
+ :param omci_agent: (OpenOMCIAgent) OMCI Adapter agent
+ :param device_id: (str) ONU Device ID
+ """
+ super(AdtnMibResyncTask, self).__init__(omci_agent, device_id)
+ self.omci_fixed = False
+
+ def compare_mibs(self, db_copy, db_active):
+ """
+ Compare the our db_copy with the ONU's active copy
+
+ :param db_copy: (dict) OpenOMCI's copy of the database
+ :param db_active: (dict) ONU's database snapshot
+ :return: (dict), (dict), (list) Differences
+ """
+ on_olt_only, on_onu_only, attr_diffs = super(AdtnMibResyncTask, self).\
+ compare_mibs(db_copy, db_active)
+
+ if not self.omci_fixed:
+ # Exclude 'max_gem_payload_size' in GAL Ethernet Profile
+ attr_diffs = [attr for attr in attr_diffs
+ if attr[0] != GalEthernetProfile.class_id
+ or attr[2] != 'max_gem_payload_size']
+
+ # Exclude any changes to GEM Port Network CTP
+ attr_diffs = [attr for attr in attr_diffs
+ if attr[0] != GemPortNetworkCtp.class_id]
+
+ if on_olt_only is not None:
+ # Exclude IEEE 8021.p Mapper Service Profile from OLT Only as not
+ # reported in current IBONT 602 software
+ on_olt_only = [(cid, eid) for cid, eid in on_olt_only
+ if cid != Ieee8021pMapperServiceProfile.class_id]
+
+ return on_olt_only, on_onu_only, attr_diffs
diff --git a/voltha/adapters/adtran_onu/omci/adtn_mib_sync.py b/voltha/adapters/adtran_onu/omci/adtn_mib_sync.py
index 50494e6..b2d3db4 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_mib_sync.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_mib_sync.py
@@ -37,8 +37,31 @@
advertise_events=advertise_events,
audit_delay=AdtnMibSynchronizer.ADTN_AUDIT_DELAY,
resync_delay=AdtnMibSynchronizer.ADTN_RESYNC_DELAY)
+ self._first_in_sync = True
self._omci_managed = False # TODO: Look up model number/check handler
+ def increment_mib_data_sync(self):
+ if self._omci_managed:
+ super(AdtnMibSynchronizer, self).increment_mib_data_sync()
+
+ # IBONT 602 does not support MDS
+ self._mib_data_sync = 0
+
+ def on_enter_in_sync(self):
+ """ Early first sync """
+ if not self._omci_managed:
+ # IBONT 602 does not support MDS, accelerate first forced resync
+ # after a MIB reset occurs or on first startup
+ if self._first_in_sync:
+ self._first_in_sync = False
+ # self._audit_delay = 10 # Re-enable after BBWF
+ # self._resync_delay = 10
+ else:
+ self._audit_delay = MibSynchronizer.DEFAULT_AUDIT_DELAY
+ self._resync_delay = AdtnMibSynchronizer.ADTN_RESYNC_DELAY
+
+ super(AdtnMibSynchronizer, self).on_enter_in_sync()
+
def on_enter_auditing(self):
"""
Perform a MIB Audit. If our last MIB resync was too long in the
@@ -56,3 +79,7 @@
def _check_if_mib_data_sync_supported(self):
return False # TODO: Look up to see if we are/check handler
+
+ def on_mib_reset_response(self, topic, msg):
+ self._first_in_sync = True
+ super(AdtnMibSynchronizer, self).on_mib_reset_response(topic, msg)
diff --git a/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py b/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py
index 9ca1c19..214a5aa 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py
@@ -47,7 +47,7 @@
"""
Class initialization
- :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+ :param omci_agent: (OpenOMCIAgent) OMCI Adapter agent
:param handler: (AdtranOnuHandler) ONU Handler
:param flow_entry: (FlowEntry) Flow to install
"""
@@ -66,7 +66,7 @@
# self._input_tpid = AdtnRemoveFlowTask.default_tpid
# self._output_tpid = AdtnRemoveFlowTask.default_tpid
- is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+ is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types
# uni_port = flow_entry.in_port if is_upstream else flow_entry.out_port
pon_port = flow_entry.out_port if is_upstream else flow_entry.in_port
diff --git a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
index 6dc510a..e4839e5 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
@@ -55,12 +55,13 @@
task_priority = Task.DEFAULT_PRIORITY + 10
default_tpid = 0x8100 # TODO: Move to a better location
name = "ADTRAN Service Download Task"
+ free_tcont_alloc_id = 0xFFFF
def __init__(self, omci_agent, handler):
"""
Class initialization
- :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+ :param omci_agent: (OpenOMCIAgent) OMCI Adapter agent
:param device_id: (str) ONU Device ID
"""
super(AdtnServiceDownloadTask, self).__init__(AdtnServiceDownloadTask.name,
@@ -72,8 +73,8 @@
self._onu_device = omci_agent.get_device(handler.device_id)
self._local_deferred = None
self._pon = handler.pon_port()
+ self._extended_vlan_me_created = False
- # self._vlan_tcis_1 = self._handler.vlan_tcis_1
self._input_tpid = AdtnServiceDownloadTask.default_tpid
self._output_tpid = AdtnServiceDownloadTask.default_tpid
@@ -91,7 +92,7 @@
# TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
#
self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
- self._gal_enet_profile_entity_id = 0x100
+ self._gal_enet_profile_entity_id = self._handler.gal_enet_profile_entity_id
# Next to are specific
self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
@@ -196,6 +197,9 @@
except TimeoutError as e:
self.deferred.errback(failure.Failure(e))
+
+ except Exception as e:
+ self.deferred.errback(failure.Failure(e))
else:
# TODO: Provide better error reason, what was missing...
e = ServiceResourcesFailure('Required resources are not available')
@@ -216,17 +220,25 @@
# - ONU created TCONT (created on ONU startup)
tcont_idents = self._onu_device.query_mib(Tcont.class_id)
- self.log.debug('tcont-idents', tcont_idents=tcont_idents)
+ self.log.info('tcont-idents', tcont_idents=tcont_idents)
for tcont in self._pon.tconts.itervalues():
- free_entity_id = next((k for k, v in tcont_idents.items()
- if isinstance(k, int) and
- v.get('attributes', {}).get('alloc_id', 0) == 0xFFFF), None)
- if free_entity_id is None:
- self.log.error('no-available-tconts')
- break
- # TODO: Need to restore on failure. Need to check status/results
- yield tcont.add_to_hardware(omci_cc, free_entity_id)
+ if tcont.entity_id is None:
+ free_entity_id = next((k for k, v in tcont_idents.items()
+ if isinstance(k, int) and
+ v.get('attributes', {}).get('alloc_id', 0) ==
+ AdtnServiceDownloadTask.free_tcont_alloc_id), None)
+
+ if free_entity_id is None:
+ self.log.error('no-available-tconts')
+ raise ServiceResourcesFailure('No Available TConts')
+
+ try:
+ yield tcont.add_to_hardware(omci_cc, free_entity_id)
+
+ except Exception as e:
+ self.log.exception('tcont-set', e=e, eid=free_entity_id)
+ raise
################################################################################
# GEMS (GemPortNetworkCtp and GemInterworkingTp)
@@ -254,17 +266,20 @@
# - Ieee8021pMapperServiceProfile
# - GalEthernetProfile
#
-
for gem_port in self._pon.gem_ports.itervalues():
- tcont = gem_port.tcont
- if tcont is None:
- self.log.error('unknown-tcont-reference', gem_id=gem_port.gem_id)
- continue
- # TODO: Need to restore on failure. Need to check status/results
- yield gem_port.add_to_hardware(omci_cc,
- tcont.entity_id,
- self._ieee_mapper_service_profile_entity_id,
- self._gal_enet_profile_entity_id)
+ if not gem_port.in_hardware:
+ tcont = gem_port.tcont
+ if tcont is None:
+ raise Exception('unknown-tcont-reference', gem_id=gem_port.gem_id)
+
+ try:
+ yield gem_port.add_to_hardware(omci_cc,
+ tcont.entity_id,
+ self._ieee_mapper_service_profile_entity_id,
+ self._gal_enet_profile_entity_id)
+ except Exception as e:
+ self.log.exception('gem-add-failed', e=e, gem=gem_port)
+ raise
################################################################################
# Update the IEEE 802.1p Mapper Service Profile config
@@ -276,8 +291,11 @@
#
# TODO: All p-bits currently go to the one and only GEMPORT ID for now
gem_ports = self._pon.gem_ports
- gem_entity_ids = [gem_port.entity_id for _, gem_port in gem_ports.items()] \
- if len(gem_ports) else [OmciNullPointer]
+
+ if len(gem_ports):
+ gem_entity_ids = [gem_port.entity_id for _, gem_port in gem_ports.items()]
+ else:
+ gem_entity_ids = [OmciNullPointer]
frame = Ieee8021pMapperServiceProfileFrame(
self._ieee_mapper_service_profile_entity_id, # 802.1p mapper Service Mapper Profile ID
@@ -307,6 +325,7 @@
).create()
results = yield omci_cc.send(frame)
self.check_status_and_state(results, 'create-extended-vlan-tagging-operation-configuration-data')
+ self._extended_vlan_me_created = True
################################################################################
# Update Extended VLAN Tagging Operation Config Data
@@ -367,10 +386,12 @@
except TimeoutError as e:
self.log.warn('rx-timeout-download', frame=hexlify(frame))
+ self.cleanup_on_error()
raise
except Exception as e:
self.log.exception('omci-setup-2', e=e)
+ self.cleanup_on_error()
raise
returnValue(None)
@@ -411,11 +432,39 @@
returnValue(None)
+ @inlineCallbacks
+ def cleanup_on_error(self):
+ omci_cc = self._onu_device.omci_cc
+ if self._extended_vlan_me_created:
+ try:
+ eid = self._mac_bridge_service_profile_entity_id
+ frame = ExtendedVlanTaggingOperationConfigurationDataFrame(eid).delete()
+ results = yield omci_cc.send(frame)
+ status = results.fields['omci_message'].fields['success_code']
+ self.log.debug('delete-extended-vlan-me', status=status)
+ except Exception as e:
+ self.log.exception('extended-vlan-cleanup', e=e)
+ # Continue processing
+ for gem_port in self._pon.gem_ports.itervalues():
+ if gem_port.in_hardware:
+ try:
+ yield gem_port.remove_from_hardware(omci_cc)
+ except Exception as e:
+ self.log.exception('gem-port-cleanup', e=e)
+ # Continue processing
+ for tcont in self._pon.tconts.itervalues():
+ if tcont.entity_id != AdtnServiceDownloadTask.free_tcont_alloc_id:
+ try:
+ yield tcont.remove_from_hardware(omci_cc)
+ except Exception as e:
+ self.log.exception('tcont-cleanup', e=e)
+ # Continue processing
+ returnValue('Cleanup Complete')
diff --git a/voltha/adapters/adtran_onu/omci/omci.py b/voltha/adapters/adtran_onu/omci/omci.py
index 15244b8..5966d2c 100644
--- a/voltha/adapters/adtran_onu/omci/omci.py
+++ b/voltha/adapters/adtran_onu/omci/omci.py
@@ -262,21 +262,18 @@
from voltha.extensions.omci.omci_cc import OMCI_CC, OmciCCRxEvents
# OMCI MIB Database sync status
-
bus = self._onu_omci_device.event_bus
topic = OnuDeviceEntry.event_bus_topic(self._handler.device_id,
OnuDeviceEvents.MibDatabaseSyncEvent)
self._in_sync_subscription = bus.subscribe(topic, self.in_sync_handler)
# OMCI Capabilities (MEs and Message Types
-
bus = self._onu_omci_device.event_bus
topic = OnuDeviceEntry.event_bus_topic(self._handler.device_id,
OnuDeviceEvents.OmciCapabilitiesEvent)
self._capabilities_subscription = bus.subscribe(topic, self.capabilities_handler)
# OMCI-CC Connectivity Events (for reachability/heartbeat)
-
bus = self._onu_omci_device.omci_cc.event_bus
topic = OMCI_CC.event_bus_topic(self._handler.device_id,
OmciCCRxEvents.Connectivity)
@@ -329,6 +326,7 @@
if self._capabilities_subscription is not None:
from adtn_mib_download_task import AdtnMibDownloadTask
from adtn_service_download_task import AdtnServiceDownloadTask
+ self._mib_download_task = None
def success(_results):
if self._mib_downloaded:
@@ -349,12 +347,13 @@
if not self._mib_downloaded:
self._mib_download_task = AdtnMibDownloadTask(self.omci_agent,
self._handler)
- else:
+ elif not self._service_downloaded:
self._mib_download_task = AdtnServiceDownloadTask(self.omci_agent,
self._handler)
- self._mib_download_deferred = \
- self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
- self._mib_download_deferred.addCallbacks(success, failure)
+ if self._mib_download_task is not None:
+ self._mib_download_deferred = \
+ self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
+ self._mib_download_deferred.addCallbacks(success, failure)
def onu_is_reachable(self, _topic, msg):
"""
diff --git a/voltha/adapters/adtran_onu/onu_gem_port.py b/voltha/adapters/adtran_onu/onu_gem_port.py
index 0e02ead..a8fe3ed 100644
--- a/voltha/adapters/adtran_onu/onu_gem_port.py
+++ b/voltha/adapters/adtran_onu/onu_gem_port.py
@@ -17,6 +17,7 @@
from voltha.adapters.adtran_olt.xpon.gem_port import GemPort
from twisted.internet.defer import inlineCallbacks, returnValue
from voltha.extensions.omci.omci_me import GemPortNetworkCtpFrame, GemInterworkingTpFrame
+from voltha.extensions.omci.omci_defs import ReasonCodes
class OnuGemPort(GemPort):
@@ -44,6 +45,8 @@
name=name,
handler=handler)
self._entity_id = entity_id
+ self._tcont_entity_id = None
+ self._interworking = False
self.log = structlog.get_logger(device_id=handler.device_id, gem_id=gem_id)
@property
@@ -54,6 +57,10 @@
def encryption(self):
return self._encryption
+ @property
+ def in_hardware(self):
+ return self._tcont_entity_id is not None and self._interworking
+
@encryption.setter
def encryption(self, value):
assert isinstance(value, bool), 'encryption is a boolean'
@@ -78,79 +85,110 @@
tcont_entity_id,
ieee_mapper_service_profile_entity_id,
gal_enet_profile_entity_id):
+
self.log.debug('add-to-hardware', gem_id=self.gem_id,
+ gem_entity_id=self.entity_id,
tcont_entity_id=tcont_entity_id,
ieee_mapper_service_profile_entity_id=ieee_mapper_service_profile_entity_id,
gal_enet_profile_entity_id=gal_enet_profile_entity_id)
- try:
- direction = "downstream" if self.multicast else "bi-directional"
- assert not self.multicast, 'MCAST is not supported yet'
- frame = GemPortNetworkCtpFrame(
+ if self._tcont_entity_id is not None and self._tcont_entity_id != tcont_entity_id:
+ raise KeyError('GEM Port already assigned to TCONT: {}'.format(self._tcont_entity_id))
+
+ results = None
+ if self._tcont_entity_id is None:
+ try:
+ direction = "downstream" if self.multicast else "bi-directional"
+ assert not self.multicast, 'MCAST is not supported yet'
+
+ frame = GemPortNetworkCtpFrame(
+ self.entity_id, # same entity id as GEM port
+ port_id=self.gem_id,
+ tcont_id=tcont_entity_id,
+ direction=direction,
+ upstream_tm=0x8000 # TM ID, 32768 unique ID set in TD set TODO: Parameterize
+ # This is Priority Queue ME with this entity ID
+ # and the ME's related port value is 0x01.00.0007
+ # which is slot=0x01, tcont# = 0x00, priority= 0x0007
+ ).create()
+ results = yield omci.send(frame)
+
+ status = results.fields['omci_message'].fields['success_code']
+ error_mask = results.fields['omci_message'].fields['parameter_error_attributes_mask']
+ self.log.debug('create-gem-port-network-ctp', status=status, error_mask=error_mask)
+
+ if status == ReasonCodes.Success or status == ReasonCodes.InstanceExists:
+ self._tcont_entity_id = tcont_entity_id
+ else:
+ raise Exception('GEM Port create failed with status: {}'.format(status))
+
+ except Exception as e:
+ self.log.exception('gemport-create', e=e)
+ raise
+
+ if not self._interworking:
+ try:
+ extra = {'gal_loopback_configuration': 0} # No loopback
+
+ frame = GemInterworkingTpFrame(
self.entity_id, # same entity id as GEM port
- port_id=self.gem_id,
- tcont_id=tcont_entity_id,
- direction=direction,
- upstream_tm=0x8000 # TM ID, 32768 unique ID set in TD set TODO: Parameterize
- # This is Priority Queue ME with this entity ID
- # and the ME's related port value is 0x01.00.0007
- # which is slot=0x01, tcont# = 0x00, priority= 0x0007
- ).create()
- results = yield omci.send(frame)
+ gem_port_network_ctp_pointer=self.entity_id,
+ interworking_option=5, # IEEE 802.1
+ service_profile_pointer=ieee_mapper_service_profile_entity_id,
+ interworking_tp_pointer=0x0,
+ pptp_counter=1,
+ gal_profile_pointer=gal_enet_profile_entity_id,
+ attributes=extra
+ ).create()
+ results = yield omci.send(frame)
- status = results.fields['omci_message'].fields['success_code']
- error_mask = results.fields['omci_message'].fields['parameter_error_attributes_mask']
- self.log.debug('create-gem-port-network-ctp', status=status, error_mask=error_mask)
+ status = results.fields['omci_message'].fields['success_code']
+ error_mask = results.fields['omci_message'].fields['parameter_error_attributes_mask']
+ self.log.debug('create-gem-interworking-tp', status=status, error_mask=error_mask)
- except Exception as e:
- self.log.exception('gemport-create', e=e)
- raise
+ if status == ReasonCodes.Success or status == ReasonCodes.InstanceExists:
+ self._interworking = True
+ else:
+ raise Exception('GEM Interworking create failed with status: {}'.format(status))
- try:
- frame = GemInterworkingTpFrame(
- self.entity_id, # same entity id as GEM port
- gem_port_network_ctp_pointer=self.entity_id,
- interworking_option=5, # IEEE 802.1
- service_profile_pointer=ieee_mapper_service_profile_entity_id,
- interworking_tp_pointer=0x0,
- pptp_counter=1,
- gal_profile_pointer=gal_enet_profile_entity_id
- ).create()
- results = yield omci.send(frame)
-
- status = results.fields['omci_message'].fields['success_code']
- error_mask = results.fields['omci_message'].fields['parameter_error_attributes_mask']
- self.log.debug('create-gem-interworking-tp', status=status, error_mask=error_mask)
-
- except Exception as e:
- self.log.exception('interworking-create', e=e)
- raise
+ except Exception as e:
+ self.log.exception('interworking-create', e=e)
+ raise
returnValue(results)
@inlineCallbacks
def remove_from_hardware(self, omci):
self.log.debug('remove-from-hardware', gem_id=self.gem_id)
- try:
- frame = GemInterworkingTpFrame(self.entity_id).delete()
- results = yield omci.send(frame)
- status = results.fields['omci_message'].fields['success_code']
- self.log.debug('delete-gem-interworking-tp', status=status)
+ results = None
+ if self._interworking:
+ try:
+ frame = GemInterworkingTpFrame(self.entity_id).delete()
+ results = yield omci.send(frame)
+ status = results.fields['omci_message'].fields['success_code']
+ self.log.debug('delete-gem-interworking-tp', status=status)
- except Exception as e:
- self.log.exception('interworking-delete', e=e)
- raise
+ if status == ReasonCodes.Success:
+ self._interworking = False
- try:
- frame = GemPortNetworkCtpFrame(self.entity_id).delete()
- results = yield omci.send(frame)
+ except Exception as e:
+ self.log.exception('interworking-delete', e=e)
+ raise
- status = results.fields['omci_message'].fields['success_code']
- self.log.debug('delete-gem-port-network-ctp', status=status)
+ if self._tcont_entity_id is not None:
+ try:
+ frame = GemPortNetworkCtpFrame(self.entity_id).delete()
+ results = yield omci.send(frame)
- except Exception as e:
- self.log.exception('gemport-delete', e=e)
- raise
+ status = results.fields['omci_message'].fields['success_code']
+ self.log.debug('delete-gem-port-network-ctp', status=status)
+
+ if status == ReasonCodes.Success:
+ self._tcont_entity_id = None
+
+ except Exception as e:
+ self.log.exception('gemport-delete', e=e)
+ raise
returnValue(results)
diff --git a/voltha/adapters/adtran_onu/onu_tcont.py b/voltha/adapters/adtran_onu/onu_tcont.py
index db97810..c94a339 100644
--- a/voltha/adapters/adtran_onu/onu_tcont.py
+++ b/voltha/adapters/adtran_onu/onu_tcont.py
@@ -18,12 +18,14 @@
from voltha.adapters.adtran_olt.xpon.tcont import TCont
from voltha.adapters.adtran_olt.xpon.traffic_descriptor import TrafficDescriptor
from voltha.extensions.omci.omci_me import TcontFrame
-
+from voltha.extensions.omci.omci_defs import ReasonCodes
class OnuTCont(TCont):
"""
Adtran ONU specific implementation
"""
+ free_tcont_alloc_id = 0xFFFF
+
def __init__(self, handler, alloc_id, traffic_descriptor, name=None, vont_ani=None):
super(OnuTCont, self).__init__(alloc_id, traffic_descriptor,
name=name, vont_ani=vont_ani)
@@ -50,15 +52,23 @@
def add_to_hardware(self, omci, tcont_entity_id):
self.log.debug('add-to-hardware', tcont_entity_id=tcont_entity_id)
- self._entity_id = tcont_entity_id
+ if self._entity_id == tcont_entity_id:
+ returnValue('Already set')
+
+ elif self.entity_id is not None:
+ raise KeyError('TCONT already assigned: {}'.format(self.entity_id))
try:
- frame = TcontFrame(self.entity_id, self.alloc_id).set()
+ frame = TcontFrame(tcont_entity_id, self.alloc_id).set()
results = yield omci.send(frame)
status = results.fields['omci_message'].fields['success_code']
+ if status == ReasonCodes.Success:
+ self._entity_id = tcont_entity_id
+
failed_attributes_mask = results.fields['omci_message'].fields['failed_attributes_mask']
unsupported_attributes_mask = results.fields['omci_message'].fields['unsupported_attributes_mask']
+
self.log.debug('set-tcont', status=status,
failed_attributes_mask=failed_attributes_mask,
unsupported_attributes_mask=unsupported_attributes_mask)
@@ -72,15 +82,16 @@
@inlineCallbacks
def remove_from_hardware(self, omci):
self.log.debug('remove-from-hardware', tcont_entity_id=self.entity_id)
-
- # Release tcont by setting alloc_id=0xFFFF
try:
- frame = TcontFrame(self.entity_id, 0xFFFF).set()
+ frame = TcontFrame(self.entity_id, OnuTCont.free_tcont_alloc_id).set()
results = yield omci.send(frame)
status = results.fields['omci_message'].fields['success_code']
self.log.debug('delete-tcont', status=status)
+ if status == ReasonCodes.Success:
+ self._entity_id = None
+
except Exception as e:
self.log.exception('tcont-delete', e=e)
raise