diff --git a/src/config.yaml b/src/config.yaml index 437142c..7365db6 100644 --- a/src/config.yaml +++ b/src/config.yaml @@ -176,3 +176,9 @@ options: for. Defaults to 20 minutes. If some deploys get a 401 response code when trying to download from the temporary URL, try raising this duration. + hardware-enablement-options: + default: + type: string + description: | + Options passed to the managed service to set configuration keys that + enable the use of specific hardware. diff --git a/src/lib/charm/openstack/ironic/ironic.py b/src/lib/charm/openstack/ironic/ironic.py index bd5ab30..83363e3 100644 --- a/src/lib/charm/openstack/ironic/ironic.py +++ b/src/lib/charm/openstack/ironic/ironic.py @@ -11,10 +11,13 @@ from charms_openstack.adapters import ( OpenStackRelationAdapters, ) from charmhelpers.contrib.openstack.utils import os_release +from charmhelpers.contrib.openstack import templating +from charmhelpers.core import host import charm.openstack.ironic.controller_utils as controller_utils import charms_openstack.adapters as adapters import charmhelpers.contrib.network.ip as ch_ip +import charmhelpers.contrib.openstack.utils as ch_utils import charms.leadership as leadership import charms.reactive as reactive @@ -37,6 +40,10 @@ PACKAGES = [ IRONIC_DIR = "/etc/ironic/" IRONIC_CONF = os.path.join(IRONIC_DIR, "ironic.conf") +IRONIC_CONF_D = os.path.join(IRONIC_DIR, "conf.d") +IRONIC_CONF_HW_ENABLEMENT = os.path.join(IRONIC_CONF_D, + "90-hardware-enablement.conf") +IRONIC_DEFAULT = "/etc/default/ironic-conductor" ROOTWRAP_CONF = os.path.join(IRONIC_DIR, "rootwrap.conf") FILTERS_DIR = os.path.join(IRONIC_DIR, "rootwrap.d") IRONIC_LIB_FILTERS = os.path.join( @@ -194,6 +201,8 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): restart_map = { IRONIC_CONF: ['ironic-conductor', ], + IRONIC_DEFAULT: ['ironic-conductor', ], + IRONIC_CONF_HW_ENABLEMENT: ['ironic-conductor', ], IRONIC_UTILS_FILTERS: ['ironic-conductor', ], IRONIC_LIB_FILTERS: ['ironic-conductor', ], ROOTWRAP_CONF: ['ironic-conductor', ], @@ -313,6 +322,7 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): def install(self): self.configure_source() super().install() + self._reconfigure_ironic_conductor() self.pxe_config._copy_resources() self.assess_status() @@ -434,3 +444,29 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): return ('blocked', msg) return (None, None) + + def upgrade_charm(self): + """Custom upgrade charm. + + Side effects: + - Create /etc/ironic/conf.d/ directory. + - Reconfigure ironic-conductor service to use the previously created + directory. + """ + self._reconfigure_ironic_conductor() + super().upgrade_charm() + + def _reconfigure_ironic_conductor(self): + """Reconfigure ironic-conductor daemon. + + Set /etc/default/ironic-conductor to pass --config-dir in DAEMON_ARGS. + """ + if not os.path.isdir(IRONIC_CONF_D): + host.mkdir(IRONIC_CONF_D) + + # reconfigure ironic-conductor to run it with --conf-dir + release = ch_utils.os_release('ironic-common') + configs = templating.OSConfigRenderer(templates_dir='templates/', + openstack_release=release) + configs.register(config_file=IRONIC_DEFAULT, contexts=[]) + configs.write_all() diff --git a/src/templates/90-hardware-enablement.conf b/src/templates/90-hardware-enablement.conf new file mode 100644 index 0000000..e7ce3e5 --- /dev/null +++ b/src/templates/90-hardware-enablement.conf @@ -0,0 +1,7 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +############################################################################### +{%- if options.hardware_enablement_options %} +{{ options.hardware_enablement_options }} +{%- endif %} \ No newline at end of file diff --git a/src/templates/etc_default_ironic-conductor b/src/templates/etc_default_ironic-conductor new file mode 100644 index 0000000..fcf8b60 --- /dev/null +++ b/src/templates/etc_default_ironic-conductor @@ -0,0 +1,5 @@ +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +############################################################################### +DAEMON_ARGS="--config-dir=/etc/ironic/conf.d/" diff --git a/src/tests/bundles/focal-victoria.yaml b/src/tests/bundles/focal-victoria.yaml index 3e2eeb2..a799921 100644 --- a/src/tests/bundles/focal-victoria.yaml +++ b/src/tests/bundles/focal-victoria.yaml @@ -291,6 +291,9 @@ services: disable-secure-erase: true use-ipxe: true enabled-network-interfaces: "flat, noop" + hardware-enablement-options: | + [ipmi] + debug = true neutron-ironic-agent: charm: ch:neutron-api-plugin-ironic num_units: 0 diff --git a/src/tests/bundles/focal-wallaby.yaml b/src/tests/bundles/focal-wallaby.yaml index 2c3b7fb..d1e4caa 100644 --- a/src/tests/bundles/focal-wallaby.yaml +++ b/src/tests/bundles/focal-wallaby.yaml @@ -291,6 +291,9 @@ services: disable-secure-erase: true use-ipxe: true enabled-network-interfaces: "flat, noop" + hardware-enablement-options: | + [ipmi] + debug = true neutron-ironic-agent: charm: ch:neutron-api-plugin-ironic num_units: 0 diff --git a/unit_tests/test_lib_charm_openstack_ironic.py b/unit_tests/test_lib_charm_openstack_ironic.py index 87da886..34e4da6 100644 --- a/unit_tests/test_lib_charm_openstack_ironic.py +++ b/unit_tests/test_lib_charm_openstack_ironic.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock +from unittest import mock +import charms_openstack.test_mocks import charms_openstack.test_utils as test_utils import charms.leadership as leadership import charmhelpers.core.hookenv as hookenv @@ -440,3 +441,48 @@ class TestIronicCharm(test_utils.PatchHelper): ' (bogus) is not enabled in enabled-deploy-interfaces: direct, ' 'iscsi') self.assertEqual(target.custom_assess_status_check(), expected_status) + + @mock.patch('charms_openstack.charm.OpenStackCharm.upgrade_charm') + def test_upgrade_charm(self, upgrade_charm): + os_release.return_value = "ussuri" + cfg_data = { + "openstack-origin": "distro", + } + hookenv.config.return_value = cfg_data + target = ironic.IronicConductorCharm() + target.upgrade_charm() + # check the parent's upgrade_charm was called + upgrade_charm.assert_called() + os_module = charms_openstack.test_mocks.charmhelpers.contrib.openstack + templating = os_module.templating + templating.OSConfigRenderer.assert_called_with( + templates_dir='templates/', openstack_release='ussuri') + configs = templating.OSConfigRenderer() + configs.register.assert_called_with(config_file=ironic.IRONIC_DEFAULT, + contexts=[]) + configs.write_all.assert_called_with() + + @mock.patch('charms_openstack.charm.OpenStackCharm.install') + def test_install(self, install): + os_release.return_value = "ussuri" + cfg_data = { + "openstack-origin": "distro", + } + charmhelpers = charms_openstack.test_mocks.charmhelpers + os_utils = charmhelpers.contrib.openstack.utils + os_utils.get_source_and_pgp_key.return_value = (None, None) + + hookenv.config.return_value = cfg_data + + target = ironic.IronicConductorCharm() + target.install() + install.assert_called_with() + os_module = charms_openstack.test_mocks.charmhelpers.contrib.openstack + templating = os_module.templating + templating.OSConfigRenderer.assert_called_with( + templates_dir='templates/', openstack_release='ussuri') + configs = templating.OSConfigRenderer(templates_dir='templates/', + openstack_release='ussuri') + configs.register.assert_called_with(config_file=ironic.IRONIC_DEFAULT, + contexts=[]) + configs.write_all.assert_called_with()