Files
charm-ironic-conductor/unit_tests/test_lib_charm_openstack_ironic.py
Felipe Reyes b61503d6d0 Add configuration option 'hardware-enablement-options'
This new config option allows operators to pass custom configuration
options to the ironic-conductor service.

Usage example:

    cat << EOF > ./config.txt
    [DEFAULT]
    enabled_hardware_types = intel-ipmi, ipmi, idrac
    enabled_power_interfaces = ipmitool, idrac-wsman
    EOF
    juju config ironic-conductor \
        hardware-enablement-options=@./config.txt

Closes-Bug: #2002151
Change-Id: I3e2ca81b272e61e4069d80b52902b243a5f0ba19
(cherry picked from commit 41efb85ef6)
(cherry picked from commit 0ec212e237)
(cherry picked from commit e377181d42)
(cherry picked from commit 166be64d1f)
(cherry picked from commit e495f42d6d)
(cherry picked from commit 07fe82be4b)
2023-03-08 12:45:22 -03:00

489 lines
20 KiB
Python

# Copyright 2020 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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
import charms.reactive as reactive
from charmhelpers.contrib.openstack.utils import os_release
from charm.openstack.ironic import ironic
from charm.openstack.ironic import controller_utils as ctrl_util
class TestIronicCharmConfigProperties(test_utils.PatchHelper):
def setUp(self):
super().setUp()
self.patch_release(ironic.IronicConductorCharm.release)
def test_deployment_interface_ip(self):
cls = mock.MagicMock()
self.patch_object(ironic, 'ch_ip')
ironic.deployment_interface_ip(cls)
self.ch_ip.get_relation_ip.assert_called_with('deployment')
def test_internal_interface_ip(self):
cls = mock.MagicMock()
self.patch_object(ironic, 'ch_ip')
ironic.internal_interface_ip(cls)
self.ch_ip.get_relation_ip.assert_called_with('internal')
def test_temp_url_secret(self):
cls = mock.MagicMock()
leadership.leader_get.return_value = "fake"
self.assertEqual(ironic.temp_url_secret(cls), "fake")
leadership.leader_get.assert_called_with("temp_url_secret")
class TestIronicCharm(test_utils.PatchHelper):
def setUp(self):
super().setUp()
hookenv.config.return_value = {}
self.patch_release(ironic.IronicConductorCharm.release)
self.patch_object(ironic.controller_utils, 'get_pxe_config_class')
self.mocked_pxe_cfg = mock.MagicMock()
self.mocked_pxe_cfg.TFTP_ROOT = ctrl_util.PXEBootBase.TFTP_ROOT
self.mocked_pxe_cfg.HTTP_ROOT = ctrl_util.PXEBootBase.HTTP_ROOT
self.mocked_pxe_cfg.IRONIC_USER = ctrl_util.PXEBootBase.IRONIC_USER
self.mocked_pxe_cfg.IRONIC_GROUP = ctrl_util.PXEBootBase.IRONIC_GROUP
self.mocked_pxe_cfg.determine_packages.return_value = [
"fakepkg1", "fakepkg2"]
self.mocked_pxe_cfg.get_restart_map.return_value = {
"fake_config": [
"fake_svc", ]}
self.mocked_pxe_cfg.HTTPD_SERVICE_NAME = "fakehttpd"
self.get_pxe_config_class.return_value = self.mocked_pxe_cfg
def test_setup_power_adapter_config_train(self):
os_release.return_value = "train"
cfg_data = {
"enabled-hw-types": "ipmi, redfish, idrac",
}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
target._setup_power_adapter_config()
expected = {
"enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish",
"enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, "
"ipmitool, redfish, noop"),
"enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect",
"enabled_power_interfaces": "idrac-redfish, ipmitool, redfish",
"enabled_console_interfaces": ("ipmitool-shellinabox, "
"ipmitool-socat, no-console"),
"enabled_raid_interfaces": "idrac-wsman, no-raid",
"enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor",
"enabled_boot_interfaces": "pxe",
"enabled_bios_interfaces": "no-bios"
}
self.assertEqual(
target.config["hardware_type_cfg"],
expected)
def test_setup_power_adapter_config_train_ipxe(self):
os_release.return_value = "train"
cfg_data = {
"enabled-hw-types": "ipmi, redfish, idrac",
"use-ipxe": True,
}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
target._setup_power_adapter_config()
expected = {
"enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish",
"enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, "
"ipmitool, redfish, noop"),
"enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect",
"enabled_power_interfaces": "idrac-redfish, ipmitool, redfish",
"enabled_console_interfaces": ("ipmitool-shellinabox, "
"ipmitool-socat, no-console"),
"enabled_raid_interfaces": "idrac-wsman, no-raid",
"enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor",
"enabled_boot_interfaces": "pxe, ipxe",
"enabled_bios_interfaces": "no-bios"
}
self.assertEqual(
target.config["hardware_type_cfg"],
expected)
def test_setup_power_adapter_config_unknown(self):
# test that it defaults to latest, in this case ussuri
os_release.return_value = "unknown"
cfg_data = {
"enabled-hw-types": "ipmi, redfish, idrac",
}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
target._setup_power_adapter_config()
expected = {
"enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish",
"enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, "
"ipmitool, redfish, noop"),
"enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect",
"enabled_power_interfaces": "idrac-redfish, ipmitool, redfish",
"enabled_console_interfaces": ("ipmitool-shellinabox, "
"ipmitool-socat, no-console"),
"enabled_raid_interfaces": "idrac-wsman, no-raid",
"enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor",
"enabled_boot_interfaces": "pxe, redfish-virtual-media",
"enabled_bios_interfaces": "idrac-wsman, no-bios"
}
self.assertEqual(
target.config["hardware_type_cfg"],
expected)
def test_setup_power_adapter_config_ussuri(self):
os_release.return_value = "ussuri"
cfg_data = {
"enabled-hw-types": "ipmi, redfish, idrac",
}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
target._setup_power_adapter_config()
self.maxDiff = None
expected = {
"enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish",
"enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, "
"ipmitool, redfish, noop"),
"enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect",
"enabled_power_interfaces": "idrac-redfish, ipmitool, redfish",
"enabled_console_interfaces": ("ipmitool-shellinabox, "
"ipmitool-socat, no-console"),
"enabled_raid_interfaces": "idrac-wsman, no-raid",
"enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor",
"enabled_boot_interfaces": "pxe, redfish-virtual-media",
"enabled_bios_interfaces": "idrac-wsman, no-bios"
}
self.assertEqual(
target.config["hardware_type_cfg"],
expected)
def test_get_amqp_credentials(self):
cfg_data = {
"rabbit-user": "ironic",
"rabbit-vhost": "openstack",
}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
self.get_pxe_config_class.assert_called_with(cfg_data)
result = target.get_amqp_credentials()
self.assertEqual(result, ('ironic', 'openstack'))
def test_get_database_setup(self):
cfg_data = {
"database-user": "ironic",
"database": "ironicdb",
}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
self.get_pxe_config_class.assert_called_with(cfg_data)
result = target.get_database_setup()
self.assertEqual(
result,
[{
"database": cfg_data["database"],
"username": cfg_data["database-user"]}])
def test_enabled_network_interfaces(self):
cfg_data = {
"enabled-network-interfaces": "fake, fake2"}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
self.get_pxe_config_class.assert_called_with(cfg_data)
self.assertEqual(
target.enabled_network_interfaces,
["fake", "fake2"])
def test_enabled_deploy_interfaces(self):
cfg_data = {
"enabled-deploy-interfaces": "fake, fake2"}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
self.get_pxe_config_class.assert_called_with(cfg_data)
self.assertEqual(
target.enabled_deploy_interfaces,
["fake", "fake2"])
def test_configure_defaults_no_cfg(self):
cfg_data = {
"default-network-interface": "",
"default-deploy-interface": ""}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
self.assertEqual(
target.config.get("default-network-interface"),
ironic.DEFAULT_NET_IFACE)
self.assertEqual(
target.config.get("default-deploy-interface"),
ironic.DEFAULT_DEPLOY_IFACE)
def test_configure_defaults_with_user_defined_val(self):
cfg_data = {
"default-network-interface": "fake_net",
"default-deploy-interface": "fake_deploy"}
hookenv.config.return_value = cfg_data
target = ironic.IronicConductorCharm()
target._configure_defaults()
self.assertEqual(
target.config.get("default-network-interface"),
"fake_net")
self.assertEqual(
target.config.get("default-deploy-interface"),
"fake_deploy")
def test_setup_pxe_config(self):
hookenv.config.return_value = {
"default-network-interface": "fake_net",
"default-deploy-interface": "fake_deploy"}
target = ironic.IronicConductorCharm()
target._setup_pxe_config(self.mocked_pxe_cfg)
expected_pkgs = ironic.PACKAGES + ["fakepkg1", "fakepkg2"]
expected_cfg = {
'tftpboot': ctrl_util.PXEBootBase.TFTP_ROOT,
'httpboot': ctrl_util.PXEBootBase.HTTP_ROOT,
'ironic_user': ctrl_util.PXEBootBase.IRONIC_USER,
'ironic_group': ctrl_util.PXEBootBase.IRONIC_GROUP,
'hardware_type_cfg': {
'enabled_hardware_types': 'intel-ipmi, ipmi',
'enabled_management_interfaces': ('intel-ipmitool, ipmitool,'
' noop'),
'enabled_inspect_interfaces': 'no-inspect',
'enabled_power_interfaces': 'ipmitool',
'enabled_console_interfaces': ('ipmitool-shellinabox, '
'ipmitool-socat, no-console'),
'enabled_raid_interfaces': 'no-raid',
'enabled_vendor_interfaces': 'ipmitool, no-vendor',
'enabled_boot_interfaces': 'pxe',
'enabled_bios_interfaces': 'no-bios'},
'default-network-interface': 'fake_net',
'default-deploy-interface': 'fake_deploy'}
self.assertEqual(
target.packages.sort(),
expected_pkgs.sort())
self.assertEqual(target.config, expected_cfg)
self.assertEqual(
target.restart_map.get("fake_config", []), ["fake_svc"])
self.assertTrue("fakehttpd" in target.services)
def test_validate_network_interfaces(self):
target = ironic.IronicConductorCharm()
with self.assertRaises(ValueError):
target._validate_network_interfaces(["bogus"])
self.assertIsNone(
target._validate_network_interfaces(["neutron"]))
def test_validate_deploy_interfaces(self):
target = ironic.IronicConductorCharm()
with self.assertRaises(ValueError) as err:
target._validate_deploy_interfaces(["bogus"])
expected_msg = (
'Deploy interface bogus is not valid.'
' Valid interfaces are: direct, iscsi')
self.assertIsNone(
target._validate_deploy_interfaces(["direct"]))
self.assertEqual(str(err.exception), expected_msg)
def test_validate_deploy_interfaces_tmp_secret(self):
# leadership.set.temp_url_secret is not set, and "direct"
# boot method is enabled. Validate will fail, until
# set-temp-url-secret action is run
reactive.is_flag_set.side_effect = [False, True]
target = ironic.IronicConductorCharm()
with self.assertRaises(ValueError) as err:
target._validate_deploy_interfaces(["direct"])
expected_msg = (
'run set-temp-url-secret action on '
'leader to enable direct deploy method')
self.assertEqual(str(err.exception), expected_msg)
def test_validate_default_net_interface(self):
hookenv.config.return_value = {
"default-network-interface": "flat",
"enabled-network-interfaces": "neutron, flat, noop"}
target = ironic.IronicConductorCharm()
self.assertIsNone(target._validate_default_net_interface())
def test_validate_default_net_interface_invalid_default(self):
hookenv.config.return_value = {
"default-network-interface": "bogus",
"enabled-network-interfaces": "neutron, flat, noop"}
target = ironic.IronicConductorCharm()
with self.assertRaises(ValueError) as err:
target._validate_default_net_interface()
expected_msg = (
"default-network-interface (bogus) is not enabled "
"in enabled-network-interfaces: neutron, flat, noop")
self.assertEqual(str(err.exception), expected_msg)
def test_validate_default_deploy_interface(self):
hookenv.config.return_value = {
"default-deploy-interface": "direct",
"enabled-deploy-interfaces": "direct, iscsi"}
target = ironic.IronicConductorCharm()
self.assertIsNone(target._validate_default_deploy_interface())
def test_validate_default_deploy_interface_invalid_default(self):
hookenv.config.return_value = {
"default-deploy-interface": "bogus",
"enabled-deploy-interfaces": "direct, iscsi"}
target = ironic.IronicConductorCharm()
with self.assertRaises(ValueError) as err:
target._validate_default_deploy_interface()
expected_msg = (
"default-deploy-interface (bogus) is not enabled "
"in enabled-deploy-interfaces: direct, iscsi")
self.assertEqual(str(err.exception), expected_msg)
def test_custom_assess_status_check_all_good(self):
hookenv.config.return_value = {
"default-deploy-interface": "direct",
"enabled-deploy-interfaces": "direct, iscsi",
"default-network-interface": "flat",
"enabled-network-interfaces": "neutron, flat, noop"}
target = ironic.IronicConductorCharm()
self.assertEqual(target.custom_assess_status_check(), (None, None))
def test_custom_assess_status_check_invalid_enabled_net_ifaces(self):
hookenv.config.return_value = {
"default-deploy-interface": "direct",
"enabled-deploy-interfaces": "direct, iscsi",
"default-network-interface": "flat",
"enabled-network-interfaces": "bogus, noop"}
target = ironic.IronicConductorCharm()
expected_status = (
'blocked',
'invalid enabled-network-interfaces config, Network interface '
'bogus is not valid. Valid interfaces are: neutron, flat, noop'
)
self.assertEqual(target.custom_assess_status_check(), expected_status)
def test_custom_assess_status_check_invalid_enabled_deploy_ifaces(self):
hookenv.config.return_value = {
"default-deploy-interface": "direct",
"enabled-deploy-interfaces": "bogus, iscsi",
"default-network-interface": "flat",
"enabled-network-interfaces": "neutron, flat, noop"}
target = ironic.IronicConductorCharm()
expected_status = (
'blocked',
'invalid enabled-deploy-interfaces config, Deploy interface '
'bogus is not valid. Valid interfaces are: direct, iscsi'
)
self.assertEqual(target.custom_assess_status_check(), expected_status)
def test_custom_assess_status_check_invalid_default_net_iface(self):
hookenv.config.return_value = {
"default-deploy-interface": "direct",
"enabled-deploy-interfaces": "direct, iscsi",
"default-network-interface": "bogus",
"enabled-network-interfaces": "neutron, flat, noop"}
target = ironic.IronicConductorCharm()
expected_status = (
'blocked',
'invalid default-network-interface config, '
'default-network-interface (bogus) is not enabled '
'in enabled-network-interfaces: neutron, flat, noop'
)
self.assertEqual(target.custom_assess_status_check(), expected_status)
def test_custom_assess_status_check_invalid_default_deploy_iface(self):
hookenv.config.return_value = {
"default-deploy-interface": "bogus",
"enabled-deploy-interfaces": "direct, iscsi",
"default-network-interface": "flat",
"enabled-network-interfaces": "neutron, flat, noop"}
target = ironic.IronicConductorCharm()
expected_status = (
'blocked',
'invalid default-deploy-interface config, default-deploy-interface'
' (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()