import os import re import pytest from tests.helpers import validates, get_base_template_from_yaml_files, get_param from tests.utils.nested_files import get_nested_files from tests.structures import Heat INTERNAL_UUID_PATTERN = re.compile(r"^int_(?P.+?)_net_id$") INTERNAL_NAME_PATTERN = re.compile(r"^int_(?P.+?)_net_name$") INTERNAL_PORT = re.compile( r"^(?P.+)_(?P\d+)_int_" r"(?P.+)_port_(?P\d+)$" ) EXTERNAL_PORT = re.compile( r"^(?P.+)_(?P\d+)_(?!int_)" r"(?P.+)_port_(?P\d+)$" ) EXTERNAL_UUID_PATTERN = re.compile(r"^(?!int_)(?P.+?)_net_id$") EXTERNAL_NAME_PATTERN = re.compile(r"^(?!int_)(?P.+?)_net_name$") INTERNAL_NETWORK_PATTERN = re.compile(r"^int_(?P.+?)" r"_(network|RVN)$") def is_incremental_module(yaml_file, base_path, nested_paths): return yaml_file != base_path and yaml_file not in nested_paths @validates("R-86182", "R-22688") def test_internal_network_parameters(yaml_files): base_path = get_base_template_from_yaml_files(yaml_files) if not base_path: pytest.skip("No base module found") base_heat = Heat(filepath=base_path) nested_paths = get_nested_files(yaml_files) incremental_modules = [ f for f in yaml_files if is_incremental_module(f, base_path, nested_paths) ] errors = [] for module in incremental_modules: heat = Heat(filepath=module) for rid, port in heat.neutron_port_resources.items(): rid_match = INTERNAL_PORT.match(rid) if not rid_match: continue network = (port.get("properties") or {}).get("network") or {} if isinstance(network, dict) and ( "get_resource" in network or "get_attr" in network ): continue param = get_param(network) if not param: errors.append( ( "The internal port ({}) must either connect to a network " "in the base module using get_param or to a network " "created in this module ({})" ).format(rid, os.path.split(module)[1]) ) continue param_match = INTERNAL_UUID_PATTERN.match( param ) or INTERNAL_NAME_PATTERN.match(param) if not param_match: errors.append( ( "The internal port ({}) network parameter ({}) does not " "match one of the required naming conventions of " "int_{{network-role}}_net_id or " "int_{{network-role}}_net_name " "for connecting to an internal network. " "If this is not an internal port, then change the resource " "ID to adhere to the external port naming convention." ).format(rid, param) ) continue if param not in base_heat.yml.get("outputs", {}): base_module = os.path.split(base_path)[1] errors.append( ( "The internal network parameter ({}) attached to port ({}) " "must be defined in the output section of the base module ({})." ).format(param, rid, base_module) ) continue param_network_role = param_match.groupdict().get("network_role") rid_network_role = rid_match.groupdict().get("network_role") if param_network_role.lower() != rid_network_role.lower(): errors.append( ( "The network role ({}) extracted from the resource ID ({}) " "does not match network role ({}) extracted from the " "network parameter ({})" ).format(rid_network_role, rid, param_network_role, param) ) resources = base_heat.get_all_resources(os.path.split(base_path)[0]) networks = { rid: resource for rid, resource in resources.items() if resource.get("type") in {"OS::Neutron::Net", "OS::ContrailV2::VirtualNetwork"} } matches = (INTERNAL_NETWORK_PATTERN.match(n) for n in networks) roles = {m.groupdict()["network_role"].lower() for m in matches if m} if param_network_role.lower() not in roles: errors.append( ( "No internal network with a network role of {} was " "found in the base modules networks: {}" ).format(param_network_role, ", ".join(networks)) ) assert not errors, ". ".join(errors) @validates("R-62983") def test_external_network_parameter(heat_template): heat = Heat(filepath=heat_template) errors = [] for rid, port in heat.neutron_port_resources.items(): rid_match = EXTERNAL_PORT.match(rid) if not rid_match: continue # only test external ports network = (port.get("properties") or {}).get("network") or {} if not isinstance(network, dict) or "get_param" not in network: errors.append( ( "The external port ({}) must assign the network property " "using get_param. If this port is for an internal network, " "then change the resource ID format to the external format." ).format(rid) ) continue param = get_param(network) if not param: errors.append( ( "The get_param function on the network property of port ({}) " "must only take a single, string parameter." ).format(rid) ) continue param_match = EXTERNAL_NAME_PATTERN.match(param) or EXTERNAL_UUID_PATTERN.match( param ) if not param_match: errors.append( ( "The network parameter ({}) on port ({}) does not match one of " "{{network-role}}_net_id or {{network-role}}_net_name." ).format(param, rid) ) continue rid_network_role = rid_match.groupdict()["network_role"] param_network_role = param_match.groupdict()["network_role"] if rid_network_role.lower() != param_network_role.lower(): errors.append( ( "The network role ({}) extracted from the resource ID ({}) " "does not match network role ({}) extracted from the " "network parameter ({})" ).format(rid_network_role, rid, param_network_role, param) ) assert not errors, ". ".join(errors)