diff --git a/config.yaml b/config.yaml index ecdbd39b..c5f22df7 100644 --- a/config.yaml +++ b/config.yaml @@ -914,3 +914,10 @@ options: Seconds between block device allocation retries. The default value is 3. Please refer to the description of the "block-device-allocate-retries" config for more details. + enable-vtpm: + type: boolean + default: False + description: | + Enable emulated Trusted Platform Module support on the hypervisors. + A key manager, e.g. Barbican, is a required service for this + capability to be enabled. diff --git a/hooks/nova_compute_context.py b/hooks/nova_compute_context.py index 05d08acb..f5385a74 100644 --- a/hooks/nova_compute_context.py +++ b/hooks/nova_compute_context.py @@ -1001,3 +1001,19 @@ class NovaComputePlacementContext(context.OSContextGenerator): ctxt[ratio_config] = relation_get(ratio_config, **rel) return ctxt + + +class NovaComputeSWTPMContext(context.OSContextGenerator): + + def __call__(self): + cmp_os_release = CompareOpenStackReleases(os_release('nova-common')) + ctxt = {} + + # SWTPM enablement is introduced in Victoria, but we'll enable it for + # Wallaby and newer releases. + if cmp_os_release >= 'wallaby': + ctxt = { + 'swtpm_enabled': config('enable-vtpm'), + } + + return ctxt diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py index 537532a4..89df3279 100755 --- a/hooks/nova_compute_hooks.py +++ b/hooks/nova_compute_hooks.py @@ -34,6 +34,7 @@ from charmhelpers.core.hookenv import ( log, DEBUG, INFO, + ERROR, relation_ids, remote_service_name, related_units, @@ -125,6 +126,7 @@ from nova_compute_utils import ( remove_old_packages, MULTIPATH_PACKAGES, USE_FQDN_KEY, + SWTPM_PACKAGES, ) from charmhelpers.contrib.network.ip import ( @@ -316,6 +318,7 @@ def config_changed(): install_vaultlocker() install_multipath() + install_swtpm() configure_local_ephemeral_storage() @@ -348,6 +351,19 @@ def install_multipath(): apt_install(MULTIPATH_PACKAGES, fatal=True) +def install_swtpm(): + if config('enable-vtpm'): + installed = len(filter_installed_packages(SWTPM_PACKAGES)) == 0 + if not installed: + try: + apt_install(SWTPM_PACKAGES, fatal=True) + except Exception: + log('Error installing swtpm packages. Try adding extra ' + 'repositories containing swtpm packages or disabling ' + 'vtpm by setting enable-vtpm=False.', ERROR) + raise + + @hooks.hook('amqp-relation-joined') def amqp_joined(relation_id=None): relation_set(relation_id=relation_id, diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py index f5b6313f..6f33d88b 100644 --- a/hooks/nova_compute_utils.py +++ b/hooks/nova_compute_utils.py @@ -113,6 +113,7 @@ from nova_compute_context import ( NovaComputeAvailabilityZoneContext, NeutronPluginSubordinateConfigContext, NovaComputePlacementContext, + NovaComputeSWTPMContext, ) import charmhelpers.contrib.openstack.vaultlocker as vaultlocker @@ -175,6 +176,11 @@ HELD_PACKAGES = [ 'python-psutil', ] +SWTPM_PACKAGES = [ + 'swtpm', + 'swtpm-tools', +] + VERSION_PACKAGE = 'nova-common' DEFAULT_INSTANCE_PATH = '/var/lib/nova/instances' @@ -282,7 +288,8 @@ LIBVIRT_BIN_DAEMON = 'libvirt-bin' LIBVIRT_RESOURCE_MAP = { QEMU_CONF: { 'services': [LIBVIRT_BIN_DAEMON], - 'contexts': [NovaComputeLibvirtContext()], + 'contexts': [NovaComputeLibvirtContext(), + NovaComputeSWTPMContext()], }, QEMU_KVM: { 'services': ['qemu-kvm'], @@ -393,15 +400,21 @@ def resource_map(): resource_map.pop(NOVA_API_AA_PROFILE_PATH) resource_map.pop(NOVA_NETWORK_AA_PROFILE_PATH) - if virt_type == 'ironic': - # NOTE(gsamfira): OpenStack versions prior to Victoria do not have a - # dedicated nova-compute-ironic package which provides a suitable - # nova-compute.conf file. We use a template to compensate for that. - if cmp_os_release < 'victoria': - resource_map[NOVA_COMPUTE_CONF] = { - "services": ["nova-compute"], - "contexts": [], - } + if cmp_os_release >= 'wallaby': + resource_map[NOVA_COMPUTE_CONF] = { + "services": ["nova-compute"], + "contexts": [NovaComputeSWTPMContext(), + NovaComputeVirtContext()] + } + resource_map[QEMU_CONF] = { + "services": [LIBVIRTD_DAEMON], + "contexts": [NovaComputeSWTPMContext()] + } + elif cmp_os_release >= 'train': + resource_map[NOVA_COMPUTE_CONF] = { + "services": ["nova-compute"], + "contexts": [NovaComputeVirtContext()] + } cmp_distro_codename = CompareHostReleases( lsb_release()['DISTRIB_CODENAME'].lower()) @@ -553,6 +566,9 @@ def determine_packages(): if virt_type == 'lxd': packages.append('python3-nova-lxd') + if config('enable-vtpm') and cmp_release >= 'wallaby': + packages.extend(SWTPM_PACKAGES) + packages = sorted(set(packages).union(get_subordinate_release_packages( release).install)) diff --git a/templates/qemu.conf b/templates/qemu.conf index 889d679a..8b6163bf 100644 --- a/templates/qemu.conf +++ b/templates/qemu.conf @@ -11,3 +11,8 @@ cgroup_device_acl = [ "/dev/rtc", "/dev/hpet", "/dev/net/tun", "/dev/vfio/vfio", ] + +{% if swtpm_enabled -%} +swtpm_user = "swtpm" +swtpm_group = "swtpm" +{% endif -%} diff --git a/templates/wallaby/nova-compute.conf b/templates/wallaby/nova-compute.conf new file mode 100644 index 00000000..3006918a --- /dev/null +++ b/templates/wallaby/nova-compute.conf @@ -0,0 +1,16 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +############################################################################### +[DEFAULT] +{% if virt_type == "ironic" -%} +compute_driver=ironic.IronicDriver +{% else -%} +compute_driver=libvirt.LibvirtDriver +{% if swtpm_enabled -%} +[libvirt] +swtpm_enabled=True +swtpm_user=swtpm +swtpm_group=swtpm +{% endif -%} +{% endif -%} diff --git a/tests/bundles/focal-xena.yaml b/tests/bundles/focal-xena.yaml index 888ae0da..d1f4c83d 100644 --- a/tests/bundles/focal-xena.yaml +++ b/tests/bundles/focal-xena.yaml @@ -31,6 +31,8 @@ machines: '17': '18': '19': + '20': + '21': applications: @@ -49,6 +51,12 @@ applications: placement-mysql-router: charm: ch:mysql-router channel: latest/edge + barbican-mysql-router: + charm: ch:mysql-router + channel: latest/edge + vault-mysql-router: + charm: ch:mysql-router + channel: latest/edge mysql-innodb-cluster: charm: ch:mysql-innodb-cluster @@ -61,6 +69,27 @@ applications: - '2' channel: latest/edge + barbican: + charm: ch:barbican + channel: latest/edge + num_units: 1 + options: + openstack-origin: *openstack-origin + to: + - '20' + + barbican-vault: + charm: ch:barbican-vault + channel: latest/edge + num_units: 0 + + vault: + charm: ch:vault + channel: latest/edge + num_units: 1 + to: + - '21' + ceph-osd: charm: ch:ceph-osd num_units: 6 @@ -177,6 +206,8 @@ applications: ec-profile-k: 4 ec-profile-m: 2 libvirt-image-backend: rbd + enable-vtpm: True + extra-repositories: ppa:openstack-charmers/swtpm to: - '10' @@ -262,3 +293,25 @@ relations: - - 'placement:placement' - 'nova-cloud-controller:placement' + + - - 'vault:shared-db' + - 'vault-mysql-router:shared-db' + - - 'vault-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'barbican:shared-db' + - 'barbican-mysql-router:shared-db' + - - 'barbican-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'keystone:identity-service' + - 'barbican:identity-service' + + - - 'rabbitmq-server:amqp' + - 'barbican:amqp' + + - - 'barbican-vault:secrets' + - 'barbican:secrets' + + - - 'vault:secrets' + - 'barbican-vault:secrets-storage' diff --git a/tests/bundles/focal-yoga.yaml b/tests/bundles/focal-yoga.yaml index 71df4ba0..9dd07533 100644 --- a/tests/bundles/focal-yoga.yaml +++ b/tests/bundles/focal-yoga.yaml @@ -31,6 +31,8 @@ machines: '17': '18': '19': + '20': + '21': applications: @@ -49,6 +51,12 @@ applications: placement-mysql-router: charm: ch:mysql-router channel: latest/edge + barbican-mysql-router: + charm: ch:mysql-router + channel: latest/edge + vault-mysql-router: + charm: ch:mysql-router + channel: latest/edge mysql-innodb-cluster: charm: ch:mysql-innodb-cluster @@ -61,6 +69,27 @@ applications: - '2' channel: latest/edge + barbican: + charm: ch:barbican + channel: latest/edge + num_units: 1 + options: + openstack-origin: *openstack-origin + to: + - '20' + + barbican-vault: + charm: ch:barbican-vault + channel: latest/edge + num_units: 0 + + vault: + charm: ch:vault + channel: latest/edge + num_units: 1 + to: + - '21' + ceph-osd: charm: ch:ceph-osd num_units: 6 @@ -177,6 +206,8 @@ applications: ec-profile-k: 4 ec-profile-m: 2 libvirt-image-backend: rbd + enable-vtpm: True + extra-repositories: ppa:openstack-charmers/swtpm to: - '10' @@ -262,3 +293,25 @@ relations: - - 'placement:placement' - 'nova-cloud-controller:placement' + + - - 'vault:shared-db' + - 'vault-mysql-router:shared-db' + - - 'vault-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'barbican:shared-db' + - 'barbican-mysql-router:shared-db' + - - 'barbican-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'keystone:identity-service' + - 'barbican:identity-service' + + - - 'rabbitmq-server:amqp' + - 'barbican:amqp' + + - - 'barbican-vault:secrets' + - 'barbican:secrets' + + - - 'vault:secrets' + - 'barbican-vault:secrets-storage' diff --git a/tests/bundles/impish-xena.yaml b/tests/bundles/impish-xena.yaml index 99a464dd..d93da6a4 100644 --- a/tests/bundles/impish-xena.yaml +++ b/tests/bundles/impish-xena.yaml @@ -31,6 +31,8 @@ machines: '17': '18': '19': + '20': + '21': applications: @@ -49,6 +51,12 @@ applications: placement-mysql-router: charm: ch:mysql-router channel: latest/edge + barbican-mysql-router: + charm: ch:mysql-router + channel: latest/edge + vault-mysql-router: + charm: ch:mysql-router + channel: latest/edge mysql-innodb-cluster: charm: ch:mysql-innodb-cluster @@ -61,6 +69,27 @@ applications: - '2' channel: latest/edge + barbican: + charm: ch:barbican + channel: latest/edge + num_units: 1 + options: + openstack-origin: *openstack-origin + to: + - '20' + + barbican-vault: + charm: ch:barbican-vault + channel: latest/edge + num_units: 0 + + vault: + charm: ch:vault + channel: latest/edge + num_units: 1 + to: + - '21' + ceph-osd: charm: ch:ceph-osd num_units: 6 @@ -177,6 +206,8 @@ applications: ec-profile-k: 4 ec-profile-m: 2 libvirt-image-backend: rbd + enable-vtpm: True + extra-repositories: ppa:openstack-charmers/swtpm to: - '10' @@ -262,3 +293,25 @@ relations: - - 'placement:placement' - 'nova-cloud-controller:placement' + + - - 'vault:shared-db' + - 'vault-mysql-router:shared-db' + - - 'vault-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'barbican:shared-db' + - 'barbican-mysql-router:shared-db' + - - 'barbican-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'keystone:identity-service' + - 'barbican:identity-service' + + - - 'rabbitmq-server:amqp' + - 'barbican:amqp' + + - - 'barbican-vault:secrets' + - 'barbican:secrets' + + - - 'vault:secrets' + - 'barbican-vault:secrets-storage' diff --git a/tests/bundles/jammy-yoga.yaml b/tests/bundles/jammy-yoga.yaml index 0c42df8f..617a39e1 100644 --- a/tests/bundles/jammy-yoga.yaml +++ b/tests/bundles/jammy-yoga.yaml @@ -31,6 +31,8 @@ machines: '17': '18': '19': + '20': + '21': applications: @@ -49,6 +51,12 @@ applications: placement-mysql-router: charm: ch:mysql-router channel: latest/edge + barbican-mysql-router: + charm: ch:mysql-router + channel: latest/edge + vault-mysql-router: + charm: ch:mysql-router + channel: latest/edge mysql-innodb-cluster: charm: ch:mysql-innodb-cluster @@ -61,6 +69,27 @@ applications: - '2' channel: latest/edge + barbican: + charm: ch:barbican + channel: latest/edge + num_units: 1 + options: + openstack-origin: *openstack-origin + to: + - '20' + + barbican-vault: + charm: ch:barbican-vault + channel: latest/edge + num_units: 0 + + vault: + charm: ch:vault + channel: latest/edge + num_units: 1 + to: + - '21' + ceph-osd: charm: ch:ceph-osd num_units: 6 @@ -177,6 +206,7 @@ applications: ec-profile-k: 4 ec-profile-m: 2 libvirt-image-backend: rbd + enable-vtpm: True to: - '10' @@ -262,3 +292,25 @@ relations: - - 'placement:placement' - 'nova-cloud-controller:placement' + + - - 'vault:shared-db' + - 'vault-mysql-router:shared-db' + - - 'vault-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'barbican:shared-db' + - 'barbican-mysql-router:shared-db' + - - 'barbican-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'keystone:identity-service' + - 'barbican:identity-service' + + - - 'rabbitmq-server:amqp' + - 'barbican:amqp' + + - - 'barbican-vault:secrets' + - 'barbican:secrets' + + - - 'vault:secrets' + - 'barbican-vault:secrets-storage' diff --git a/tests/tests.yaml b/tests/tests.yaml index 2200d158..0e15b047 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -13,23 +13,36 @@ dev_bundles: configure: - ceph: + - zaza.openstack.charm_tests.vault.setup.auto_initialize_no_validation_no_wait - zaza.openstack.charm_tests.glance.setup.add_cirros_image - zaza.openstack.charm_tests.glance.setup.add_lts_image + - zaza.openstack.charm_tests.glance.setup.add_uefi_image - zaza.openstack.charm_tests.keystone.setup.add_demo_user - zaza.openstack.charm_tests.neutron.setup.basic_overcloud_network - zaza.openstack.charm_tests.nova.setup.create_flavors - zaza.openstack.charm_tests.nova.setup.manage_ssh_key +- zaza.openstack.charm_tests.vault.setup.auto_initialize_no_validation_no_wait - zaza.openstack.charm_tests.glance.setup.add_cirros_image - zaza.openstack.charm_tests.glance.setup.add_lts_image +- zaza.openstack.charm_tests.glance.setup.add_uefi_image - zaza.openstack.charm_tests.keystone.setup.add_demo_user - zaza.openstack.charm_tests.neutron.setup.basic_overcloud_network - zaza.openstack.charm_tests.nova.setup.create_flavors - zaza.openstack.charm_tests.nova.setup.manage_ssh_key +target_deploy_status: + barbican-vault: + workload-status: waiting + workload-status-message-prefix: "'secrets-storage' incomplete" + vault: + workload-status: blocked + workload-status-message-prefix: "Vault needs to be initialized" + tests: - ceph: - zaza.openstack.charm_tests.nova.tests.CirrosGuestCreateTest - zaza.openstack.charm_tests.nova.tests.LTSGuestCreateTest + - zaza.openstack.charm_tests.nova.tests.VTPMGuestCreateTest - zaza.openstack.charm_tests.nova.tests.CloudActions - zaza.openstack.charm_tests.nova.tests.NovaCompute - zaza.openstack.charm_tests.nova.tests.SecurityTests @@ -37,6 +50,7 @@ tests: - zaza.openstack.charm_tests.ceph.tests.BlueStoreCompressionCharmOperation - zaza.openstack.charm_tests.nova.tests.CirrosGuestCreateTest - zaza.openstack.charm_tests.nova.tests.LTSGuestCreateTest +- zaza.openstack.charm_tests.nova.tests.VTPMGuestCreateTest - zaza.openstack.charm_tests.nova.tests.CloudActions - zaza.openstack.charm_tests.nova.tests.NovaCompute - zaza.openstack.charm_tests.nova.tests.NovaComputeActionTest diff --git a/unit_tests/test_nova_compute_contexts.py b/unit_tests/test_nova_compute_contexts.py index 25a5971d..1ab50506 100644 --- a/unit_tests/test_nova_compute_contexts.py +++ b/unit_tests/test_nova_compute_contexts.py @@ -1400,3 +1400,39 @@ class NovaComputePlacementContextTest(CharmTestCase): 'initial_cpu_allocation_ratio': 8, 'initial_ram_allocation_ratio': 24, 'initial_disk_allocation_ratio': 32}, ctxt()) + + +class NovaComputeSWTPMContextTest(CharmTestCase): + + def setUp(self): + super(NovaComputeSWTPMContextTest, self).setUp(context, TO_PATCH) + self.config.side_effect = self.test_config.get + self.os_release.return_value = 'wallaby' + self.maxDiff = None + + def test_before_wallaby(self): + """Tests context values indicate swtpm disabled prior to Wallaby""" + self.os_release.return_value = 'victoria' + self.test_config.set('enable-vtpm', True) + + ctxt = context.NovaComputeSWTPMContext() + self.assertEqual({}, ctxt()) + + def test_wallaby_or_later_enabled(self): + """Tests context values after Wallaby with vtpm enabled""" + self.test_config.set('enable-vtpm', True) + for stack_release in ['wallaby', 'xena']: + self.os_release.return_value = stack_release + ctxt = context.NovaComputeSWTPMContext() + self.assertEqual({ + 'swtpm_enabled': True, + }, ctxt(), msg=stack_release) + + def test_wallaby_disabled(self): + """Tests context values when config says don't do, but version is + capable.""" + self.test_config.set('enable-vtpm', False) + ctxt = context.NovaComputeSWTPMContext() + self.assertEqual({ + 'swtpm_enabled': False, + }, ctxt()) diff --git a/unit_tests/test_nova_compute_hooks.py b/unit_tests/test_nova_compute_hooks.py index 15a4fec3..aa43f3ef 100644 --- a/unit_tests/test_nova_compute_hooks.py +++ b/unit_tests/test_nova_compute_hooks.py @@ -357,6 +357,7 @@ class NovaComputeRelationsTests(CharmTestCase): compute_joined): self.service_running.return_value = True self.test_config.set('use-multipath', False) + self.test_config.set('enable-vtpm', False) hooks.config_changed() self.assertEqual(self.filter_installed_packages.call_count, 0) self.service_start.assert_not_called() @@ -365,6 +366,7 @@ class NovaComputeRelationsTests(CharmTestCase): def test_config_changed_use_multipath_true(self, compute_joined): self.test_config.set('use-multipath', True) + self.test_config.set('enable-vtpm', False) self.filter_installed_packages.return_value = [] self.service_running.return_value = True hooks.config_changed() @@ -373,6 +375,26 @@ class NovaComputeRelationsTests(CharmTestCase): fatal=True) self.service_start.assert_not_called() + @patch.object(hooks, 'compute_joined') + def test_config_changed_vtpm_enabled(self, compute_joined): + """Tests that when vtpm is enabled, package installs are attempted.""" + self.test_config.set('enable-vtpm', True) + self.filter_installed_packages.return_value = [] + self.service_running.return_value = True + hooks.config_changed() + self.assertEqual(self.filter_installed_packages.call_count, 1) + self.apt_install.assert_called_with(hooks.SWTPM_PACKAGES, + fatal=True) + self.service_start.assert_not_called() + + @patch.object(hooks, 'compute_joined') + def test_config_changed_vtpm_disabled(self, compute_joined): + self.service_running.return_value = True + self.test_config.set('enable-vtpm', False) + hooks.config_changed() + self.assertEqual(self.filter_installed_packages.call_count, 0) + self.service_start.assert_not_called() + @patch.object(hooks, 'compute_joined') def test_config_changed_iscsid_not_running(self, compute_joined): diff --git a/unit_tests/test_nova_compute_utils.py b/unit_tests/test_nova_compute_utils.py index e2621587..b8d3f450 100644 --- a/unit_tests/test_nova_compute_utils.py +++ b/unit_tests/test_nova_compute_utils.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import configparser import os import tempfile import charmhelpers.contrib.openstack.utils as os_utils +import charmhelpers.contrib.openstack.templating as os_templating import nova_compute_context as compute_context import nova_compute_utils as utils @@ -108,6 +110,45 @@ class NovaComputeUtilsTests(CharmTestCase): ] self.assertTrue(ex.sort() == result.sort()) + @patch.object(utils, 'get_subordinate_release_packages') + @patch.object(utils, 'nova_metadata_requirement') + def test_determine_packages_swtpm_victoria(self, en_meta, sub_pkgs): + """Tests that on a version less than Wallaby, the swtpm libraries are + not installed.""" + self.os_release.return_value = 'victoria' + en_meta.return_value = (False, None) + self.relation_ids.return_value = [] + sub_pkgs.return_value = os_utils.SubordinatePackages(set(), set()) + result = utils.determine_packages() + self.assertTrue(utils.BASE_PACKAGES.sort() == result.sort()) + + @patch.object(utils, 'get_subordinate_release_packages') + @patch.object(utils, 'nova_metadata_requirement') + def test_determine_packages_swtpm_wallaby(self, en_meta, sub_pkgs): + """Tests that on a version equal to Wallaby, the swtpm libraries are + not installed.""" + self.os_release.return_value = 'wallaby' + en_meta.return_value = (False, None) + self.relation_ids.return_value = [] + sub_pkgs.return_value = os_utils.SubordinatePackages(set(), set()) + result = utils.determine_packages() + ex = utils.BASE_PACKAGES + utils.SWTPM_PACKAGES + self.assertTrue(ex.sort() == result.sort()) + + @patch.object(utils, 'get_subordinate_release_packages') + @patch.object(utils, 'nova_metadata_requirement') + def test_determine_packages_swtpm_disabled_w(self, en_meta, sub_pkgs): + """Tests that on a version equal to Wallaby, with vtpm disabled the + packages are not selected for installation""" + self.os_release.return_value = 'wallaby' + en_meta.return_value = (False, None) + self.test_config.set('enable-vtpm', False) + self.relation_ids.return_value = [] + sub_pkgs.return_value = os_utils.SubordinatePackages(set(), set()) + result = utils.determine_packages() + ex = utils.BASE_PACKAGES + utils.SWTPM_PACKAGES + self.assertTrue(ex.sort() == result.sort()) + @patch.object(utils, 'get_subordinate_release_packages') @patch.object(utils, 'nova_metadata_requirement') def test_determine_packages_ironic(self, en_meta, @@ -600,27 +641,122 @@ class NovaComputeUtilsTests(CharmTestCase): result = utils.resource_map()['/etc/nova/nova.conf']['services'] self.assertTrue('nova-api-metadata' in result) + @patch.object(compute_context, 'os_release') + def _get_rendered_template(self, template, resource_map, _os_release): + _os_release.return_value = self.os_release.return_value + # Validate the template written is configured for the IronicDriver + renderer = os_templating.OSConfigRenderer( + templates_dir=utils.TEMPLATES, + openstack_release=self.os_release() + ) + renderer.register(template, resource_map[template]['contexts']) + return renderer.render(template) + + def _get_rendered_config(self, template, resource_map): + content = self._get_rendered_template(template, resource_map) + rendered_config = configparser.ConfigParser() + rendered_config.read_string(content) + return rendered_config + + @patch.object(compute_context, 'relation_ids') + @patch.object(compute_context, 'os_release') @patch.object(utils, 'nova_metadata_requirement') - def test_resource_map_ironic_pre_victoria(self, _metadata): + def test_resource_map_ironic(self, _metadata, _os_release, _relation_ids): _metadata.return_value = (True, None) self.relation_ids.return_value = [] self.os_release.return_value = 'train' + _os_release.return_value = 'train' self.test_config.set('virt-type', 'ironic') result = utils.resource_map() self.assertTrue(utils.NOVA_COMPUTE_CONF in result) - self.assertEqual( - result[utils.NOVA_COMPUTE_CONF]["services"], ["nova-compute"]) - self.assertEqual( - result[utils.NOVA_COMPUTE_CONF]["contexts"], []) + nova_config = self._get_rendered_config(utils.NOVA_COMPUTE_CONF, + result) + driver = nova_config.get('DEFAULT', 'compute_driver') + self.assertEqual(driver, 'ironic.IronicDriver') + + @patch.object(compute_context, 'relation_ids') + @patch.object(compute_context, 'os_release') @patch.object(utils, 'nova_metadata_requirement') - def test_resource_map_ironic(self, _metadata): + def test_resource_map_kvm(self, _metadata, _os_release, _relation_ids): + """Tests that compute_driver is set to LibvirtDriver for kvm + virt-type""" _metadata.return_value = (True, None) self.relation_ids.return_value = [] - self.os_release.return_value = 'victoria' - self.test_config.set('virt-type', 'ironic') + self.os_release.return_value = 'wallaby' + _os_release.return_value = 'wallaby' + self.test_config.set('virt-type', 'kvm') result = utils.resource_map() - self.assertTrue(utils.NOVA_COMPUTE_CONF not in result) + self.assertTrue(utils.NOVA_COMPUTE_CONF in result) + + nova_config = self._get_rendered_config(utils.NOVA_COMPUTE_CONF, + result) + driver = nova_config.get('DEFAULT', 'compute_driver') + self.assertEqual(driver, 'libvirt.LibvirtDriver') + + @patch.object(compute_context, 'relation_ids') + @patch.object(compute_context, 'config') + @patch.object(compute_context, 'os_release') + @patch.object(utils, 'nova_metadata_requirement') + def test_resource_map_vtpm_enabled(self, _metadata, _os_release, _config, + _relation_ids): + """Tests that the proper config values are set in nova-compute when + vtpm is enabled.""" + _metadata.return_value = (True, None) + _config.side_effect = self.test_config.get + self.relation_ids.return_value = [] + self.os_release.return_value = 'wallaby' + _os_release.return_value = 'wallaby' + self.test_config.set('enable-vtpm', True) + result = utils.resource_map() + self.assertTrue(utils.NOVA_COMPUTE_CONF in result) + self.assertTrue(utils.QEMU_CONF in result) + + # By default, swtpm should be enabled with the proper swtpm_user and + # swtpm_group configured for the Ubuntu packages. + nova_config = self._get_rendered_config(utils.NOVA_COMPUTE_CONF, + result) + self.assertTrue(nova_config.get('libvirt', 'swtpm_enabled')) + self.assertEqual(nova_config.get('libvirt', 'swtpm_user'), 'swtpm') + self.assertEqual(nova_config.get('libvirt', 'swtpm_group'), 'swtpm') + + # Also, when vtpm is enabled the qemu.conf file renders the + # swtpm_user and swtpm_group information. + qemu_conf = self._get_rendered_template(utils.QEMU_CONF, result) + self.assertTrue(qemu_conf.find('swtpm_user = "swtpm"\n') >= 0) + self.assertTrue(qemu_conf.find('swtpm_group = "swtpm"\n') >= 0) + + @patch.object(compute_context, 'relation_ids') + @patch.object(compute_context, 'config') + @patch.object(compute_context, 'os_release') + @patch.object(utils, 'nova_metadata_requirement') + def test_resource_map_vtpm_disabled(self, _metadata, _os_release, _config, + _relation_ids): + """Tests that the proper config values are set in nova-compute when + vtpm is not enabled.""" + _metadata.return_value = (True, None) + _config.side_effect = self.test_config.get + self.relation_ids.return_value = [] + self.os_release.return_value = 'wallaby' + _os_release.return_value = 'wallaby' + # Now, test the rendered files when vtpm is disabled + self.test_config.set('enable-vtpm', False) + result = utils.resource_map() + self.assertTrue(utils.NOVA_COMPUTE_CONF in result) + self.assertTrue(utils.QEMU_CONF in result) + + # By default, swtpm should be enabled with the proper swtpm_user and + # swtpm_group configured for the Ubuntu packages. + nova_config = self._get_rendered_config(utils.NOVA_COMPUTE_CONF, + result) + self.assertFalse(nova_config.has_option('libvirt', 'swtpm_enabled')) + self.assertFalse(nova_config.has_option('libvirt', 'swtpm_user')) + self.assertFalse(nova_config.has_option('libvirt', 'swtpm_group')) + + # Also, the qemu.conf file should not have the swtpm_user/group anymore + qemu_conf = self._get_rendered_template(utils.QEMU_CONF, result) + self.assertFalse(qemu_conf.find('swtpm_user = "swtpm"\n') >= 0) + self.assertFalse(qemu_conf.find('swtpm_group = "swtpm"\n') >= 0) def fake_user(self, username='foo'): user = MagicMock()