Dual-tag support for individual ONU gemport selection
Change-Id: I71b158c9a683a6b79df5a2b2719aff2ca7bfb1f3
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index 26c411b..8f6ed6c 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -627,13 +627,8 @@
self.startup = port.start()
results = yield self.startup
- if reconciling:
- start_downlinks = device.admin_state == AdminState.ENABLED
- else:
- start_downlinks = self.autoactivate
-
for port in self.southbound_ports.itervalues():
- self.startup = port.start() if start_downlinks else port.stop()
+ self.startup = port.start() if port.admin_state == AdminState.ENABLED else port.stop()
results = yield self.startup
returnValue(results)
diff --git a/voltha/adapters/adtran_olt/adtran_olt.py b/voltha/adapters/adtran_olt/adtran_olt.py
index e4f70a4..1b66359 100644
--- a/voltha/adapters/adtran_olt/adtran_olt.py
+++ b/voltha/adapters/adtran_olt/adtran_olt.py
@@ -50,8 +50,8 @@
self.config = config
self.descriptor = Adapter(
id=self.name,
- vendor='Adtran Inc.',
- version='0.3',
+ vendor='Adtran, Inc.',
+ version='0.4',
config=AdapterConfig(log_level=LogLevel.INFO)
)
log.debug('adtran_olt.__init__', adapter_agent=adapter_agent)
@@ -200,25 +200,73 @@
return device
def download_image(self, device, request):
+ """
+ This is called to request downloading a specified image into
+ the standby partition of a device based on a NBI call.
+ This call is expected to be non-blocking.
+ :param device: A Voltha.Device object.
+ A Voltha.ImageDownload object.
+ :return: (Deferred) Shall be fired to acknowledge the download.
+ """
log.info('image_download', device=device, request=request)
raise NotImplementedError()
def get_image_download_status(self, device, request):
+ """
+ This is called to inquire about a requested image download
+ status based on a NBI call.
+ The adapter is expected to update the DownloadImage DB object
+ with the query result
+ :param device: A Voltha.Device object.
+ A Voltha.ImageDownload object.
+ :return: (Deferred) Shall be fired to acknowledge
+ """
log.info('get_image_download', device=device, request=request)
raise NotImplementedError()
def cancel_image_download(self, device, request):
+ """
+ This is called to cancel a requested image download
+ based on a NBI call. The admin state of the device will not
+ change after the download.
+ :param device: A Voltha.Device object.
+ A Voltha.ImageDownload object.
+ :return: (Deferred) Shall be fired to acknowledge
+ """
log.info('cancel_image_download', device=device)
raise NotImplementedError()
def activate_image_update(self, device, request):
- log.info('activate_image_update', device=device, \
- request=request)
+ """
+ This is called to activate a downloaded image from
+ a standby partition into active partition.
+ Depending on the device implementation, this call
+ may or may not cause device reboot.
+ If no reboot, then a reboot is required to make the
+ activated image running on device
+ This call is expected to be non-blocking.
+ :param device: A Voltha.Device object.
+ A Voltha.ImageDownload object.
+ :return: (Deferred) OperationResponse object.
+ """
+ log.info('activate_image_update', device=device, request=request)
raise NotImplementedError()
def revert_image_update(self, device, request):
- log.info('revert_image_update', device=device, \
- request=request)
+ """
+ This is called to deactivate the specified image at
+ active partition, and revert to previous image at
+ standby partition.
+ Depending on the device implementation, this call
+ may or may not cause device reboot.
+ If no reboot, then a reboot is required to make the
+ previous image running on device
+ This call is expected to be non-blocking.
+ :param device: A Voltha.Device object.
+ A Voltha.ImageDownload object.
+ :return: (Deferred) OperationResponse object.
+ """
+ log.info('revert_image_update', device=device, request=request)
raise NotImplementedError()
def self_test_device(self, device):
@@ -393,7 +441,7 @@
"""
log.info('create-interface', data=data)
handler = self.devices_handlers[device.id]
- handler.create_interface(device, data)
+ handler.create_interface(data)
def update_interface(self, device, data):
"""
@@ -401,7 +449,8 @@
in the devices
"""
log.info('update-interface', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.update_interface(data)
def remove_interface(self, device, data):
"""
@@ -409,9 +458,10 @@
in the devices
"""
log.info('remove-interface', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.remove_interface(data)
- def receive_onu_detect_state(self, device_id, state):
+ def receive_onu_detect_state(self, proxy_address, state):
"""
Receive onu detect state in ONU adapter
:param proxy_address: ONU device address
@@ -421,52 +471,142 @@
raise NotImplementedError()
def create_tcont(self, device, tcont_data, traffic_descriptor_data):
+ """
+ API to create tcont object in the devices
+ :param device: device id
+ :tcont_data: tcont data object
+ :traffic_descriptor_data: traffic descriptor data object
+ :return: None
+ """
log.info('create-tcont', tcont_data=tcont_data,
traffic_descriptor_data=traffic_descriptor_data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.create_tcont(tcont_data, traffic_descriptor_data)
def update_tcont(self, device, tcont_data, traffic_descriptor_data):
+ """
+ API to update tcont object in the devices
+ :param device: device id
+ :tcont_data: tcont data object
+ :traffic_descriptor_data: traffic descriptor data object
+ :return: None
+ """
log.info('update-tcont', tcont_data=tcont_data,
traffic_descriptor_data=traffic_descriptor_data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.update_tcont(tcont_data, traffic_descriptor_data)
def remove_tcont(self, device, tcont_data, traffic_descriptor_data):
+ """
+ API to delete tcont object in the devices
+ :param device: device id
+ :tcont_data: tcont data object
+ :traffic_descriptor_data: traffic descriptor data object
+ :return: None
+ """
log.info('remove-tcont', tcont_data=tcont_data,
traffic_descriptor_data=traffic_descriptor_data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.remove_tcont(tcont_data, traffic_descriptor_data)
def create_gemport(self, device, data):
+ """
+ API to create gemport object in the devices
+ :param device: device id
+ :data: gemport data object
+ :return: None
+ """
log.info('create-gemport', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.create_gemport(data)
def update_gemport(self, device, data):
+ """
+ API to update gemport object in the devices
+ :param device: device id
+ :data: gemport data object
+ :return: None
+ """
log.info('update-gemport', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.update_gemport(data)
def remove_gemport(self, device, data):
+ """
+ API to delete gemport object in the devices
+ :param device: device id
+ :data: gemport data object
+ :return: None
+ """
log.info('remove-gemport', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.remove_gemport(data)
def create_multicast_gemport(self, device, data):
+ """
+ API to create multicast gemport object in the devices
+ :param device: device id
+ :data: multicast gemport data object
+ :return: None
+ """
log.info('create-mcast-gemport', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.create_multicast_gemport(data)
def update_multicast_gemport(self, device, data):
+ """
+ API to update multicast gemport object in the devices
+ :param device: device id
+ :data: multicast gemport data object
+ :return: None
+ """
log.info('update-mcast-gemport', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.update_multicast_gemport(data)
def remove_multicast_gemport(self, device, data):
+ """
+ API to delete multicast gemport object in the devices
+ :param device: device id
+ :data: multicast gemport data object
+ :return: None
+ """
log.info('remove-mcast-gemport', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.remove_multicast_gemport(data)
def create_multicast_distribution_set(self, device, data):
+ """
+ API to create multicast distribution rule to specify
+ the multicast VLANs that ride on the multicast gemport
+ :param device: device id
+ :data: multicast distribution data object
+ :return: None
+ """
log.info('create-mcast-distribution-set', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.create_multicast_distribution_set(data)
def update_multicast_distribution_set(self, device, data):
+ """
+ API to update multicast distribution rule to specify
+ the multicast VLANs that ride on the multicast gemport
+ :param device: device id
+ :data: multicast distribution data object
+ :return: None
+ """
log.info('update-mcast-distribution-set', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.create_multicast_distribution_set(data)
def remove_multicast_distribution_set(self, device, data):
+ """
+ API to delete multicast distribution rule to specify
+ the multicast VLANs that ride on the multicast gemport
+ :param device: device id
+ :data: multicast distribution data object
+ :return: None
+ """
log.info('remove-mcast-distribution-set', data=data)
- raise NotImplementedError()
+ handler = self.devices_handlers[device.id]
+ handler.create_multicast_distribution_set(data)
diff --git a/voltha/adapters/adtran_olt/adtran_olt_handler.py b/voltha/adapters/adtran_olt/adtran_olt_handler.py
index fd90bf2..a4cba1c 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -19,6 +19,8 @@
from twisted.internet.defer import returnValue, inlineCallbacks, succeed
from adtran_device_handler import AdtranDeviceHandler
+from tcont import TCont, TrafficDescriptor, BestEffort
+from gem_port import GemPort
from codec.olt_state import OltState
from flow.flow_entry import FlowEntry
from net.adtran_zmq import AdtranZmqClient
@@ -71,13 +73,17 @@
# xPON config dictionaries
- self._channel_groups = {} # Name -> dict
- self._channel_partitions = {} # Name -> dict
- self._channel_pairs = {} # Name -> dict
- self._channel_terminations = {} # Name -> dict
- self._v_ont_anis = {} # Name -> dict
- self._ont_anis = {} # Name -> dict
- self._v_enets = {} # Name -> dict
+ self._channel_groups = {} # Name -> dict
+ self._channel_partitions = {} # Name -> dict
+ self._channel_pairs = {} # Name -> dict
+ self._channel_terminations = {} # Name -> dict
+ self._v_ont_anis = {} # Name -> dict
+ self._ont_anis = {} # Name -> dict
+ self._v_enets = {} # Name -> dict
+ self._tconts = {} # Name -> dict
+ self._traffic_descriptors = {} # Name -> dict
+ self._gem_ports = {} # Name -> dict
+ self._cached_xpon_pon_info = {} # PON-id -> dict
def __del__(self):
# OLT Specific things here.
@@ -242,7 +248,7 @@
admin_state=admin_state)
# TODO: For now, limit number of PON ports to make debugging easier
- if self.autoactivate and len(self.southbound_ports) >= self.max_pon_ports:
+ if len(self.southbound_ports) >= self.max_pon_ports:
break
self.num_southbound_ports = len(self.southbound_ports)
@@ -446,7 +452,7 @@
# Now drop all flows from this device that were not in this bulk update
try:
- FlowEntry.drop_missing_flows(device.id, valid_flows)
+ yield FlowEntry.drop_missing_flows(device.id, valid_flows)
except Exception as e:
self.log.exception('bulk-flow-update-remove', e=e)
@@ -533,42 +539,87 @@
:param pon_id: (int) PON Identifier
:return: (dict) reduced xPON information for the specific PON port
"""
- terminations = {key: val for key, val in self._channel_terminations.iteritems()
- if val[pon_id_type] == pon_id}
+ if pon_id not in self._cached_xpon_pon_info:
- pair_names = set([term['channel-pair'] for term in terminations.itervalues()])
+ terminations = {key: val for key, val in self._channel_terminations.iteritems()
+ if val[pon_id_type] == pon_id}
- pairs = {key: val for key, val in self._channel_pairs.iteritems()
- if key in pair_names}
+ pair_names = set([term['channel-pair'] for term in terminations.itervalues()])
+ pairs = {key: val for key, val in self._channel_pairs.iteritems()
+ if key in pair_names}
- partition_names = set([pair['channel-partition'] for pair in pairs.itervalues()])
+ partition_names = set([pair['channel-partition'] for pair in pairs.itervalues()])
+ partitions = {key: val for key, val in self._channel_partitions.iteritems()
+ if key in partition_names}
- partitions = {key: val for key, val in self._channel_partitions.iteritems()
- if key in partition_names}
+ v_ont_anis = {key: val for key, val in self._v_ont_anis.iteritems()
+ if val['preferred-channel-pair'] in pair_names}
+ v_ont_ani_names = set(v_ont_anis.keys())
- v_ont_anis = {key: val for key, val in self._v_ont_anis.iteritems()
- if val['preferred-channel-pair'] in pair_names}
+ group_names = set(pair['channel-group'] for pair in pairs.itervalues())
+ groups = {key: val for key, val in self._channel_groups.iteritems()
+ if key in group_names}
- return {
- 'channel-terminations': terminations,
- 'channel-pairs': pairs,
- 'channel-partitions': partitions,
- 'v_ont_anis': v_ont_anis
- }
+ venets = {key: val for key, val in self._v_enets.iteritems()
+ if val['v-ont-ani'] in v_ont_ani_names}
- def create_interface(self, device, data):
+ tconts = {key: val for key, val in self._tconts.iteritems()
+ if val.vont_ani in v_ont_ani_names}
+ tcont_names = set(tconts.keys())
+
+ gem_ports = {key: val for key, val in self._gem_ports.iteritems()
+ if val.tconf_ref in tcont_names}
+
+ self._cached_xpon_pon_info[pon_id] = {
+ 'channel-terminations': terminations,
+ 'channel-pairs': pairs,
+ 'channel-partitions': partitions,
+ 'channel-groups': groups,
+ 'v-ont-anis': v_ont_anis,
+ 'v-enets': venets,
+ 'tconts': tconts,
+ 'gem-ports': gem_ports
+ }
+ return self._cached_xpon_pon_info[pon_id]
+
+ def _get_xpon_collection(self, data):
+ if isinstance(data, ChannelgroupConfig):
+ return self._channel_groups
+ elif isinstance(data, ChannelpartitionConfig):
+ return self._channel_partitions
+ elif isinstance(data, ChannelpairConfig):
+ return self._channel_pairs
+ elif isinstance(data, ChannelterminationConfig):
+ return self._channel_terminations
+ elif isinstance(data, OntaniConfig):
+ return self._ont_anis
+ elif isinstance(data, VOntaniConfig):
+ return self._v_ont_anis
+ elif isinstance(data, VEnetConfig):
+ return self._v_enets
+ return None
+
+ def create_interface(self, data):
"""
Create XPON interfaces
- :param device: (Device)
- :param data: (ChannelgroupConfig) Channel Group configuration
+ :param data: (xpon config info)
"""
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)
- self._channel_groups[name] = {
+
+ if name in items:
+ raise KeyError("Channel group '{}' already exists".format(name))
+
+ items[name] = {
'name': name,
'enabled': interface.enabled,
'system-id': inst_data.system_id,
@@ -578,6 +629,9 @@
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
@@ -589,7 +643,7 @@
DOT1X: 'don1x'
}.get(value, 'unknown')
- self._channel_partitions[name] = {
+ items[name] = {
'name': name,
'enabled': interface.enabled,
'authentication-method': _auth_method_enum_to_string(inst_data.authentication_method),
@@ -601,7 +655,11 @@
elif isinstance(data, ChannelpairConfig):
self.log.debug('create_interface-channel-pair', interface=interface, data=inst_data)
- self._channel_pairs[name] = {
+
+ if name in items:
+ raise KeyError("Channel pair '{}' already exists".format(name))
+
+ items[name] = {
'name': name,
'enabled': interface.enabled,
'channel-group': inst_data.channelgroup_ref,
@@ -611,7 +669,11 @@
elif isinstance(data, ChannelterminationConfig):
self.log.debug('create_interface-channel-termination', interface=interface, data=inst_data)
- self._channel_terminations[name] = {
+
+ if name in items:
+ raise KeyError("Channel termination '{}' already exists".format(name))
+
+ items[name] = {
'name': name,
'enabled': interface.enabled,
'xgs-ponid': inst_data.xgs_ponid,
@@ -623,7 +685,11 @@
elif isinstance(data, OntaniConfig):
self.log.debug('create_interface-ont-ani', interface=interface, data=inst_data)
- self._ont_anis[name] = {
+
+ if name in items:
+ raise KeyError("ONT ANI '{}' already exists".format(name))
+
+ items[name] = {
'name': name,
'enabled': interface.enabled,
'upstream-fec': inst_data.upstream_fec_indicator,
@@ -632,7 +698,11 @@
elif isinstance(data, VOntaniConfig):
self.log.debug('create_interface-v-ont-ani', interface=interface, data=inst_data)
- self._v_ont_anis[name] = {
+
+ if name in items:
+ raise KeyError("vONT ANI '{}' already exists".format(name))
+
+ items[name] = {
'name': name,
'enabled': interface.enabled,
'onu-id': inst_data.onu_id,
@@ -644,7 +714,11 @@
elif isinstance(data, VEnetConfig):
self.log.debug('create_interface-v-enet', interface=interface, data=inst_data)
- self._v_enets[name] = {
+
+ if name in items:
+ raise KeyError("vENET '{}' already exists".format(name))
+
+ items[name] = {
'name': name,
'enabled': interface.enabled,
'v-ont-ani': inst_data.v_ontani_ref
@@ -653,6 +727,46 @@
else:
raise NotImplementedError('Unknown data type')
+ def update_interface(self, data):
+ """
+ Update XPON interfaces
+ :param data: (xpon config info)
+ """
+ name = data.name
+ interface = data.interface
+ inst_data = data.data
+
+ items = self._get_xpon_collection(data)
+
+ if items is None:
+ raise ValueError('Unknown data type: {}'.format(type(data)))
+
+ if name not in items:
+ raise KeyError("'{}' not found. Type: {}".format(name, type(data)))
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+ raise NotImplementedError('TODO: not yet supported')
+
+ def delete_interface(self, data):
+ """
+ Deleete XPON interfaces
+ :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)
+
+ if item in items:
+ del items[name]
+ self._cached_xpon_pon_info = {} # Clear cached data
+
+ pass # TODO Do something....
+ raise NotImplementedError('TODO: not yet supported')
+
def on_channel_termination_config(self, name, operation, pon_type='xgs-ponid'):
supported_operations = ['create']
@@ -692,11 +806,187 @@
pon_port.xpon_name = name
pon_port.discovery_tick = polling_period
pon_port.authentication_method = authentication_method
- # pon_port.deployment_range = deployment_range
- # pon_port.fec_enable = downstream_fec
- # pon_port.mcast_aes = mcast_aes
+ # TODO: pon_port.deployment_range = deployment_range
+ # TODO: pon_port.fec_enable = downstream_fec
+ # TODO: pon_port.mcast_aes = mcast_aes
- if enabled:
- pon_port.start()
- else:
- pon_port.stop()
+ pon_port.admin_state = AdminState.ENABLED if enabled else AdminState.DISABLED
+
+ def create_tcont(self, tcont_data, traffic_descriptor_data):
+ """
+ Create TCONT information
+ :param tcont_data:
+ :param traffic_descriptor_data:
+ """
+ traffic_descriptor = TrafficDescriptor.create(traffic_descriptor_data)
+ tcont = TCont.create(tcont_data, traffic_descriptor)
+
+ if tcont.name in self._tconts:
+ raise KeyError("TCONT '{}' already exists".format(tcont.name))
+
+ if traffic_descriptor.name in self._traffic_descriptors:
+ raise KeyError("Traffic Descriptor '{}' already exists".format(traffic_descriptor.name))
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+
+ self._tconts[tcont.name] = tcont
+ self._traffic_descriptors[traffic_descriptor.name] = traffic_descriptor
+
+ def update_tcont(self, tcont_data, traffic_descriptor_data):
+ """
+ Update TCONT information
+ :param tcont_data:
+ :param traffic_descriptor_data:
+ """
+ if tcont_data.name not in self._tconts:
+ raise KeyError("TCONT '{}' does not exists".format(tcont_data.name))
+
+ if traffic_descriptor_data.name not in self._traffic_descriptors:
+ raise KeyError("Traffic Descriptor '{}' does not exists".
+ format(traffic_descriptor_data.name))
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+
+ traffic_descriptor = TrafficDescriptor.create(traffic_descriptor_data)
+ tcont = TCont.create(tcont_data, traffic_descriptor)
+ #
+ pass
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def remove_tcont(self, tcont_data, traffic_descriptor_data):
+ """
+ Remove TCONT information
+ :param tcont_data:
+ :param traffic_descriptor_data:
+ """
+ tcont = self._tconts.get(tcont_data.name)
+ traffic_descriptor = self._traffic_descriptors.get(traffic_descriptor_data.name)
+
+ if traffic_descriptor is not None:
+ del self._traffic_descriptors[traffic_descriptor_data.name]
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+ pass # Perform any needed operations
+ # raise NotImplementedError('TODO: Not yet supported')
+
+ if tcont is not None:
+ del self._tconts[tcont_data.name]
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+ pass # Perform any needed operations
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def create_gemport(self, data):
+ """
+ Create GEM Port
+ :param data:
+ """
+ gem_port = GemPort.create(data)
+
+ if gem_port.name in self._gem_ports:
+ raise KeyError("GEM Port '{}' already exists".format(gem_port.name))
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+ self._gem_ports[gem_port.name] = gem_port
+
+ # TODO: On GEM Port changes, may need to add ONU Flow(s)
+
+ def update_gemport(self, data):
+ """
+ Update GEM Port
+ :param data:
+ """
+ if data.name not in self._gem_ports:
+ raise KeyError("GEM Port '{}' does not exists".format(data.name))
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+ gem_port = GemPort.create(data)
+ #
+ # TODO: On GEM Port changes, may need to add/delete/modify ONU Flow(s)
+ pass
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def delete_gemport(self, data):
+ """
+ Delete GEM Port
+ :param data:
+ """
+ gem_port = self._gem_ports.get(data.name)
+
+ if gem_port is not None:
+ del self._gem_ports[data.name]
+
+ self._cached_xpon_pon_info = {} # Clear cached data
+ #
+ # TODO: On GEM Port changes, may need to delete ONU Flow(s)
+ pass # Perform any needed operations
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def create_multicast_gemport(self, data):
+ """
+ API to create multicast gemport object in the devices
+ :data: multicast gemport data object
+ :return: None
+ """
+ #
+ #
+ #
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def update_multicast_gemport(self, data):
+ """
+ API to update multicast gemport object in the devices
+ :data: multicast gemport data object
+ :return: None
+ """
+ #
+ #
+ #
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def remove_multicast_gemport(self, data):
+ """
+ API to delete multicast gemport object in the devices
+ :data: multicast gemport data object
+ :return: None
+ """
+ #
+ #
+ #
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def create_multicast_distribution_set(self, data):
+ """
+ API to create multicast distribution rule to specify
+ the multicast VLANs that ride on the multicast gemport
+ :data: multicast distribution data object
+ :return: None
+ """
+ #
+ #
+ #
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def update_multicast_distribution_set(self, data):
+ """
+ API to update multicast distribution rule to specify
+ the multicast VLANs that ride on the multicast gemport
+ :data: multicast distribution data object
+ :return: None
+ """
+ #
+ #
+ #
+ raise NotImplementedError('TODO: Not yet supported')
+
+ def remove_multicast_distribution_set(self, data):
+ """
+ API to delete multicast distribution rule to specify
+ the multicast VLANs that ride on the multicast gemport
+ :data: multicast distribution data object
+ :return: None
+ """
+ #
+ #
+ #
+ raise NotImplementedError('TODO: Not yet supported')
diff --git a/voltha/adapters/adtran_olt/flow/evc.py b/voltha/adapters/adtran_olt/flow/evc.py
index acea0ee..ee31788 100644
--- a/voltha/adapters/adtran_olt/flow/evc.py
+++ b/voltha/adapters/adtran_olt/flow/evc.py
@@ -194,7 +194,7 @@
def cancel_defers(self):
d, self._install_deferred = self._install_deferred, None
- if d is not None:
+ if d is not None and not d.called:
try:
d.cancel()
except:
diff --git a/voltha/adapters/adtran_olt/flow/evc_map.py b/voltha/adapters/adtran_olt/flow/evc_map.py
index e3a2753..6f54e57 100644
--- a/voltha/adapters/adtran_olt/flow/evc_map.py
+++ b/voltha/adapters/adtran_olt/flow/evc_map.py
@@ -55,10 +55,20 @@
EXPLICIT_PRIORITY = 1
DEFAULT = INHERIT_PRIORITY
+ @staticmethod
+ def xml(value):
+ if value is None:
+ value = EVCMap.PriorityOption.DEFAULT
+ if value == EVCMap.PriorityOption.INHERIT_PRIORITY:
+ return '<inherit-pri/>'
+ elif value == EVCMap.PriorityOption.EXPLICIT_PRIORITY:
+ return '<explicit-pri/>'
+ raise ValueError('Invalid PriorityOption enumeration')
+
def __init__(self, flow, evc, is_ingress_map):
self._flow = flow
self._evc = evc
- self._gem_ids = None
+ self._gem_ids_and_vid = None
self._is_ingress_map = is_ingress_map
self._installed = False
self._status_message = None
@@ -66,18 +76,19 @@
self._name = None
self._enabled = True
self._uni_port = None
- self._evc_connection = EVCMap.EvcConnection.NO_EVC_CONNECTION
+ self._evc_connection = EVCMap.EvcConnection.DEFAULT
self._evc_name = None
+ self._is_pon_port = None
- self._men_priority = EVCMap.PriorityOption.INHERIT_PRIORITY
+ self._men_priority = EVCMap.PriorityOption.DEFAULT
self._men_pri = 0 # If Explicit Priority
self._c_tag = None
- self._men_ctag_priority = EVCMap.PriorityOption.INHERIT_PRIORITY
+ self._men_ctag_priority = EVCMap.PriorityOption.DEFAULT
self._men_ctag_pri = 0 # If Explicit Priority
self._match_ce_vlan_id = None
- self._match_untagged = True
+ self._match_untagged = False
self._match_destination_mac_address = None
self._match_l2cp = False
self._match_broadcast = False
@@ -167,12 +178,12 @@
else:
xml += EVCMap.EvcConnection.xml(self._evc_connection)
- # if self._match_untagged:
- # xml += '<match-untagged>True</match-untagged>'
- if self._c_tag is not None:
- xml += '<ctag>{}</ctag>'.format(self._c_tag)
-
- # TODO: The following is not yet supported
+ xml += '<match-untagged>{}</match-untagged>'.format('true'
+ if self._match_untagged
+ else 'false')
+ # if self._c_tag is not None:
+ # xml += '<ctag>{}</ctag>'.format(self._c_tag)
+ # TODO: The following is not yet supported (and in some cases, not decoded)
# self._men_priority = EVCMap.PriorityOption.INHERIT_PRIORITY
# self._men_pri = 0 # If Explicit Priority
#
@@ -192,11 +203,21 @@
def _ingress_xml():
from ..onu import Onu
xml = '<evc-maps xmlns="http://www.adtran.com/ns/yang/adtran-evc-maps">'
- for onu_id, gem_ids in self._gem_ids.iteritems():
- for gem_id in gem_ids:
+ for onu_id, gem_ids_and_vid in self._gem_ids_and_vid.iteritems():
+ first_gem_id = True
+ vid = gem_ids_and_vid[1]
+
+ for gem_id in gem_ids_and_vid[0]:
xml += '<evc-map>'
xml += '<name>{}.{}.{}</name>'.format(self.name, onu_id, gem_id)
xml += '<ce-vlan-id>{}</ce-vlan-id>'.format(Onu.gem_id_to_gvid(gem_id))
+
+ if first_gem_id and vid is not None:
+ first_gem_id = False
+ xml += '<network-ingress-filter>'
+ xml += '<men-ctag>{}</men-ctag>'.format(vid) # Added in august 2017 model
+ xml += '</network-ingress-filter>'
+
xml += _common_xml()
xml += '</evc-map>'
xml += '</evc-maps>'
@@ -219,6 +240,9 @@
self._installed = results.ok
self.status = '' if results.ok else results.error
+ if self._pon_port is not None:
+ self._pon_port.add_pon_evc_map(self)
+
except Exception as e:
log.exception('install', name=self.name, e=e)
raise
@@ -235,8 +259,8 @@
xml = '<evc-maps xmlns="http://www.adtran.com/ns/yang/adtran-evc-maps"' + \
' xc:operation = "delete">'
- for onu_id, gem_ids in self._gem_ids.iteritems():
- for gem_id in gem_ids:
+ for onu_id, gem_ids_and_vid in self._gem_ids_and_vid.iteritems():
+ for gem_id in gem_ids_and_vid[0]:
xml += '<evc-map>'
xml += '<name>{}.{}.{}</name>'.format(self.name, onu_id, gem_id)
xml += '</evc-map>'
@@ -255,6 +279,9 @@
def _failure(results):
log.error('remove-failed', results=results)
+ if self._pon_port is not None:
+ self._pon_port.remove_pon_evc_map(self)
+
# TODO: create generator of XML once we have MANY to install at once
map_xml = _ingress_xml() if self._is_ingress_map else _egress_xml()
d = self._flow.handler.netconf_client.edit_config(map_xml, lock_timeout=30)
@@ -279,6 +306,50 @@
self._evc = None
returnValue('Done')
+ def add_onu(self, onu):
+ """
+ Add an ONU to a pon-wide EVC Map
+
+ :param onu: (Onu) ONU to add
+ :return: (defeered)
+ """
+ if self._pon_port is not None:
+ gem_ids = onu.gem_ids(True)
+ vid = onu.onu_vid
+ pass # TODO: Implement this
+
+ def remove_onu(self, onu):
+ """
+ Remove an ONU to a pon-wide EVC Map
+
+ :param onu: (Onu) ONU to add
+ :return: (defeered)
+ """
+ if self._pon_port is not None:
+ gem_ids = onu.gem_ids(True)
+ vid = onu.onu_vid
+ pass # TODO: Implement this
+
+ def add_gem_id(self, onu, gem_id):
+ """
+ Add a GEM ID to and existing EVC_MAP
+
+ :param onu: (Onu) ONU
+ :param gem_id: (Int) GEM ID
+ :return: (defeered)
+ """
+ pass # TODO: Implement this
+
+ def remove_gem_id(self, onu, gem_id):
+ """
+ Remove a GEM ID from and existing EVC_MAP
+
+ :param onu: (Onu) ONU
+ :param gem_id: (Int) GEM ID
+ :return: (defeered)
+ """
+ pass # TODO: Implement this
+
def _decode(self):
from evc import EVC
from flow_entry import FlowEntry
@@ -298,7 +369,7 @@
is_uni = flow.handler.is_uni_port(flow.in_port)
if is_pon or is_uni:
- self._uni_port = self._flow.handler.get_port_name(flow.in_port)
+ self._uni_port = flow.handler.get_port_name(flow.in_port)
self._evc.ce_vlan_preservation = False
else:
self._status_message = 'EVC-MAPS without UNI or PON ports are not supported'
@@ -323,15 +394,16 @@
pon_port = flow.handler.get_southbound_port(flow.in_port)
if pon_port is not None:
- self._gem_ids = pon_port.gem_ids(self._flow.onu_vid, self._needs_acl_support)
+ if flow.onu_vid is None:
+ self._pon_port = pon_port # EVC Map is for all ONUs on port
+
+ self._gem_ids_and_vid = pon_port.gem_ids(flow.onu_vid, self._needs_acl_support)
+
# TODO: Only EAPOL ACL support for the first demo
if self._needs_acl_support and self._eth_type != FlowEntry.EtherType.EAPOL.value:
- self._gem_ids = dict()
+ self._gem_ids_and_vid = dict()
- # if flow.vlan_id is None and flow.inner_vid is None:
- # self._match_untagged = True
- # else:
- # self._match_untagged = False
+ # self._match_untagged = flow.vlan_id is None and flow.inner_vid is None
self._c_tag = flow.inner_vid
# If a push of a single VLAN is present with a POP of the VLAN in the EVC's
diff --git a/voltha/adapters/adtran_olt/flow/flow_entry.py b/voltha/adapters/adtran_olt/flow/flow_entry.py
index 0cf59ba..b5bb36b 100644
--- a/voltha/adapters/adtran_olt/flow/flow_entry.py
+++ b/voltha/adapters/adtran_olt/flow/flow_entry.py
@@ -420,7 +420,7 @@
self.evc_map = None
self.evc = None
- returnValue(succeed('Done'))
+ returnValue('Done')
######################################################
# Bulk operations
diff --git a/voltha/adapters/adtran_olt/gem_port.py b/voltha/adapters/adtran_olt/gem_port.py
index 292890c..0be0d35 100644
--- a/voltha/adapters/adtran_olt/gem_port.py
+++ b/voltha/adapters/adtran_olt/gem_port.py
@@ -13,6 +13,7 @@
# limitations under the License.
import structlog
+from voltha.protos.bbf_fiber_gemport_body_pb2 import GemportsConfigData
log = structlog.get_logger()
@@ -25,11 +26,19 @@
encryption=False,
omci_transport=False,
multicast=False,
+ tcont_ref=None,
+ ident=None,
+ traffic_class=None,
+ intf_ref=None,
exception=False, # TODO: Debug only, remove in production
name=None):
self.name = name
self.gem_id = gem_id
- self.alloc_id = alloc_id
+ self._alloc_id = alloc_id
+ self.tconf_ref = tcont_ref
+ self.intf_ref = intf_ref
+ self.traffic_class = traffic_class
+ self.id = ident
self.encryption = encryption
self.omci_transport = omci_transport
self.multicast = multicast
@@ -40,6 +49,27 @@
self.alloc_id,
self.gem_id)
+ @staticmethod
+ def create(data):
+ assert isinstance(data, GemportsConfigData)
+
+ return GemPort(data.gemport_id, None,
+ encryption=data.aes_indicator,
+ tcont_ref=data.tcont_ref,
+ ident=data.id,
+ name=data.name,
+ traffic_class=data.traffic_class,
+ intf_ref=data.itf_ref) # v_enet
+
+ @property
+ def alloc_id(self):
+ if self._alloc_id is None:
+ #
+ # TODO: Resolve this (needs to be OLT handler)
+ #
+ pass
+ return self._alloc_id
+
def to_dict(self):
return {
'port-id': self.gem_id,
diff --git a/voltha/adapters/adtran_olt/onu.py b/voltha/adapters/adtran_olt/onu.py
index 35d60ec..bff3682 100644
--- a/voltha/adapters/adtran_olt/onu.py
+++ b/voltha/adapters/adtran_olt/onu.py
@@ -28,7 +28,7 @@
'PMC?': 'pmcs_onu', # TODO: Get actual VSSN for this vendor
'PSMO': 'ponsim_onu',
'SIM?': 'simulated_onu', # TODO: Get actual VSSN for this vendor
- 'TBT?': 'tibit_onu', # TODO: Get actual VSSN for this vendor
+ 'TBIT': 'tibit_onu',
}
@@ -64,6 +64,7 @@
self._pon = onu_info['pon']
self._name = '{}@{}'.format(self._pon.name, self._onu_id)
self._xpon_name = onu_info['xpon-name']
+ # TODO: Change to OrderedDict sorted by ascending gem-id
self._gem_ports = {} # gem-id -> GemPort
self._tconts = {} # alloc-id -> TCont
self._onu_vid = onu_info['onu-vid']
diff --git a/voltha/adapters/adtran_olt/pon_port.py b/voltha/adapters/adtran_olt/pon_port.py
index 47bf18f..1ab4a7b 100644
--- a/voltha/adapters/adtran_olt/pon_port.py
+++ b/voltha/adapters/adtran_olt/pon_port.py
@@ -66,6 +66,7 @@
self._onus = {} # serial_number-base64 -> ONU (allowed list)
self._onu_by_id = {} # onu-id -> ONU
self._next_onu_id = Onu.MIN_ONU_ID
+ self._pon_evc_map = {} # evc-map name -> EVC Map
self._admin_state = AdminState.DISABLED
self._oper_status = OperStatus.DISCOVERED
@@ -136,6 +137,19 @@
return self._state
@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 adapter_agent(self):
return self.olt.adapter_agent
@@ -151,7 +165,7 @@
if self.discovery_tick != value:
self._discovery_tick = value / 10
- if self._discovery_deferred is not None:
+ if self._discovery_deferred is not None and not self._discovery_deferred.called:
self._discovery_deferred.cancel()
self._discovery_deferred = None
@@ -430,12 +444,12 @@
:param onu_vid: (int) ONU VLAN ID if customer ONU specific. None if for all ONUs
on PON
:param exception_gems: (boolean) Select from special purpose ACL GEM-Portas
- :return: (dict) key -> onu-id, value -> frozenset of GEM Port IDs
+ :return: (dict) key -> onu-id, value -> tuple(frozenset of GEM Port IDs, onu_vid)
"""
gem_ids = {}
for onu_id, onu in self._onu_by_id.iteritems():
if onu_vid is None or onu_vid == onu.onu_vid:
- gem_ids[onu_id] = onu.gem_ids(exception_gems)
+ gem_ids[onu_id] = (onu.gem_ids(exception_gems), onu.onu_vid)
return gem_ids
def get_pon_config(self):
@@ -540,7 +554,7 @@
def _process_status_onu_discovered_list(self, discovered_onus):
"""
- Look for new or missing ONUs
+ Look for new ONUs
:param discovered_onus: (frozenset) Set of ONUs currently discovered
"""
@@ -555,9 +569,9 @@
my_onus = frozenset(self._onus.keys())
new_onus = discovered_onus - my_onus
- missing_onus = my_onus - discovered_onus
+ # TODO: Remove later if not needed -> missing_onus = my_onus - discovered_onus
- return new_onus, missing_onus
+ return new_onus, None # , missing_onus
def _get_onu_info(self, serial_number):
"""
@@ -572,21 +586,30 @@
onu_id = self.get_next_onu_id()
enabled = True
channel_speed = 0
+ tconts = get_tconts(serial_number, onu_id)
+ gem_ports = get_gem_ports(serial_number, onu_id)
elif self.activation_method == "autodiscovery":
if self.authentication_method == 'serial-number':
gpon_info = self.olt.get_xpon_info(self.pon_id)
try:
- vont_info = next(info for _, info in gpon_info['v_ont_anis'].items()
+ vont_info = next(info for _, info in gpon_info['v-ont-anis'].items()
if info.get('expected-serial-number') == serial_number)
onu_id = vont_info['onu-id']
enabled = vont_info['enabled']
channel_speed = vont_info['upstream-channel-speed']
+ tconts = {key: val for key, val in gpon_info['tconts'].iteritems()
+ if val.vont_ani == vont_info['name']}
+ tcont_names = set(tconts.keys())
+
+ gem_ports = {key: val for key, val in gpon_info['gem-ports'].iteritems()
+ if val.tconf_ref in tcont_names}
+
except StopIteration:
- return None
+ return None # Can happen if vont-ani has not yet been configured
else:
return None
else:
@@ -600,11 +623,13 @@
'enabled': enabled,
'upstream-channel-speed': channel_speed,
'password': Onu.DEFAULT_PASSWORD,
- 't-conts': get_tconts(self.pon_id, serial_number, onu_id),
- 'gem-ports': get_gem_ports(self.pon_id, serial_number, onu_id),
+ 't-conts': tconts,
+ 'gem-ports': gem_ports,
'onu-vid': self.olt.get_channel_id(self._pon_id, onu_id)
}
- return onu_info
+ # Hold off ONU activation until at least one GEM Port is defined.
+
+ return onu_info if len(gem_ports) > 0 else None
except Exception as e:
self.log.exception('get-onu-info', e=e)
@@ -640,6 +665,14 @@
yield onu.create(tconts, gem_ports)
self.activate_onu(onu)
+ if len(self._pon_evc_map) > 0:
+ # Add gem-id's to maps
+ dl = []
+ for evc_map in self._pon_evc_map.itervalues():
+ dl = evc_map.add_onu(onu)
+
+ yield defer.gatherResults(dl)
+
except Exception as e:
del self._onus[serial_number]
del self._onu_by_id[onu.onu_id]
@@ -677,19 +710,52 @@
if onu_id not in used_ids:
return onu_id
+ @inlineCallbacks
def delete_onu(self, onu_id):
uri = AdtranOltHandler.GPON_ONU_CONFIG_URI.format(self._pon_id, onu_id)
name = 'pon-delete-onu-{}-{}'.format(self._pon_id, onu_id)
+ onu = self._onu_by_id.get(onu_id)
+
# Remove from any local dictionary
if onu_id in self._onu_by_id:
del self._onu_by_id[onu_id]
for sn in [onu.serial_numbers for onu in self._onus.itervalues() if onu.onu_id == onu_id]:
del self._onus[sn]
+ try:
+ yield self._parent.rest_client.request('DELETE', uri, name=name)
+
+ except Exception as e:
+ self.log.exception('onu', serial_number=onu.serial_number, e=e)
+
+ if onu is not None and len(self._pon_evc_map) > 0:
+ # Drop gem-id's from any existing maps
+ dl = []
+ for evc_map in self._pon_evc_map.itervalues():
+ dl = evc_map.remove_onu(onu)
+ try:
+ yield defer.gatherResults(dl)
+
+ except Exception as e:
+ self.log.exception('maps', serial_number=onu.serial_number, e=e)
# TODO: Need removal from VOLTHA child_device method
- return self._parent.rest_client.request('DELETE', uri, name=name)
+ def add_pon_evc_map(self, evc_map):
+ """
+ Add an EVC MAP that covers all ONUs on a PON (typically control exception flows)
+ :param evc_map: (EVCMap) EVC Map
+ """
+ assert evc_map.name not in self._pon_evc_map
+ self._pon_evc_map[evc_map.name] = evc_map
+
+ def remove_pon_evc_map(self, evc_map):
+ """
+ Remove an EVC MAP that covers all ONUs on a PON (typically control exception flows)
+ :param evc_map: (EVCMap) EVC Map
+ """
+ if evc_map.name in self._pon_evc_map:
+ del self._pon_evc_map[evc_map.name]
@inlineCallbacks
def channel_partition(self, name, partition=0, xpon_system=0, operation=None):
diff --git a/voltha/adapters/adtran_olt/tcont.py b/voltha/adapters/adtran_olt/tcont.py
index 9b78f24..3dc4da0 100644
--- a/voltha/adapters/adtran_olt/tcont.py
+++ b/voltha/adapters/adtran_olt/tcont.py
@@ -14,6 +14,8 @@
import structlog
from enum import Enum
+from voltha.protos.bbf_fiber_tcont_body_pb2 import TcontsConfigData
+from voltha.protos.bbf_fiber_traffic_descriptor_profile_body_pb2 import TrafficDescriptorProfileData
log = structlog.get_logger()
@@ -22,24 +24,35 @@
"""
Class to wrap TCont capabilities
"""
- def __init__(self, alloc_id, traffic_descriptor, best_effort=None, name=None):
+ def __init__(self, alloc_id, traffic_descriptor, best_effort=None,
+ name=None, ident=None, vont_ani=None):
self.alloc_id = alloc_id
self.traffic_descriptor = traffic_descriptor
self.best_effort = best_effort
self.name = name
+ self.id = ident
+ self.vont_ani = vont_ani # (string) reference
def __str__(self):
return "TCont: {}, alloc-id: {}".format(self.name,self.alloc_id)
+ @staticmethod
+ def create(data, td):
+ assert isinstance(data, TcontsConfigData)
+ assert isinstance(td, TrafficDescriptor)
+
+ return TCont(data.alloc_id, td, best_effort=td.best_effort,
+ name=data.name, ident=data.id, vont_ani=data.interface_reference)
+
class TrafficDescriptor(object):
"""
Class to wrap the uplink traffic descriptor.
"""
class AdditionalBwEligibility(Enum):
- NON_ASSURED_SHARING = 1
- BEST_EFFORT_SHARING = 2
- NONE = 3
+ NONE = 0
+ BEST_EFFORT_SHARING = 1
+ NON_ASSURED_SHARING = 2 # Should match xpon.py values
DEFAULT = NONE
@staticmethod
@@ -50,11 +63,26 @@
TrafficDescriptor.AdditionalBwEligibility.NONE: "none"
}.get(value, "unknown")
+ @staticmethod
+ def from_value(value):
+ """
+ Matches both Adtran and xPON values
+ :param value:
+ :return:
+ """
+ return {
+ 0: TrafficDescriptor.AdditionalBwEligibility.NONE,
+ 1: TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING,
+ 2: TrafficDescriptor.AdditionalBwEligibility.NON_ASSURED_SHARING,
+ }.get(value, TrafficDescriptor.AdditionalBwEligibility.DEFAULT)
+
def __init__(self, fixed, assured, maximum,
additional=AdditionalBwEligibility.DEFAULT,
best_effort=None,
- name=None):
+ name=None,
+ ident=None):
self.name = name
+ self.id = ident
self.fixed_bandwidth = fixed # bps
self.assured_bandwidth = assured # bps
self.maximum_bandwidth = maximum # bps
@@ -69,6 +97,27 @@
self.assured_bandwidth,
self.maximum_bandwidth)
+ @staticmethod
+ def create(data):
+ assert isinstance(data, TrafficDescriptorProfileData)
+
+ additional = TrafficDescriptor.AdditionalBwEligibility.from_value(
+ data.additional_bw_eligibility_indicator)
+
+ if additional == TrafficDescriptor.AdditionalBwEligibility.BEST_EFFORT_SHARING:
+ best_effort = BestEffort(data.maximum_bandwidth,
+ data.priority,
+ data.weight)
+ else:
+ best_effort = None
+
+ return TrafficDescriptor(data.fixed_bandwidth, data.assured_bandwidth,
+ data.maximum_bandwidth,
+ name=data.name,
+ ident=data.id,
+ best_effort=best_effort,
+ additional=additional)
+
def to_dict(self):
val = {
'fixed-bandwidth': self.fixed_bandwidth,
@@ -99,8 +148,3 @@
'weight': self.weight
}
return val
-
-
-
-
-