diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index b7fc859..da8078b 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -122,6 +122,23 @@ class TempestTestWithKeystoneV3(TempestTestBase): return super().run() +class TempestTestWithKeystoneMinimal(TempestTestBase): + """Tempest test class to validate an OpenStack setup with Keystone V2.""" + + def run(self): + """Run tempest tests as specified in tests/tests.yaml. + + Allow test to run even if some components are missing (like + external network setup). + See TempestTestBase.run() for the available test options. + + :returns: Status of tempest run + :rtype: bool + """ + tempest_utils.render_tempest_config_keystone_v3(minimal=True) + return super().run() + + class TempestTest(TempestTestBase): """Tempest test class. diff --git a/zaza/openstack/charm_tests/tempest/utils.py b/zaza/openstack/charm_tests/tempest/utils.py index fa6c051..7463e11 100644 --- a/zaza/openstack/charm_tests/tempest/utils.py +++ b/zaza/openstack/charm_tests/tempest/utils.py @@ -21,6 +21,8 @@ import shutil import subprocess import urllib.parse +from neutronclient.common import exceptions as neutronexceptions + import zaza.model as model import zaza.utilities.deployment_env as deployment_env import zaza.openstack.utilities.juju as juju_utils @@ -51,13 +53,18 @@ def render_tempest_config_keystone_v2(): _setup_tempest('tempest_v2.j2', 'accounts.j2') -def render_tempest_config_keystone_v3(): +def render_tempest_config_keystone_v3(minimal=False): """Render tempest config for Keystone V3 API. + :param minimal: Run in minimal mode eg ignore missing setup + :type minimal: bool :returns: None :rtype: None """ - _setup_tempest('tempest_v3.j2', 'accounts.j2') + _setup_tempest( + 'tempest_v3.j2', + 'accounts.j2', + minimal=minimal) def get_workspace(): @@ -105,20 +112,22 @@ def _init_workspace(workspace_path): pass -def _setup_tempest(tempest_template, accounts_template): +def _setup_tempest(tempest_template, accounts_template, minimal=False): """Initialize tempest and render tempest config. :param tempest_template: tempest.conf template :type tempest_template: module :param accounts_template: accounts.yaml template :type accounts_template: module + :param minimal: Run in minimal mode eg ignore missing setup + :type minimal: bool :returns: None :rtype: None """ workspace_name, workspace_path = get_workspace() destroy_workspace(workspace_name, workspace_path) _init_workspace(workspace_path) - context = _get_tempest_context(workspace_path) + context = _get_tempest_context(workspace_path, missing_fatal=not minimal) _render_tempest_config( os.path.join(workspace_path, 'etc/tempest.conf'), context, @@ -129,9 +138,13 @@ def _setup_tempest(tempest_template, accounts_template): accounts_template) -def _get_tempest_context(workspace_path): +def _get_tempest_context(workspace_path, missing_fatal=True): """Generate the tempest config context. + :param workspace_path: path to workspace directory + :type workspace_path: str + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: Context dictionary :rtype: dict """ @@ -153,8 +166,14 @@ def _get_tempest_context(workspace_path): _add_application_ips(ctxt) for svc_name, ctxt_func in ctxt_funcs.items(): if svc_name in ctxt['enabled_services']: - ctxt_func(ctxt, keystone_session) - _add_environment_var_config(ctxt, ctxt['enabled_services']) + ctxt_func( + ctxt, + keystone_session, + missing_fatal=missing_fatal) + _add_environment_var_config( + ctxt, + ctxt['enabled_services'], + missing_fatal=missing_fatal) _add_auth_config(ctxt) if 'octavia' in ctxt['enabled_services']: _add_octavia_config(ctxt) @@ -194,13 +213,15 @@ def _add_application_ips(ctxt): ctxt['ncc'] = juju_utils.get_application_ip('nova-cloud-controller') -def _add_nova_config(ctxt, keystone_session): +def _add_nova_config(ctxt, keystone_session, missing_fatal=True): """Add nova config to context. :param ctxt: Context dictionary :type ctxt: dict :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: None :rtype: None """ @@ -213,54 +234,52 @@ def _add_nova_config(ctxt, keystone_session): ctxt['flavor_ref_alt'] = flavor.id -def _add_neutron_config(ctxt, keystone_session): +def _add_neutron_config(ctxt, keystone_session, missing_fatal=True): """Add neutron config to context. :param ctxt: Context dictionary :type ctxt: dict :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: None :rtype: None """ - current_release = openstack_utils.get_os_release() - focal_ussuri = openstack_utils.get_os_release('focal_ussuri') neutron_client = openstack_utils.get_neutron_session_client( keystone_session) - net = neutron_client.find_resource("network", "ext_net") - ctxt['ext_net'] = net['id'] - router = neutron_client.find_resource("router", "provider-router") - ctxt['provider_router_id'] = router['id'] - # For focal+ with OVN, we use the same settings as upstream gate. - # This is because the l3_agent_scheduler extension is only - # applicable for OVN when conventional layer-3 agent enabled: - # https://docs.openstack.org/networking-ovn/2.0.1/features.html - # This enables test_list_show_extensions to run successfully. - if current_release >= focal_ussuri: - extensions = ('address-scope,agent,allowed-address-pairs,' - 'auto-allocated-topology,availability_zone,' - 'binding,default-subnetpools,external-net,' - 'extra_dhcp_opt,multi-provider,net-mtu,' - 'network_availability_zone,network-ip-availability,' - 'port-security,provider,quotas,rbac-address-scope,' - 'rbac-policies,standard-attr-revisions,security-group,' - 'standard-attr-description,subnet_allocation,' - 'standard-attr-tag,standard-attr-timestamp,trunk,' - 'quota_details,router,extraroute,ext-gw-mode,' - 'fip-port-details,pagination,sorting,project-id,' - 'dns-integration,qos') - ctxt['neutron_api_extensions'] = extensions - else: - ctxt['neutron_api_extensions'] = 'all' + try: + net = neutron_client.find_resource("network", "ext_net") + ctxt['ext_net'] = net['id'] + router = neutron_client.find_resource("router", "provider-router") + ctxt['provider_router_id'] = router['id'] + except neutronexceptions.NotFound: + if missing_fatal: + raise + extensions = ('address-scope,agent,allowed-address-pairs,' + 'auto-allocated-topology,availability_zone,' + 'binding,default-subnetpools,external-net,' + 'extra_dhcp_opt,multi-provider,net-mtu,' + 'network_availability_zone,network-ip-availability,' + 'port-security,provider,quotas,rbac-address-scope,' + 'rbac-policies,standard-attr-revisions,security-group,' + 'standard-attr-description,subnet_allocation,' + 'standard-attr-tag,standard-attr-timestamp,trunk,' + 'quota_details,router,extraroute,ext-gw-mode,' + 'fip-port-details,pagination,sorting,project-id,' + 'dns-integration,qos') + ctxt['neutron_api_extensions'] = extensions -def _add_glance_config(ctxt, keystone_session): +def _add_glance_config(ctxt, keystone_session, missing_fatal=True): """Add glance config to context. :param ctxt: Context dictionary :type ctxt: dict :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: None :rtype: None """ @@ -276,13 +295,15 @@ def _add_glance_config(ctxt, keystone_session): ctxt['image_alt_id'] = image_alt[0].id -def _add_cinder_config(ctxt, keystone_session): +def _add_cinder_config(ctxt, keystone_session, missing_fatal=True): """Add cinder config to context. :param ctxt: Context dictionary :type ctxt: dict :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: None :rtype: None """ @@ -297,13 +318,15 @@ def _add_cinder_config(ctxt, keystone_session): break -def _add_keystone_config(ctxt, keystone_session): +def _add_keystone_config(ctxt, keystone_session, missing_fatal=True): """Add keystone config to context. :param ctxt: Context dictionary :type ctxt: dict :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: None :rtype: None """ @@ -313,11 +336,13 @@ def _add_keystone_config(ctxt, keystone_session): ctxt['default_domain_id'] = domain.id -def _add_octavia_config(ctxt): +def _add_octavia_config(ctxt, missing_fatal=True): """Add octavia config to context. :param ctxt: Context dictionary :type ctxt: dict + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: None :rtype: None :raises: subprocess.CalledProcessError @@ -334,11 +359,15 @@ def _add_octavia_config(ctxt): ]) -def _add_environment_var_config(ctxt, services): +def _add_environment_var_config(ctxt, services, missing_fatal=True): """Add environment variable config to context. :param ctxt: Context dictionary :type ctxt: dict + :param services: List of services + :type services: List[str] + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool :returns: None :rtype: None """ @@ -353,7 +382,7 @@ def _add_environment_var_config(ctxt, services): else: if var not in IGNORABLE_VARS: missing_vars.append(var) - if missing_vars: + if missing_vars and missing_fatal: raise ValueError( ("Environment variables [{}] must all be set to run this" " test").format(', '.join(missing_vars)))