summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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: