From 7ff7001982ae58943fc2b29089eee8c02b09bc00 Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Wed, 19 Aug 2020 14:28:36 +0000 Subject: [PATCH] Add Ironic virt type Adds support for the Ironic virt driver. Change-Id: I72a7fb65395e4ceddc77a92bccc7b4563af0750a --- hooks/ironic-api-relation-changed | 1 + hooks/nova_compute_context.py | 15 ++++++++ hooks/nova_compute_hooks.py | 6 +++ hooks/nova_compute_utils.py | 34 ++++++++++++++++- metadata.yaml | 2 + templates/parts/section-ironic | 15 ++++++++ templates/train/nova-compute.conf | 2 + templates/train/nova.conf | 2 + unit_tests/test_nova_compute_contexts.py | 31 ++++++++++++++++ unit_tests/test_nova_compute_utils.py | 47 ++++++++++++++++++++++++ 10 files changed, 153 insertions(+), 2 deletions(-) create mode 120000 hooks/ironic-api-relation-changed create mode 100644 templates/parts/section-ironic create mode 100644 templates/train/nova-compute.conf diff --git a/hooks/ironic-api-relation-changed b/hooks/ironic-api-relation-changed new file mode 120000 index 00000000..3ba0bdea --- /dev/null +++ b/hooks/ironic-api-relation-changed @@ -0,0 +1 @@ +nova_compute_hooks.py \ No newline at end of file diff --git a/hooks/nova_compute_context.py b/hooks/nova_compute_context.py index 14c7912d..a15cc94b 100644 --- a/hooks/nova_compute_context.py +++ b/hooks/nova_compute_context.py @@ -341,6 +341,21 @@ class NovaComputeVirtContext(context.OSContextGenerator): return ctxt +class IronicAPIContext(context.OSContextGenerator): + interfaces = ["ironic-api"] + + def __call__(self): + ctxt = {} + for rid in relation_ids('ironic-api'): + for unit in related_units(rid): + is_ready = relation_get( + 'ironic-api-ready', rid=rid, unit=unit) + if is_ready: + ctxt["ironic_api_ready"] = is_ready + return ctxt + return ctxt + + def assert_libvirt_rbd_imagebackend_allowed(): os_rel = "Juno" os_ver = get_os_version_package('nova-common') diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py index a1aa9371..15b31d73 100755 --- a/hooks/nova_compute_hooks.py +++ b/hooks/nova_compute_hooks.py @@ -364,6 +364,12 @@ def compute_changed(): import_keystone_ca_cert() +@hooks.hook('ironic-api-relation-changed') +@restart_on_change(restart_map()) +def ironic_api_changed(): + CONFIGS.write(NOVA_CONF) + + @hooks.hook('ceph-access-relation-joined') @hooks.hook('ceph-relation-joined') @restart_on_change(restart_map()) diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py index f027adb0..438b74b3 100644 --- a/hooks/nova_compute_utils.py +++ b/hooks/nova_compute_utils.py @@ -96,6 +96,7 @@ from nova_compute_context import ( NovaComputeCephContext, NeutronComputeContext, InstanceConsoleContext, + IronicAPIContext, CEPH_CONF, ceph_config_file, HostIPContext, @@ -170,6 +171,7 @@ LIBVIRTD_CONF = '/etc/libvirt/libvirtd.conf' LIBVIRT_BIN = '/etc/default/libvirt-bin' LIBVIRT_BIN_OVERRIDES = '/etc/init/libvirt-bin.override' NOVA_CONF = '%s/nova.conf' % NOVA_CONF_DIR +NOVA_COMPUTE_CONF = '%s/nova-compute.conf' % NOVA_CONF_DIR VENDORDATA_FILE = '%s/vendor_data.json' % NOVA_CONF_DIR QEMU_KVM = '/etc/default/qemu-kvm' NOVA_API_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'.format(NOVA_API_AA_PROFILE)) @@ -203,6 +205,7 @@ BASE_RESOURCE_MAP = { context.OSConfigFlagContext(), CloudComputeContext(), LxdContext(), + IronicAPIContext(), NovaComputeLibvirtContext(), NovaComputeCephContext(), context.SyslogContext(), @@ -292,6 +295,7 @@ VIRT_TYPES = { 'uml': ['nova-compute-uml'], 'lxc': ['nova-compute-lxc'], 'lxd': ['nova-compute-lxd'], + 'ironic': ['nova-compute-ironic'], } @@ -332,7 +336,8 @@ def resource_map(): hook execution. ''' # TODO: Cache this on first call? - if config('virt-type').lower() == 'lxd': + virt_type = config('virt-type').lower() + if virt_type in ('lxd', 'ironic'): resource_map = deepcopy(BASE_RESOURCE_MAP) else: resource_map = deepcopy(LIBVIRT_RESOURCE_MAP) @@ -363,6 +368,16 @@ 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": [], + } + cmp_distro_codename = CompareHostReleases( lsb_release()['DISTRIB_CODENAME'].lower()) if (cmp_distro_codename >= 'yakkety' or cmp_os_release >= 'ocata'): @@ -391,7 +406,8 @@ def resource_map(): # NOTE(james-page): If not on an upstart based system, don't write # and override file for libvirt-bin. if not os.path.exists('/etc/init'): - del resource_map[LIBVIRT_BIN_OVERRIDES] + if LIBVIRT_BIN_OVERRIDES in resource_map: + del resource_map[LIBVIRT_BIN_OVERRIDES] return resource_map @@ -463,6 +479,18 @@ def determine_packages(): packages.append('ceph-common') virt_type = config('virt-type') + if virt_type == 'ironic' and release < 'victoria': + # ironic compute driver is part of nova and + # gets installed allong with python3-nova + # The nova-compute-ironic metapackage that satisfies + # nova-compute-hypervisor does not exist for versions of + # OpenStack prior to Victoria. Use nova-compute-vmware, + # as that package has the least amount of dependencies. + # We also add python3-ironicclient here. This is a dependency + # which gets installed by nova-compute-ironic in Victoria and later. + VIRT_TYPES[virt_type] = [ + 'nova-compute-vmware', + 'python3-ironicclient'] try: packages.extend(VIRT_TYPES[virt_type]) except KeyError: @@ -876,6 +904,8 @@ def get_optional_relations(): optional_interfaces['neutron-plugin'] = ['neutron-plugin'] if config('encrypt'): optional_interfaces['vault'] = ['secrets-storage'] + if config('virt-type').lower() == 'ironic': + optional_interfaces['baremetal'] = ['ironic-api'] return optional_interfaces diff --git a/metadata.yaml b/metadata.yaml index 5bff4d8b..d8dabf49 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -53,6 +53,8 @@ requires: interface: keystone-credentials secrets-storage: interface: vault-kv + ironic-api: + interface: baremetal peers: compute-peer: interface: nova diff --git a/templates/parts/section-ironic b/templates/parts/section-ironic new file mode 100644 index 00000000..012f1837 --- /dev/null +++ b/templates/parts/section-ironic @@ -0,0 +1,15 @@ +{% if virt_type == 'ironic' and auth_host and ironic_api_ready -%} +{% if api_version and api_version == "3" -%} +{% set auth_ver = "v3" -%} +{% else -%} +{% set auth_ver = "v2.0" -%} +{% endif -%} +[ironic] +auth_type = password +auth_url = {{auth_protocol}}://{{auth_host}}:{{auth_port}}/{{auth_ver}} +project_name = {{ admin_tenant_name }} +username = {{ admin_user }} +password = {{ admin_password }} +project_domain_name = {{ admin_domain_name }} +user_domain_name = {{ admin_domain_name }} +{% endif -%} diff --git a/templates/train/nova-compute.conf b/templates/train/nova-compute.conf new file mode 100644 index 00000000..90942428 --- /dev/null +++ b/templates/train/nova-compute.conf @@ -0,0 +1,2 @@ +[DEFAULT] +compute_driver=ironic.IronicDriver \ No newline at end of file diff --git a/templates/train/nova.conf b/templates/train/nova.conf index 04d67fea..4c1be641 100644 --- a/templates/train/nova.conf +++ b/templates/train/nova.conf @@ -316,6 +316,8 @@ disable_libvirt_livesnapshot = False {% include "parts/section-placement" %} +{% include "parts/section-ironic" %} + [compute] {% if cpu_shared_set -%} cpu_shared_set = {{ cpu_shared_set }} diff --git a/unit_tests/test_nova_compute_contexts.py b/unit_tests/test_nova_compute_contexts.py index c2512b50..e3d4921d 100644 --- a/unit_tests/test_nova_compute_contexts.py +++ b/unit_tests/test_nova_compute_contexts.py @@ -767,6 +767,37 @@ class NovaComputeContextTests(CharmTestCase): 'pcid, vmx, pdpe1gb') +class IronicAPIContextTests(CharmTestCase): + + def setUp(self): + super(IronicAPIContextTests, self).setUp(context, TO_PATCH) + self.relation_get.side_effect = self.test_relation.get + + def test_ironic_api_ready_no_relation(self): + self.relation_ids.return_value = [] + self.assertEqual( + context.IronicAPIContext()(), {}) + + def test_ironic_api_ready(self): + self.relation_ids.return_value = ['ironic-api:0'] + self.related_units.return_value = 'ironic-api/0' + self.test_relation.set({ + 'ironic-api-ready': True, + }) + self.assertEqual( + context.IronicAPIContext()(), + {'ironic_api_ready': True}) + + def test_ironic_api_not_ready(self): + self.relation_ids.return_value = ['ironic-api:0'] + self.related_units.return_value = 'ironic-api/0' + self.test_relation.set({ + 'ready': False, + }) + self.assertEqual( + context.IronicAPIContext()(), {}) + + class SerialConsoleContextTests(CharmTestCase): def setUp(self): diff --git a/unit_tests/test_nova_compute_utils.py b/unit_tests/test_nova_compute_utils.py index 123a68e4..3023deeb 100644 --- a/unit_tests/test_nova_compute_utils.py +++ b/unit_tests/test_nova_compute_utils.py @@ -101,6 +101,31 @@ class NovaComputeUtilsTests(CharmTestCase): ] self.assertTrue(ex == result) + @patch.object(utils, 'nova_metadata_requirement') + def test_determine_packages_ironic(self, en_meta): + self.os_release.return_value = 'victoria' + self.test_config.set('virt-type', 'ironic') + en_meta.return_value = (False, None) + self.relation_ids.return_value = [] + result = utils.determine_packages() + ex = utils.BASE_PACKAGES + [ + 'nova-compute-ironic' + ] + self.assertTrue(ex.sort() == result.sort()) + + @patch.object(utils, 'nova_metadata_requirement') + def test_determine_packages_ironic_pre_victoria(self, en_meta): + self.os_release.return_value = 'train' + self.test_config.set('virt-type', 'ironic') + en_meta.return_value = (False, None) + self.relation_ids.return_value = [] + result = utils.determine_packages() + ex = utils.BASE_PACKAGES + [ + 'nova-compute-vmware', + 'python3-ironicclient' + ] + self.assertTrue(ex.sort() == result.sort()) + @patch.object(utils, 'nova_metadata_requirement') @patch.object(utils, 'network_manager') @patch('platform.machine') @@ -526,6 +551,28 @@ class NovaComputeUtilsTests(CharmTestCase): result = utils.resource_map()['/etc/nova/nova.conf']['services'] self.assertTrue('nova-api-metadata' in result) + @patch.object(utils, 'nova_metadata_requirement') + def test_resource_map_ironic_pre_victoria(self, _metadata): + _metadata.return_value = (True, None) + self.relation_ids.return_value = [] + self.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"], []) + + @patch.object(utils, 'nova_metadata_requirement') + def test_resource_map_ironic(self, _metadata): + _metadata.return_value = (True, None) + self.relation_ids.return_value = [] + self.os_release.return_value = 'victoria' + self.test_config.set('virt-type', 'ironic') + result = utils.resource_map() + self.assertTrue(utils.NOVA_COMPUTE_CONF not in result) + def fake_user(self, username='foo'): user = MagicMock() user.pw_dir = '/home/' + username