summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJaime Caamaño Ruiz <jcaamano@suse.com>2018-03-02 18:14:37 +0100
committerJaime Caamaño Ruiz <jcaamano@suse.com>2018-04-04 16:01:48 +0200
commitf66a8164cfb2ef172ee3ea274992b9c07306f08c (patch)
tree1af3520c091dc72a50eed660774d38cd427f4bdc
parentcc16df5b8317d46e06f4331edf602eaa2a77acbf (diff)
Support sfp redirect action on acl flow check
Starting Oxygen, ODL networking-sfc translator supports symmetric paths and maps flow classifiers to ACLs with SFP redirect action instead of RSP redirect action. A SFP may have two RSPs associated if the path is symmetric, one otherwise. One of the two RSPs will be flagged as the reverse path and the other is the forward path. Traffic from the logical-source-port will be classified to the forward path, while traffic from the logical-destination-port will be classified to the reverse path. This patch updates the ACL flow check logic to account for the above. Change-Id: I2d8b9e6c2a1033d469668db29b9c18f525e89370 Signed-off-by: Jaime Caamaño Ruiz <jcaamano@suse.com>
-rw-r--r--sfc/lib/odl_utils.py198
-rw-r--r--sfc/tests/functest/sfc_chain_deletion.py7
-rw-r--r--sfc/tests/functest/sfc_one_chain_two_service_functions.py3
-rw-r--r--sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py6
4 files changed, 150 insertions, 64 deletions
diff --git a/sfc/lib/odl_utils.py b/sfc/lib/odl_utils.py
index edd52054..e1980423 100644
--- a/sfc/lib/odl_utils.py
+++ b/sfc/lib/odl_utils.py
@@ -12,6 +12,15 @@ import sfc.lib.openstack_utils as os_sfc_utils
logger = logging.getLogger(__name__)
+ODL_MODULE_EXCEPTIONS = {
+ "service-function-path-state": "service-function-path"
+}
+
+ODL_PLURAL_EXCEPTIONS = {
+ "service-function-path-state": "service-function-paths-state"
+}
+
+
def actual_rsps_in_compute(ovs_logger, compute_ssh):
'''
Example flows that match the regex (line wrapped because of flake8)
@@ -20,31 +29,36 @@ def actual_rsps_in_compute(ovs_logger, compute_ssh):
load:0x27->NXM_NX_NSP[0..23],load:0xff->NXM_NX_NSI[],
load:0xffffff->NXM_NX_NSH_C1[],load:0->NXM_NX_NSH_C2[],resubmit(,17)
'''
- match_rsp = re.compile(
- r'.+tp_dst=([0-9]+).+load:(0x[0-9a-f]+)->NXM_NX_NSP\[0\.\.23\].+')
+ match_rsp = re.compile(r'.+'
+ r'(tp_(?:src|dst)=[0-9]+)'
+ r'.+'
+ r'load:(0x[0-9a-f]+)->NXM_NX_NSP\[0\.\.23\]'
+ r'.+')
# First line is OFPST_FLOW reply (OF1.3) (xid=0x2):
# This is not a flow so ignore
flows = (ovs_logger.ofctl_dump_flows(compute_ssh, 'br-int', '101')
.strip().split('\n')[1:])
matching_flows = [match_rsp.match(f) for f in flows]
- # group(1) = 22 (tp_dst value) | group(2) = 0xff (rsp value)
- rsps_in_compute = ['{0}_{1}'.format(mf.group(2), mf.group(1))
+ # group(1) = tsp_dst=22 | group(2) = 0xff (rsp value)
+ rsps_in_compute = ['{0}|{1}'.format(mf.group(2), mf.group(1))
for mf in matching_flows if mf is not None]
return rsps_in_compute
-def get_active_rsps(odl_ip, odl_port):
+def get_active_rsps_on_ports(odl_ip, odl_port, neutron_ports):
'''
Queries operational datastore and returns the RSPs for which we have
- created a classifier (ACL). These are considered as active RSPs
- for which classification rules should exist in the compute nodes
+ created a classifier (ACL) on the specified neutron ports. These are
+ considered as active RSPs on those ports for which classification rules
+ should exist in the compute node on which such ports are located.
- This function enhances the returned dictionary with the
- destination port of the ACL.
+ This function enhances each returned RSP with the openflow matches on
+ the tcp ports that classify traffic into that RSP.
'''
+ port_ids = [port.id for port in neutron_ports]
acls = get_odl_acl_list(odl_ip, odl_port)
- rsps = []
+ rsps = {}
for acl in acls['access-lists']['acl']:
try:
# We get the first ace. ODL creates a new ACL
@@ -55,47 +69,95 @@ def get_active_rsps(odl_ip, odl_port):
acl['acl-name']))
continue
- if not ('netvirt-sfc-acl:rsp-name' in ace['actions']):
+ matches = ace['matches']
+
+ # We are just interested in the destination-port-range matches
+ # that we use throughout the tests
+ if matches.get('destination-port-range') is None:
+ continue
+ tcp_port = matches['destination-port-range']['lower-port']
+
+ # A single ace may classify traffic into a forward path
+ # and optionally into a reverse path if destination port is set
+ src_port = matches.get('netvirt-sfc-acl:source-port-uuid')
+ dst_port = matches.get('netvirt-sfc-acl:destination-port-uuid')
+ forward_of_match = None
+ reverse_of_match = None
+ if src_port in port_ids:
+ forward_of_match = 'tp_dst=' + str(tcp_port)
+ if dst_port in port_ids:
+ # For classification to the reverse path
+ # the openflow match inverts
+ reverse_of_match = 'tp_src=' + str(tcp_port)
+
+ # This ACL does not apply to any of the given ports
+ if not forward_of_match and not reverse_of_match:
continue
- rsp_name = ace['actions']['netvirt-sfc-acl:rsp-name']
- rsp = get_odl_resource_elem(odl_ip,
- odl_port,
- 'rendered-service-path',
- rsp_name,
- datastore='operational')
- '''
- Rsps are returned in the format:
- {
- "rendered-service-path": [
- {
- "name": "Path-red-Path-83",
- "path-id": 83,
- ...
- "rendered-service-path-hop": [
- {
- ...
- "service-function-name": "testVNF1",
- "service-index": 255
- ...
- 'rendered-service-path' Is returned as a list with one
- element (we select by name and the names are unique)
- '''
- rsp_port = rsp['rendered-service-path'][0]
- rsp_port['dst-port'] = (ace['matches']
- ['destination-port-range']['lower-port'])
- rsps.append(rsp_port)
- return rsps
-
-
-def promised_rsps_in_computes(odl_ip, odl_port):
+ actions = ace['actions']
+ rsp_names = get_rsps_from_netvirt_acl_actions(odl_ip,
+ odl_port,
+ actions)
+
+ for rsp_name in rsp_names:
+ rsp = rsps.get(rsp_name)
+ if not rsp:
+ rsp = get_rsp(odl_ip, odl_port, rsp_name)
+ of_matches = rsp.get('of-matches', [])
+ if reverse_of_match and rsp.get('reverse-path'):
+ of_matches.append(reverse_of_match)
+ elif forward_of_match and not rsp.get('reverse-path'):
+ of_matches.append(forward_of_match)
+ rsp['of-matches'] = of_matches
+ rsps[rsp_name] = rsp
+
+ return rsps.values()
+
+
+def get_rsps_from_netvirt_acl_actions(odl_ip, odl_port, netvirt_acl_actions):
+ '''
+ Return the list of RSPs referenced from the netvirt sfc redirect action
+ '''
+ rsp_names = []
+
+ if 'netvirt-sfc-acl:rsp-name' in netvirt_acl_actions:
+ rsp_names.append(netvirt_acl_actions['netvirt-sfc-acl:rsp-name'])
+
+ if 'netvirt-sfc-acl:sfp-name' in netvirt_acl_actions:
+ # If the acl redirect action is a sfp instead of rsp
+ # we need to get the rsps associated to that sfp
+ sfp_name = netvirt_acl_actions['netvirt-sfc-acl:sfp-name']
+ sfp_state = get_odl_resource_elem(odl_ip,
+ odl_port,
+ 'service-function-path-state',
+ sfp_name,
+ datastore='operational')
+ sfp_rsps = sfp_state.get('sfp-rendered-service-path', [])
+ sfp_rsp_names = [rsp['name'] for rsp in sfp_rsps if 'name' in rsp]
+ rsp_names.extend(sfp_rsp_names)
+
+ return rsp_names
+
+
+def get_rsp(odl_ip, odl_port, rsp_name):
+ rsp = get_odl_resource_elem(odl_ip,
+ odl_port,
+ 'rendered-service-path',
+ rsp_name,
+ datastore='operational')
+ return rsp
+
+
+def promised_rsps_in_compute(odl_ip, odl_port, compute_ports):
'''
- Return a list of rsp_port which represents the rsp id and the destination
- port configured in ODL
+ Return a list of rsp|of_match which represents the RSPs and openflow
+ matches on the source/destination port that classify traffic into such
+ RSP as configured in ODL ACLs
'''
- rsps = get_active_rsps(odl_ip, odl_port)
- rsps_in_computes = ['{0}_{1}'.format(hex(rsp['path-id']), rsp['dst-port'])
- for rsp in rsps]
+ rsps = get_active_rsps_on_ports(odl_ip, odl_port, compute_ports)
+ rsps_in_computes = ['{0}|{1}'.format(hex(rsp['path-id']), of_match)
+ for rsp in rsps
+ for of_match in rsp['of-matches']]
return rsps_in_computes
@@ -116,19 +178,20 @@ def timethis(func):
@timethis
def wait_for_classification_rules(ovs_logger, compute_nodes, odl_ip, odl_port,
- compute_client_name, timeout=200):
+ compute_name, neutron_ports, timeout=200):
'''
Check if the classification rules configured in ODL are implemented in OVS.
We know by experience that this process might take a while
'''
try:
- compute = find_compute(compute_client_name, compute_nodes)
+ compute = find_compute(compute_name, compute_nodes)
# Find the configured rsps in ODL. Its format is nsp_destPort
promised_rsps = []
timeout2 = 10
while not promised_rsps:
- promised_rsps = promised_rsps_in_computes(odl_ip, odl_port)
+ promised_rsps = promised_rsps_in_compute(odl_ip, odl_port,
+ neutron_ports)
timeout2 -= 1
if timeout2 == 0:
os_sfc_utils.get_tacker_items()
@@ -137,7 +200,8 @@ def wait_for_classification_rules(ovs_logger, compute_nodes, odl_ip, odl_port,
time.sleep(3)
while timeout > 0:
- logger.info("RSPs in ODL Operational DataStore:")
+ logger.info("RSPs in ODL Operational DataStore"
+ "for compute '{}':".format(compute_name))
logger.info("{0}".format(promised_rsps))
# Fetch the rsps implemented in the compute
@@ -181,8 +245,18 @@ def get_odl_ip_port(nodes):
return ip, port
-def pluralize(s):
- return '{0}s'.format(s)
+def pluralize(resource):
+ plural = ODL_PLURAL_EXCEPTIONS.get(resource, None)
+ if not plural:
+ plural = '{0}s'.format(resource)
+ return plural
+
+
+def get_module(resource):
+ module = ODL_MODULE_EXCEPTIONS.get(resource, None)
+ if not module:
+ module = resource
+ return module
def format_odl_resource_list_url(odl_ip, odl_port, resource,
@@ -190,7 +264,8 @@ def format_odl_resource_list_url(odl_ip, odl_port, resource,
odl_pwd='admin'):
return ('http://{usr}:{pwd}@{ip}:{port}/restconf/{ds}/{rsrc}:{rsrcs}'
.format(usr=odl_user, pwd=odl_pwd, ip=odl_ip, port=odl_port,
- ds=datastore, rsrc=resource, rsrcs=pluralize(resource)))
+ ds=datastore, rsrc=get_module(resource),
+ rsrcs=pluralize(resource)))
def format_odl_resource_elem_url(odl_ip, odl_port, resource,
@@ -216,7 +291,12 @@ def get_odl_resource_elem(odl_ip, odl_port, resource,
elem_name, datastore='config'):
url = format_odl_resource_elem_url(
odl_ip, odl_port, resource, elem_name, datastore=datastore)
- return requests.get(url).json()
+ response = requests.get(url).json()
+ # Response is in the format of a dictionary containing
+ # a single value that is an array with the element requested:
+ # {'resource' : [element]}
+ # Return just the element
+ return response.get(resource, [{}])[0]
def delete_odl_resource_elem(odl_ip, odl_port, resource, elem_name,
@@ -302,10 +382,10 @@ def find_compute(compute_client_name, compute_nodes):
return compute
-def check_vnffg_deletion(odl_ip, odl_port, ovs_logger, compute_client_name,
- compute_nodes, retries=20):
+def check_vnffg_deletion(odl_ip, odl_port, ovs_logger, neutron_ports,
+ compute_client_name, compute_nodes, retries=20):
'''
- First, RSPs are checked in the oprational datastore of ODL. Nothing should
+ First, RSPs are checked in the operational datastore of ODL. Nothing
should exist. As it might take a while for ODL to remove that, some
retries are needed.
@@ -316,7 +396,7 @@ def check_vnffg_deletion(odl_ip, odl_port, ovs_logger, compute_client_name,
# Check RSPs
while retries_counter > 0:
- if (get_active_rsps(odl_ip, odl_port)):
+ if get_active_rsps_on_ports(odl_ip, odl_port, neutron_ports):
retries_counter -= 1
time.sleep(3)
else:
diff --git a/sfc/tests/functest/sfc_chain_deletion.py b/sfc/tests/functest/sfc_chain_deletion.py
index f96eb120..9fde460f 100644
--- a/sfc/tests/functest/sfc_chain_deletion.py
+++ b/sfc/tests/functest/sfc_chain_deletion.py
@@ -163,7 +163,8 @@ def main():
# Start measuring the time it takes to implement the classification rules
t1 = threading.Thread(target=odl_utils.wait_for_classification_rules,
args=(ovs_logger, compute_nodes, odl_ip,
- odl_port, openstack_sfc.get_compute_client(),))
+ odl_port, openstack_sfc.get_compute_client(),
+ [neutron_port],))
try:
t1.start()
@@ -209,6 +210,7 @@ def main():
os_sfc_utils.delete_vnffgd(tacker_client, vnffgd_name='red')
if not odl_utils.check_vnffg_deletion(odl_ip, odl_port, ovs_logger,
+ [neutron_port],
openstack_sfc.get_compute_client(),
compute_nodes):
logger.debug("The chains were not correctly removed")
@@ -220,7 +222,8 @@ def main():
# Start measuring the time it takes to implement the classification rules
t2 = threading.Thread(target=odl_utils.wait_for_classification_rules,
args=(ovs_logger, compute_nodes, odl_ip,
- odl_port, openstack_sfc.get_compute_client(),))
+ odl_port, openstack_sfc.get_compute_client(),
+ [neutron_port],))
try:
t2.start()
except Exception as e:
diff --git a/sfc/tests/functest/sfc_one_chain_two_service_functions.py b/sfc/tests/functest/sfc_one_chain_two_service_functions.py
index 58323bf3..07f7814c 100644
--- a/sfc/tests/functest/sfc_one_chain_two_service_functions.py
+++ b/sfc/tests/functest/sfc_one_chain_two_service_functions.py
@@ -187,7 +187,8 @@ def main():
# Start measuring the time it takes to implement the classification rules
t1 = threading.Thread(target=odl_utils.wait_for_classification_rules,
args=(ovs_logger, compute_nodes, odl_ip,
- odl_port, openstack_sfc.get_compute_client(),))
+ odl_port, openstack_sfc.get_compute_client(),
+ [neutron_port],))
try:
t1.start()
except Exception as e:
diff --git a/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py b/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py
index 5c5abb33..a5133f00 100644
--- a/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py
+++ b/sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py
@@ -185,7 +185,8 @@ def main():
# Start measuring the time it takes to implement the classification rules
t1 = threading.Thread(target=odl_utils.wait_for_classification_rules,
args=(ovs_logger, compute_nodes, odl_ip,
- odl_port, openstack_sfc.get_compute_client(),))
+ odl_port, openstack_sfc.get_compute_client(),
+ [neutron_port],))
try:
t1.start()
@@ -275,7 +276,8 @@ def main():
# Start measuring the time it takes to implement the classification rules
t2 = threading.Thread(target=odl_utils.wait_for_classification_rules,
args=(ovs_logger, compute_nodes, odl_ip,
- odl_port, openstack_sfc.get_compute_client(),))
+ odl_port, openstack_sfc.get_compute_client(),
+ [neutron_port],))
try:
t2.start()
except Exception as e: