blob: 27ce42fb9747eb68db9dc3ce23ec045af0d8088f [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 random
from enum import Enum
import structlog
from twisted.internet.defer import inlineCallbacks, returnValue
from voltha.core.logical_device_agent import mac_str_to_tuple
from voltha.protos.common_pb2 import OperStatus, AdminState
from voltha.protos.device_pb2 import Port
from voltha.protos.logical_device_pb2 import LogicalPort
from voltha.protos.openflow_13_pb2 import OFPPF_100GB_FD, OFPPF_FIBER, OFPPS_LIVE, ofp_port
import voltha.core.flow_decomposer as fd
log = structlog.get_logger()
_evc_list = {} # Key -> Name: List of encoded EVCs
EVC_NAME_FORMAT = 'EVC-VOLTHA-{}-{}'
EVC_NAME_REGEX = 'EVC-VOLTHA-{}'.format('regex-here')
DEFAULT_STPID = 0x8100
class EVC(object):
"""
Class to wrap EVC functionality
"""
class SwitchingMethod(Enum):
SINGLE_TAGGED = 0
DOUBLE_TAGGED = 1
MAC_SWITCHED = 2
class Men2UniManipulation(Enum):
SYMETRIC = 0
POP_OUT_TAG_ONLY = 1
class ElineFlowType(Enum):
NNI_TO_UNI = 0,
UNI_TO_NNI = 1,
NNI_TO_NNI = 2,
ACL_FILTER = 3,
UNKNOWN = 4,
UNSUPPORTED = 5 # Or Invalid
def __init__(self, flow_entry):
self._installed = False
self._status_message = None
self._parent = flow_entry # FlowEntry parent
self._flow = flow_entry.flow
self._handler = flow_entry.handler
self._evc_maps = [] # One if E-Line
self._flow_type = EVC.ElineFlowType.UNKNOWN
# EVC related properties
self._name = EVC.flow_to_name(flow_entry.flow, flow_entry.handler)
self._enabled = True
self._ce_vlan_preservation = True
self._men_ports = []
self._s_tag = -1
self._stpid = DEFAULT_STPID
self._switching_method = EVC.SwitchingMethod.SINGLE_TAGGED
self._men_to_uni_tag_manipulation = EVC.Men2UniManipulation.SYMETRIC
self._valid = self._decode()
@staticmethod
def flow_to_name(flow, handler):
return EVC_NAME_FORMAT.format(flow.id, handler.id)
@staticmethod
def create(flow_entry):
# Does it already exist?
evc = _evc_list.get(EVC.flow_to_name(flow_entry.flow, flow_entry.handler))
if evc is None:
evc = EVC(flow_entry.flow, flow_entry.handler)
if evc is not None:
pass # Look up any EVC that
return
pass # Start decode here
return evc
@property
def valid(self):
return self._valid
@property
def installed(self):
return self._installed
@property
def status(self):
return self._status_message
def install(self):
if not self._installed:
if self._name in _evc_list:
self._status_message = "EVC '{}' already is installed".format(self._name)
raise Exception(self._status_message) # TODO: A unique exception type would work here
raise NotImplemented('TODO: Implement this')
# xml = '<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">' \
# '<evcs xmlns="http://www.adtran.com/ns/yang/adtran-evcs">' \
# '<adtn-evc:evc xmlns:adtn-evc="http://www.adtran.com/ns/yang/adtran-evcs">'
#
# xml += '<adtn-evc:name>' + name + '</adtn-evc:name>'
#
# if stag:
# xml += '<adtn-evc:stag>' + stag + '</adtn-evc:stag>'
#
# if preserve:
# xml += '<adtn-evc:ce-vlan-preservation>' + preserve + '</adtn-evc:ce-vlan-preservation>'
#
# if enabled:
# xml += '<adtn-evc:enabled>' + enabled + '</adtn-evc:enabled>'
# else:
# xml += '<adtn-evc:enabled>' + "true" + '</adtn-evc:enabled>'
#
# xml += '</adtn-evc:evc></evc></config>'
#
# print "Creating EVC %s" % name
#
# print mgr.mgr.edit_config(target="running",
# config=xml,
# default_operation="merge",
# format="xml")
self._installed = True
_evc_list[self.name] = self
pass
return self._installed
def remove(self):
if self._installed:
raise NotImplemented('TODO: Implement this')
# xml = '<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">' \
# '<evcs xmlns="http://www.adtran.com/ns/yang/adtran-evcs">' \
# '<adtn-evc:evc xmlns:adtn-evc="http://www.adtran.com/ns/yang/adtran-evcs" nc:operation="delete">'
#
# xml += '<adtn-evc:name>' + name + '</adtn-evc:name>'
#
# xml += '</adtn-evc:evc></evc></config>'
#
# print "Deleting EVC %s" % name
#
# print mgr.mgr.edit_config(target="running",
# config=xml,
# default_operation="merge",
# format="xml")
self._installed = False
_evc_list.pop(self.name)
pass
return not self._installed
def enable(self):
if not self._enabled:
raise NotImplemented("TODO: Implement this")
self._enabled = False
def disable(self):
if self._enabled:
raise NotImplemented("TODO: Implement this")
self._enabled = True
def _decode(self):
"""
Examine flow rules and extract appropriate settings for both this EVC
and creates any EVC-Maps required.
"""
from evc_map import EVCMap
# Determine this flow's type
status = self._decode_traffic_selector() and self._decode_traffic_treatment()
if status:
ingress_map = EVCMap.createIngressMap(self._flow, self._device)
egress_map = EVCMap.createEgressMap(self._flow, self._device)
status = ingress_map.valid and egress_map.valid
if status:
self._evc_maps.append(ingress_map)
self._evc_maps.append(egress_map)
else:
self._status_message = 'Ingress MAP invalid: {}'.format(ingress_map.status)\
if not ingress_map.valid else 'Egress MAP invalid: {}'.format(egress_map.status)
return status
def _is_men_port(self, port):
return port in self._handler.northbound_ports(port)
def _is_uni_port(self, port):
return port in self._handler.southbound_ports(port)
def _is_logical_port(self, port):
return not self._is_men_port(port) and not self._is_uni_port(port)
def _get_port_name(self, port):
if self._is_logical_port(port):
raise NotImplemented('TODO: Logical ports not yet supported')
if self._is_men_port(port):
return self._handler.northbound_ports[port].name
return None
def _decode_traffic_selector(self):
"""
Extract EVC related traffic selection settings
"""
in_port = fd.get_in_port(self._flow)
assert in_port is not None
if self._is_men_port(in_port):
log.debug('in_port is a MEN Port', port=in_port)
self._men_ports.append(self._get_port_name(in_port))
else:
pass # UNI Ports handled in the EVC Maps
for field in fd.get_ofb_fields(self._flow):
log.debug('Found OFB field', field=field)
self._status_message = 'Unsupported field.type={}'.format(field.type)
return False
return True
def _decode_traffic_treatment(self):
out_port = fd.get_out_port(self._flow)
num_outputs = 0
if self._is_men_port(out_port):
log.debug('out_port is a MEN Port', port=out_port)
self._men_ports.append(self._get_port_name(out_port))
else:
pass # UNI Ports handled in the EVC Maps
for action in fd.get_actions(self._flow):
if action.type == fd.OUTPUT:
num_outputs += 1 # Handled earlier
assert num_outputs <= 1 # Only E-LINE supported and no UNI<->UNI
else:
# TODO: May need to modify ce-preservation
log.debug('Found action', action=action)
return True
# BULK operations
@staticmethod
def enable_all(regex_=EVC_NAME_REGEX):
raise NotImplemented("TODO: Implement this")
@staticmethod
def disable_all(regex_=EVC_NAME_REGEX):
raise NotImplemented("TODO: Implement this")
@staticmethod
def remove_all(regex_=EVC_NAME_REGEX):
"""
Remove all matching EVCs and associated EVC MAPs from hardware
:param regex_: (String) Regular expression for name matching
"""
raise NotImplemented("TODO: Implement this")