VOL-671: ADTRAN OLT support for new OLT drivers
Change-Id: I0c1d33f71e0dd3ebff6b467af1ee97db0943e37c
diff --git a/voltha/adapters/adtran_olt/README.md b/voltha/adapters/adtran_olt/README.md
index 1ba04f7..5ec8732 100644
--- a/voltha/adapters/adtran_olt/README.md
+++ b/voltha/adapters/adtran_olt/README.md
@@ -14,7 +14,6 @@
| -T | --rc_port | 8081 | REST TCP Port |
| -z | --zmq_port | 5656 | ZeroMQ OMCI Proxy Port |
| -M | --multicast_vlan | 4000 | Multicast VLANs (comma-delimeted) |
-| -V | --packet_in_vlan | 4000 | OpenFlow Packet-In/Out VLAN, Zero to disable |
| -v | --untagged_vlan | 4092 | VLAN wrapper for untagged ONU frames |
For example, if your Adtran OLT is address 10.17.174.193 with the default TCP ports and
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index f015856..6f28504 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -60,7 +60,7 @@
_DEFAULT_NETCONF_PASSWORD = ""
_DEFAULT_NETCONF_PORT = 830
-FIXED_ONU = True # Enhanced ONU support
+FIXED_ONU = False # TODO: Deprecate this. Enhanced ONU support
class AdtranDeviceHandler(object):
@@ -267,7 +267,7 @@
parser.add_argument('--utility_vlan', '-B', action='store',
default='{}'.format(DEFAULT_UTILITY_VLAN),
help='VLAN for Untagged Frames from ONUs'),
- parser.add_argument('--no_exception_gems', '-X', action='store_true', default=not FIXED_ONU,
+ parser.add_argument('--no_exception_gems', '-X', action='store_true', default=True,
help='Native OpenFlow Packet-In/Out support')
try:
args = parser.parse_args(shlex.split(device.extra_args))
@@ -331,32 +331,37 @@
self.parse_provisioning_options(device)
############################################################################
- # Start initial discovery of RESTCONF support (if any)
-
- try:
- self.startup = self.make_restconf_connection()
- results = yield self.startup
- self._rest_support = results
- self.log.debug('HELLO_Contents: {}'.format(pprint.PrettyPrinter().pformat(results)))
-
- # See if this is a virtualized OLT. If so, no NETCONF support available
- self.is_virtual_olt = 'module-info' in results and\
- any(mod.get('module-name', None) == 'adtran-ont-mock'
- for mod in results['module-info'])
-
- except Exception as e:
- self.log.exception('Initial_RESTCONF_hello_failed', e=e)
- self.activate_failed(device, e.message, reachable=False)
+ # Currently, only virtual OLT (pizzabox) is supported
+ # self.is_virtual_olt = Add test for MOCK Device if we want to support it
############################################################################
# Start initial discovery of NETCONF support (if any)
-
try:
self.startup = self.make_netconf_connection()
yield self.startup
except Exception as e:
- self.log.exception('NETCONF_connection_failed', e=e)
+ self.log.exception('netconf-connection', e=e)
+ self.activate_failed(device, e.message, reachable=False)
+
+ ############################################################################
+ # Update access information on network device for full protocol support
+ try:
+ self.startup = self.ready_network_access()
+ yield self.startup
+
+ except Exception as e:
+ self.log.exception('network-setup', e=e)
+ self.activate_failed(device, e.message, reachable=False)
+
+ ############################################################################
+ # Restconf setup
+ try:
+ self.startup = self.make_restconf_connection()
+ yield self.startup
+
+ except Exception as e:
+ self.log.exception('restconf-setup', e=e)
self.activate_failed(device, e.message, reachable=False)
############################################################################
@@ -382,7 +387,7 @@
self.adapter_agent.update_device(device)
except Exception as e:
- self.log.exception('Device_info_failed', e=e)
+ self.log.exception('device-info', e=e)
self.activate_failed(device, e.message, reachable=False)
try:
@@ -404,7 +409,7 @@
self.adapter_agent.add_port(device.id, port.get_port())
except Exception as e:
- self.log.exception('NNI_enumeration', e=e)
+ self.log.exception('NNI-enumeration', e=e)
self.activate_failed(device, e.message)
try:
@@ -517,7 +522,6 @@
reactor.callLater(10, self.start_kpi_collection, device.id)
# Signal completion
-
self.log.info('activated')
except Exception as e:
@@ -525,11 +529,17 @@
if done_deferred is not None:
done_deferred.errback(e)
raise
+
if done_deferred is not None:
done_deferred.callback('activated')
returnValue(done_deferred)
+ @inlineCallbacks
+ def ready_network_access(self):
+ # Override in device specific class if needed
+ returnValue('nop')
+
def activate_failed(self, device, reason, reachable=True):
"""
Activation process (adopt_device) has failed.
diff --git a/voltha/adapters/adtran_olt/adtran_olt_handler.py b/voltha/adapters/adtran_olt/adtran_olt_handler.py
index bd81b47..96acf0f 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -74,12 +74,12 @@
self.status_poll_skew = self.status_poll_interval / 10
self._pon_agent = None
self._pio_agent = None
- self._is_async_control = False
self._ssh_deferred = None
self._system_id = None
self._download_protocols = None
self._download_deferred = None
self._downloads = {} # name -> Download obj
+ self._pio_exception_map = []
def __del__(self):
# OLT Specific things here.
@@ -141,7 +141,8 @@
the device type specification returned by device_types().
"""
from codec.physical_entities_state import PhysicalEntitiesState
- # TODO: After a CLI 'reboot' command, the device info may get messed up (prints labels and not values) Enter device and type 'show'
+ # TODO: After a CLI 'reboot' command, the device info may get messed up (prints labels and not values)
+ # # Enter device and type 'show'
device = {
'model': 'n/a',
'hardware_version': 'unknown',
@@ -388,9 +389,6 @@
:param reconciling: (boolean) True if taking over for another VOLTHA
"""
- # Make sure configured for ZMQ remote access
- self._ready_network_access()
-
# ZeroMQ clients
self._zmq_startup()
@@ -406,7 +404,7 @@
def on_heatbeat_alarm(self, active):
if not active:
- self._ready_network_access()
+ self.ready_network_access()
@inlineCallbacks
def _get_download_protocols(self):
@@ -433,10 +431,8 @@
self._download_deferred = reactor.callLater(10, self._get_download_protocols)
@inlineCallbacks
- def _ready_network_access(self):
+ def ready_network_access(self):
from net.rcmd import RCmd
- # Software version
- self._is_async_control = self._olt_version() >= 2
# Check for port status
command = 'netstat -pan | grep -i 0.0.0.0:{} | wc -l'.format(self.pon_agent_port)
@@ -464,25 +460,26 @@
def v2_v3_method():
# Old V2 method
- command = "sed --in-place=voltha-sav 's/^#export ZMQ_LISTEN/export ZMQ_LISTEN/' " \
- "/etc/ngpon2_agent/ngpon2_agent_feature_flags; "
+ # For V2 images, want -> export ZMQ_LISTEN_ON_ANY_ADDRESS=1
+ # For V3+ images, want -> export AGENT_LISTEN_ON_ANY_ADDRESS=1
# V3 unifies listening port, compatible with v2
- # command = "sed --in-place '/add feature flags/aexport ZMQ_LISTEN_ON_ANY_ADDRESS=1' " \
- # "/etc/ngpon2_agent/ngpon2_agent_feature_flags; "
- # command += "sed --in-place '/^export ZMQ_LISTEN/aAGENT_LISTEN_ON_ANY_ADDRESS=1' " \
- # "/etc/ngpon2_agent/ngpon2_agent_feature_flags; "
+ cmd = "sed --in-place '/add feature/aexport ZMQ_LISTEN_ON_ANY_ADDRESS=1' " \
+ "/etc/ngpon2_agent/ngpon2_agent_feature_flags; "
+ cmd += "sed --in-place '/add feature/aexport AGENT_LISTEN_ON_ANY_ADDRESS=1' " \
+ "/etc/ngpon2_agent/ngpon2_agent_feature_flags; "
- command += 'ps -ae | grep -i ngpon2_agent; '
- command += 'service_supervisor stop ngpon2_agent; service_supervisor start ngpon2_agent; '
- command += 'ps -ae | grep -i ngpon2_agent'
+ # Note: 'ps' commands are to help decorate the logfile with useful info
+ cmd += 'ps -ae | grep -i ngpon2_agent; '
+ cmd += 'service_supervisor stop ngpon2_agent; service_supervisor start ngpon2_agent; '
+ cmd += 'ps -ae | grep -i ngpon2_agent'
- self.log.debug('create-request', command=command)
- return RCmd(self.ip_address, self.netconf_username, self.netconf_password, command)
+ self.log.debug('create-request', command=cmd)
+ return RCmd(self.ip_address, self.netconf_username, self.netconf_password, cmd)
# Look for version
next_run = 15
- version = v2_v3_method if self._olt_version() > 1 else v1_method
+ version = v2_v3_method # NOTE: Only v2 or later supported.
if version is not None:
try:
@@ -496,7 +493,9 @@
next_run = 0
if next_run > 0:
- self._ssh_deferred = reactor.callLater(next_run, self._ready_network_access)
+ self._ssh_deferred = reactor.callLater(next_run, self.ready_network_access)
+
+ returnValue('retrying' if next_run > 0 else 'ready')
def _zmq_startup(self):
# ZeroMQ clients
@@ -535,7 +534,7 @@
def reenable(self, done_deferred=None):
super(AdtranOltHandler, self).reenable(done_deferred=done_deferred)
- self._ready_network_access()
+ self.ready_network_access()
self._zmq_startup()
# Register for adapter messages
@@ -558,7 +557,7 @@
def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
super(AdtranOltHandler, self)._finish_reboot(timeout, previous_oper_status, previous_conn_status)
- self._ready_network_access()
+ self.ready_network_access()
# Download support
self._download_deferred = reactor.callLater(0, self._get_download_protocols)
@@ -584,8 +583,8 @@
if self._pon_agent is not None:
for packet in packets:
try:
- pon_id, onu_id, msg_bytes, is_omci = \
- self._pon_agent.decode_packet(packet, self._is_async_control)
+ pon_id, onu_id, msg_bytes, is_omci = self._pon_agent.decode_packet(packet)
+
if is_omci:
proxy_address = self._pon_onu_id_to_proxy_address(pon_id, onu_id)
@@ -632,14 +631,23 @@
if url_type == PioClient.UrlType.EVCMAPS_RESPONSE:
exception_map = self._pio_agent.decode_query_response_packet(packet)
self.log.debug('rx-pio-packet', exception_map=exception_map)
+ # update latest pio exception map
+ self._pio_exception_map = exception_map
elif url_type == PioClient.UrlType.PACKET_IN:
try:
from scapy.layers.l2 import Ether, Dot1Q
ifindex, evc_map, packet = self._pio_agent.decode_packet(packet)
- # convert ifindex to physical port number (HACK)
- port_no = (ifindex - 60000) + 4
+ # convert ifindex to physical port number
+ # pon port numbers start at 60001 and end at 600016 (16 pons)
+ if ifindex > 60000 and ifindex < 60017:
+ port_no = (ifindex - 60000) + 4
+ # nni port numbers start at 1401 and end at 1404 (16 nnis)
+ elif ifindex > 1400 and ifindex < 1405:
+ port_no = ifindex - 1400
+ else:
+ raise ValueError('Unknown physical port. ifindex: {}'.format(ifindex))
logical_port_no = self._compute_logical_port_no(port_no, evc_map, packet)
@@ -674,7 +682,7 @@
if self.pio_port is not None or self.io_port is not None:
from scapy.layers.l2 import Ether, Dot1Q
- from scapy.layers.inet import IP, UDP
+ from scapy.layers.inet import UDP
from common.frameio.frameio import hexify
self.log.debug('sending-packet-out', egress_port=egress_port,
@@ -710,33 +718,43 @@
elif pkt.type == 2:
exceptiontype = 'igmp'
elif pkt.type == FlowEntry.EtherType.IPv4:
- ippkt = IP(pkt.payload)
- if ippkt.proto == FlowEntry.IpProtocol.UDP:
- udppkt = UDP(ippkt.payload)
- # packet out from DHCP server is reversed ports
- if udppkt.sport == 67 and udppkt.dport == 68:
- exceptiontype = 'dhcp'
+ if UDP in pkt and pkt[UDP].sport == 67 and pkt[UDP].dport == 68:
+ exceptiontype = 'dhcp'
if exceptiontype is None:
self.log.warn('packet-out-exceptiontype-unknown', eEtherType=pkt.type)
- elif port is not None and ctag is not None and vlan_id is not None and evcmapname is not None:
- self.log.debug('sending-pio-packet-out', port=port, ctag=ctag, vlan_id=vlan_id, evcmapname=evcmapname, exceptiontype=exceptiontype)
+ elif port is not None and ctag is not None and vlan_id is not None and \
+ evcmapname is not None and self.pio_exception_exists(evcmapname, exceptiontype):
+
+ self.log.debug('sending-pio-packet-out', port=port, ctag=ctag, vlan_id=vlan_id,
+ evcmapname=evcmapname, exceptiontype=exceptiontype)
out_pkt = (
Ether(src=pkt.src, dst=pkt.dst) /
- Dot1Q(vlan=port) /
Dot1Q(vlan=vlan_id) /
Dot1Q(vlan=ctag, type=pkt.type) /
pkt.payload
)
data = self._pio_agent.encode_packet(port, str(out_pkt), evcmapname, exceptiontype)
+ self.log.debug('pio-packet-out', message=data)
try:
self._pio_agent.send(data)
except Exception as e:
self.log.exception('pio-send', egress_port=egress_port, e=e)
else:
- self.log.debug('packet-out-flow-not-found', egress_port=egress_port)
+ self.log.warn('packet-out-flow-not-found', egress_port=egress_port)
+
+ def pio_exception_exists(self, name, exp):
+ # verify exception is in the OLT's reported exception map for this evcmap name
+ if exp is None:
+ return False
+ entry = next((entry for entry in self._pio_exception_map if entry['evc-map-name'] == name), None)
+ if entry is None:
+ return False
+ if exp not in entry['exception-types']:
+ return False
+ return True
def send_packet_exceptions_request(self):
if self._pio_agent is not None:
@@ -905,8 +923,7 @@
onu = pon.onu(onu_id)
if onu is not None and onu.enabled:
- data = self._pon_agent.encode_omci_packet(msg, pon_id, onu_id,
- self._is_async_control)
+ data = self._pon_agent.encode_omci_packet(msg, pon_id, onu_id)
try:
self._pon_agent.send(data)
diff --git a/voltha/adapters/adtran_olt/flow/acl.py b/voltha/adapters/adtran_olt/flow/acl.py
index 6d012f2..170d997 100644
--- a/voltha/adapters/adtran_olt/flow/acl.py
+++ b/voltha/adapters/adtran_olt/flow/acl.py
@@ -20,7 +20,7 @@
log = structlog.get_logger()
-_acl_list = {} # Key -> Name: List of encoded EVCs
+_acl_list = {} # Key -> device-id -> Name: List of encoded EVCs
ACL_NAME_FORMAT = 'VOLTHA-ACL-{}-{}' # format(flow_entry.handler.device_id, flow_entry.flow.id)
ACL_NAME_REGEX_ALL = 'VOLTHA-ACL-*'
@@ -217,8 +217,12 @@
log.debug('installing-acl', installed=self._installed)
if not self._installed and self._enabled:
- if self._name in _acl_list:
- self._status_message = "ACL '{}' already is installed".format(self._name)
+ if self._handler.device_id not in _acl_list:
+ _acl_list[self._handler.device_id] = {}
+
+ acls_installed = _acl_list[self._handler.device_id]
+ if self._name in acls_installed:
+ self._status_message = "ACL '{}' id already installed".format(self._name)
raise Exception(self._status_message)
try:
@@ -230,7 +234,7 @@
self._status_message = '' if results.ok else results.error
if self._installed:
- _acl_list[self._name] = self
+ acls_installed[self._name] = self
except Exception as e:
log.exception('install-failure', name=self._name, e=e)
@@ -251,7 +255,9 @@
self._status_message = '' if results.ok else results.error
if not self._installed:
- _acl_list.pop(self._name)
+ acls_installed = _acl_list.get(self._handler.device_id)
+ if acls_installed is not None and self._name in acls_installed:
+ del acls_installed[self._name]
returnValue(not self._installed)
diff --git a/voltha/adapters/adtran_olt/flow/evc.py b/voltha/adapters/adtran_olt/flow/evc.py
index f6c1fc6..ebd1f7f 100644
--- a/voltha/adapters/adtran_olt/flow/evc.py
+++ b/voltha/adapters/adtran_olt/flow/evc.py
@@ -344,6 +344,7 @@
"""
log.info('deleting', evc=self, delete_maps=delete_maps)
+ assert self._flow, 'Delete EVC must have flow reference'
try:
dl = [self.remove()]
self._valid = False
diff --git a/voltha/adapters/adtran_olt/flow/evc_map.py b/voltha/adapters/adtran_olt/flow/evc_map.py
index 6dfee88..17228d1 100644
--- a/voltha/adapters/adtran_olt/flow/evc_map.py
+++ b/voltha/adapters/adtran_olt/flow/evc_map.py
@@ -231,10 +231,10 @@
# self._udp_src = None
return xml
- def _ingress_install_xml(self, onu_s_gem_ids_and_vid):
+ def _ingress_install_xml(self, onu_s_gem_ids_and_vid, acl_list):
from ..onu import Onu
- if len(self._new_acls):
+ if len(acl_list):
xml = '<evc-maps xmlns="http://www.adtran.com/ns/yang/adtran-evc-maps"' +\
' xmlns:adtn-evc-map-acl="http://www.adtran.com/ns/yang/adtran-evc-map-access-control-list">'
else:
@@ -258,12 +258,12 @@
xml += '<men-ctag>{}</men-ctag>'.format(vid) # Added in August 2017 model
xml += '</network-ingress-filter>'
- if len(self._new_acls):
+ if len(acl_list):
xml += '<adtn-evc-map-acl:access-lists>'
- xml += ' <adtn-evc-map-acl:ingress-acl>'
- for acl in self._new_acls.itervalues():
+ for acl in acl_list:
+ xml += ' <adtn-evc-map-acl:ingress-acl>'
xml += acl.evc_map_ingress_xml()
- xml += ' </adtn-evc-map-acl:ingress-acl>'
+ xml += ' </adtn-evc-map-acl:ingress-acl>'
xml += '</adtn-evc-map-acl:access-lists>'
xml += self._common_install_xml()
xml += '</evc-map>'
@@ -277,6 +277,27 @@
xml += EVCMap._xml_trailer()
return xml
+ def _ingress_remove_acl_xml(self, onu_s_gem_ids_and_vid, acl):
+ xml = '<evc-maps xmlns="http://www.adtran.com/ns/yang/adtran-evc-maps"' +\
+ ' xmlns:adtn-evc-map-acl="http://www.adtran.com/ns/yang/adtran-evc-map-access-control-list">'
+ for onu_or_vlan_id, gem_ids_and_vid in onu_s_gem_ids_and_vid.iteritems():
+ first_gem_id = True
+ vid = gem_ids_and_vid[1]
+ ident = '{}.{}'.format(self._pon_id, onu_or_vlan_id) if vid is None \
+ else onu_or_vlan_id
+
+ for gem_id in gem_ids_and_vid[0]:
+ xml += '<evc-map>'
+ xml += '<name>{}.{}.{}</name>'.format(self.name, ident, gem_id)
+ xml += '<adtn-evc-map-acl:access-lists>'
+ xml += ' <adtn-evc-map-acl:ingress-acl xc:operation="delete">'
+ xml += acl.evc_map_ingress_xml()
+ xml += ' </adtn-evc-map-acl:ingress-acl>'
+ xml += '</adtn-evc-map-acl:access-lists>'
+ xml += '</evc-map>'
+ xml += '</evc-maps>'
+ return xml
+
@inlineCallbacks
def install(self):
def gem_ports():
@@ -287,10 +308,10 @@
if self._valid and len(gem_ports()) > 0:
# Install ACLs first (if not yet installed)
- acl_list = self._new_acls.values()
+ work_acls = self._new_acls.copy()
self._new_acls = dict()
- for acl in acl_list:
+ for acl in work_acls.itervalues():
try:
yield acl.install()
# if not results.ok:
@@ -298,14 +319,14 @@
except Exception as e:
log.exception('acl-install', name=self.name, e=e)
- self._new_acls.update(acl_list)
+ self._new_acls.update(work_acls)
raise
# Now EVC-MAP
if not self._installed or self._needs_update:
try:
self._cancel_deferred()
- map_xml = self._ingress_install_xml(self._gem_ids_and_vid) \
+ map_xml = self._ingress_install_xml(self._gem_ids_and_vid, work_acls.values()) \
if self._is_ingress_map else self._egress_install_xml()
log.debug('install', xml=map_xml, name=self.name)
@@ -316,13 +337,14 @@
self.status = '' if results.ok else results.error
if results.ok:
- self._existing_acls.update(acl_list)
+ self._existing_acls.update(work_acls)
+
else:
- self._new_acls.update(acl_list)
+ self._new_acls.update(work_acls)
except Exception as e:
log.exception('map-install', name=self.name, e=e)
- self._new_acls.update(acl_list)
+ self._new_acls.update(work_acls)
raise
returnValue(self._installed and self._valid)
@@ -389,6 +411,7 @@
flows = [flow] if flow is not None else list(self._flows.values())
removing_all = len(flows) == len(self._flows)
+ log.debug('delete', removing_all=removing_all)
if not removing_all:
for f in flows:
self._remove_flow(f)
@@ -477,6 +500,7 @@
if tmp_map is None or not tmp_map.valid:
return None
+ self._flows[flow.flow_id] = flow
self._needs_update = True
if len(tmp_map._new_acls) > 0:
@@ -500,26 +524,45 @@
EVC-MAP over to another EVC.
:param flow: (FlowEntry) Flow to remove
- :param removing_all: (bool) If True, all flows are being removed from EVC-MAP
"""
try:
- self._flows.pop(flow.flow_id)
+ del self._flows[flow.flow_id]
if not flow.handler.exception_gems: # ! FIXED_ONU
# Remove any ACLs
- acl = ACL.create(flow)
+ acl_name = ACL.flow_to_name(flow)
+ acl = None
+
+ # if not yet installed just remove it from list
+ if acl_name in self._new_acls:
+ del self._new_acls[acl_name]
+ else:
+ acl = self._existing_acls[acl_name]
if acl is not None:
# Remove ACL from EVC-MAP entry
try:
- # TODO: Create EVC-MAP with proper 'delete-acl-list' request
- # TODO: and send it
- pass
+ map_xml = self._ingress_remove_acl_xml(self._gem_ids_and_vid, acl)
+ log.debug('remove', xml=map_xml, name=acl.name)
+ results = yield self._handler.netconf_client.edit_config(map_xml)
+ if results.ok:
+ del self._existing_acls[acl.name]
- # TODO: Scan EVC to see if it needs to move back to the Utility
- # or Untagged EVC from a user data EVC
- pass
+ # Scan EVC to see if it needs to move back to the Utility
+ # or Untagged EVC from a user data EVC
+ if not self._evc.service_evc and\
+ len(self._flows) > 0 and\
+ all(f.is_acl_flow for f in self._flows.itervalues()):
+
+ self._evc.remove_evc_map(self)
+ first_flow = self._flows.itervalues().next()
+ self._evc = first_flow.get_utility_evc(None, True)
+ self._evc.add_evc_map(self)
+ log.debug('moved-acl-flows-to-utility-evc', newevcname=self._evc.name)
+
+ self._needs_update = True
+ self._evc.schedule_install()
except Exception as e:
log.exception('acl-remove-from-evc', e=e)
diff --git a/voltha/adapters/adtran_olt/flow/flow_entry.py b/voltha/adapters/adtran_olt/flow/flow_entry.py
index 7af92c8..993eb38 100644
--- a/voltha/adapters/adtran_olt/flow/flow_entry.py
+++ b/voltha/adapters/adtran_olt/flow/flow_entry.py
@@ -286,11 +286,7 @@
downstream_flow.evc = MCastEVC.create(downstream_flow)
elif downstream_flow.is_acl_flow:
- if any(flow.eth_type == FlowEntry.EtherType.EAPOL for flow in upstream_flows) and\
- downstream_flow.handler.utility_vlan != downstream_flow.handler.untagged_vlan:
- downstream_flow.evc = UntaggedEVC.create(downstream_flow)
- else:
- downstream_flow.evc = UtilityEVC.create(downstream_flow)
+ downstream_flow.evc = downstream_flow.get_utility_evc(upstream_flows)
else:
downstream_flow.evc = EVC(downstream_flow)
@@ -332,6 +328,15 @@
return downstream_flow.evc if all_maps_valid else None
+ def get_utility_evc(self, upstream_flows=None, use_default_vlan_id=False):
+ assert self.is_acl_flow, 'Utility evcs are for acl flows only'
+ if upstream_flows is not None and\
+ any(flow.eth_type == FlowEntry.EtherType.EAPOL for flow in upstream_flows) and\
+ self.handler.utility_vlan != self.handler.untagged_vlan:
+ return UntaggedEVC.create(self, use_default_vlan_id)
+
+ return UtilityEVC.create(self, use_default_vlan_id)
+
@property
def _needs_acl_support(self): # FIXED_ONU- maybe
if self.ipv4_dst is not None: # In case MCAST downstream has ACL on it
@@ -695,20 +700,26 @@
"""
from ..onu import Onu
- log.debug('get-packetout-info', device_id=device_id, logical_port=logical_port)
all_flow_entries = _existing_upstream_flow_entries.get(device_id) or {}
for flow_entry in all_flow_entries.itervalues():
log.debug('get-packetout-info', flow_entry=flow_entry)
- if flow_entry.evc_map is not None and flow_entry.evc_map.valid and flow_entry.logical_port == logical_port:
+
+ # match logical port
+ if flow_entry.evc_map is not None and flow_entry.evc_map.valid and \
+ flow_entry.logical_port == logical_port:
evc_map = flow_entry.evc_map
gem_ids_and_vid = evc_map.gem_ids_and_vid
+
+ # must have valid gem id
if len(gem_ids_and_vid) > 0:
for onu_id, gem_ids_with_vid in gem_ids_and_vid.iteritems():
- log.debug('get-packetout-info', onu_id=onu_id, gem_ids_with_vid=gem_ids_with_vid)
+ log.debug('get-packetout-info', onu_id=onu_id,
+ gem_ids_with_vid=gem_ids_with_vid)
if len(gem_ids_with_vid) > 0:
gem_ids = gem_ids_with_vid[0]
ctag = gem_ids_with_vid[1]
gem_id = gem_ids[0] # TODO: always grab fist in list
- return flow_entry.in_port, ctag, Onu.gem_id_to_gvid(gem_id), evc_map.get_evcmap_name(onu_id, gem_id)
+ return flow_entry.in_port, ctag, Onu.gem_id_to_gvid(gem_id), \
+ evc_map.get_evcmap_name(onu_id, gem_id)
return None, None, None, None
diff --git a/voltha/adapters/adtran_olt/flow/untagged_evc.py b/voltha/adapters/adtran_olt/flow/untagged_evc.py
index 84a92c7..3a4a3dc 100644
--- a/voltha/adapters/adtran_olt/flow/untagged_evc.py
+++ b/voltha/adapters/adtran_olt/flow/untagged_evc.py
@@ -43,15 +43,16 @@
def __str__(self):
return "VOLTHA-UNTAGGED-{}: MEN: {}, VLAN: {}".format(self._name, self._men_ports, self._s_tag)
- def _create_name(self):
+ def _create_name(self, vlan_id=None):
#
# TODO: Take into account selection criteria and output to make the name
#
- return EVC_NAME_FORMAT.format(self._flow.vlan_id)
+ return EVC_NAME_FORMAT.format(self._flow.vlan_id if vlan_id is None else vlan_id)
@staticmethod
- def create(flow_entry):
+ def create(flow_entry, use_default_vlan_id=False):
device_id = flow_entry.device_id
+ vlan_id = flow_entry.vlan_id if use_default_vlan_id else flow_entry.handler.untagged_vlan
evc_table = _untagged_evcs.get(device_id)
if evc_table is None:
@@ -64,6 +65,12 @@
if evc is None:
# Create EVC and initial EVC Map
evc = UntaggedEVC(flow_entry)
+
+ # reapply the stag and name if forced vlan id
+ if use_default_vlan_id:
+ evc._s_tag = vlan_id
+ evc._create_name(vlan_id)
+
evc_table[flow_entry.vlan_id] = evc
else:
if flow_entry.flow_id in evc.downstream_flows: # TODO: Debug only to see if flow_ids are unique
@@ -100,7 +107,7 @@
"""
log.info('removing', evc=self, remove_maps=remove_maps)
- device_id = self._handler.device_id
+ device_id = self._flow.handler.device_id
flow_id = self._flow.id
evc_table = _untagged_evcs.get(device_id)
@@ -124,6 +131,7 @@
"""
log.info('deleting', evc=self, delete_maps=delete_maps)
+ assert self._flow, 'Delete EVC must have flow reference'
try:
dl = [self.remove()]
if delete_maps:
diff --git a/voltha/adapters/adtran_olt/flow/utility_evc.py b/voltha/adapters/adtran_olt/flow/utility_evc.py
index cb8a4ae..60a463a 100644
--- a/voltha/adapters/adtran_olt/flow/utility_evc.py
+++ b/voltha/adapters/adtran_olt/flow/utility_evc.py
@@ -25,7 +25,7 @@
_utility_evcs = {} # device-id -> flow dictionary
# |
- # +-> untagged-vlan-id -> evcs
+ # +-> utility-vlan-id -> evcs
class UtilityEVC(EVC):
@@ -40,15 +40,16 @@
def __str__(self):
return "VOLTHA-UTILITY-{}: MEN: {}, VLAN: {}".format(self._name, self._men_ports, self._s_tag)
- def _create_name(self):
+ def _create_name(self, vlan_id=None):
#
# TODO: Take into account selection criteria and output to make the name
#
- return EVC_NAME_FORMAT.format(self._flow.vlan_id)
+ return EVC_NAME_FORMAT.format(self._flow.vlan_id if vlan_id is None else vlan_id)
@staticmethod
- def create(flow_entry):
+ def create(flow_entry, use_default_vlan_id=False):
device_id = flow_entry.device_id
+ vlan_id = flow_entry.vlan_id if not use_default_vlan_id else flow_entry.handler.utility_vlan
evc_table = _utility_evcs.get(device_id)
if evc_table is None:
@@ -56,12 +57,18 @@
evc_table = _utility_evcs[device_id]
try:
- evc = evc_table.get(flow_entry.vlan_id)
+ evc = evc_table.get(vlan_id)
if evc is None:
# Create EVC and initial EVC Map
evc = UtilityEVC(flow_entry)
- evc_table[flow_entry.vlan_id] = evc
+
+ # reapply the stag and name if forced vlan id
+ if use_default_vlan_id:
+ evc._s_tag = vlan_id
+ evc._name = evc._create_name(vlan_id)
+
+ evc_table[vlan_id] = evc
else:
if flow_entry.flow_id in evc.downstream_flows: # TODO: Debug only to see if flow_ids are unique
pass
@@ -71,7 +78,7 @@
return evc
except Exception as e:
- log.exception('untagged-create', e=e)
+ log.exception('utility-create', e=e)
return None
@property
@@ -97,7 +104,7 @@
"""
log.info('removing', evc=self, remove_maps=remove_maps)
- device_id = self._handler.device_id
+ device_id = self._flow.handler.device_id
flow_id = self._flow.id
evc_table = _utility_evcs.get(device_id)
@@ -121,6 +128,7 @@
"""
log.info('deleting', evc=self, delete_maps=delete_maps)
+ assert self._flow, 'Delete EVC must have flow reference'
try:
dl = [self.remove()]
if delete_maps:
diff --git a/voltha/adapters/adtran_olt/net/pon_zmq.py b/voltha/adapters/adtran_olt/net/pon_zmq.py
index 3cea8e2..9afbe8c 100644
--- a/voltha/adapters/adtran_olt/net/pon_zmq.py
+++ b/voltha/adapters/adtran_olt/net/pon_zmq.py
@@ -27,25 +27,7 @@
def __init__(self, ip_address, rx_callback, port):
super(PonClient, self).__init__(ip_address, rx_callback, port)
- def encode_omci_packet(self, msg, pon_index, onu_id, is_async_control):
- """
- Create an OMCI Tx Packet for the specified ONU
-
- :param msg: (str) OMCI message to send
- :param pon_index: (unsigned int) PON Port index
- :param onu_id: (unsigned int) ONU ID
- :param is_async_control: (bool) Newer async/JSON support
-
- :return: (bytes) octet string to send
- """
- assert msg, 'No message provided'
-
- return PonClient._encode_omci_message_json(msg, pon_index, onu_id) \
- if is_async_control else \
- PonClient._encode_omci_message_legacy(msg, pon_index, onu_id)
-
- @staticmethod
- def _encode_omci_message_legacy(msg, pon_index, onu_id):
+ def encode_omci_packet(self, msg, pon_index, onu_id):
"""
Create an OMCI Tx Packet for the specified ONU
@@ -55,29 +37,6 @@
:return: (bytes) octet string to send
"""
- s = struct.Struct('!II')
-
- # Check if length is prepended (32-bits = 4 bytes ASCII)
- msglen = len(msg)
- assert msglen == 40*2 or msglen == 44*2, 'Invalid OMCI message length'
-
- if len(msg) > 40*2:
- msg = msg[:40*2]
-
- return s.pack(pon_index, onu_id) + binascii.unhexlify(msg)
-
- @staticmethod
- def _encode_omci_message_json(msg, pon_index, onu_id):
- """
- Create an OMCI Tx Packet for the specified ONU
-
- :param msg: (str) OMCI message to send
- :param pon_index: (unsigned int) PON Port index
- :param onu_id: (unsigned int) ONU ID
-
- :return: (bytes) octet string to send
- """
-
return json.dumps({"operation": "NOTIFY",
"url": "adtran-olt-pon-control/omci-message",
"pon-id": pon_index,
@@ -85,39 +44,14 @@
"message-contents": msg.decode("hex").encode("base64")
})
- def decode_packet(self, packet, is_async_control):
+ def decode_packet(self, packet):
"""
Decode the PON-Agent packet provided by the ZMQ client
:param packet: (bytes) Packet
- :param is_async_control: (bool) Newer async/JSON support
:return: (long, long, bytes, boolean) PON Index, ONU ID, Frame Contents (OMCI or Ethernet),\
and a flag indicating if it is OMCI
"""
- return PonClient._decode_omci_message_json(packet) if is_async_control \
- else PonClient._decode_omci_message_legacy(packet)
-
- @staticmethod
- def _decode_omci_message_legacy(packet):
- """
- Decode the packet provided by the ZMQ client (binary legacy format)
-
- :param packet: (bytes) Packet
- :return: (long, long, bytes) PON Index, ONU ID, OMCI Frame Contents
- """
- (pon_index, onu_id) = struct.unpack_from('!II', packet)
- omci_msg = packet[8:]
-
- return pon_index, onu_id, omci_msg, True
-
- @staticmethod
- def _decode_omci_message_json(packet):
- """
- Decode the packet provided by the ZMQ client (JSON format)
-
- :param packet: (string) Packet
- :return: (long, long, bytes) PON Index, ONU ID, OMCI Frame Contents
- """
msg = json.loads(packet)
pon_id = msg['pon-id']
onu_id = msg['onu-id']