diff --git a/ironic/conductor/base_manager.py b/ironic/conductor/base_manager.py index 52bf434a20..b472b78fbb 100644 --- a/ironic/conductor/base_manager.py +++ b/ironic/conductor/base_manager.py @@ -171,7 +171,8 @@ class BaseConductorManager(object): self._register_and_validate_hardware_interfaces(hardware_types) except (exception.DriverLoadError, exception.DriverNotFound, exception.ConductorHardwareInterfacesAlreadyRegistered, - exception.InterfaceNotFoundInEntrypoint) as e: + exception.InterfaceNotFoundInEntrypoint, + exception.NoValidDefaultForInterface) as e: with excutils.save_and_reraise_exception(): LOG.error(_LE('Failed to register hardware types. %s'), e) self.del_host() @@ -263,6 +264,8 @@ class BaseConductorManager(object): hardware type object. :raises: ConductorHardwareInterfacesAlreadyRegistered :raises: InterfaceNotFoundInEntrypoint + :raises: NoValidDefaultForInterface if the default value cannot be + calculated and is not provided in the configuration """ # first unregister, in case we have cruft laying around self.conductor.unregister_all_hardware_interfaces() @@ -272,6 +275,9 @@ class BaseConductorManager(object): for interface_type, interface_names in interface_map.items(): default_interface = driver_factory.default_interface( ht, interface_type) + if default_interface is None: + raise exception.NoValidDefaultForInterface( + interface_type=interface_type, node=None, driver=ht) self.conductor.register_hardware_interfaces(ht_name, interface_type, interface_names, diff --git a/ironic/tests/unit/conductor/test_base_manager.py b/ironic/tests/unit/conductor/test_base_manager.py index 74ebfe8891..aff8c8f9d3 100644 --- a/ironic/tests/unit/conductor/test_base_manager.py +++ b/ironic/tests/unit/conductor/test_base_manager.py @@ -338,6 +338,31 @@ class RegisterInterfacesTestCase(mgr_utils.ServiceSetUpMixin, # we're iterating over dicts, don't worry about order reg_mock.assert_has_calls(expected_calls) + def test__register_and_validate_no_valid_default(self, + esi_mock, + default_mock, + reg_mock, + unreg_mock): + # these must be same order as esi_mock side effect + hardware_types = collections.OrderedDict(( + ('fake-hardware', fake_hardware.FakeHardware()), + )) + esi_mock.side_effect = [ + collections.OrderedDict(( + ('management', ['fake', 'noop']), + ('deploy', ['agent', 'iscsi']), + )), + ] + default_mock.return_value = None + + self.assertRaises( + exception.NoValidDefaultForInterface, + self.service._register_and_validate_hardware_interfaces, + hardware_types) + + unreg_mock.assert_called_once_with(mock.ANY) + self.assertFalse(reg_mock.called) + class StartConsolesTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase): diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 7998721504..4b2b45e1ac 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -637,7 +637,6 @@ class VendorPassthruTestCase(mgr_utils.ServiceSetUpMixin, self._test_vendor_passthru_async('fake', None) def test_vendor_passthru_async_hw_type(self): - self.config(enabled_vendor_interfaces=['fake']) self._test_vendor_passthru_async('fake-hardware', 'fake') @mock.patch.object(task_manager.TaskManager, 'upgrade_lock') @@ -3845,12 +3844,10 @@ class RaidHardwareTypeTestCases(RaidTestCases): raid_interface = 'fake' def test_get_raid_logical_disk_properties_iface_not_supported(self): - self.config(enabled_raid_interfaces=[]) - self._start_service() - exc = self.assertRaises(messaging.rpc.ExpectedException, - self.service.get_raid_logical_disk_properties, - self.context, self.driver_name) - self.assertEqual(exception.UnsupportedDriverExtension, exc.exc_info[0]) + # NOTE(jroll) we don't run this test as get_logical_disk_properties + # is supported on all RAID implementations, and we cannot have a + # null interface for a hardware type + pass def test_set_target_raid_config_iface_not_supported(self): # NOTE(jroll): it's impossible for a dynamic driver to have a null @@ -4624,7 +4621,8 @@ class ManagerCheckDeployTimeoutsTestCase(mgr_utils.CommonMixIn, @mgr_utils.mock_record_keepalive -class ManagerTestProperties(tests_db_base.DbTestCase): +class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, + tests_db_base.DbTestCase): def setUp(self): super(ManagerTestProperties, self).setUp() @@ -4633,7 +4631,7 @@ class ManagerTestProperties(tests_db_base.DbTestCase): def _check_driver_properties(self, driver, expected): mgr_utils.mock_the_extension_manager(driver=driver) self.driver = driver_factory.get_driver(driver) - self.service.init_host() + self._start_service() properties = self.service.get_driver_properties(self.context, driver) self.assertEqual(sorted(expected), sorted(properties.keys())) @@ -4753,16 +4751,13 @@ class ManagerTestProperties(tests_db_base.DbTestCase): @mgr_utils.mock_record_keepalive -class ManagerTestHardwareTypeProperties(tests_db_base.DbTestCase): - - def setUp(self): - super(ManagerTestHardwareTypeProperties, self).setUp() - self.service = manager.ConductorManager('test-host', 'test-topic') +class ManagerTestHardwareTypeProperties(mgr_utils.ServiceSetUpMixin, + tests_db_base.DbTestCase): def _check_hardware_type_properties(self, hardware_type, expected): self.config(enabled_hardware_types=[hardware_type]) self.hardware_type = driver_factory.get_hardware_type(hardware_type) - self.service.init_host() + self._start_service() properties = self.service.get_driver_properties(self.context, hardware_type) self.assertEqual(sorted(expected), sorted(properties.keys())) diff --git a/releasenotes/notes/conductor-startup-invalid-defaults-b7bd0589a3bf474b.yaml b/releasenotes/notes/conductor-startup-invalid-defaults-b7bd0589a3bf474b.yaml new file mode 100644 index 0000000000..195b90fbdd --- /dev/null +++ b/releasenotes/notes/conductor-startup-invalid-defaults-b7bd0589a3bf474b.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - The conductor will now fail to start up if invalid configuration is + provided, such that a default interface implementation for any enabled + hardware type cannot be found.