blob: cb8dd4a7bd181e3144288ac0c50714c70662eac3 [file] [log] [blame]
# Copyright 2017-present Adtran, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import structlog
from evc import EVC
from evc_map import EVCMap
from enum import IntEnum
from utility_evc import UtilityEVC
import pyvoltha.common.openflow.utils as fd
from pyvoltha.protos.openflow_13_pb2 import OFPP_MAX, OFPP_CONTROLLER, OFPVID_PRESENT, OFPXMC_OPENFLOW_BASIC
from twisted.internet.defer import returnValue, inlineCallbacks, gatherResults
log = structlog.get_logger()
# IP Protocol numbers
_supported_ip_protocols = [
1, # ICMP
2, # IGMP
6, # TCP
17, # UDP
]
class FlowEntry(object):
"""
Provide a class that wraps the flow rule and also provides state/status for a FlowEntry.
When a new flow is sent, it is first decoded to check for any potential errors. If None are
found, the entry is created and it is analyzed to see if it can be combined to with any other flows
to create or modify an existing EVC.
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
CONTROLLER_UNI = 2 # Trap packet on UNI and send to controller
NNI_PON = 3 # NNI port to PON Port (all UNIs) - Utility VLAN & multicast
# 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
upstream_flow_types = {FlowDirection.UPSTREAM, FlowDirection.CONTROLLER_UNI}
downstream_flow_types = {FlowDirection.DOWNSTREAM, FlowDirection.NNI_PON}
LEGACY_CONTROL_VLAN = 4000
# Well known EtherTypes
class EtherType(IntEnum):
EAPOL = 0x888E
IPv4 = 0x0800
IPv6 = 0x86DD
ARP = 0x0806
LLDP = 0x88CC
# Well known IP Protocols
class IpProtocol(IntEnum):
IGMP = 2
UDP = 17
def __init__(self, flow, handler):
self._flow = flow
self._handler = handler
self.flow_id = flow.id
self.evc = None # EVC this flow is part of
self.evc_map = None # EVC-MAP this flow is part of
self._flow_direction = FlowEntry.FlowDirection.OTHER
self._logical_port = None # Currently ONU VID is logical port if not doing xPON
self._is_multicast = False
self._is_acl_flow = False
self._bandwidth = None
# A value used to locate possible related flow entries
self.signature = None
self.downstream_signature = None # Valid for upstream EVC-MAP Flows
# Selection properties
self.in_port = None
self.vlan_id = None
self.pcp = None
self.eth_type = None
self.ip_protocol = None
self.ipv4_dst = None
self.udp_dst = None # UDP Port #
self.udp_src = None # UDP Port #
self.inner_vid = None
# Actions
self.output = None
self.pop_vlan = False
self.push_vlan_tpid = None
self.push_vlan_id = None
self._name = self.create_flow_name()
def __str__(self):
return 'flow_entry: {}, in: {}, out: {}, vid: {}, inner:{}, eth: {}, IP: {}'.format(
self.name, self.in_port, self.output, self.vlan_id, self.inner_vid,
self.eth_type, self.ip_protocol)
def __repr__(self):
return str(self)
@property
def name(self):
return self._name # TODO: Is a name really needed in production?
def create_flow_name(self):
return 'flow-{}-{}'.format(self.device_id, self.flow_id)
@property
def flow(self):
return self._flow
@property
def handler(self):
return self._handler
@property
def device_id(self):
return self.handler.device_id
@property
def bandwidth(self):
""" Bandwidth in Mbps (if any) """
return self._bandwidth
@property
def flow_direction(self):
return self._flow_direction
@property
def is_multicast_flow(self):
return self._is_multicast
@property
def is_acl_flow(self):
return self._is_acl_flow or self._needs_acl_support
@property
def logical_port(self):
return self._logical_port # NNI or UNI Logical Port
@staticmethod
def create(flow, handler):
"""
Create the appropriate FlowEntry wrapper for the flow. This method returns a two
results.
The first result is the flow entry that was created. This could be a match to an
existing flow since it is a bulk update. None is returned only if no match to
an existing entry is found and decode failed (unsupported field)
The second result is the EVC this flow should be added to. This could be an
existing flow (so your adding another EVC-MAP) or a brand new EVC (no existing
EVC-MAPs). None is returned if there are not a valid EVC that can be created YET.
:param flow: (Flow) Flow entry passed to VOLTHA adapter
:param handler: (AdtranDeviceHandler) handler for the device
:return: (FlowEntry, EVC)
"""
# Exit early if it already exists
try:
flow_entry = FlowEntry(flow, handler)
######################################################################
# Decode the flow entry
if not flow_entry._decode(flow):
# TODO: When we support individual flow mods, we will need to return
# this flow back always
return None, None
######################################################################
# Initialize flow_entry database (dicts) if needed and determine if
# the flows have already been handled.
downstream_sig_table = handler.downstream_flows
upstream_flow_table = handler.upstream_flows
log.debug('flow-entry-decoded', flow=flow_entry, signature=flow_entry.signature,
downstream_signature=flow_entry.downstream_signature)
if flow_entry.flow_direction in FlowEntry.upstream_flow_types and\
flow_entry.flow_id in upstream_flow_table:
log.debug('flow-entry-upstream-exists', flow=flow_entry)
return flow_entry, None
if flow_entry.flow_direction in FlowEntry.downstream_flow_types:
sig_table = downstream_sig_table.get(flow_entry.signature)
if sig_table is not None and flow_entry in sig_table.flows:
log.debug('flow-entry-downstream-exists', flow=flow_entry)
return flow_entry, None
######################################################################
# Look for any matching flows in the other direction that might help
# make an EVC and then save it off in the device specific flow table
#
# TODO: For now, only support for E-LINE services between NNI and UNI
downstream_flow = None
upstream_flows = None
downstream_sig = None
if flow_entry._is_multicast: # Uni-directional flow
assert flow_entry._flow_direction in FlowEntry.downstream_flow_types, \
'Only downstream Multicast supported'
downstream_flow = flow_entry
downstream_sig = flow_entry.signature
upstream_flows = []
elif flow_entry.flow_direction in FlowEntry.downstream_flow_types:
downstream_flow = flow_entry
downstream_sig = flow_entry.signature
elif flow_entry.flow_direction in FlowEntry.upstream_flow_types:
downstream_sig = flow_entry.downstream_signature
if downstream_sig is None:
# TODO: When we support individual flow mods, we will need to return
# this flow back always
log.debug('flow-entry-empty-downstream', flow=flow_entry)
return None, None
# Make sure a slot exists for the downstream signature and get its flow table
downstream_sig_table = downstream_sig_table.add(downstream_sig)
evc = downstream_sig_table.evc
# Save the new flow_entry to proper flow table
if flow_entry.flow_direction in FlowEntry.upstream_flow_types:
upstream_flow_table.add(flow_entry)
downstream_flow = evc.flow_entry if evc is not None else \
next((_flow for _flow in downstream_sig_table.flows.itervalues()
if isinstance(_flow, FlowEntry)), None)
elif flow_entry.flow_direction in FlowEntry.downstream_flow_types:
downstream_sig_table.flows.add(flow_entry)
# Now find all the upstream flows
if downstream_flow is not None:
upstream_flows = [_flow for _flow in upstream_flow_table.itervalues()
if _flow.downstream_signature == downstream_flow.signature]
if len(upstream_flows) == 0 and not downstream_flow.is_multicast_flow:
upstream_flows = None
log.debug('flow-entry-search-results', flow=flow_entry,
downstream_flow=downstream_flow, upstream_flows=upstream_flows)
######################################################################
# Compute EVC and and maps
evc = FlowEntry._create_evc_and_maps(evc, downstream_flow, upstream_flows)
# Save off EVC (if we have one) for this flow if it is new
if evc is not None and evc.valid and downstream_sig_table.evc is None:
downstream_sig_table.evc = evc
return flow_entry, evc
except Exception as e:
log.exception('flow-entry-processing', e=e)
return None, None
@staticmethod
def _create_evc_and_maps(evc, downstream_flow, upstream_flows):
"""
Give a set of flows, find (or create) the EVC and any needed EVC-MAPs
:param evc: (EVC) Existing EVC for downstream flow. May be null if not created
:param downstream_flow: (FlowEntry) NNI -> UNI flow (provides much of the EVC values)
:param upstream_flows: (list of FlowEntry) UNI -> NNI flows (provides much of the EVC-MAP values)
:return: EVC object
"""
log.debug('flow-evc-and-maps', downstream_flow=downstream_flow,
upstream_flows=upstream_flows)
if (evc is None and downstream_flow is None) or upstream_flows is None:
return None
# Get any existing EVC if a flow is already created
if downstream_flow.evc is None:
if evc is not None:
downstream_flow.evc = evc
elif downstream_flow.is_multicast_flow:
from mcast import MCastEVC
downstream_flow.evc = MCastEVC.create(downstream_flow)
elif downstream_flow.is_acl_flow:
downstream_flow.evc = downstream_flow.get_utility_evc()
else:
downstream_flow.evc = EVC(downstream_flow)
if not downstream_flow.evc.valid:
log.debug('flow-evc-and-maps-downstream-invalid',
downstream_flow=downstream_flow,
upstream_flows=upstream_flows)
return None
# Create EVC-MAPs. Note upstream_flows is empty list for multicast
# For Packet In/Out support. The upstream flows for will have matching
# signatures. So the first one to get created should create the EVC and
# if it needs and ACL, do so then. The second one should just reference
# the first map.
#
# If the second has and ACL, then it should add it to the map.
# TODO: What to do if the second (or third, ...) is the data one.
# What should it do then?
sig_map_map = {f.signature: f.evc_map for f in upstream_flows
if f.evc_map is not None}
for flow in upstream_flows:
if flow.evc_map is None:
if flow.signature in sig_map_map:
# Found an explicitly matching existing EVC-MAP. Add flow to this EVC-MAP
flow.evc_map = sig_map_map[flow.signature].add_flow(flow, downstream_flow.evc)
else:
# May need to create a MAP or search for an existing ACL/user EVC-Map
# upstream_flow_table = _existing_upstream_flow_entries[flow.device_id]
upstream_flow_table = flow.handler.upstream_flows
existing_flow = EVCMap.find_matching_ingress_flow(flow, upstream_flow_table)
if existing_flow is None:
flow.evc_map = EVCMap.create_ingress_map(flow, downstream_flow.evc)
else:
flow.evc_map = existing_flow.add_flow(flow, downstream_flow.evc)
all_maps_valid = all(flow.evc_map.valid for flow in upstream_flows) \
or downstream_flow.is_multicast_flow
log.debug('flow-evc-and-maps-downstream',
downstream_flow=downstream_flow,
upstream_flows=upstream_flows, all_valid=all_maps_valid)
return downstream_flow.evc if all_maps_valid else None
def get_utility_evc(self, use_default_vlan_id=False):
assert self.is_acl_flow, 'Utility evcs are for acl flows only'
return UtilityEVC.create(self, use_default_vlan_id)
@property
def _needs_acl_support(self):
if self.ipv4_dst is not None: # In case MCAST downstream has ACL on it
return False
return self.eth_type is not None or self.ip_protocol is not None or\
self.ipv4_dst is not None or self.udp_dst is not None or self.udp_src is not None
def _decode(self, flow):
"""
Examine flow rules and extract appropriate settings
"""
log.debug('start-decode')
status = self._decode_traffic_selector(flow) and self._decode_traffic_treatment(flow)
# Determine direction of the flow and apply appropriate modifications
# to the decoded flows
if status:
if not self._decode_flow_direction():
return False
if self._flow_direction in FlowEntry.downstream_flow_types:
status = self._apply_downstream_mods()
elif self._flow_direction in FlowEntry.upstream_flow_types:
status = self._apply_upstream_mods()
else:
# TODO: Need to code this - Perhaps this is an NNI_PON for Multicast support?
log.error('unsupported-flow-direction')
status = False
log.debug('flow-evc-decode', direction=self._flow_direction, is_acl=self._is_acl_flow,
inner_vid=self.inner_vid, vlan_id=self.vlan_id, pop_vlan=self.pop_vlan,
push_vid=self.push_vlan_id, status=status)
# Create a signature that will help locate related flow entries on a device.
if status:
# These are not exact, just ones that may be put together to make an EVC. The
# basic rules are:
#
# 1 - Port numbers in increasing order
ports = [self.in_port, self.output]
ports.sort()
assert len(ports) == 2, 'Invalid port count: {}'.format(len(ports))
# 3 - The outer VID
# 4 - The inner VID. Wildcard if downstream
if self.push_vlan_id is None:
outer = self.vlan_id
inner = self.inner_vid
else:
outer = self.push_vlan_id
inner = self.vlan_id
upstream_sig = '{}'.format(ports[0])
downstream_sig = '{}'.format(ports[0])
upstream_sig += '.{}'.format(ports[1])
downstream_sig += '.{}'.format(ports[1] if self.handler.is_nni_port(ports[1]) else '*')
upstream_sig += '.{}.{}'.format(outer, inner)
downstream_sig += '.{}.*'.format(outer)
if self._flow_direction in FlowEntry.downstream_flow_types:
self.signature = downstream_sig
elif self._flow_direction in FlowEntry.upstream_flow_types:
self.signature = upstream_sig
self.downstream_signature = downstream_sig
else:
log.error('unsupported-flow')
status = False
log.debug('flow-evc-decode', upstream_sig=self.signature, downstream_sig=self.downstream_signature)
return status
def _decode_traffic_selector(self, flow):
"""
Extract EVC related traffic selection settings
"""
self.in_port = fd.get_in_port(flow)
if self.in_port > OFPP_MAX:
log.warn('logical-input-ports-not-supported', in_port=self.in_port)
return False
for field in fd.get_ofb_fields(flow):
if field.type == fd.IN_PORT:
if self._handler.is_nni_port(self.in_port) or self._handler.is_uni_port(self.in_port):
self._logical_port = self.in_port
elif field.type == fd.VLAN_VID:
if field.vlan_vid >= OFPVID_PRESENT + 4095:
self.vlan_id = None # pre-ONOS v1.13.5 or old EAPOL Rule
else:
self.vlan_id = field.vlan_vid & 0xfff
log.debug('*** field.type == VLAN_VID', value=field.vlan_vid, vlan_id=self.vlan_id)
elif field.type == fd.VLAN_PCP:
log.debug('*** field.type == VLAN_PCP', value=field.vlan_pcp)
self.pcp = field.vlan_pcp
elif field.type == fd.ETH_TYPE:
log.debug('*** field.type == ETH_TYPE', value=field.eth_type)
self.eth_type = field.eth_type
elif field.type == fd.IP_PROTO:
log.debug('*** field.type == IP_PROTO', value=field.ip_proto)
self.ip_protocol = field.ip_proto
if self.ip_protocol not in _supported_ip_protocols:
log.error('Unsupported IP Protocol', protocol=self.ip_protocol)
return False
elif field.type == fd.IPV4_DST:
log.debug('*** field.type == IPV4_DST', value=field.ipv4_dst)
self.ipv4_dst = field.ipv4_dst
elif field.type == fd.UDP_DST:
log.debug('*** field.type == UDP_DST', value=field.udp_dst)
self.udp_dst = field.udp_dst
elif field.type == fd.UDP_SRC:
log.debug('*** field.type == UDP_SRC', value=field.udp_src)
self.udp_src = field.udp_src
elif field.type == fd.METADATA:
if self._handler.is_nni_port(self.in_port):
# Downstream flow
log.debug('*** field.type == METADATA', value=field.table_metadata)
if field.table_metadata > 4095:
# ONOS v1.13.5 or later. c-vid in upper 32-bits
vid = field.table_metadata & 0x0FFF
if vid > 0:
self.inner_vid = vid # CTag is never '0'
elif field.table_metadata > 0:
# Pre-ONOS v1.13.5 (vid without the 4096 offset)
self.inner_vid = field.table_metadata
else:
# Upstream flow
pass # Not used upstream at this time
log.debug('*** field.type == METADATA', value=field.table_metadata,
inner_vid=self.inner_vid)
else:
log.warn('unsupported-selection-field', type=field.type)
self._status_message = 'Unsupported field.type={}'.format(field.type)
return False
return True
def _decode_traffic_treatment(self, flow):
# Loop through traffic treatment
for act in fd.get_actions(flow):
if act.type == fd.OUTPUT:
self.output = act.output.port
elif act.type == fd.POP_VLAN:
log.debug('*** action.type == POP_VLAN')
self.pop_vlan = True
elif act.type == fd.PUSH_VLAN:
log.debug('*** action.type == PUSH_VLAN', value=act.push)
tpid = act.push.ethertype
self.push_vlan_tpid = tpid
elif act.type == fd.SET_FIELD:
log.debug('*** action.type == SET_FIELD', value=act.set_field.field)
assert (act.set_field.field.oxm_class == OFPXMC_OPENFLOW_BASIC)
field = act.set_field.field.ofb_field
if field.type == fd.VLAN_VID:
self.push_vlan_id = field.vlan_vid & 0xfff
else:
log.debug('unsupported-set-field')
else:
log.warn('unsupported-action', action=act)
self._status_message = 'Unsupported action.type={}'.format(act.type)
return False
return True
def _decode_flow_direction(self):
# Determine direction of the flow
def port_type(port_number):
if port_number in self._handler.northbound_ports:
return FlowEntry.PortType.NNI
elif port_number in self._handler.southbound_ports:
return FlowEntry.PortType.PON
elif port_number <= OFPP_MAX:
return FlowEntry.PortType.UNI
elif port_number in {OFPP_CONTROLLER, 0xFFFFFFFD}: # OFPP_CONTROLLER is wrong in proto-file
return FlowEntry.PortType.CONTROLLER
return FlowEntry.PortType.OTHER
flow_dir_map = {
(FlowEntry.PortType.UNI, FlowEntry.PortType.NNI): FlowEntry.FlowDirection.UPSTREAM,
(FlowEntry.PortType.NNI, FlowEntry.PortType.UNI): FlowEntry.FlowDirection.DOWNSTREAM,
(FlowEntry.PortType.UNI, FlowEntry.PortType.CONTROLLER): FlowEntry.FlowDirection.CONTROLLER_UNI,
(FlowEntry.PortType.NNI, FlowEntry.PortType.PON): FlowEntry.FlowDirection.NNI_PON,
# The following are not yet supported
# (FlowEntry.PortType.NNI, FlowEntry.PortType.CONTROLLER): FlowEntry.FlowDirection.CONTROLLER_NNI,
# (FlowEntry.PortType.PON, FlowEntry.PortType.CONTROLLER): FlowEntry.FlowDirection.CONTROLLER_PON,
# (FlowEntry.PortType.NNI, FlowEntry.PortType.NNI): FlowEntry.FlowDirection.NNI_NNI,
# (FlowEntry.PortType.UNI, FlowEntry.PortType.UNI): FlowEntry.FlowDirection.UNI_UNI,
}
self._flow_direction = flow_dir_map.get((port_type(self.in_port), port_type(self.output)),
FlowEntry.FlowDirection.OTHER)
return self._flow_direction != FlowEntry.FlowDirection.OTHER
def _apply_downstream_mods(self):
# This is a downstream flow. It could be any one of the following:
#
# Legacy control VLAN:
# This is the old VLAN 4000 that was used to attach EAPOL and other
# controller flows to. Eventually these will change to CONTROLLER_UNI
# flows. For these, use the 'utility' VLAN instead so 4000 if available
# for other uses (AT&T uses it for downstream multicast video).
#
# Multicast VLAN:
# This is downstream multicast data.
# TODO: Test this to see if this needs to be in a separate NNI_PON mod-method
#
# User Data flow:
# This is for user data. Eventually we may need to support ACLs?
#
# May be for to controller flow downstream (no ethType)
if self.vlan_id == FlowEntry.LEGACY_CONTROL_VLAN and self.eth_type is None and self.pcp == 0:
return False # Do not install this flow. Utility VLAN is in charge
elif self.flow_direction == FlowEntry.FlowDirection.NNI_PON and \
self.vlan_id == self.handler.utility_vlan:
# Utility VLAN downstream flow/EVC
self._is_acl_flow = True
elif self.vlan_id in self._handler.multicast_vlans:
# multicast (ethType = IP) # TODO: May need to be an NNI_PON flow
self._is_multicast = True
self._is_acl_flow = True
else:
# Currently do not support ACLs on user data flows downstream
assert not self._needs_acl_support # User data, no special modifications needed at this time
return True
def _apply_upstream_mods(self):
#
# This is an upstream flow. It could be any of the following
#
# ACL/Packet capture:
# This is either a legacy (FlowDirection.UPSTREAM) or a new one
# that specifies an output port of controller (FlowDirection.CONTROLLER_UNI).
# Either way, these need to be placed on the Utility VLAN if the ONU attached
# does not have a user-data flow (C-Tag). If there is a C-Tag available,
# then place it on that VLAN.
#
# Once a user-data flow is established, move any of the ONUs ACL flows
# over to that VLAN (this is handled elsewhere).
#
# User Data flows:
# No special modifications are needed
#
try:
# Do not handle PON level ACLs in this method
assert(self._flow_direction != FlowEntry.FlowDirection.CONTROLLER_PON)
# Is this a legacy (VLAN 4000) upstream to-controller flow
if self._needs_acl_support and FlowEntry.LEGACY_CONTROL_VLAN == self.push_vlan_id:
self._flow_direction = FlowEntry.FlowDirection.CONTROLLER_UNI
self._is_acl_flow = True
self.push_vlan_id = self.handler.utility_vlan
return True
except Exception as e:
# TODO: Need to support flow retry if the ONU is not yet activated !!!!
log.exception('tag-fixup', e=e)
return False
@staticmethod
def drop_missing_flows(handler, valid_flow_ids):
dl = []
try:
flow_table = handler.upstream_flows
flows_to_drop = [flow for flow_id, flow in flow_table.items()
if flow_id not in valid_flow_ids]
dl.extend([flow.remove() for flow in flows_to_drop])
for sig_table in handler.downstream_flows.itervalues():
flows_to_drop = [flow for flow_id, flow in sig_table.flows.items()
if isinstance(flow, FlowEntry) and flow_id not in valid_flow_ids]
dl.extend([flow.remove() for flow in flows_to_drop])
except Exception as _e:
pass
return gatherResults(dl, consumeErrors=True) if len(dl) > 0 else returnValue('no-flows-to-drop')
@inlineCallbacks
def remove(self):
"""
Remove this flow entry from the list of existing entries and drop EVC
if needed
"""
# Remove from exiting table list
flow_id = self.flow_id
flow_table = None
if self.flow_direction in FlowEntry.upstream_flow_types:
flow_table = self._handler.upstream_flows
elif self.flow_direction in FlowEntry.downstream_flow_types:
sig_table = self._handler.downstream_flows.get(self.signature)
flow_table = sig_table.flows if sig_table is not None else None
if flow_table is None or flow_id not in flow_table.keys():
returnValue('NOP')
# Remove from flow table and clean up flow table if empty
flow_table.remove(flow_id)
evc_map, self.evc_map = self.evc_map, None
evc = None
if self.flow_direction in FlowEntry.downstream_flow_types:
sig_table = self._handler.downstream_flows.get(self.signature)
if len(flow_table) == 0: # Only 'evc' entry present
evc = sig_table.evc
else:
assert sig_table.evc is not None, 'EVC flow re-assignment error'
# Remove flow from the hardware
try:
dl = []
if evc_map is not None:
dl.append(evc_map.delete(self))
if evc is not None:
dl.append(evc.delete())
yield gatherResults(dl, consumeErrors=True)
except Exception as e:
log.exception('removal', e=e)
if self.flow_direction in FlowEntry.downstream_flow_types:
# If this flow owns the EVC, assign it to a remaining flow
sig_table = self._handler.downstream_flows.get(self.signature)
flow_evc = sig_table.evc
if flow_evc is not None and flow_evc.flow_entry is not None and flow_id == flow_evc.flow_entry.flow_id:
flow_evc.flow_entry = next((_flow for _flow in flow_table.itervalues()
if isinstance(_flow, FlowEntry)
and _flow.flow_id != flow_id), None)
# If evc was deleted, remove the signature table since now flows exist with
# that signature
if evc is not None:
self._handler.downstream_flows.remove(self.signature)
self.evc = None
returnValue('Done')
@staticmethod
def find_evc_map_flows(onu):
"""
For a given OLT, find all the EVC Maps for a specific ONU
:param onu: (Onu) onu
:return: (list) of matching flows
"""
# EVCs are only in the downstream table, EVC Maps are in upstream
onu_ports = onu.uni_ports
all_flow_entries = onu.olt.upstream_flows
evc_maps = [flow_entry.evc_map for flow_entry in all_flow_entries.itervalues()
if flow_entry.in_port in onu_ports
and flow_entry.evc_map is not None
and flow_entry.evc_map.valid]
return evc_maps
@staticmethod
def sync_flows_by_onu(onu, reflow=False):
"""
Check status of all flows on a per-ONU basis. Called when values
within the ONU are modified that may affect traffic.
:param onu: (Onu) ONU to examine
:param reflow: (boolean) Flag, if True, requests that the flow be sent to
hardware even if the values in hardware are
consistent with the current flow settings
"""
evc_maps = FlowEntry.find_evc_map_flows(onu)
evcs = {}
for evc_map in evc_maps:
if reflow or evc_map.reflow_needed():
evc_map.needs_update = False
if not evc_map.installed:
evc = evc_map.evc
if evc is not None:
evcs[evc.name] = evc
for evc in evcs.itervalues():
evc.installed = False
evc.schedule_install(delay=2)
######################################################
# Bulk operations
@staticmethod
def clear_all(handler):
"""
Remove all flows for the device.
:param handler: voltha adapter device handler
"""
handler.downstream_flows.clear_all()
handler.upstream_flows.clear_all()
@staticmethod
def get_packetout_info(handler, logical_port):
"""
Find parameters needed to send packet out successfully to the OLT.
:param handler: voltha adapter device handler
:param logical_port: (int) logical port number for packet to go out.
:return: physical port number, ctag, stag, evcmap name
"""
from adapters.adtran_olt.onu import Onu
for flow_entry in handler.upstream_flows.itervalues():
log.debug('get-packetout-info', flow_entry=flow_entry)
# 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)
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 first 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 None, None, None, None