diff options
author | Jaime Caamaño Ruiz <jcaamano@suse.com> | 2018-03-02 18:14:37 +0100 |
---|---|---|
committer | Jaime Caamaño Ruiz <jcaamano@suse.com> | 2018-04-04 16:01:48 +0200 |
commit | f66a8164cfb2ef172ee3ea274992b9c07306f08c (patch) | |
tree | 1af3520c091dc72a50eed660774d38cd427f4bdc | |
parent | cc16df5b8317d46e06f4331edf602eaa2a77acbf (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.py | 198 | ||||
-rw-r--r-- | sfc/tests/functest/sfc_chain_deletion.py | 7 | ||||
-rw-r--r-- | sfc/tests/functest/sfc_one_chain_two_service_functions.py | 3 | ||||
-rw-r--r-- | sfc/tests/functest/sfc_two_chains_SSH_and_HTTP.py | 6 |
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: |