diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 358f15a64..72b5c7c41 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -543,6 +543,26 @@ def test_upload_image_to_glance(self): expected_status='active', msg='Image status wait') + def test_get_app_names_for_charm(self): + self.patch_object(openstack_utils.juju_utils, "get_full_juju_status", + return_value={ + "applications": { + "nova-compute-kvm": { + "charm-name": 'nova-compute', + }, + "nova-compute-sriov": { + "charm-name": 'nova-compute' + }, + "cinder": { + "charm-name": 'cinder-volume', + }, + }, + }) + self.assertEqual( + ['nova-compute-kvm', 'nova-compute-sriov'], + openstack_utils.get_app_names_for_charm('nova-compute')) + self.get_full_juju_status.assert_called_once_with(model_name=None) + def test_is_ceph_image_backend_True(self): self.patch_object(openstack_utils.juju_utils, "get_full_juju_status", return_value={ diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index fa8e89879..9c0a1e6f7 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -1285,7 +1285,8 @@ def test_check_pool_types(self): """Check type of pools created for clients.""" app_pools = [ ('glance', 'glance'), - ('nova-compute', 'nova'), + (zaza_openstack.get_app_names_for_charm('nova-compute')[0], + 'nova'), ('cinder-ceph', 'cinder-ceph')] runtime_pool_details = zaza_ceph.get_ceph_pool_details() for app, pool_name in app_pools: diff --git a/zaza/openstack/charm_tests/nova/tests.py b/zaza/openstack/charm_tests/nova/tests.py index 34f02731c..946ab1ac1 100644 --- a/zaza/openstack/charm_tests/nova/tests.py +++ b/zaza/openstack/charm_tests/nova/tests.py @@ -19,6 +19,7 @@ import json import logging import os +import re import tempfile import tenacity import unittest @@ -127,7 +128,9 @@ def check_tpm(stdin, stdout, stderr): password=password, privkey=privkey, verify=check_tpm) - @test_utils.skipUntilVersion('nova-compute', 'nova-common', '3:23.0.0') + @test_utils.skipUntilVersion( + openstack_utils.get_app_names_for_charm('nova-compute')[0], + 'nova-common', '3:23.0.0') def test_launch_vtpm_1_2_instance(self): """Launch an instance using TPM 1.2.""" self.RESOURCE_PREFIX = 'zaza-nova' @@ -138,7 +141,9 @@ def test_launch_vtpm_1_2_instance(self): # Note: TPM 1.2 presents tpm0 as a device self._check_tpm_device(instance, '/dev/tpm0') - @test_utils.skipUntilVersion('nova-compute', 'nova-common', '3:23.0.0') + @test_utils.skipUntilVersion( + openstack_utils.get_app_names_for_charm('nova-compute')[0], + 'nova-common', '3:23.0.0') def test_launch_vtpm_2_instance(self): """Launch an instance using TPM 2.0.""" self.RESOURCE_PREFIX = 'zaza-nova' @@ -265,8 +270,9 @@ def fetch_nova_service_hostname(self, unit_name): def test_940_enable_disable_actions(self): """Test disable/enable actions on nova-compute units.""" - nova_units = zaza.model.get_units('nova-compute', - model_name=self.model_name) + nova_units = zaza.model.get_units( + self.application_name, + model_name=self.model_name) # Check that nova-compute services are enabled before testing for service in self.nova_client.services.list(binary='nova-compute'): @@ -307,8 +313,9 @@ def check_instance_count(expect_count, unit_name): instances = result.data.get('results', {}).get('instance-count') self.assertEqual(instances, str(expect_count)) - nova_unit = zaza.model.get_units('nova-compute', - model_name=self.model_name)[0] + nova_unit = zaza.model.get_units( + self.application_name, + model_name=self.model_name)[0] check_instance_count(0, nova_unit.entity_id) @@ -345,8 +352,9 @@ def wait_for_nova_compute_count(expected_count): sleep_timeout = 10 return False - all_units = zaza.model.get_units('nova-compute', - model_name=self.model_name) + all_units = zaza.model.get_units( + self.application_name, + model_name=self.model_name) unit_to_remove = all_units[0] @@ -359,6 +367,9 @@ def wait_for_nova_compute_count(expected_count): if service_count < 1: self.fail("Unit '{}' has no nova-compute services registered in" " nova-cloud-controller".format(unit_to_remove.name)) + + # This elif would be removed in a future code when we have support + # for more than 1 nova-compute node in the CI elif service_count > 1: self.fail("Unexpected number of nova-compute services registered" " in nova-cloud controller. Expecting: 1, found: " @@ -372,7 +383,7 @@ def wait_for_nova_compute_count(expected_count): # Wait for nova-compute service to be removed from the # nova-cloud-controller - if not wait_for_nova_compute_count(0): + if not wait_for_nova_compute_count(service_count - 1): self.fail("nova-compute service was not unregistered from the " "nova-cloud-controller as expected.") @@ -382,7 +393,7 @@ def wait_for_nova_compute_count(expected_count): 'register-to-cloud', raise_on_failure=True) - if not wait_for_nova_compute_count(1): + if not wait_for_nova_compute_count(service_count): self.fail("nova-compute service was not re-registered to the " "nova-cloud-controller as expected.") @@ -398,12 +409,14 @@ def test_311_pci_alias_config_compute(self): """ # We are not touching the behavior of anything older than QUEENS if self.current_release >= self.XENIAL_QUEENS: - self._test_pci_alias_config("nova-compute", ['nova-compute']) + self._test_pci_alias_config( + self.application_name, ['nova-compute']) def test_500_hugepagereport_action(self): """Test hugepagereport action.""" - for unit in zaza.model.get_units('nova-compute', - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): logging.info('Running `hugepagereport` action' ' on unit {}'.format(unit.entity_id)) action = zaza.model.run_action( @@ -446,12 +459,13 @@ def test_920_change_aa_profile(self): logging.info( 'Waiting for services ({}) to be restarted'.format(services)) zaza.model.block_until_services_restarted( - 'nova-compute', + self.application_name, mtime, services, model_name=self.model_name) - for unit in zaza.model.get_units('nova-compute', - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): logging.info('Checking number of profiles in complain ' 'mode in {}'.format(unit.entity_id)) run = zaza.model.run_on_unit( @@ -470,10 +484,74 @@ def test_901_pause_resume(self): with self.pause_resume(['nova-compute']): logging.info("Testing pause resume") + def test_904_test_ceph_keys(self): + """Test if the ceph keys in /etc/ceph are correct.""" + # only run if configured as rbd with ceph image backend + if zaza.model.get_application_config( + self.application_name)['libvirt-image-backend'].get( + 'value') != 'rbd': + return + + # Regex for + # [client.nova-compute] + # key = AQBm5xJl8CSnFxAACB9GVr2llNO0G8zWZuZnjQ == + regex = re.compile(r"^\[client.(.+)\]\n\tkey = (.+)$") + key_dict = {} + + # The new and correct behavior is to have "nova-compute" named keyring + # and one other named after the charm app, IF the charm app name is + # different than "nova-compute". Example: + # for a charm app named "nova-compute-kvm", + # it should have both nova-compute-kvm and nova-compute keyrings. + # for a charm app named "nova-compute", + # it should have only nova-compute keyring. + + # Previous behaviors: + # The old behavior is to have only 1 keyring named after the charm app. + # the intermediary behavior with the partial fix is to always have only + # the "nova-compute" keyring. + + nova_compute_apps = openstack_utils.get_app_names_for_charm( + 'nova-compute') + + def check_keyring(app_name): + """Check matching keyring name and different from existing ones.""" + keyring_file = ( + '/etc/ceph/ceph.client.{}.keyring'.format(app_name)) + data = str(generic_utils.get_file_contents( + unit, keyring_file)) + + result = regex.findall(data) + + # Assert keyring file name matches intended name + self.assertEqual(2, len(result)) + self.assertEqual(result[0], app_name) + + # Confirm the keys are different from each already checked + # nova-compute charm app + for k, v in key_dict.items(): + if k == result[0]: + self.assertEqual(v, result[1]) + else: + self.assertNotEqual(v, result[1]) + key_dict[result[0]] = result[1] + + # We typically only have 1 nova-compute charm app in the CI, + # so this code is future-proofing + for app_name in nova_compute_apps: + for unit in zaza.model.get_units( + app_name, model_name=self.model_name): + + check_keyring('nova-compute') + # extra future code for the new fixed version + # if app_name != 'nova-compute': + # check_keyring(app_name) + def test_930_check_virsh_default_network(self): """Test default virt network is not present.""" - for unit in zaza.model.get_units('nova-compute', - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): logging.info('Checking default network is absent on ' 'unit {}'.format(unit.entity_id)) run = zaza.model.run_on_unit( @@ -492,8 +570,9 @@ class NovaComputeActionTest(test_utils.OpenStackBaseTest): def test_virsh_audit_action(self): """Test virsh-audit action.""" - for unit in zaza.model.get_units('nova-compute', - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): logging.info('Running `virsh-audit` action' ' on unit {}'.format(unit.entity_id)) action = zaza.model.run_action( @@ -520,8 +599,9 @@ def test_vgpu_in_nova_conf(self): vgpu-device-mappings has been set to something not empty like "{'nvidia-108': ['0000:c1:00.0']}". """ - for unit in zaza.model.get_units('nova-compute', - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): nova_conf_file = '/etc/nova/nova.conf' nova_conf = str(generic_utils.get_file_contents(unit, nova_conf_file)) @@ -775,12 +855,14 @@ class NovaCloudControllerActionTest(test_utils.OpenStackBaseTest): def test_sync_compute_az_action(self): """Test sync-compute-availability-zones action.""" juju_units_az_map = {} - compute_config = zaza.model.get_application_config('nova-compute') + compute_config = zaza.model.get_application_config( + self.application_name) default_az = compute_config['default-availability-zone']['value'] use_juju_az = compute_config['customize-failure-domain']['value'] - for unit in zaza.model.get_units('nova-compute', - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): zone = default_az if use_juju_az: result = zaza.model.run_on_unit(unit.name, @@ -943,12 +1025,12 @@ def test_230_resize_to_the_same_host(self): # Make config change, wait for the services restart on # nova-cloud-controller with self.config_change(set_default, set_alternate): - # The config change should propagate to nova-cmopute units + # The config change should propagate to nova-compute units # Wait for them to get settled after relation data has changed logging.info( 'Waiting for services ({}) to be restarted'.format(services)) zaza.model.block_until_services_restarted( - 'nova-compute', + self.application_name, mtime, services, model_name=self.model_name) @@ -957,8 +1039,9 @@ def test_230_resize_to_the_same_host(self): # it's changed nova_cfg = ConfigParser() - for unit in zaza.model.get_units('nova-compute', - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): logging.info('Checking value of {} in {}'.format( nova_config_key, unit.entity_id)) result = zaza.model.run_on_unit( @@ -1156,8 +1239,9 @@ def test_security_checklist(self): else: expected_failures.extend(tls_checks) - for unit in zaza.model.get_units(self.application_name, - model_name=self.model_name): + for unit in zaza.model.get_units( + self.application_name, + model_name=self.model_name): logging.info('Running `security-checklist` action' ' on unit {}'.format(unit.entity_id)) test_utils.audit_assertions( diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index c6873a678..89288e87d 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -472,7 +472,7 @@ def restart_on_changed(self, config_file, default_config, alternate_config, a service. :type pgrep_full: bool """ - # lead_unit is only useed to grab a timestamp, the assumption being + # lead_unit is only used to grab a timestamp, the assumption being # that all the units times are in sync. mtime = model.get_unit_time( diff --git a/zaza/openstack/charm_tests/vault/setup.py b/zaza/openstack/charm_tests/vault/setup.py index 05e8cc547..912282cbc 100644 --- a/zaza/openstack/charm_tests/vault/setup.py +++ b/zaza/openstack/charm_tests/vault/setup.py @@ -25,6 +25,7 @@ import zaza.openstack.utilities.cert import zaza.openstack.utilities.generic import zaza.openstack.utilities.exceptions as zaza_exceptions +from zaza.openstack.utilities.openstack import get_app_names_for_charm import zaza.utilities.juju as juju_utils @@ -187,12 +188,18 @@ def auto_initialize(cacert=None, validation_application='keystone', wait=True, validate_ca(cacertificate, application=validation_application) # Once validation has completed restart nova-compute to work around # bug #1826382 + nova_compute_app_names = get_app_names_for_charm('nova-compute') + charm_apps_to_restart_svcs = (nova_compute_app_names + + ['nova-cloud-controller']) + cmd_map = { 'nova-cloud-controller': ('systemctl restart ' 'nova-scheduler nova-conductor'), - 'nova-compute': 'systemctl restart nova-compute', } - for app in ('nova-compute', 'nova-cloud-controller',): + for nova_compute_app_name in nova_compute_app_names: + cmd_map[nova_compute_app_name] = 'systemctl restart nova-compute' + + for app in charm_apps_to_restart_svcs: try: for unit in zaza.model.get_units(app): result = zaza.model.run_on_unit( diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index e548884d3..a43d3ae4e 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -811,7 +811,8 @@ def add_interface_to_netplan(server_name, mac_address): application_names = ('neutron-openvswitch',) elif ovn_present(): # OVN chassis is a subordinate to nova-compute - application_names = ('nova-compute', 'ovn-dedicated-chassis') + application_names = (get_app_names_for_charm('nova-compute')[0], + 'ovn-dedicated-chassis') else: application_names = ('neutron-gateway',) @@ -2693,6 +2694,20 @@ def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2', return image +def get_app_names_for_charm(charm_name, model_name=None): + """Get all application names for a given charm in the model. + + :returns: List of application names + :rtype: List[str] + """ + status = juju_utils.get_full_juju_status(model_name=model_name) + result = [] + for app_name, app_data in status['applications'].items(): + if charm_name == app_data['charm-name']: + result.append(app_name) + return result + + def is_ceph_image_backend(model_name=None): """Check if glance is related to ceph, therefore using ceph as backend.