From 1e24ef9dde30602c02e834c4723a259823f027ec Mon Sep 17 00:00:00 2001 From: Zenghui Shi Date: Tue, 8 May 2018 14:49:05 +0800 Subject: [PATCH] BIOS Settings: Add BIOSInterface * Adds 'bios' interface to 'BaseDriver' * Adds BIOSInterface driver class * Adds fake & no-bios drivers and entries * Implements it for 'fake-hardare' hardware type * Adds configuration parameters: + [DEFAULT]/enabled_bios_interfaces + [DEFAULT]/default_bios_interface * Adds 'bios_interface' field to Node object * Handle 'bios_interface' field in _convert_to_version * Adds bios in CLEANING_INTERFACE_PRIORITY Drivers can implement this interface to do BIOS configuration. Co-Authored-By: Yolanda Robla Mota Co-Authored-By: Luong Anh Tuan Change-Id: I7e57130242b6cab21b54e35dc3c0b7819bdc43c0 Story: #1712032 --- ironic/common/driver_factory.py | 3 +- ironic/common/release_mappings.py | 2 +- ironic/conductor/manager.py | 5 + ironic/conductor/utils.py | 7 +- ironic/conf/default.py | 17 ++++ ironic/drivers/base.py | 92 ++++++++++++++++++- ironic/drivers/fake.py | 2 +- ironic/drivers/fake_hardware.py | 4 + ironic/drivers/hardware_type.py | 4 + ironic/drivers/modules/fake.py | 28 ++++++ ironic/drivers/modules/noop.py | 13 +++ ironic/objects/node.py | 28 +++++- .../unit/api/controllers/v1/test_driver.py | 8 +- .../tests/unit/common/test_driver_factory.py | 10 +- ironic/tests/unit/conductor/mgr_utils.py | 1 + ironic/tests/unit/conductor/test_manager.py | 1 + ironic/tests/unit/conductor/test_utils.py | 1 + .../tests/unit/drivers/modules/test_noop.py | 10 +- ironic/tests/unit/drivers/test_base.py | 51 +++++++++- ironic/tests/unit/objects/test_node.py | 63 +++++++++++++ ironic/tests/unit/objects/test_objects.py | 2 +- setup.cfg | 4 + 22 files changed, 335 insertions(+), 21 deletions(-) diff --git a/ironic/common/driver_factory.py b/ironic/common/driver_factory.py index d33e71838e..88da2d5446 100644 --- a/ironic/common/driver_factory.py +++ b/ironic/common/driver_factory.py @@ -596,7 +596,8 @@ def calculate_migration_delta(driver_name, driver_class, None if a migration is not possible. """ # NOTE(dtantsur): provide defaults for optional interfaces - defaults = {'console': 'no-console', + defaults = {'bios': 'no-bios', + 'console': 'no-console', 'inspect': 'no-inspect', 'raid': 'no-raid', 'rescue': 'no-rescue', diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index 92d07a0c82..f8471cb690 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -103,7 +103,7 @@ RELEASE_MAPPING = { 'api': '1.39', 'rpc': '1.44', 'objects': { - 'Node': ['1.23'], + 'Node': ['1.24'], 'Conductor': ['1.2'], 'Chassis': ['1.3'], 'Port': ['1.8'], diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py index 6e3182358c..e0eb434792 100644 --- a/ironic/conductor/manager.py +++ b/ironic/conductor/manager.py @@ -1869,6 +1869,11 @@ class ConductorManager(base_manager.BaseConductorManager): task.node.instance_info) task.node.driver_internal_info['is_whole_disk_image'] = iwdi for iface_name in task.driver.non_vendor_interfaces: + # TODO(zshi): Remove this check in 'bios' API patch + # Do not have to return the validation result for 'bios' + # interface. + if iface_name == 'bios': + continue iface = getattr(task.driver, iface_name, None) result = reason = None if iface: diff --git a/ironic/conductor/utils.py b/ironic/conductor/utils.py index 0a0799e89f..b75c30015d 100644 --- a/ironic/conductor/utils.py +++ b/ironic/conductor/utils.py @@ -34,9 +34,10 @@ CLEANING_INTERFACE_PRIORITY = { # by which interface is implementing the clean step. The clean step of the # interface with the highest value here, will be executed first in that # case. - 'power': 4, - 'management': 3, - 'deploy': 2, + 'power': 5, + 'management': 4, + 'deploy': 3, + 'bios': 2, 'raid': 1, } diff --git a/ironic/conf/default.py b/ironic/conf/default.py index a1c6b87887..c53db8317f 100644 --- a/ironic/conf/default.py +++ b/ironic/conf/default.py @@ -52,6 +52,18 @@ _DEFAULT_IFACE_HELP = _('Default {0} interface to be used for nodes that ' 'be found by enumerating the ' '"ironic.hardware.interfaces.{0}" entrypoint.') +# TODO(zshi) Remove this in BIOS API patch. +_ENABLED_IFACE_HELP_FOR_BIOS = (_ENABLED_IFACE_HELP + + _(' This option is part of BIOS feature ' + 'work, which is not currently exposed to ' + 'users.')) + +# TODO(zshi) Remove this in BIOS API patch. +_DEFAULT_IFACE_HELP_FOR_BIOS = (_DEFAULT_IFACE_HELP + + _(' This option is part of BIOS feature ' + 'work, which is not currently exposed to ' + 'users.')) + api_opts = [ cfg.StrOpt( 'auth_strategy', @@ -103,6 +115,11 @@ driver_opts = [ 'A complete list of hardware types present on your ' 'system may be found by enumerating the ' '"ironic.hardware.types" entrypoint.')), + cfg.ListOpt('enabled_bios_interfaces', + default=['no-bios'], + help=_ENABLED_IFACE_HELP_FOR_BIOS.format('bios')), + cfg.StrOpt('default_bios_interface', + help=_DEFAULT_IFACE_HELP_FOR_BIOS.format('bios')), cfg.ListOpt('enabled_boot_interfaces', default=['pxe'], help=_ENABLED_IFACE_HELP.format('boot')), diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py index 3e671339c3..99f7e74f84 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py @@ -176,14 +176,21 @@ class BareDriver(BaseDriver): """ core_interfaces = BaseDriver.core_interfaces + ('network',) + bios = None + """`Standard` attribute for BIOS related features. + + A reference to an instance of :class:BIOSInterface. + May be None, if unsupported by a driver. + """ + storage = None """`Standard` attribute for (remote) storage interface. A reference to an instance of :class:StorageInterface. """ - standard_interfaces = (BaseDriver.standard_interfaces + ('rescue', - 'storage',)) + standard_interfaces = (BaseDriver.standard_interfaces + ('bios', + 'rescue', 'storage',)) ALL_INTERFACES = set(BareDriver().all_interfaces) @@ -917,6 +924,87 @@ class InspectInterface(BaseInterface): """ +class BIOSInterface(BaseInterface): + interface_type = 'bios' + + def __new__(cls, *args, **kwargs): + # Wrap the apply_configuration and factory_reset into a decorator + # which call cache_bios_settings() to update the node's BIOS setting + # table after apply_configuration and factory_reset have finished. + + super_new = super(BIOSInterface, cls).__new__ + instance = super_new(cls, *args, **kwargs) + + def wrapper(func): + @six.wraps(func) + def wrapped(self, task, *args, **kwargs): + func(task, *args, **kwargs) + instance.cache_bios_settings(task) + return wrapped + + for n, method in inspect.getmembers(instance, inspect.ismethod): + if n == "apply_configuration": + instance.apply_configuration = wrapper(method) + elif n == "factory_reset": + instance.factory_reset = wrapper(method) + return instance + + @abc.abstractmethod + def apply_configuration(self, task, settings): + """Validate & apply BIOS settings on the given node. + + This method takes the BIOS settings from the settings param and + applies BIOS settings on the given node. It may also validate the + given bios settings before applying any settings and manage + failures when setting an invalid BIOS config. In the case of + needing password to update the BIOS config, it will be taken from + the driver_info properties. After the BIOS configuration is done, + cache_bios_settings will be called to update the node's BIOS setting + table with the BIOS configuration applied on the node. + + :param task: a TaskManager instance. + :param settings: Dictonary containing the BIOS configuration. + :raises: UnsupportedDriverExtension, if the node's driver doesn't + support BIOS configuration. + :raises: InvalidParameterValue, if validation of settings fails. + :raises: MissingParameterValue, if some required parameters are + missing. + :returns: states.CLEANWAIT if BIOS configuration is in progress + asynchronously or None if it is complete. + """ + + @abc.abstractmethod + def factory_reset(self, task): + """Reset BIOS configuration to factory default on the given node. + + This method resets BIOS configuration to factory default on the + given node. After the BIOS reset action is done, cache_bios_settings + will be called to update the node's BIOS settings table with default + bios settings. + + :param task: a TaskManager instance. + :raises: UnsupportedDriverExtension, if the node's driver doesn't + support BIOS reset. + :returns: states.CLEANWAIT if BIOS configuration is in progress + asynchronously or None if it is complete. + """ + + @abc.abstractmethod + def cache_bios_settings(self, task): + """Store or update BIOS properties on the given node. + + This method stores BIOS properties to the bios_settings table during + 'cleaning' operation and updates bios_settings table when + apply_configuration() and factory_reset() are called to set new BIOS + configurations. It will also update the timestamp of each bios setting. + + :param task: a TaskManager instance. + :raises: UnsupportedDriverExtension, if the node's driver doesn't + support getting BIOS properties from bare metal. + :returns: None. + """ + + class RAIDInterface(BaseInterface): interface_type = 'raid' diff --git a/ironic/drivers/fake.py b/ironic/drivers/fake.py index 219692f9fb..6793d448e5 100644 --- a/ironic/drivers/fake.py +++ b/ironic/drivers/fake.py @@ -73,7 +73,7 @@ class FakeDriver(base.BaseDriver): def to_hardware_type(cls): return 'fake-hardware', { iface: 'fake' - for iface in ['boot', 'console', 'deploy', 'inspect', + for iface in ['bios', 'boot', 'console', 'deploy', 'inspect', 'management', 'power', 'raid', 'rescue', 'vendor'] } diff --git a/ironic/drivers/fake_hardware.py b/ironic/drivers/fake_hardware.py index 2eb6344876..32bca24dc1 100644 --- a/ironic/drivers/fake_hardware.py +++ b/ironic/drivers/fake_hardware.py @@ -32,6 +32,10 @@ class FakeHardware(hardware_type.AbstractHardwareType): All fake implementations are still expected to be enabled in the configuration. """ + @property + def supported_bios_interfaces(self): + """List of classes of supported bios interfaces.""" + return [fake.FakeBIOS, noop.NoBIOS] @property def supported_boot_interfaces(self): diff --git a/ironic/drivers/hardware_type.py b/ironic/drivers/hardware_type.py index 3011019533..ec42cb3bff 100644 --- a/ironic/drivers/hardware_type.py +++ b/ironic/drivers/hardware_type.py @@ -62,6 +62,10 @@ class AbstractHardwareType(object): """List of supported power interfaces.""" # Optional hardware interfaces + @property + def supported_bios_interfaces(self): + """List of supported bios interfaces.""" + return [noop.NoBIOS] @property def supported_console_interfaces(self): diff --git a/ironic/drivers/modules/fake.py b/ironic/drivers/modules/fake.py index 3fef1fe552..0167845642 100644 --- a/ironic/drivers/modules/fake.py +++ b/ironic/drivers/modules/fake.py @@ -29,6 +29,7 @@ from ironic.common import exception from ironic.common.i18n import _ from ironic.common import states from ironic.drivers import base +from ironic import objects class FakePower(base.PowerInterface): @@ -236,6 +237,33 @@ class FakeRAID(base.RAIDInterface): pass +class FakeBIOS(base.BIOSInterface): + """Example implementation of simple BIOSInterface.""" + + def get_properties(self): + return {} + + def validate(self, task): + pass + + def apply_configuration(self, task, settings): + node_id = task.node.id + try: + objects.BIOSSettingList.create(task.context, node_id, settings) + except exception.BIOSSettingAlreadyExists: + objects.BIOSSettingList.save(task.context, node_id, settings) + + def factory_reset(self, task): + node_id = task.node.id + setting_objs = objects.BIOSSettingList.get_by_node_id( + task.context, node_id) + for setting in setting_objs: + objects.BIOSSetting.delete(task.context, node_id, setting.name) + + def cache_bios_settings(self, task): + pass + + class FakeStorage(base.StorageInterface): """Example implementation of simple storage Interface.""" diff --git a/ironic/drivers/modules/noop.py b/ironic/drivers/modules/noop.py index 2b9c255626..0663f4776a 100644 --- a/ironic/drivers/modules/noop.py +++ b/ironic/drivers/modules/noop.py @@ -68,3 +68,16 @@ class NoRAID(FailMixin, base.RAIDInterface): def validate_raid_config(self, task, raid_config): _fail(self, task) + + +class NoBIOS(FailMixin, base.BIOSInterface): + """BIOS interface implementation that raises errors on all requests.""" + + def apply_configuration(self, task, settings): + _fail(self, task, settings) + + def factory_reset(self, task): + _fail(self, task) + + def cache_bios_settings(self, task): + pass diff --git a/ironic/objects/node.py b/ironic/objects/node.py index 3554e45618..c68f5f45e4 100644 --- a/ironic/objects/node.py +++ b/ironic/objects/node.py @@ -59,7 +59,8 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): # Version 1.21: Add storage_interface field # Version 1.22: Add rescue_interface field # Version 1.23: Add traits field - VERSION = '1.23' + # Version 1.24: Add bios_interface field + VERSION = '1.24' dbapi = db_api.get_instance() @@ -119,6 +120,7 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): 'extra': object_fields.FlexibleDictField(nullable=True), + 'bios_interface': object_fields.StringField(nullable=True), 'boot_interface': object_fields.StringField(nullable=True), 'console_interface': object_fields.StringField(nullable=True), 'deploy_interface': object_fields.StringField(nullable=True), @@ -130,7 +132,6 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): 'rescue_interface': object_fields.StringField(nullable=True), 'storage_interface': object_fields.StringField(nullable=True), 'vendor_interface': object_fields.StringField(nullable=True), - 'traits': object_fields.ObjectField('TraitList', nullable=True), } @@ -476,6 +477,9 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): Version 1.23: traits field was added. Its default value is None. For versions prior to this, it should be set to None (or removed). + Version 1.24: bios_interface field was added. Its default value is + None. For versions prior to this, it should be set to None (or + removed). :param target_version: the desired version of the object :param remove_unavailable_fields: True to remove fields that are @@ -511,6 +515,21 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): elif self.traits is not None: self.traits = None + bios_iface_is_set = self.obj_attr_is_set('bios_interface') + if target_version >= (1, 24): + # Target version supports bios_interface. + if not bios_iface_is_set: + # Set it to its default value if it is not set. + self.bios_interface = None + elif bios_iface_is_set: + # Target version does not support bios_interface, and it is set. + if remove_unavailable_fields: + # (De)serialising: remove unavailable fields. + delattr(self, 'bios_interface') + elif self.bios_interface is not None: + # DB: set unavailable field to the default of None. + self.bios_interface = None + @base.IronicObjectRegistry.register class NodePayload(notification.NotificationPayloadBase): @@ -558,6 +577,11 @@ class NodePayload(notification.NotificationPayloadBase): 'uuid': ('node', 'uuid') } + # TODO(zshi): At a later point in time, once bios_interface is able + # to be leveraged, we need to add the bios_interface field to payload + # and increment the object versions for all objects that inherit the + # NodePayload object. + # Version 1.0: Initial version, based off of Node version 1.18. # Version 1.1: Type of network_interface changed to just nullable string # similar to version 1.20 of Node. diff --git a/ironic/tests/unit/api/controllers/v1/test_driver.py b/ironic/tests/unit/api/controllers/v1/test_driver.py index 155e11122e..ddd74ec84a 100644 --- a/ironic/tests/unit/api/controllers/v1/test_driver.py +++ b/ironic/tests/unit/api/controllers/v1/test_driver.py @@ -215,9 +215,11 @@ class TestListDrivers(base.BaseApiTest): if use_dynamic: for iface in driver_base.ALL_INTERFACES: - if latest_if or iface not in ['rescue', 'storage']: - self.assertIn('default_%s_interface' % iface, data) - self.assertIn('enabled_%s_interfaces' % iface, data) + if iface != 'bios': + if latest_if or iface not in ['rescue', 'storage']: + self.assertIn('default_%s_interface' % iface, data) + self.assertIn('enabled_%s_interfaces' % iface, data) + self.assertIsNotNone(data['default_deploy_interface']) self.assertIsNotNone(data['enabled_deploy_interfaces']) else: diff --git a/ironic/tests/unit/common/test_driver_factory.py b/ironic/tests/unit/common/test_driver_factory.py index 1e0ebc9c5a..a2f0a34c32 100644 --- a/ironic/tests/unit/common/test_driver_factory.py +++ b/ironic/tests/unit/common/test_driver_factory.py @@ -108,7 +108,7 @@ class DriverLoadTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, node.id) as task: for iface in drivers_base.ALL_INTERFACES: impl = getattr(task.driver, iface) - if iface == 'rescue': + if iface in ['bios', 'rescue']: self.assertIsNone(impl) else: self.assertIsNotNone(impl) @@ -572,6 +572,11 @@ class DefaultInterfaceTestCase(db_base.DbTestCase): class TestFakeHardware(hardware_type.AbstractHardwareType): + @property + def supported_bios_interfaces(self): + """List of supported bios interfaces.""" + return [fake.FakeBIOS] + @property def supported_boot_interfaces(self): """List of supported boot interfaces.""" @@ -796,6 +801,7 @@ class HardwareTypeLoadTestCase(db_base.DbTestCase): def _test_enabled_supported_interfaces(self, enable_storage): ht = fake_hardware.FakeHardware() expected = { + 'bios': set(['fake', 'no-bios']), 'boot': set(['fake']), 'console': set(['fake', 'no-console']), 'deploy': set(['fake']), @@ -850,6 +856,7 @@ class ClassicDriverMigrationTestCase(base.TestCase): delta = driver_factory.calculate_migration_delta( 'drv', self.driver_cls, False) self.assertEqual({'driver': 'hw-type', + 'bios_interface': 'no-bios', 'console_interface': 'new-console', 'inspect_interface': 'new-inspect', 'raid_interface': 'no-raid', @@ -881,6 +888,7 @@ class ClassicDriverMigrationTestCase(base.TestCase): delta = driver_factory.calculate_migration_delta( 'drv', self.driver_cls, True) self.assertEqual({'driver': 'hw-type', + 'bios_interface': 'no-bios', 'console_interface': 'new-console', 'inspect_interface': 'no-inspect', 'raid_interface': 'no-raid', diff --git a/ironic/tests/unit/conductor/mgr_utils.py b/ironic/tests/unit/conductor/mgr_utils.py index 6466dfa887..6ab44079db 100644 --- a/ironic/tests/unit/conductor/mgr_utils.py +++ b/ironic/tests/unit/conductor/mgr_utils.py @@ -177,6 +177,7 @@ class ServiceSetUpMixin(object): self.config(enabled_raid_interfaces=['fake', 'no-raid']) self.config(enabled_rescue_interfaces=['fake', 'no-rescue']) self.config(enabled_vendor_interfaces=['fake', 'no-vendor']) + self.config(enabled_bios_interfaces=['fake', 'no-bios']) self.service = manager.ConductorManager(self.hostname, 'test-topic') mock_the_extension_manager() diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index eb6cd3eeda..26ed64afab 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -3405,6 +3405,7 @@ class MiscTestCase(mgr_utils.ServiceSetUpMixin, mgr_utils.CommonMixIn, 'network': {'result': True}, 'storage': {'result': True}, 'rescue': {'reason': reason, 'result': None}} + self.assertEqual(expected, ret) mock_iwdi.assert_called_once_with(self.context, node.instance_info) diff --git a/ironic/tests/unit/conductor/test_utils.py b/ironic/tests/unit/conductor/test_utils.py index 25a7f939a6..b2136ad96a 100644 --- a/ironic/tests/unit/conductor/test_utils.py +++ b/ironic/tests/unit/conductor/test_utils.py @@ -730,6 +730,7 @@ class NodePowerActionTestCase(db_base.DbTestCase): raid_interface='no-raid', rescue_interface='no-rescue', vendor_interface='no-vendor', + bios_interface='no-bios', power_state=states.POWER_ON) self.config(enabled_boot_interfaces=['fake']) self.config(enabled_deploy_interfaces=['fake']) diff --git a/ironic/tests/unit/drivers/modules/test_noop.py b/ironic/tests/unit/drivers/modules/test_noop.py index ec6f21776e..07919e4784 100644 --- a/ironic/tests/unit/drivers/modules/test_noop.py +++ b/ironic/tests/unit/drivers/modules/test_noop.py @@ -29,10 +29,18 @@ def hardware_interface_extension_manager(interface): class NoInterfacesTestCase(base.TestCase): - iface_types = ['console', 'inspect', 'raid', 'rescue', 'vendor'] + iface_types = ['bios', 'console', 'inspect', 'raid', 'rescue', 'vendor'] task = mock.Mock(node=mock.Mock(driver='pxe_foobar', spec=['driver']), spec=['node']) + def test_bios(self): + self.assertRaises(exception.UnsupportedDriverExtension, + getattr(noop.NoBIOS(), 'apply_configuration'), + self, self.task, '') + self.assertRaises(exception.UnsupportedDriverExtension, + getattr(noop.NoBIOS(), 'factory_reset'), + self, self.task) + def test_console(self): for method in ('start_console', 'stop_console', 'get_console'): self.assertRaises(exception.UnsupportedDriverExtension, diff --git a/ironic/tests/unit/drivers/test_base.py b/ironic/tests/unit/drivers/test_base.py index 224fc67609..b7494773e0 100644 --- a/ironic/tests/unit/drivers/test_base.py +++ b/ironic/tests/unit/drivers/test_base.py @@ -422,6 +422,43 @@ class TestDeployInterface(base.TestCase): self.assertTrue(mock_log.called) +class MyBIOSInterface(driver_base.BIOSInterface): + + def get_properties(self): + pass + + def validate(self, task): + pass + + def apply_configuration(self, task, settings): + pass + + def factory_reset(self, task): + pass + + def cache_bios_settings(self, task): + pass + + +class TestBIOSInterface(base.TestCase): + + @mock.patch.object(MyBIOSInterface, 'cache_bios_settings', autospec=True) + def test_apply_configuration_wrapper(self, cache_bios_settings_mock): + bios = MyBIOSInterface() + task_mock = mock.MagicMock() + + bios.apply_configuration(bios, task_mock, "") + cache_bios_settings_mock.assert_called_once_with(bios, task_mock) + + @mock.patch.object(MyBIOSInterface, 'cache_bios_settings', autospec=True) + def test_factory_reset_wrapper(self, cache_bios_settings_mock): + bios = MyBIOSInterface() + task_mock = mock.MagicMock() + + bios.factory_reset(bios, task_mock) + cache_bios_settings_mock.assert_called_once_with(bios, task_mock) + + class TestBootInterface(base.TestCase): def test_validate_rescue_default_impl(self): @@ -449,16 +486,20 @@ class TestBaseDriver(base.TestCase): # get modified by a child class self.assertEqual(('deploy', 'power'), driver_base.BaseDriver.core_interfaces) - self.assertEqual(('boot', 'console', 'inspect', 'management', 'raid'), - driver_base.BaseDriver.standard_interfaces) + self.assertEqual( + ('boot', 'console', 'inspect', 'management', 'raid'), + driver_base.BaseDriver.standard_interfaces + ) # Ensure that instantiating an instance of a derived class does not # change our variables. driver_base.BareDriver() self.assertEqual(('deploy', 'power'), driver_base.BaseDriver.core_interfaces) - self.assertEqual(('boot', 'console', 'inspect', 'management', 'raid'), - driver_base.BaseDriver.standard_interfaces) + self.assertEqual( + ('boot', 'console', 'inspect', 'management', 'raid'), + driver_base.BaseDriver.standard_interfaces + ) class TestBareDriver(base.TestCase): @@ -469,7 +510,7 @@ class TestBareDriver(base.TestCase): self.assertEqual(('deploy', 'power', 'network'), driver_base.BareDriver.core_interfaces) self.assertEqual( - ('boot', 'console', 'inspect', 'management', 'raid', + ('boot', 'console', 'inspect', 'management', 'raid', 'bios', 'rescue', 'storage'), driver_base.BareDriver.standard_interfaces ) diff --git a/ironic/tests/unit/objects/test_node.py b/ironic/tests/unit/objects/test_node.py index 903716dce1..ef4df2942e 100644 --- a/ironic/tests/unit/objects/test_node.py +++ b/ironic/tests/unit/objects/test_node.py @@ -497,6 +497,69 @@ class TestConvertToVersion(db_base.DbTestCase): self.assertIsNone(node.traits) self.assertEqual({}, node.obj_get_changes()) + def test_bios_supported_missing(self): + # bios_interface not set, should be set to default. + node = obj_utils.get_test_node(self.ctxt, **self.fake_node) + delattr(node, 'bios_interface') + node.obj_reset_changes() + + node._convert_to_version("1.24") + + self.assertIsNone(node.bios_interface) + self.assertEqual({'bios_interface': None}, + node.obj_get_changes()) + + def test_bios_supported_set(self): + # bios_interface set, no change required. + node = obj_utils.get_test_node(self.ctxt, **self.fake_node) + + node.bios_interface = 'fake' + node.obj_reset_changes() + node._convert_to_version("1.24") + self.assertEqual('fake', node.bios_interface) + self.assertEqual({}, node.obj_get_changes()) + + def test_bios_unsupported_missing(self): + # bios_interface not set, no change required. + node = obj_utils.get_test_node(self.ctxt, **self.fake_node) + + delattr(node, 'bios_interface') + node.obj_reset_changes() + node._convert_to_version("1.23") + self.assertNotIn('bios_interface', node) + self.assertEqual({}, node.obj_get_changes()) + + def test_bios_unsupported_set_remove(self): + # bios_interface set, should be removed. + node = obj_utils.get_test_node(self.ctxt, **self.fake_node) + + node.bios_interface = 'fake' + node.obj_reset_changes() + node._convert_to_version("1.23") + self.assertNotIn('bios_interface', node) + self.assertEqual({}, node.obj_get_changes()) + + def test_bios_unsupported_set_no_remove_non_default(self): + # bios_interface set, should be set to default. + node = obj_utils.get_test_node(self.ctxt, **self.fake_node) + + node.bios_interface = 'fake' + node.obj_reset_changes() + node._convert_to_version("1.23", False) + self.assertIsNone(node.bios_interface) + self.assertEqual({'bios_interface': None}, + node.obj_get_changes()) + + def test_bios_unsupported_set_no_remove_default(self): + # bios_interface set, no change required. + node = obj_utils.get_test_node(self.ctxt, **self.fake_node) + + node.bios_interface = None + node.obj_reset_changes() + node._convert_to_version("1.23", False) + self.assertIsNone(node.bios_interface) + self.assertEqual({}, node.obj_get_changes()) + class TestNodePayloads(db_base.DbTestCase): diff --git a/ironic/tests/unit/objects/test_objects.py b/ironic/tests/unit/objects/test_objects.py index 3f199531ec..486084ea19 100644 --- a/ironic/tests/unit/objects/test_objects.py +++ b/ironic/tests/unit/objects/test_objects.py @@ -663,7 +663,7 @@ class TestObject(_LocalTest, _TestObject): # version bump. It is an MD5 hash of the object fields and remotable methods. # The fingerprint values should only be changed if there is a version bump. expected_object_fingerprints = { - 'Node': '1.23-6bebf8dbcd2ce15407c946bd091f80b4', + 'Node': '1.24-7d3d504e5e0d2535b2390d558b27196a', 'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6', 'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905', 'Port': '1.8-898a47921f4a1f53fcdddd4eeb179e0b', diff --git a/setup.cfg b/setup.cfg index 3c52c8f043..fd1a8cadda 100644 --- a/setup.cfg +++ b/setup.cfg @@ -87,6 +87,10 @@ ironic.drivers = pxe_iscsi_cimc = ironic.drivers.pxe:PXEAndCIMCDriver pxe_agent_cimc = ironic.drivers.agent:AgentAndCIMCDriver +ironic.hardware.interfaces.bios = + fake = ironic.drivers.modules.fake:FakeBIOS + no-bios = ironic.drivers.modules.noop:NoBIOS + ironic.hardware.interfaces.boot = fake = ironic.drivers.modules.fake:FakeBoot ilo-pxe = ironic.drivers.modules.ilo.boot:IloPXEBoot