From 53e7baef4205767af8e6c15e6e0e5369e04aea5e Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Wed, 27 Jun 2018 17:59:30 +0200 Subject: [PATCH] Remove support for creating and loading classic drivers * removes any bits related to loading classic drivers from the drivers factory code * removes exceptions that only happen when classic drivers can be loaded * removes the BaseDriver, moves the useful functionality to the BareDriver class * /v1/drivers/?type=classic now always returns an empty list * removes the migration updating classic drivers to hardware types The documentation will be updated separately. Change-Id: I8ee58dfade87ae2a2544c5dcc27702c069f5089d --- devstack/lib/ironic | 2 + ironic/api/controllers/v1/driver.py | 57 +--- ironic/cmd/dbsync.py | 2 - ironic/common/driver_factory.py | 309 ++---------------- ironic/common/exception.py | 14 +- ironic/common/hash_ring.py | 3 +- ironic/conductor/base_manager.py | 64 ++-- ironic/conductor/manager.py | 168 +++------- ironic/conductor/utils.py | 12 - ironic/conf/default.py | 12 +- ironic/db/api.py | 33 -- ironic/db/sqlalchemy/api.py | 88 ----- ironic/drivers/base.py | 154 +++------ ironic/drivers/ipmi.py | 2 +- ironic/tests/base.py | 2 - .../unit/api/controllers/v1/test_driver.py | 134 ++++---- .../unit/api/controllers/v1/test_node.py | 16 - .../tests/unit/common/test_driver_factory.py | 87 +---- ironic/tests/unit/common/test_hash_ring.py | 13 +- .../tests/unit/conductor/test_base_manager.py | 103 ++---- ironic/tests/unit/conductor/test_manager.py | 4 +- ironic/tests/unit/conductor/test_rpcapi.py | 32 +- ironic/tests/unit/db/test_api.py | 35 -- ironic/tests/unit/db/test_conductor.py | 91 ------ ironic/tests/unit/drivers/test_base.py | 156 +-------- .../no-classic-drivers-e68d8527491314c3.yaml | 12 + 26 files changed, 327 insertions(+), 1278 deletions(-) create mode 100644 releasenotes/notes/no-classic-drivers-e68d8527491314c3.yaml diff --git a/devstack/lib/ironic b/devstack/lib/ironic index ca5e0e70e8..da4ef217fb 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -2508,6 +2508,8 @@ function ironic_configure_tempest { iniset $TEMPEST_CONFIG baremetal whole_disk_image_checksum $(md5sum $FILES/${IRONIC_WHOLEDISK_IMAGE_NAME}.img) fi + # NOTE(dtantsur): keep this option here until the defaults change in + # ironic-tempest-plugin to disable classic drivers testing. iniset $TEMPEST_CONFIG baremetal enabled_drivers "" iniset $TEMPEST_CONFIG baremetal enabled_hardware_types $IRONIC_ENABLED_HARDWARE_TYPES iniset $TEMPEST_CONFIG baremetal enabled_bios_interfaces $IRONIC_ENABLED_BIOS_INTERFACES diff --git a/ironic/api/controllers/v1/driver.py b/ironic/api/controllers/v1/driver.py index d3f3c508cf..ce6901d35d 100644 --- a/ironic/api/controllers/v1/driver.py +++ b/ironic/api/controllers/v1/driver.py @@ -131,14 +131,11 @@ class Driver(base.APIBase): enabled_vendor_interfaces = [wtypes.text] @staticmethod - def convert_with_links(name, hosts, driver_type, detail=False, - interface_info=None): + def convert_with_links(name, hosts, detail=False, interface_info=None): """Convert driver/hardware type info to an API-serializable object. - :param name: name of driver or hardware type. + :param name: name of a hardware type. :param hosts: list of conductor hostnames driver is active on. - :param driver_type: 'classic' for classic drivers, 'dynamic' for - hardware types. :param detail: boolean, whether to include detailed info, such as the 'type' field and default/enabled interfaces fields. :param interface_info: optional list of dicts of hardware interface @@ -169,8 +166,10 @@ class Driver(base.APIBase): ] if api_utils.allow_dynamic_drivers(): - driver.type = driver_type - if driver_type == 'dynamic' and detail: + # NOTE(dtantsur): only dynamic drivers (based on hardware types) + # are supported starting with the Rocky release. + driver.type = 'dynamic' + if detail: if interface_info is None: # TODO(jroll) objectify this interface_info = (pecan.request.dbapi @@ -192,12 +191,6 @@ class Driver(base.APIBase): setattr(driver, default_key, default) setattr(driver, enabled_key, list(enabled)) - elif detail: - for iface_type in driver_base.ALL_INTERFACES: - # always return None for classic drivers - setattr(driver, 'default_%s_interface' % iface_type, None) - setattr(driver, 'enabled_%s_interfaces' % iface_type, None) - hide_fields_in_newer_versions(driver) return driver @@ -223,10 +216,9 @@ class DriverList(base.APIBase): """A list containing drivers objects""" @staticmethod - def convert_with_links(drivers, hardware_types, detail=False): + def convert_with_links(hardware_types, detail=False): """Convert drivers and hardware types to an API-serializable object. - :param drivers: dict mapping driver names to conductor hostnames. :param hardware_types: dict mapping hardware type names to conductor hostnames. :param detail: boolean, whether to include detailed info, such as @@ -234,10 +226,7 @@ class DriverList(base.APIBase): :returns: an API-serializable driver collection object. """ collection = DriverList() - collection.drivers = [ - Driver.convert_with_links(dname, list(drivers[dname]), 'classic', - detail=detail) - for dname in drivers] + collection.drivers = [] # NOTE(jroll) we return hardware types in all API versions, # but restrict type/default/enabled fields to 1.30. @@ -255,7 +244,7 @@ class DriverList(base.APIBase): collection.drivers.append( Driver.convert_with_links(htname, list(hardware_types[htname]), - 'dynamic', detail=detail, + detail=detail, interface_info=interface_info)) return collection @@ -392,14 +381,13 @@ class DriversController(rest.RestController): '"type" filter must be one of "classic" or "dynamic", ' 'if specified.')) - driver_list = {} - hw_type_dict = {} - if type is None or type == 'classic': - driver_list = pecan.request.dbapi.get_active_driver_dict() if type is None or type == 'dynamic': hw_type_dict = pecan.request.dbapi.get_active_hardware_type_dict() - return DriverList.convert_with_links(driver_list, hw_type_dict, - detail=detail) + else: + # NOTE(dtantsur): we don't support classic drivers starting with + # the Rocky release. + hw_type_dict = {} + return DriverList.convert_with_links(hw_type_dict, detail=detail) @METRICS.timer('DriversController.get_one') @expose.expose(Driver, wtypes.text) @@ -412,20 +400,11 @@ class DriversController(rest.RestController): cdict = pecan.request.context.to_policy_values() policy.authorize('baremetal:driver:get', cdict, cdict) - def _find_driver(driver_dict, driver_type): - for name, hosts in driver_dict.items(): - if name == driver_name: - return Driver.convert_with_links(name, list(hosts), - driver_type, detail=True) - hw_type_dict = pecan.request.dbapi.get_active_hardware_type_dict() - driver = _find_driver(hw_type_dict, 'dynamic') - if driver: - return driver - driver_dict = pecan.request.dbapi.get_active_driver_dict() - driver = _find_driver(driver_dict, 'classic') - if driver: - return driver + for name, hosts in hw_type_dict.items(): + if name == driver_name: + return Driver.convert_with_links(name, list(hosts), + detail=True) raise exception.DriverNotFound(driver_name=driver_name) diff --git a/ironic/cmd/dbsync.py b/ironic/cmd/dbsync.py index 2525b3d501..223cb7eafa 100644 --- a/ironic/cmd/dbsync.py +++ b/ironic/cmd/dbsync.py @@ -64,8 +64,6 @@ dbapi = db_api.get_instance() # object, in case it is lazy loaded. The attribute will be accessed when needed # by doing getattr on the object ONLINE_MIGRATIONS = ( - # TODO(dtantsur): remove when classic drivers are removed (Rocky?) - (dbapi, 'migrate_to_hardware_types'), # Added in Rocky # TODO(rloo): remove in Stein (port, 'migrate_vif_port_id'), diff --git a/ironic/common/driver_factory.py b/ironic/common/driver_factory.py index df57d7a478..3f7c2fe2cf 100644 --- a/ironic/common/driver_factory.py +++ b/ironic/common/driver_factory.py @@ -17,7 +17,6 @@ import collections from oslo_concurrency import lockutils from oslo_log import log -import stevedore from stevedore import named from ironic.common import exception @@ -25,7 +24,6 @@ from ironic.common.i18n import _ from ironic.conf import CONF from ironic.drivers import base as driver_base from ironic.drivers import fake_hardware -from ironic.drivers import hardware_type LOG = log.getLogger(__name__) @@ -37,90 +35,58 @@ def build_driver_for_task(task): """Builds a composable driver for a given task. Starts with a `BareDriver` object, and attaches implementations of the - various driver interfaces to it. For classic drivers these all come from - the monolithic driver singleton, for hardware types - from separate + various driver interfaces to it. They come from separate driver factories and are configurable via the database. :param task: The task containing the node to build a driver for. :returns: A driver object for the task. - :raises: DriverNotFound if node.driver could not be found in either - "ironic.drivers" or "ironic.hardware.types" namespaces. + :raises: DriverNotFound if node.driver could not be found in the + "ironic.hardware.types" namespaces. :raises: InterfaceNotFoundInEntrypoint if some node interfaces are set to invalid or unsupported values. - :raises: IncompatibleInterface if driver is a hardware type and - the requested implementation is not compatible with it. + :raises: IncompatibleInterface the requested implementation is not + compatible with it with the hardware type. """ node = task.node - driver_name = node.driver - driver_or_hw_type = get_driver_or_hardware_type(driver_name) - try: - check_and_update_node_interfaces( - node, driver_or_hw_type=driver_or_hw_type) - except exception.MustBeNone as e: - # NOTE(rloo). This was raised because nodes with classic drivers - # cannot have any interfaces (except for network and - # storage) set. However, there was a small window - # where this was possible so instead of breaking those - # users totally, we'll spam them with warnings instead. - LOG.warning('%s They will be ignored. To avoid this warning, ' - 'please set them to None.', e) + hw_type = get_hardware_type(node.driver) + check_and_update_node_interfaces(node, hw_type=hw_type) bare_driver = driver_base.BareDriver() - _attach_interfaces_to_driver(bare_driver, node, driver_or_hw_type) + _attach_interfaces_to_driver(bare_driver, node, hw_type) return bare_driver -def _attach_interfaces_to_driver(bare_driver, node, driver_or_hw_type): +def _attach_interfaces_to_driver(bare_driver, node, hw_type): """Attach interface implementations to a bare driver object. - For classic drivers, copies implementations from the singleton driver - object, then attaches the dynamic interfaces (network and storage - interfaces for classic drivers, all interfaces for dynamic drivers - made of hardware types). - - For hardware types, load all interface implementations dynamically. - :param bare_driver: BareDriver instance to attach interfaces to :param node: Node object - :param driver_or_hw_type: classic driver or hardware type instance + :param hw_type: hardware type instance :raises: InterfaceNotFoundInEntrypoint if the entry point was not found. :raises: IncompatibleInterface if driver is a hardware type and the requested implementation is not compatible with it. """ - if isinstance(driver_or_hw_type, hardware_type.AbstractHardwareType): - # For hardware types all interfaces are dynamic - dynamic_interfaces = _INTERFACE_LOADERS - else: - # Copy implementations from the classic driver singleton - for iface in driver_or_hw_type.all_interfaces: - impl = getattr(driver_or_hw_type, iface, None) - setattr(bare_driver, iface, impl) - - # NOTE(TheJulia): This list of interfaces to be applied - # to classic drivers, thus requiring separate treatment. - dynamic_interfaces = ['network', 'storage'] - - for iface in dynamic_interfaces: + for iface in _INTERFACE_LOADERS: impl_name = getattr(node, '%s_interface' % iface) - impl = get_interface(driver_or_hw_type, iface, impl_name) + impl = get_interface(hw_type, iface, impl_name) setattr(bare_driver, iface, impl) -def get_interface(driver_or_hw_type, interface_type, interface_name): +def get_interface(hw_type, interface_type, interface_name): """Get interface implementation instance. For hardware types also validates compatibility. - :param driver_or_hw_type: a hardware type or classic driver instance. + :param hw_type: a hardware type instance. :param interface_type: name of the interface type (e.g. 'boot'). :param interface_name: name of the interface implementation from an appropriate entry point (ironic.hardware.interfaces.). :returns: instance of the requested interface implementation. :raises: InterfaceNotFoundInEntrypoint if the entry point was not found. - :raises: IncompatibleInterface if driver_or_hw_type is a hardware type and + :raises: IncompatibleInterface if hw_type is a hardware type and the requested implementation is not compatible with it. """ factory = _INTERFACE_LOADERS[interface_type]() @@ -132,35 +98,31 @@ def get_interface(driver_or_hw_type, interface_type, interface_name): entrypoint=factory._entrypoint_name, valid=factory.names) - if not isinstance(driver_or_hw_type, hardware_type.AbstractHardwareType): - # NOTE(dtantsur): classic drivers do not have notion of compatibility - return impl_instance - - if isinstance(driver_or_hw_type, fake_hardware.FakeHardware): + if isinstance(hw_type, fake_hardware.FakeHardware): # NOTE(dtantsur): special-case fake hardware type to allow testing with # any combinations of interface implementations. return impl_instance - supported_impls = getattr(driver_or_hw_type, + supported_impls = getattr(hw_type, 'supported_%s_interfaces' % interface_type) if type(impl_instance) not in supported_impls: raise exception.IncompatibleInterface( interface_type=interface_type, interface_impl=impl_instance, - hardware_type=driver_or_hw_type.__class__.__name__) + hardware_type=hw_type.__class__.__name__) return impl_instance -def default_interface(driver_or_hw_type, interface_type, +def default_interface(hw_type, interface_type, driver_name=None, node=None): """Calculate and return the default interface implementation. Finds the first implementation that is supported by the hardware type and is enabled in the configuration. - :param driver_or_hw_type: classic driver or hardware type instance object. + :param hw_type: hardware type instance object. :param interface_type: type of the interface (e.g. 'boot'). - :param driver_name: entrypoint name of the driver_or_hw_type object. Is + :param driver_name: entrypoint name of the hw_type object. Is used for exception message. :param node: the identifier of a node. If specified, is used for exception message. @@ -169,23 +131,11 @@ def default_interface(driver_or_hw_type, interface_type, :raises: NoValidDefaultForInterface if no default interface can be found. """ factory = _INTERFACE_LOADERS[interface_type] - is_hardware_type = isinstance(driver_or_hw_type, - hardware_type.AbstractHardwareType) # Explicit interface defaults additional_defaults = { 'storage': 'noop' } - if not is_hardware_type: - # For non hardware types we need to set a fallback for the network - # interface however hardware_types specify their own defaults if not in - # the config file. - if (CONF.dhcp.dhcp_provider == 'neutron' - and 'flat' in CONF.enabled_network_interfaces): - additional_defaults['network'] = 'flat' - elif 'noop' in CONF.enabled_network_interfaces: - additional_defaults['network'] = 'noop' - # The fallback default from the configuration impl_name = getattr(CONF, 'default_%s_interface' % interface_type) if impl_name is None: @@ -193,9 +143,9 @@ def default_interface(driver_or_hw_type, interface_type, if impl_name is not None: # Check that the default is correct for this type - get_interface(driver_or_hw_type, interface_type, impl_name) - elif is_hardware_type: - supported = getattr(driver_or_hw_type, + get_interface(hw_type, interface_type, impl_name) + else: + supported = getattr(hw_type, 'supported_%s_interfaces' % interface_type) # Mapping of classes to entry points enabled = {obj.__class__: name for (name, obj) in factory().items()} @@ -211,22 +161,18 @@ def default_interface(driver_or_hw_type, interface_type, if impl_name is None: # NOTE(rloo). No i18n on driver_type_str because translating substrings # on their own may cause the final string to look odd. - if is_hardware_type: - driver_type_str = 'hardware type' - else: - driver_type_str = 'driver' - driver_name = driver_name or driver_or_hw_type.__class__.__name__ + driver_name = driver_name or hw_type.__class__.__name__ node_info = "" if node is not None: node_info = _(' node %s with') % node raise exception.NoValidDefaultForInterface( - interface_type=interface_type, driver_type=driver_type_str, - driver=driver_name, node_info=node_info) + interface_type=interface_type, driver=driver_name, + node_info=node_info) return impl_name -def check_and_update_node_interfaces(node, driver_or_hw_type=None): +def check_and_update_node_interfaces(node, hw_type=None): """Ensure that node interfaces (e.g. for creation or updating) are valid. Updates (but doesn't save to the database) hardware interfaces with @@ -236,56 +182,22 @@ def check_and_update_node_interfaces(node, driver_or_hw_type=None): a driver instance is built for a node. :param node: node object to check and potentially update - :param driver_or_hw_type: classic driver or hardware type instance object; - will be detected from node.driver if missing + :param hw_type: hardware type instance object; will be detected from + node.driver if missing :returns: True if any changes were made to the node, otherwise False :raises: InterfaceNotFoundInEntrypoint on validation failure :raises: NoValidDefaultForInterface if the default value cannot be calculated and is not provided in the configuration - :raises: DriverNotFound if the node's driver or hardware type is not found - :raises: MustBeNone if one or more of the node's interface - fields were specified when they should not be. + :raises: DriverNotFound if the node's hardware type is not found """ - if driver_or_hw_type is None: - driver_or_hw_type = get_driver_or_hardware_type(node.driver) - is_hardware_type = isinstance(driver_or_hw_type, - hardware_type.AbstractHardwareType) + if hw_type is None: + hw_type = get_hardware_type(node.driver) - if is_hardware_type: - factories = list(_INTERFACE_LOADERS) - else: - # Only network and storage interfaces are dynamic for classic drivers - factories = ['network', 'storage'] + factories = list(_INTERFACE_LOADERS) - # These are interfaces that cannot be specified via the node. E.g., - # for classic drivers, none are allowed except for network & storage. - not_allowed_ifaces = driver_base.ALL_INTERFACES - set(factories) - - updates = node.obj_what_changed() # Result - whether the node object was modified result = False - bad_interface_fields = [] - for iface in not_allowed_ifaces: - field_name = '%s_interface' % iface - # NOTE(vsaienko): reset *_interface fields that shouldn't exist for - # classic driver, only when driver was changed and field not set - # explicitly - if 'driver' in updates and field_name not in updates: - setattr(node, field_name, None) - result = True - # NOTE(dtantsur): objects raise NotImplementedError on accessing fields - # that are known, but missing from an object. Thus, we cannot just use - # getattr(node, field_name, None) here. - elif field_name in node: - impl_name = getattr(node, field_name) - if impl_name is not None: - bad_interface_fields.append(field_name) - - if bad_interface_fields: - raise exception.MustBeNone(node=node.uuid, driver=node.driver, - node_fields=','.join(bad_interface_fields)) - # Walk through all dynamic interfaces and check/update them for iface in factories: field_name = '%s_interface' % iface @@ -296,11 +208,11 @@ def check_and_update_node_interfaces(node, driver_or_hw_type=None): impl_name = getattr(node, field_name) if impl_name is not None: # Check that the provided value is correct for this type - get_interface(driver_or_hw_type, iface, impl_name) + get_interface(hw_type, iface, impl_name) # Not changing the result, proceeding with the next interface continue - impl_name = default_interface(driver_or_hw_type, iface, + impl_name = default_interface(hw_type, iface, driver_name=node.driver, node=node.uuid) # Set the calculated default and set result to True @@ -310,22 +222,6 @@ def check_and_update_node_interfaces(node, driver_or_hw_type=None): return result -def get_driver_or_hardware_type(name): - """Get driver or hardware type by its entry point name. - - First, checks the hardware types namespace, then checks the classic - drivers namespace. The first object found is returned. - - :param name: entry point name. - :returns: An instance of a hardware type or a classic driver. - :raises: DriverNotFound if neither hardware type nor classic driver found. - """ - try: - return get_hardware_type(name) - except exception.DriverNotFound: - return get_driver(name) - - def get_hardware_type(hardware_type): """Get a hardware type instance by name. @@ -339,28 +235,6 @@ def get_hardware_type(hardware_type): raise exception.DriverNotFound(driver_name=hardware_type) -# TODO(dtantsur): rename to get_classic_driver -def get_driver(driver_name): - """Simple method to get a ref to an instance of a driver. - - Driver loading is handled by the DriverFactory class. This method - conveniently wraps that class and returns the actual driver object. - - :param driver_name: the name of the driver class to load - :returns: An instance of a class which implements - ironic.drivers.base.BaseDriver - :raises: DriverNotFound if the requested driver_name could not be - found in the "ironic.drivers" namespace. - - """ - - try: - factory = DriverFactory() - return factory.get_driver(driver_name) - except KeyError: - raise exception.DriverNotFound(driver_name=driver_name) - - def _get_all_drivers(factory): """Get all drivers for `factory` as a dict name -> driver object.""" # NOTE(jroll) I don't think this needs to be ordered, but @@ -371,14 +245,6 @@ def _get_all_drivers(factory): for name in factory.names) -def drivers(): - """Get all drivers. - - :returns: Dictionary mapping driver name to driver object. - """ - return _get_all_drivers(DriverFactory()) - - def hardware_types(): """Get all hardware types. @@ -548,11 +414,6 @@ def _warn_if_unsupported(ext): 'and may be removed in a future release.', ext.name) -class DriverFactory(BaseDriverFactory): - _entrypoint_name = 'ironic.drivers' - _enabled_driver_list_config_option = 'enabled_drivers' - - class HardwareTypesFactory(BaseDriverFactory): _entrypoint_name = 'ironic.hardware.types' _enabled_driver_list_config_option = 'enabled_hardware_types' @@ -575,101 +436,3 @@ _INTERFACE_LOADERS = { # refactor them later to use _INTERFACE_LOADERS. NetworkInterfaceFactory = _INTERFACE_LOADERS['network'] StorageInterfaceFactory = _INTERFACE_LOADERS['storage'] - - -def calculate_migration_delta(driver_name, driver_class, - reset_unsupported_interfaces=False): - """Calculate an update for the given classic driver extension. - - This function calculates a database update required to convert a node - with a classic driver to hardware types and interfaces. - - This function is used in the data migrations and is not a part of the - public Python API. - - :param driver_name: the entry point name of the driver - :param driver_class: class of classic driver. - :param reset_unsupported_interfaces: if set to True, target interfaces - that are not enabled will be replaced with a no-, - if possible. - :returns: Node fields requiring update as a dict (field -> new value). - None if a migration is not possible. - """ - # NOTE(dtantsur): provide defaults for optional interfaces - defaults = {'bios': 'no-bios', - 'console': 'no-console', - 'inspect': 'no-inspect', - 'raid': 'no-raid', - 'rescue': 'no-rescue', - 'vendor': 'no-vendor'} - try: - hw_type, new_ifaces = driver_class.to_hardware_type() - except NotImplementedError: - LOG.warning('Skipping migrating nodes with driver %s, ' - 'migration not supported', driver_name) - return None - else: - ifaces = dict(defaults, **new_ifaces) - - if hw_type not in CONF.enabled_hardware_types: - LOG.warning('Skipping migrating nodes with driver %(drv)s: ' - 'hardware type %(hw_type)s is not enabled', - {'drv': driver_name, 'hw_type': hw_type}) - return None - - not_enabled = [] - delta = {'driver': hw_type} - for iface, value in ifaces.items(): - conf = 'enabled_%s_interfaces' % iface - if value not in getattr(CONF, conf): - not_enabled.append((iface, value)) - else: - delta['%s_interface' % iface] = value - - if not_enabled and reset_unsupported_interfaces: - still_not_enabled = [] - for iface, value in not_enabled: - try: - default = defaults[iface] - except KeyError: - still_not_enabled.append((iface, value)) - else: - conf = 'enabled_%s_interfaces' % iface - if default not in getattr(CONF, conf): - still_not_enabled.append((iface, value)) - else: - delta['%s_interface' % iface] = default - - not_enabled = still_not_enabled - - if not_enabled: - LOG.warning('Skipping migrating nodes with driver %(drv)s, ' - 'the following interfaces are not supported: ' - '%(ifaces)s', - {'drv': driver_name, - 'ifaces': ', '.join('%s_interface=%s' % tpl - for tpl in not_enabled)}) - return None - - return delta - - -def classic_drivers_to_migrate(): - """Get drivers requiring migration. - - This function is used in the data migrations and is not a part of the - public Python API. - - :returns: a dict mapping driver names to driver classes - """ - def failure_callback(mgr, ep, exc): - LOG.warning('Unable to load classic driver %(drv)s: %(err)s', - {'drv': ep.name, 'err': exc}) - - extension_manager = ( - stevedore.ExtensionManager( - 'ironic.drivers', - invoke_on_load=False, - on_load_failure_callback=failure_callback)) - - return {ext.name: ext.plugin for ext in extension_manager} diff --git a/ironic/common/exception.py b/ironic/common/exception.py index 77ad126166..b08bb22190 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -347,15 +347,10 @@ class NoValidDefaultForInterface(InvalidParameterValue): # NOTE(rloo): in the line below, there is no blank space after 'For' # because node_info could be an empty string. If node_info # is not empty, it should start with a space. - _msg_fmt = _("For%(node_info)s %(driver_type)s '%(driver)s', no default " + _msg_fmt = _("For%(node_info)s hardware type '%(driver)s', no default " "value found for %(interface_type)s interface.") -class MustBeNone(InvalidParameterValue): - _msg_fmt = _("For node %(node)s with driver %(driver)s, these node " - "fields must be set to None: %(node_fields)s.") - - class ImageNotFound(NotFound): _msg_fmt = _("Image %(image_id)s could not be found.") @@ -425,14 +420,9 @@ class VolumeTargetNotFound(NotFound): _msg_fmt = _("Volume target %(target)s could not be found.") -class DriverNameConflict(IronicException): - _msg_fmt = _("Classic and dynamic drivers cannot have the " - "same names '%(names)s'.") - - class NoDriversLoaded(IronicException): _msg_fmt = _("Conductor %(conductor)s cannot be started " - "because no drivers were loaded.") + "because no hardware types were loaded.") class ConductorNotFound(NotFound): diff --git a/ironic/common/hash_ring.py b/ironic/common/hash_ring.py index 61e1b385f0..e91abf4d4b 100644 --- a/ironic/common/hash_ring.py +++ b/ironic/common/hash_ring.py @@ -49,8 +49,7 @@ class HashRingManager(object): def _load_hash_rings(self): rings = {} - d2c = self.dbapi.get_active_driver_dict() - d2c.update(self.dbapi.get_active_hardware_type_dict()) + d2c = self.dbapi.get_active_hardware_type_dict() for driver_name, hosts in d2c.items(): rings[driver_name] = hashring.HashRing( diff --git a/ironic/conductor/base_manager.py b/ironic/conductor/base_manager.py index 3ed7fc7ba0..ef5de34c83 100644 --- a/ironic/conductor/base_manager.py +++ b/ironic/conductor/base_manager.py @@ -51,18 +51,17 @@ def _check_enabled_interfaces(): :raises: ConfigInvalid if an enabled interfaces config option is empty. """ - if CONF.enabled_hardware_types: - empty_confs = [] - iface_types = ['enabled_%s_interfaces' % i - for i in driver_base.ALL_INTERFACES] - for iface_type in iface_types: - conf_value = getattr(CONF, iface_type) - if not conf_value: - empty_confs.append(iface_type) - if empty_confs: - msg = (_('Configuration options %s cannot be an empty list.') % - ', '.join(empty_confs)) - raise exception.ConfigInvalid(error_msg=msg) + empty_confs = [] + iface_types = ['enabled_%s_interfaces' % i + for i in driver_base.ALL_INTERFACES] + for iface_type in iface_types: + conf_value = getattr(CONF, iface_type) + if not conf_value: + empty_confs.append(iface_type) + if empty_confs: + msg = (_('Configuration options %s cannot be an empty list.') % + ', '.join(empty_confs)) + raise exception.ConfigInvalid(error_msg=msg) class BaseConductorManager(object): @@ -111,44 +110,33 @@ class BaseConductorManager(object): self.ring_manager = hash_ring.HashRingManager() """Consistent hash ring which maps drivers to conductors.""" + # TODO(dtantsur): remove in Stein + if CONF.enabled_drivers: + raise RuntimeError("The enabled_drivers configuration option " + "no longer has any effect and must be empty") + _check_enabled_interfaces() # NOTE(deva): these calls may raise DriverLoadError or DriverNotFound # NOTE(vdrok): Instantiate network and storage interface factory on # startup so that all the interfaces are loaded at the very # beginning, and failures prevent the conductor from starting. - drivers = driver_factory.drivers() hardware_types = driver_factory.hardware_types() driver_factory.NetworkInterfaceFactory() driver_factory.StorageInterfaceFactory() # NOTE(jroll) this is passed to the dbapi, which requires a list, not # a generator (which keys() returns in py3) - driver_names = list(drivers) hardware_type_names = list(hardware_types) # check that at least one driver is loaded, whether classic or dynamic - if not driver_names and not hardware_type_names: - msg = ("Conductor %s cannot be started because no drivers " - "were loaded. This could be because no classic drivers " - "were specified in the 'enabled_drivers' config option " - "and no dynamic drivers were specified in the " - "'enabled_hardware_types' config option.") + if not hardware_type_names: + msg = ("Conductor %s cannot be started because no hardware types " + "were specified in the 'enabled_hardware_types' config " + "option.") LOG.error(msg, self.host) raise exception.NoDriversLoaded(conductor=self.host) - # check for name clashes between classic and dynamic drivers - name_clashes = set(driver_names).intersection(hardware_type_names) - if name_clashes: - name_clashes = ', '.join(name_clashes) - msg = ("Conductor %(host)s cannot be started because there is " - "one or more name conflicts between classic drivers and " - "dynamic drivers (%(names)s). Check any external driver " - "plugins and the 'enabled_drivers' and " - "'enabled_hardware_types' config options.") - LOG.error(msg, {'host': self.host, 'names': name_clashes}) - raise exception.DriverNameConflict(names=name_clashes) - self._collect_periodic_tasks(admin_context) # Check for required config options if object_store_endpoint_type is @@ -170,7 +158,7 @@ class BaseConductorManager(object): try: # Register this conductor with the cluster self.conductor = objects.Conductor.register( - admin_context, self.host, driver_names) + admin_context, self.host, hardware_type_names) except exception.ConductorAlreadyRegistered: # This conductor was already registered and did not shut down # properly, so log a warning and update the record. @@ -178,7 +166,8 @@ class BaseConductorManager(object): "previously registered. Updating registration", {'hostname': self.host}) self.conductor = objects.Conductor.register( - admin_context, self.host, driver_names, update_existing=True) + admin_context, self.host, hardware_type_names, + update_existing=True) # register hardware types and interfaces supported by this conductor # and validate them against other conductors @@ -281,13 +270,6 @@ class BaseConductorManager(object): _collect_from(iface, args=(self, admin_context)) # TODO(dtantsur): allow periodics on hardware types themselves? - # Finally, collect tasks from interfaces of classic drivers, since they - # are not necessary registered as new-style hardware interfaces. - for driver_obj in driver_factory.drivers().values(): - for iface_name in driver_obj.all_interfaces: - iface = getattr(driver_obj, iface_name, None) - _collect_from(iface, args=(self, admin_context)) - if len(periodic_task_callables) > CONF.conductor.workers_pool_size: LOG.warning('This conductor has %(tasks)d periodic tasks ' 'enabled, but only %(workers)d task workers ' diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py index 3c905bb288..2031782925 100644 --- a/ironic/conductor/manager.py +++ b/ironic/conductor/manager.py @@ -72,7 +72,6 @@ from ironic.conductor import task_manager from ironic.conductor import utils from ironic.conf import CONF from ironic.drivers import base as drivers_base -from ironic.drivers import hardware_type from ironic import objects from ironic.objects import base as objects_base from ironic.objects import fields @@ -110,7 +109,6 @@ class ConductorManager(base_manager.BaseConductorManager): # InterfaceNotFoundInEntrypoint # IncompatibleInterface, # NoValidDefaultForInterface - # MustBeNone @messaging.expected_exceptions(exception.InvalidParameterValue, exception.DriverNotFound) def create_node(self, context, node_obj): @@ -126,8 +124,6 @@ class ConductorManager(base_manager.BaseConductorManager): :raises: NoValidDefaultForInterface if no default can be calculated for some interfaces, and explicit values must be provided. :raises: InvalidParameterValue if some fields fail validation. - :raises: MustBeNone if one or more of the node's interface - fields were specified when they should not be. :raises: DriverNotFound if the driver or hardware type is not found. """ LOG.debug("RPC create_node called for node %s.", node_obj.uuid) @@ -140,7 +136,6 @@ class ConductorManager(base_manager.BaseConductorManager): # InterfaceNotFoundInEntrypoint # IncompatibleInterface, # NoValidDefaultForInterface - # MustBeNone @messaging.expected_exceptions(exception.InvalidParameterValue, exception.NodeLocked, exception.InvalidState, @@ -156,8 +151,6 @@ class ConductorManager(base_manager.BaseConductorManager): :param node_obj: a changed (but not saved) node object. :raises: NoValidDefaultForInterface if no default can be calculated for some interfaces, and explicit values must be provided. - :raises: MustBeNone if one or more of the node's interface - fields were specified when they should not be. """ node_id = node_obj.uuid LOG.debug("RPC update_node called for node %s.", node_id) @@ -329,11 +322,6 @@ class ConductorManager(base_manager.BaseConductorManager): # vendor_opts before starting validation. with task_manager.acquire(context, node_id, shared=True, purpose='calling vendor passthru') as task: - if not getattr(task.driver, 'vendor', None): - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, - extension='vendor interface') - vendor_iface = task.driver.vendor try: @@ -425,19 +413,12 @@ class ConductorManager(base_manager.BaseConductorManager): # Any locking in a top-level vendor action will need to be done by the # implementation, as there is little we could reasonably lock on here. LOG.debug("RPC driver_vendor_passthru for driver %s.", driver_name) - driver = driver_factory.get_driver_or_hardware_type(driver_name) + driver = driver_factory.get_hardware_type(driver_name) vendor = None - if isinstance(driver, hardware_type.AbstractHardwareType): - vendor_name = driver_factory.default_interface( - driver, 'vendor', driver_name=driver_name) - vendor = driver_factory.get_interface(driver, 'vendor', - vendor_name) - else: - vendor = getattr(driver, 'vendor', None) - if not vendor: - raise exception.UnsupportedDriverExtension( - driver=driver_name, - extension='vendor interface') + vendor_name = driver_factory.default_interface( + driver, 'vendor', driver_name=driver_name) + vendor = driver_factory.get_interface(driver, 'vendor', + vendor_name) try: vendor_opts = vendor.driver_routes[driver_method] @@ -484,11 +465,6 @@ class ConductorManager(base_manager.BaseConductorManager): lock_purpose = 'listing vendor passthru methods' with task_manager.acquire(context, node_id, shared=True, purpose=lock_purpose) as task: - if not getattr(task.driver, 'vendor', None): - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, - extension='vendor interface') - return get_vendor_passthru_metadata( task.driver.vendor.vendor_routes) @@ -519,19 +495,12 @@ class ConductorManager(base_manager.BaseConductorManager): # implementation, as there is little we could reasonably lock on here. LOG.debug("RPC get_driver_vendor_passthru_methods for driver %s", driver_name) - driver = driver_factory.get_driver_or_hardware_type(driver_name) + driver = driver_factory.get_hardware_type(driver_name) vendor = None - if isinstance(driver, hardware_type.AbstractHardwareType): - vendor_name = driver_factory.default_interface( - driver, 'vendor', driver_name=driver_name) - vendor = driver_factory.get_interface(driver, 'vendor', - vendor_name) - else: - vendor = getattr(driver, 'vendor', None) - if not vendor: - raise exception.UnsupportedDriverExtension( - driver=driver_name, - extension='vendor interface') + vendor_name = driver_factory.default_interface( + driver, 'vendor', driver_name=driver_name) + vendor = driver_factory.get_interface(driver, 'vendor', + vendor_name) return get_vendor_passthru_metadata(vendor.driver_routes) @@ -574,9 +543,6 @@ class ConductorManager(base_manager.BaseConductorManager): raise exception.NodeInMaintenance(op=_('rescuing'), node=node.uuid) - if not getattr(task.driver, 'rescue', None): - raise exception.UnsupportedDriverExtension( - driver=node.driver, extension='rescue') # driver validation may check rescue_password, so save it on the # node early instance_info = node.instance_info @@ -675,9 +641,6 @@ class ConductorManager(base_manager.BaseConductorManager): if node.maintenance: raise exception.NodeInMaintenance(op=_('unrescuing'), node=node.uuid) - if not getattr(task.driver, 'rescue', None): - raise exception.UnsupportedDriverExtension( - driver=node.driver, extension='rescue') try: task.driver.power.validate(task) except (exception.InvalidParameterValue, @@ -1192,14 +1155,13 @@ class ConductorManager(base_manager.BaseConductorManager): # Do caching of bios settings if supported by driver, # this will be called for both manual and automated cleaning. # TODO(zshi) remove this check when classic drivers are removed - if getattr(task.driver, 'bios', None): - try: - task.driver.bios.cache_bios_settings(task) - except Exception as e: - msg = (_('Caching of bios settings failed on node %(node)s. ' - 'Continuing with node cleaning.') - % {'node': node.uuid}) - LOG.exception(msg) + try: + task.driver.bios.cache_bios_settings(task) + except Exception as e: + msg = (_('Caching of bios settings failed on node %(node)s. ' + 'Continuing with node cleaning.') + % {'node': node.uuid}) + LOG.exception(msg) # Allow the deploy driver to set up the ramdisk again (necessary for # IPA cleaning) @@ -2023,32 +1985,29 @@ class ConductorManager(base_manager.BaseConductorManager): # interface. if iface_name == 'bios': continue - iface = getattr(task.driver, iface_name, None) + iface = getattr(task.driver, iface_name) result = reason = None - if iface: - try: - iface.validate(task) - if iface_name == 'deploy': - utils.validate_instance_info_traits(task.node) - result = True - except (exception.InvalidParameterValue, - exception.UnsupportedDriverExtension) as e: - result = False - reason = str(e) - except Exception as e: - result = False - reason = (_('Unexpected exception, traceback saved ' - 'into log by ironic conductor service ' - 'that is running on %(host)s: %(error)s') - % {'host': self.host, 'error': e}) - LOG.exception( - 'Unexpected exception occurred while validating ' - '%(iface)s driver interface for driver ' - '%(driver)s: %(err)s on node %(node)s.', - {'iface': iface_name, 'driver': task.node.driver, - 'err': e, 'node': task.node.uuid}) - else: - reason = _('not supported') + try: + iface.validate(task) + if iface_name == 'deploy': + utils.validate_instance_info_traits(task.node) + result = True + except (exception.InvalidParameterValue, + exception.UnsupportedDriverExtension) as e: + result = False + reason = str(e) + except Exception as e: + result = False + reason = (_('Unexpected exception, traceback saved ' + 'into log by ironic conductor service ' + 'that is running on %(host)s: %(error)s') + % {'host': self.host, 'error': e}) + LOG.exception( + 'Unexpected exception occurred while validating ' + '%(iface)s driver interface for driver ' + '%(driver)s: %(err)s on node %(node)s.', + {'iface': iface_name, 'driver': task.node.driver, + 'err': e, 'node': task.node.uuid}) ret_dict[iface_name] = {} ret_dict[iface_name]['result'] = result @@ -2256,9 +2215,6 @@ class ConductorManager(base_manager.BaseConductorManager): purpose=lock_purpose) as task: node = task.node - if not getattr(task.driver, 'console', None): - raise exception.UnsupportedDriverExtension(driver=node.driver, - extension='console') if not node.console_enabled: raise exception.NodeConsoleNotEnabled(node=node.uuid) @@ -2293,9 +2249,6 @@ class ConductorManager(base_manager.BaseConductorManager): with task_manager.acquire(context, node_id, shared=False, purpose='setting console mode') as task: node = task.node - if not getattr(task.driver, 'console', None): - raise exception.UnsupportedDriverExtension(driver=node.driver, - extension='console') task.driver.console.validate(task) @@ -2632,7 +2585,7 @@ class ConductorManager(base_manager.BaseConductorManager): """ LOG.debug("RPC get_driver_properties called for driver %s.", driver_name) - driver = driver_factory.get_driver_or_hardware_type(driver_name) + driver = driver_factory.get_hardware_type(driver_name) return driver.get_properties() @METRICS.timer('ConductorManager._sensors_nodes_task') @@ -2656,9 +2609,6 @@ class ConductorManager(base_manager.BaseConductorManager): node_uuid, shared=True, purpose=lock_purpose) as task: - if not getattr(task.driver, 'management', None): - continue - if task.node.maintenance: LOG.debug('Skipping sending sensors data for node ' '%s as it is in maintenance mode', @@ -2782,10 +2732,6 @@ class ConductorManager(base_manager.BaseConductorManager): 'device %(device)s', {'node': node_id, 'device': device}) with task_manager.acquire(context, node_id, purpose='setting boot device') as task: - node = task.node - if not getattr(task.driver, 'management', None): - raise exception.UnsupportedDriverExtension( - driver=node.driver, extension='management') task.driver.management.validate(task) task.driver.management.set_boot_device(task, device, persistent=persistent) @@ -2818,9 +2764,6 @@ class ConductorManager(base_manager.BaseConductorManager): LOG.debug('RPC get_boot_device called for node %s', node_id) with task_manager.acquire(context, node_id, purpose='getting boot device') as task: - if not getattr(task.driver, 'management', None): - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='management') task.driver.management.validate(task) return task.driver.management.get_boot_device(task) @@ -2846,12 +2789,7 @@ class ConductorManager(base_manager.BaseConductorManager): with task_manager.acquire(context, node_id, purpose='inject nmi') as task: - node = task.node - if not getattr(task.driver, 'management', None): - raise exception.UnsupportedDriverExtension( - driver=node.driver, extension='management') task.driver.management.validate(task) - task.driver.management.inject_nmi(task) @METRICS.timer('ConductorManager.get_supported_boot_devices') @@ -2879,9 +2817,6 @@ class ConductorManager(base_manager.BaseConductorManager): lock_purpose = 'getting supported boot devices' with task_manager.acquire(context, node_id, shared=True, purpose=lock_purpose) as task: - if not getattr(task.driver, 'management', None): - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='management') return task.driver.management.get_supported_boot_devices(task) @METRICS.timer('ConductorManager.inspect_hardware') @@ -2915,10 +2850,6 @@ class ConductorManager(base_manager.BaseConductorManager): LOG.debug('RPC inspect_hardware called for node %s', node_id) with task_manager.acquire(context, node_id, shared=False, purpose='hardware inspection') as task: - if not getattr(task.driver, 'inspect', None): - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='inspect') - task.driver.power.validate(task) task.driver.inspect.validate(task) @@ -2983,9 +2914,6 @@ class ConductorManager(base_manager.BaseConductorManager): context, node_id, purpose='setting target RAID config') as task: node = task.node - if not getattr(task.driver, 'raid', None): - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='raid') # Operator may try to unset node.target_raid_config. So, try to # validate only if it is not empty. if target_raid_config: @@ -3019,18 +2947,12 @@ class ConductorManager(base_manager.BaseConductorManager): LOG.debug("RPC get_raid_logical_disk_properties " "called for driver %s", driver_name) - driver = driver_factory.get_driver_or_hardware_type(driver_name) + driver = driver_factory.get_hardware_type(driver_name) raid_iface = None - if isinstance(driver, hardware_type.AbstractHardwareType): - raid_iface_name = driver_factory.default_interface( - driver, 'raid', driver_name=driver_name) - raid_iface = driver_factory.get_interface(driver, 'raid', - raid_iface_name) - else: - raid_iface = getattr(driver, 'raid', None) - if not raid_iface: - raise exception.UnsupportedDriverExtension( - driver=driver_name, extension='raid') + raid_iface_name = driver_factory.default_interface( + driver, 'raid', driver_name=driver_name) + raid_iface = driver_factory.get_interface(driver, 'raid', + raid_iface_name) return raid_iface.get_logical_disk_properties() diff --git a/ironic/conductor/utils.py b/ironic/conductor/utils.py index 46e0f446d8..8895d0d476 100644 --- a/ironic/conductor/utils.py +++ b/ironic/conductor/utils.py @@ -60,10 +60,6 @@ def node_set_boot_device(task, device, persistent=False): ManagementInterface fails. """ - # TODO(etingof): remove `if` once classic drivers are gone - if not getattr(task.driver, 'management', None): - return - task.driver.management.validate(task) if task.node.provision_state != states.ADOPTING: task.driver.management.set_boot_device(task, @@ -86,10 +82,6 @@ def node_get_boot_mode(task): :returns: Boot mode. One of :mod:`ironic.common.boot_mode` or `None` if boot mode can't be discovered """ - # TODO(etingof): remove `if` once classic drivers are gone - if not getattr(task.driver, 'management', None): - return - task.driver.management.validate(task) return task.driver.management.get_boot_mode(task) @@ -121,10 +113,6 @@ def node_set_boot_mode(task, mode): if task.node.provision_state == states.ADOPTING: return - # TODO(etingof): remove `if` once classic drivers are gone - if not getattr(task.driver, 'management', None): - return - task.driver.management.validate(task) boot_modes = task.driver.management.get_supported_boot_modes(task) diff --git a/ironic/conf/default.py b/ironic/conf/default.py index 7740187e8e..d4f03825e7 100644 --- a/ironic/conf/default.py +++ b/ironic/conf/default.py @@ -91,15 +91,9 @@ api_opts = [ driver_opts = [ cfg.ListOpt('enabled_drivers', default=[], - help=_('Specify the list of drivers to load during service ' - 'initialization. Missing drivers, or drivers which ' - 'fail to initialize, will prevent the conductor ' - 'service from starting. The option default is a ' - 'recommended set of production-oriented drivers. A ' - 'complete list of drivers present on your system may ' - 'be found by enumerating the "ironic.drivers" ' - 'entrypoint. An example may be found in the ' - 'developer documentation online.'), + help=_('This option is left for a start up check only. ' + 'Any non-empty value will prevent the conductor ' + 'from starting.'), deprecated_for_removal=True, deprecated_reason=_('Hardware types should be used instead ' 'of classic drivers. They are enabled ' diff --git a/ironic/db/api.py b/ironic/db/api.py index bac5b9ec3f..c7be8258da 100644 --- a/ironic/db/api.py +++ b/ironic/db/api.py @@ -517,20 +517,6 @@ class Connection(object): :raises: ConductorNotFound """ - @abc.abstractmethod - def get_active_driver_dict(self, interval): - """Retrieve drivers for the registered and active conductors. - - :param interval: Seconds since last check-in of a conductor. - :returns: A dict which maps driver names to the set of hosts - which support them. For example: - - :: - - {driverA: set([host1, host2]), - driverB: set([host2, host3])} - """ - @abc.abstractmethod def get_active_hardware_type_dict(self): """Retrieve hardware types for the registered and active conductors. @@ -909,25 +895,6 @@ class Connection(object): False otherwise. """ - @abc.abstractmethod - def migrate_to_hardware_types(self, context, max_count, - reset_unsupported_interfaces=False): - """Migrate nodes from classic drivers to hardware types. - - Go through all nodes with a classic driver and try to migrate them to a - corresponding hardware type and a correct set of hardware interfaces. - - :param context: the admin context - :param max_count: The maximum number of objects to migrate. Must be - >= 0. If zero, all the objects will be migrated. - :param reset_unsupported_interfaces: whether to reset unsupported - optional interfaces to their no-XXX versions. - :returns: A 2-tuple, 1. the total number of objects that need to be - migrated (at the beginning of this call) and 2. the number - of migrated objects. - """ - # TODO(dtantsur) Delete this in Rocky cycle. - @abc.abstractmethod def set_node_traits(self, node_id, traits, version): """Replace all of the node traits with specified list of traits. diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py index a8ac1b6fe1..a4db43be2b 100644 --- a/ironic/db/sqlalchemy/api.py +++ b/ironic/db/sqlalchemy/api.py @@ -33,7 +33,6 @@ from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound from sqlalchemy.orm import joinedload from sqlalchemy import sql -from ironic.common import driver_factory from ironic.common import exception from ironic.common.i18n import _ from ironic.common import profiler @@ -856,17 +855,6 @@ class Connection(api.Connection): 'powering process, their power state can be incorrect: ' '%(nodes)s', {'nodes': nodes}) - def get_active_driver_dict(self, interval=None): - query = model_query(models.Conductor) - result = _filter_active_conductors(query, interval=interval) - - # build mapping of drivers to the set of hosts which support them - d2c = collections.defaultdict(set) - for row in result: - for driver in row['drivers']: - d2c[driver].add(row['hostname']) - return d2c - def get_active_hardware_type_dict(self): query = (model_query(models.ConductorHardwareInterfaces, models.Conductor) @@ -1229,82 +1217,6 @@ class Connection(api.Connection): return False return True - @oslo_db_api.retry_on_deadlock - def migrate_to_hardware_types(self, context, max_count, - reset_unsupported_interfaces=False): - """Migrate nodes from classic drivers to hardware types. - - Go through all nodes with a classic driver and try to migrate them to - a corresponding hardware type and a correct set of hardware interfaces. - - If migration is not possible for any reason (e.g. the target hardware - type is not enabled), the nodes are skipped. An operator is expected to - correct the configuration and either rerun online_data_migration or - migrate the nodes manually. - - :param context: the admin context (not used) - :param max_count: The maximum number of objects to migrate. Must be - >= 0. If zero, all the objects will be migrated. - :param reset_unsupported_interfaces: whether to reset unsupported - optional interfaces to their no-XXX versions. - :returns: A 2-tuple, 1. the total number of objects that need to be - migrated (at the beginning of this call) and 2. the number - of migrated objects. - """ - reset_unsupported_interfaces = strutils.bool_from_string( - reset_unsupported_interfaces, strict=True) - - drivers = driver_factory.classic_drivers_to_migrate() - - total_to_migrate = (model_query(models.Node) - .filter(models.Node.driver.in_(list(drivers))) - .count()) - - total_migrated = 0 - for driver, driver_cls in drivers.items(): - if max_count and total_migrated >= max_count: - return total_to_migrate, total_migrated - - # UPDATE with LIMIT seems to be a MySQL-only feature, so first - # fetch the required number of Node IDs, then update them. - query = model_query(models.Node.id).filter_by(driver=driver) - if max_count: - query = query.limit(max_count - total_migrated) - ids = [obj.id for obj in query] - if not ids: - continue - - delta = driver_factory.calculate_migration_delta( - driver, driver_cls, reset_unsupported_interfaces) - if delta is None: - # NOTE(dtantsur): mark unsupported nodes as migrated. Otherwise - # calling online_data_migration without --max-count will result - # in an infinite loop. - total_migrated += len(ids) - continue - - # UPDATE with LIMIT seems to be a MySQL-only feature, so first - # fetch the required number of Node IDs, then update them. - query = model_query(models.Node.id).filter_by(driver=driver) - if max_count: - query = query.limit(max_count - total_migrated) - ids = [obj.id for obj in query] - if not ids: - LOG.debug('No nodes with driver %s', driver) - continue - - LOG.info('Migrating nodes with driver %(drv)s to %(delta)s', - {'drv': driver, 'delta': delta}) - - with _session_for_write(): - num_migrated = (model_query(models.Node) - .filter_by(driver=driver) - .filter(models.Node.id.in_(ids)) - .update(delta, synchronize_session=False)) - total_migrated += num_migrated - - return total_to_migrate, total_migrated - @staticmethod def _verify_max_traits_per_node(node_id, num_traits): """Verify that an operation would not exceed the per-node trait limit. diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py index 45131ba33d..950cabbae4 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py @@ -39,34 +39,29 @@ RAID_CONFIG_SCHEMA = os.path.join(os.path.dirname(__file__), 'raid_config_schema.json') -@six.add_metaclass(abc.ABCMeta) -class BaseDriver(object): - """Base class for all drivers. +class BareDriver(object): + """A bare driver object which will have interfaces attached later. - Defines the `core`, `standardized`, and `vendor-specific` interfaces for - drivers. Any loadable driver must implement all `core` interfaces. - Actual implementation may instantiate one or more classes, as long as - the interfaces are appropriate. + Any composable interfaces should be added as class attributes of this + class, as well as appended to core_interfaces or standard_interfaces here. """ - supported = False - """Indicates if a driver is supported. + bios = None + """`Standard` attribute for BIOS related features. - This will be set to False for drivers which are untested in first- or - third-party CI, or in the process of being deprecated. - - All classic drivers are now deprecated, and thus unsupported. + A reference to an instance of :class:BIOSInterface. """ - # NOTE(jlvillal): These should be tuples to help prevent child classes from - # accidentally modifying the base class values. - core_interfaces = ('deploy', 'power') - standard_interfaces = ('boot', 'console', 'inspect', 'management', 'raid') + boot = None + """`Standard` attribute for boot related features. - power = None - """`Core` attribute for managing power state. + A reference to an instance of :class:BootInterface. + """ - A reference to an instance of :class:PowerInterface. + console = None + """`Standard` attribute for managing console access. + + A reference to an instance of :class:ConsoleInterface. """ deploy = None @@ -75,66 +70,71 @@ class BaseDriver(object): A reference to an instance of :class:DeployInterface. """ - console = None - """`Standard` attribute for managing console access. + inspect = None + """`Standard` attribute for inspection related features. - A reference to an instance of :class:ConsoleInterface. - May be None, if unsupported by a driver. - """ - - rescue = None - """`Standard` attribute for accessing rescue features. - - A reference to an instance of :class:RescueInterface. - May be None, if unsupported by a driver. + A reference to an instance of :class:InspectInterface. """ management = None """`Standard` attribute for management related features. A reference to an instance of :class:ManagementInterface. - May be None, if unsupported by a driver. """ - boot = None - """`Standard` attribute for boot related features. + network = None + """`Core` attribute for network connectivity. - A reference to an instance of :class:BootInterface. - May be None, if unsupported by a driver. + A reference to an instance of :class:NetworkInterface. """ - vendor = None - """Attribute for accessing any vendor-specific extensions. + power = None + """`Core` attribute for managing power state. - A reference to an instance of :class:VendorInterface. - May be None, if the driver does not implement any vendor extensions. - """ - - inspect = None - """`Standard` attribute for inspection related features. - - A reference to an instance of :class:InspectInterface. - May be None, if unsupported by a driver. + A reference to an instance of :class:PowerInterface. """ raid = None """`Standard` attribute for RAID related features. A reference to an instance of :class:RaidInterface. - May be None, if unsupported by a driver. """ - def __init__(self): - pass + rescue = None + """`Standard` attribute for accessing rescue features. + + A reference to an instance of :class:RescueInterface. + """ + + storage = None + """`Standard` attribute for (remote) storage interface. + + A reference to an instance of :class:StorageInterface. + """ + + vendor = None + """Attribute for accessing any vendor-specific extensions. + + A reference to an instance of :class:VendorInterface. + """ + + @property + def core_interfaces(self): + """Interfaces that are required to be implemented.""" + return ['boot', 'deploy', 'management', 'network', 'power'] + + @property + def optional_interfaces(self): + """Interfaces that can be no-op.""" + return ['bios', 'console', 'inspect', 'raid', 'rescue', 'storage'] @property def all_interfaces(self): - return (list(self.core_interfaces + self.standard_interfaces) - + ['vendor']) + return self.non_vendor_interfaces + ['vendor'] @property def non_vendor_interfaces(self): - return list(self.core_interfaces + self.standard_interfaces) + return list(self.core_interfaces + self.optional_interfaces) def get_properties(self): """Get the properties of the driver. @@ -149,55 +149,9 @@ class BaseDriver(object): properties.update(iface.get_properties()) return properties - @classmethod - def to_hardware_type(cls): - """Return corresponding hardware type and hardware interfaces. - - :returns: a tuple with two items: - - * new driver field - the target hardware type - * dictionary containing interfaces to update, e.g. - {'deploy': 'iscsi', 'power': 'ipmitool'} - """ - raise NotImplementedError() - - -class BareDriver(BaseDriver): - """A bare driver object which will have interfaces attached later. - - Any composable interfaces should be added as class attributes of this - class, as well as appended to core_interfaces or standard_interfaces here. - """ - - network = None - """`Core` attribute for network connectivity. - - A reference to an instance of :class:NetworkInterface. - """ - 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 + ('bios', - 'rescue', 'storage',)) - ALL_INTERFACES = set(BareDriver().all_interfaces) -"""Constant holding all known interfaces. - -Includes interfaces not exposed via BaseDriver.all_interfaces. -""" +"""Constant holding all known interfaces.""" @six.add_metaclass(abc.ABCMeta) diff --git a/ironic/drivers/ipmi.py b/ironic/drivers/ipmi.py index 8f41fba3ff..96a091b418 100644 --- a/ironic/drivers/ipmi.py +++ b/ironic/drivers/ipmi.py @@ -11,7 +11,7 @@ # under the License. """ -Hardware types and classic drivers for IPMI (using ipmitool). +Hardware type for IPMI (using ipmitool). """ from ironic.drivers import generic diff --git a/ironic/tests/base.py b/ironic/tests/base.py index aa408c3fa1..ef55148df9 100644 --- a/ironic/tests/base.py +++ b/ironic/tests/base.py @@ -97,7 +97,6 @@ class TestCase(oslo_test_base.BaseTestCase): self.useFixture(fixtures.EnvironmentVariable('http_proxy')) self.policy = self.useFixture(policy_fixture.PolicyFixture()) - driver_factory.DriverFactory._extension_manager = None driver_factory.HardwareTypesFactory._extension_manager = None for factory in driver_factory._INTERFACE_LOADERS.values(): factory._extension_manager = None @@ -129,7 +128,6 @@ class TestCase(oslo_test_base.BaseTestCase): group='neutron') self.config(rescuing_network=uuidutils.generate_uuid(), group='neutron') - self.config(enabled_drivers=[]) self.config(enabled_hardware_types=['fake-hardware', 'manual-management']) for iface in drivers_base.ALL_INTERFACES: diff --git a/ironic/tests/unit/api/controllers/v1/test_driver.py b/ironic/tests/unit/api/controllers/v1/test_driver.py index ad6dcbf67a..601fde7d8f 100644 --- a/ironic/tests/unit/api/controllers/v1/test_driver.py +++ b/ironic/tests/unit/api/controllers/v1/test_driver.py @@ -30,32 +30,30 @@ from ironic.tests.unit.api import base class TestListDrivers(base.BaseApiTest): - d1 = 'fake-driver1' - d2 = 'fake-driver2' - d3 = 'fake-hardware-type' + hw1 = 'fake-hardware-type' + hw2 = 'fake-hardware-type-2' h1 = 'fake-host1' h2 = 'fake-host2' def register_fake_conductors(self): c1 = self.dbapi.register_conductor({ - 'hostname': self.h1, - 'drivers': [self.d1, self.d2], + 'hostname': self.h1, 'drivers': [], }) c2 = self.dbapi.register_conductor({ - 'hostname': self.h2, - 'drivers': [self.d2], + 'hostname': self.h2, 'drivers': [], }) for c in (c1, c2): self.dbapi.register_conductor_hardware_interfaces( - c.id, self.d3, 'deploy', ['iscsi', 'direct'], 'direct') + c.id, self.hw1, 'deploy', ['iscsi', 'direct'], 'direct') + self.dbapi.register_conductor_hardware_interfaces( + c1.id, self.hw2, 'deploy', ['iscsi', 'direct'], 'direct') def _test_drivers(self, use_dynamic, detail=False, latest_if=False): self.register_fake_conductors() headers = {} expected = [ - {'name': self.d1, 'hosts': [self.h1], 'type': 'classic'}, - {'name': self.d2, 'hosts': [self.h1, self.h2], 'type': 'classic'}, - {'name': self.d3, 'hosts': [self.h1, self.h2], 'type': 'dynamic'}, + {'name': self.hw1, 'hosts': [self.h1, self.h2], 'type': 'dynamic'}, + {'name': self.hw2, 'hosts': [self.h1], 'type': 'dynamic'}, ] expected = sorted(expected, key=lambda d: d['name']) if use_dynamic: @@ -114,13 +112,13 @@ class TestListDrivers(base.BaseApiTest): autospec=True) as mock_hw: mock_hw.return_value = [ { - 'hardware_type': self.d3, + 'hardware_type': self.hw1, 'interface_type': 'deploy', 'interface_name': 'iscsi', 'default': False, }, { - 'hardware_type': self.d3, + 'hardware_type': self.hw1, 'interface_type': 'deploy', 'interface_name': 'direct', 'default': True, @@ -135,20 +133,20 @@ class TestListDrivers(base.BaseApiTest): def test_drivers_with_dynamic_detailed_storage_interface(self): self._test_drivers_with_dynamic_detailed(latest_if=True) - def _test_drivers_type_filter(self, requested_type): + def test_drivers_type_filter_classic(self): self.register_fake_conductors() headers = {api_base.Version.string: '1.30'} - data = self.get_json('/drivers?type=%s' % requested_type, - headers=headers) - for d in data['drivers']: - # just check it's the right type, other tests handle the rest - self.assertEqual(requested_type, d['type']) - - def test_drivers_type_filter_classic(self): - self._test_drivers_type_filter('classic') + data = self.get_json('/drivers?type=classic', headers=headers) + self.assertEqual([], data['drivers']) def test_drivers_type_filter_dynamic(self): - self._test_drivers_type_filter('dynamic') + self.register_fake_conductors() + headers = {api_base.Version.string: '1.30'} + data = self.get_json('/drivers?type=dynamic', headers=headers) + self.assertNotEqual([], data['drivers']) + for d in data['drivers']: + # just check it's the right type, other tests handle the rest + self.assertEqual('dynamic', d['type']) def test_drivers_type_filter_bad_version(self): headers = {api_base.Version.string: '1.29'} @@ -184,19 +182,14 @@ class TestListDrivers(base.BaseApiTest): self.assertEqual([], data['drivers']) @mock.patch.object(rpcapi.ConductorAPI, 'get_driver_properties') - def _test_drivers_get_one_ok(self, use_dynamic, mock_driver_properties, + def _test_drivers_get_one_ok(self, mock_driver_properties, latest_if=False): # get_driver_properties mock is required by validate_link() self.register_fake_conductors() - if use_dynamic: - driver = self.d3 - driver_type = 'dynamic' - hosts = [self.h1, self.h2] - else: - driver = self.d1 - driver_type = 'classic' - hosts = [self.h1] + driver = self.hw1 + driver_type = 'dynamic' + hosts = [self.h1, self.h2] headers = {} if latest_if: @@ -213,47 +206,40 @@ class TestListDrivers(base.BaseApiTest): self.assertIn('properties', data) self.assertEqual(driver_type, data['type']) - if use_dynamic: - for iface in driver_base.ALL_INTERFACES: - 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) + for iface in driver_base.ALL_INTERFACES: + 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: - self.assertIsNone(data['default_deploy_interface']) - self.assertIsNone(data['enabled_deploy_interfaces']) + self.assertIsNotNone(data['default_deploy_interface']) + self.assertIsNotNone(data['enabled_deploy_interfaces']) self.validate_link(data['links'][0]['href']) self.validate_link(data['links'][1]['href']) self.validate_link(data['properties'][0]['href']) self.validate_link(data['properties'][1]['href']) - def test_drivers_get_one_ok_classic(self): - self._test_drivers_get_one_ok(False) - def _test_drivers_get_one_ok_dynamic(self, latest_if=False): with mock.patch.object(self.dbapi, 'list_hardware_type_interfaces', autospec=True) as mock_hw: mock_hw.return_value = [ { - 'hardware_type': self.d3, + 'hardware_type': self.hw1, 'interface_type': 'deploy', 'interface_name': 'iscsi', 'default': False, }, { - 'hardware_type': self.d3, + 'hardware_type': self.hw1, 'interface_type': 'deploy', 'interface_name': 'direct', 'default': True, }, ] - self._test_drivers_get_one_ok(True, latest_if=latest_if) - mock_hw.assert_called_once_with([self.d3]) + self._test_drivers_get_one_ok(latest_if=latest_if) + mock_hw.assert_called_once_with([self.hw1]) def test_drivers_get_one_ok_dynamic_base_interfaces(self): self._test_drivers_get_one_ok_dynamic() @@ -263,35 +249,35 @@ class TestListDrivers(base.BaseApiTest): def test_driver_properties_hidden_in_lower_version(self): self.register_fake_conductors() - data = self.get_json('/drivers/%s' % self.d1, + data = self.get_json('/drivers/%s' % self.hw1, headers={api_base.Version.string: '1.8'}) self.assertNotIn('properties', data) def test_driver_type_hidden_in_lower_version(self): self.register_fake_conductors() - data = self.get_json('/drivers/%s' % self.d1, + data = self.get_json('/drivers/%s' % self.hw1, headers={api_base.Version.string: '1.14'}) self.assertNotIn('type', data) def test_drivers_get_one_not_found(self): - response = self.get_json('/drivers/%s' % self.d1, expect_errors=True) + response = self.get_json('/drivers/nope', expect_errors=True) self.assertEqual(http_client.NOT_FOUND, response.status_int) def _test_links(self, public_url=None): cfg.CONF.set_override('public_endpoint', public_url, 'api') self.register_fake_conductors() - data = self.get_json('/drivers/%s' % self.d1) + data = self.get_json('/drivers/%s' % self.hw1) self.assertIn('links', data) self.assertEqual(2, len(data['links'])) - self.assertIn(self.d1, data['links'][0]['href']) + self.assertIn(self.hw1, data['links'][0]['href']) for l in data['links']: bookmark = l['rel'] == 'bookmark' self.assertTrue(self.validate_link(l['href'], bookmark=bookmark)) if public_url is not None: - expected = [{'href': '%s/v1/drivers/%s' % (public_url, self.d1), + expected = [{'href': '%s/v1/drivers/%s' % (public_url, self.hw1), 'rel': 'self'}, - {'href': '%s/drivers/%s' % (public_url, self.d1), + {'href': '%s/drivers/%s' % (public_url, self.hw1), 'rel': 'bookmark'}] for i in expected: self.assertIn(i, data['links']) @@ -310,7 +296,7 @@ class TestListDrivers(base.BaseApiTest): 'async': False, 'attach': False} response = self.post_json( - '/drivers/%s/vendor_passthru/do_test' % self.d1, + '/drivers/%s/vendor_passthru/do_test' % self.hw1, {'test_key': 'test_value'}) self.assertEqual(http_client.OK, response.status_int) self.assertEqual(mocked_driver_vendor_passthru.return_value['return'], @@ -323,7 +309,7 @@ class TestListDrivers(base.BaseApiTest): 'async': True, 'attach': False} response = self.post_json( - '/drivers/%s/vendor_passthru/do_test' % self.d1, + '/drivers/%s/vendor_passthru/do_test' % self.hw1, {'test_key': 'test_value'}) self.assertEqual(http_client.ACCEPTED, response.status_int) self.assertIsNone(mocked_driver_vendor_passthru.return_value['return']) @@ -334,7 +320,7 @@ class TestListDrivers(base.BaseApiTest): return_value = {'return': None, 'async': True, 'attach': False} mocked_driver_vendor_passthru.return_value = return_value response = self.put_json( - '/drivers/%s/vendor_passthru/do_test' % self.d1, + '/drivers/%s/vendor_passthru/do_test' % self.hw1, {'test_key': 'test_value'}) self.assertEqual(http_client.ACCEPTED, response.status_int) self.assertEqual(return_value['return'], response.json) @@ -345,7 +331,7 @@ class TestListDrivers(base.BaseApiTest): return_value = {'return': 'foo', 'async': False, 'attach': False} mocked_driver_vendor_passthru.return_value = return_value response = self.get_json( - '/drivers/%s/vendor_passthru/do_test' % self.d1) + '/drivers/%s/vendor_passthru/do_test' % self.hw1) self.assertEqual(return_value['return'], response) @mock.patch.object(rpcapi.ConductorAPI, 'driver_vendor_passthru') @@ -354,7 +340,7 @@ class TestListDrivers(base.BaseApiTest): return_value = {'return': None, 'async': True, 'attach': False} mock_driver_vendor_passthru.return_value = return_value response = self.delete( - '/drivers/%s/vendor_passthru/do_test' % self.d1) + '/drivers/%s/vendor_passthru/do_test' % self.hw1) self.assertEqual(http_client.ACCEPTED, response.status_int) self.assertEqual(return_value['return'], response.json) @@ -362,7 +348,7 @@ class TestListDrivers(base.BaseApiTest): # tests when given driver is not found # e.g. get_topic_for_driver fails to find the driver response = self.post_json( - '/drivers/%s/vendor_passthru/do_test' % self.d1, + '/drivers/%s/vendor_passthru/do_test' % self.hw1, {'test_key': 'test_value'}, expect_errors=True) @@ -370,7 +356,7 @@ class TestListDrivers(base.BaseApiTest): def test_driver_vendor_passthru_method_not_found(self): response = self.post_json( - '/drivers/%s/vendor_passthru' % self.d1, + '/drivers/%s/vendor_passthru' % self.hw1, {'test_key': 'test_value'}, expect_errors=True) @@ -385,11 +371,11 @@ class TestListDrivers(base.BaseApiTest): self.register_fake_conductors() return_value = {'foo': 'bar'} get_methods_mock.return_value = return_value - path = '/drivers/%s/vendor_passthru/methods' % self.d1 + path = '/drivers/%s/vendor_passthru/methods' % self.hw1 data = self.get_json(path) self.assertEqual(return_value, data) - get_methods_mock.assert_called_once_with(mock.ANY, self.d1, + get_methods_mock.assert_called_once_with(mock.ANY, self.hw1, topic=mock.ANY) # Now let's test the cache: Reset the mock @@ -407,11 +393,11 @@ class TestListDrivers(base.BaseApiTest): self.register_fake_conductors() properties = {'foo': 'description of foo'} disk_prop_mock.return_value = properties - path = '/drivers/%s/raid/logical_disk_properties' % self.d1 + path = '/drivers/%s/raid/logical_disk_properties' % self.hw1 data = self.get_json(path, headers={api_base.Version.string: "1.12"}) self.assertEqual(properties, data) - disk_prop_mock.assert_called_once_with(mock.ANY, self.d1, + disk_prop_mock.assert_called_once_with(mock.ANY, self.hw1, topic=mock.ANY) @mock.patch.object(rpcapi.ConductorAPI, 'get_raid_logical_disk_properties') @@ -420,7 +406,7 @@ class TestListDrivers(base.BaseApiTest): self.register_fake_conductors() properties = {'foo': 'description of foo'} disk_prop_mock.return_value = properties - path = '/drivers/%s/raid/logical_disk_properties' % self.d1 + path = '/drivers/%s/raid/logical_disk_properties' % self.hw1 ret = self.get_json(path, headers={api_base.Version.string: "1.4"}, expect_errors=True) @@ -434,14 +420,14 @@ class TestListDrivers(base.BaseApiTest): self.register_fake_conductors() properties = {'foo': 'description of foo'} disk_prop_mock.return_value = properties - path = '/drivers/%s/raid/logical_disk_properties' % self.d1 + path = '/drivers/%s/raid/logical_disk_properties' % self.hw1 for i in range(3): data = self.get_json(path, headers={api_base.Version.string: "1.12"}) self.assertEqual(properties, data) - disk_prop_mock.assert_called_once_with(mock.ANY, self.d1, + disk_prop_mock.assert_called_once_with(mock.ANY, self.hw1, topic=mock.ANY) - self.assertEqual(properties, driver._RAID_PROPERTIES[self.d1]) + self.assertEqual(properties, driver._RAID_PROPERTIES[self.hw1]) @mock.patch.object(rpcapi.ConductorAPI, 'get_raid_logical_disk_properties') def test_raid_logical_disk_properties_iface_not_supported( @@ -450,13 +436,13 @@ class TestListDrivers(base.BaseApiTest): self.register_fake_conductors() disk_prop_mock.side_effect = exception.UnsupportedDriverExtension( extension='raid', driver='fake-hardware') - path = '/drivers/%s/raid/logical_disk_properties' % self.d1 + path = '/drivers/%s/raid/logical_disk_properties' % self.hw1 ret = self.get_json(path, headers={api_base.Version.string: "1.12"}, expect_errors=True) self.assertEqual(http_client.NOT_FOUND, ret.status_code) self.assertTrue(ret.json['error_message']) - disk_prop_mock.assert_called_once_with(mock.ANY, self.d1, + disk_prop_mock.assert_called_once_with(mock.ANY, self.hw1, topic=mock.ANY) diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py index 8e014fd62e..d0bf8e0c49 100644 --- a/ironic/tests/unit/api/controllers/v1/test_node.py +++ b/ironic/tests/unit/api/controllers/v1/test_node.py @@ -2325,22 +2325,6 @@ class TestPatch(test_api_base.BaseApiTest): self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code) - def test_update_classic_driver_interface_fields(self): - headers = {api_base.Version.string: '1.31'} - self.mock_update_node.side_effect = ( - exception.MustBeNone('error')) - for field in api_utils.V31_FIELDS: - node = obj_utils.create_test_node(self.context, - uuid=uuidutils.generate_uuid()) - response = self.patch_json('/nodes/%s' % node.uuid, - [{'path': '/%s' % field, - 'value': 'fake', - 'op': 'add'}], - headers=headers, - expect_errors=True) - self.assertEqual(http_client.BAD_REQUEST, response.status_int) - self.assertEqual('application/json', response.content_type) - def test_update_storage_interface(self): node = obj_utils.create_test_node(self.context, uuid=uuidutils.generate_uuid()) diff --git a/ironic/tests/unit/common/test_driver_factory.py b/ironic/tests/unit/common/test_driver_factory.py index e1c281d9bb..628cea15a7 100644 --- a/ironic/tests/unit/common/test_driver_factory.py +++ b/ironic/tests/unit/common/test_driver_factory.py @@ -14,7 +14,6 @@ import mock from oslo_utils import uuidutils -import stevedore from stevedore import named from ironic.common import driver_factory @@ -386,8 +385,8 @@ class TestFakeHardware(hardware_type.AbstractHardwareType): return [fake.FakeVendorB, fake.FakeVendorA] -OPTIONAL_INTERFACES = (set(drivers_base.BareDriver().standard_interfaces) - - {'management', 'boot'}) | {'vendor'} +OPTIONAL_INTERFACES = (drivers_base.BareDriver().optional_interfaces + + ['vendor']) class HardwareTypeLoadTestCase(db_base.DbTestCase): @@ -421,11 +420,6 @@ class HardwareTypeLoadTestCase(db_base.DbTestCase): self.assertRaises(exception.DriverNotFound, driver_factory.get_hardware_type, 'fake_agent') - def test_get_driver_or_hardware_type_missing(self): - self.assertRaises(exception.DriverNotFound, - driver_factory.get_driver_or_hardware_type, - 'banana') - def test_build_driver_for_task(self): node = obj_utils.create_test_node(self.context, driver='fake-hardware', **self.node_kwargs) @@ -577,80 +571,3 @@ class HardwareTypeLoadTestCase(db_base.DbTestCase): def test_enabled_supported_interfaces_non_default(self): self._test_enabled_supported_interfaces(True) - - -class ClassicDriverMigrationTestCase(base.TestCase): - - def setUp(self): - super(ClassicDriverMigrationTestCase, self).setUp() - self.driver_cls = mock.Mock(spec=['to_hardware_type']) - self.driver_cls2 = mock.Mock(spec=['to_hardware_type']) - self.new_ifaces = { - 'console': 'new-console', - 'inspect': 'new-inspect' - } - - self.driver_cls.to_hardware_type.return_value = ('hw-type', - self.new_ifaces) - self.ext = mock.Mock(plugin=self.driver_cls) - self.ext.name = 'drv1' - self.ext2 = mock.Mock(plugin=self.driver_cls2) - self.ext2.name = 'drv2' - self.config(enabled_hardware_types=['hw-type'], - enabled_console_interfaces=['no-console', 'new-console'], - enabled_inspect_interfaces=['no-inspect', 'new-inspect'], - enabled_raid_interfaces=['no-raid'], - enabled_rescue_interfaces=['no-rescue'], - enabled_vendor_interfaces=['no-vendor']) - - def test_calculate_migration_delta(self): - 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', - 'rescue_interface': 'no-rescue', - 'vendor_interface': 'no-vendor'}, - delta) - - def test_calculate_migration_delta_not_implemeted(self): - self.driver_cls.to_hardware_type.side_effect = NotImplementedError() - delta = driver_factory.calculate_migration_delta( - 'drv', self.driver_cls, False) - self.assertIsNone(delta) - - def test_calculate_migration_delta_unsupported_hw_type(self): - self.driver_cls.to_hardware_type.return_value = ('hw-type2', - self.new_ifaces) - delta = driver_factory.calculate_migration_delta( - 'drv', self.driver_cls, False) - self.assertIsNone(delta) - - def test__calculate_migration_delta_unsupported_interface(self): - self.new_ifaces['inspect'] = 'unsupported inspect' - delta = driver_factory.calculate_migration_delta( - 'drv', self.driver_cls, False) - self.assertIsNone(delta) - - def test_calculate_migration_delta_unsupported_interface_reset(self): - self.new_ifaces['inspect'] = 'unsupported inspect' - 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', - 'rescue_interface': 'no-rescue', - 'vendor_interface': 'no-vendor'}, - delta) - - @mock.patch.object(stevedore, 'ExtensionManager', autospec=True) - def test_classic_drivers_to_migrate(self, mock_ext_mgr): - mock_ext_mgr.return_value.__iter__.return_value = iter([self.ext, - self.ext2]) - self.assertEqual({'drv1': self.driver_cls, - 'drv2': self.driver_cls2}, - driver_factory.classic_drivers_to_migrate()) diff --git a/ironic/tests/unit/common/test_hash_ring.py b/ironic/tests/unit/common/test_hash_ring.py index 94cfbb56a6..85750b79d6 100644 --- a/ironic/tests/unit/common/test_hash_ring.py +++ b/ironic/tests/unit/common/test_hash_ring.py @@ -43,11 +43,6 @@ class HashRingManagerTestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, 'hardware-type', 'deploy', ['iscsi', 'direct'], 'iscsi') - def test_hash_ring_manager_get_ring_success(self): - self.register_conductors() - ring = self.ring_manager['driver1'] - self.assertEqual(sorted(['host1', 'host2']), sorted(ring.nodes)) - def test_hash_ring_manager_hardware_type_success(self): self.register_conductors() ring = self.ring_manager['hardware-type'] @@ -65,11 +60,11 @@ class HashRingManagerTestCase(db_base.DbTestCase): # undesirable, but today is the intended behavior. self.assertRaises(exception.DriverNotFound, self.ring_manager.__getitem__, - 'driver1') + 'hardware-type') self.register_conductors() self.assertRaises(exception.DriverNotFound, self.ring_manager.__getitem__, - 'driver1') + 'hardware-type') def test_hash_ring_manager_refresh(self): CONF.set_override('hash_ring_reset_interval', 30) @@ -77,7 +72,7 @@ class HashRingManagerTestCase(db_base.DbTestCase): # hash ring will refresh only when time interval exceeded. self.assertRaises(exception.DriverNotFound, self.ring_manager.__getitem__, - 'driver1') + 'hardware-type') self.register_conductors() self.ring_manager.updated_at = time.time() - 31 - self.ring_manager.__getitem__('driver1') + self.ring_manager.__getitem__('hardware-type') diff --git a/ironic/tests/unit/conductor/test_base_manager.py b/ironic/tests/unit/conductor/test_base_manager.py index 6c75007f7d..0efd3ed486 100644 --- a/ironic/tests/unit/conductor/test_base_manager.py +++ b/ironic/tests/unit/conductor/test_base_manager.py @@ -82,19 +82,19 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): self._start_service() self.service.del_host() - @mock.patch.object(driver_factory.DriverFactory, '__getitem__', + @mock.patch.object(driver_factory.HardwareTypesFactory, '__getitem__', lambda *args: mock.MagicMock()) - @mock.patch.object(driver_factory, 'StorageInterfaceFactory') - @mock.patch.object(driver_factory, 'NetworkInterfaceFactory') - def test_start_registers_driver_names(self, net_factory, - storage_factory): + @mock.patch.object(driver_factory, 'default_interface', autospec=True) + def test_start_registers_driver_names(self, mock_def_iface): init_names = ['fake1', 'fake2'] restart_names = ['fake3', 'fake4'] - df = driver_factory.DriverFactory() + mock_def_iface.return_value = 'fake' + + df = driver_factory.HardwareTypesFactory() with mock.patch.object(df._extension_manager, 'names') as mock_names: # verify driver names are registered - self.config(enabled_drivers=init_names) + self.config(enabled_hardware_types=init_names) mock_names.return_value = init_names self._start_service() res = objects.Conductor.get_by_hostname(self.context, @@ -103,20 +103,36 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): self._stop_service() # verify that restart registers new driver names - self.config(enabled_drivers=restart_names) + self.config(enabled_hardware_types=restart_names) mock_names.return_value = restart_names self._start_service() res = objects.Conductor.get_by_hostname(self.context, self.hostname) self.assertEqual(restart_names, res['drivers']) - self.assertEqual(2, net_factory.call_count) - self.assertEqual(2, storage_factory.call_count) + @mock.patch.object(base_manager.BaseConductorManager, + '_register_and_validate_hardware_interfaces', + autospec=True) @mock.patch.object(driver_factory, 'all_interfaces', autospec=True) @mock.patch.object(driver_factory, 'hardware_types', autospec=True) - @mock.patch.object(driver_factory, 'drivers', autospec=True) - def test_start_registers_driver_specific_tasks(self, mock_drivers, - mock_hw_types, mock_ifaces): + def test_start_registers_driver_specific_tasks(self, + mock_hw_types, mock_ifaces, + mock_reg_hw_ifaces): + class TestHwType(generic.GenericHardware): + @property + def supported_management_interfaces(self): + return [] + + @property + def supported_power_interfaces(self): + return [] + + # This should not be collected, since we don't collect periodic + # tasks from hardware types + @periodics.periodic(spacing=100500) + def task(self): + pass + class TestInterface(object): @periodics.periodic(spacing=100500) def iface(self): @@ -127,23 +143,12 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): def iface(self): pass - class Driver(object): - core_interfaces = [] - standard_interfaces = ['iface'] - all_interfaces = core_interfaces + standard_interfaces - - iface = TestInterface() - - @periodics.periodic(spacing=42) - def task(self, context): - pass - - driver = Driver() + hw_type = TestHwType() iface1 = TestInterface() iface2 = TestInterface2() expected = [iface1.iface, iface2.iface] - mock_drivers.return_value = {'fake1': driver} + mock_hw_types.return_value = {'fake1': hw_type} mock_ifaces.return_value = { 'management': {'fake1': iface1}, 'power': {'fake2': iface2} @@ -156,11 +161,11 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): self.assertTrue(periodics.is_periodic(item)) self.assertIn(item, tasks) - # no periodic tasks from the Driver object - self.assertTrue(periodics.is_periodic(driver.task)) - self.assertNotIn(driver.task, tasks) + # no periodic tasks from the hardware type + self.assertTrue(periodics.is_periodic(hw_type.task)) + self.assertNotIn(hw_type.task, tasks) - @mock.patch.object(driver_factory.DriverFactory, '__init__') + @mock.patch.object(driver_factory.HardwareTypesFactory, '__init__') def test_start_fails_on_missing_driver(self, mock_df): mock_df.side_effect = exception.DriverNotFound('test') with mock.patch.object(self.dbapi, 'register_conductor') as mock_reg: @@ -177,32 +182,14 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): @mock.patch.object(base_manager, 'LOG') @mock.patch.object(driver_factory, 'HardwareTypesFactory') - @mock.patch.object(driver_factory, 'DriverFactory') - def test_start_fails_on_no_driver_or_hw_types(self, df_mock, ht_mock, - log_mock): + def test_start_fails_on_hw_types(self, ht_mock, log_mock): driver_factory_mock = mock.MagicMock(names=[]) - df_mock.return_value = driver_factory_mock ht_mock.return_value = driver_factory_mock self.assertRaises(exception.NoDriversLoaded, self.service.init_host) self.assertTrue(log_mock.error.called) - df_mock.assert_called_once_with() ht_mock.assert_called_once_with() - @mock.patch.object(base_manager, 'LOG') - @mock.patch.object(base_manager.BaseConductorManager, 'del_host', - autospec=True) - @mock.patch.object(driver_factory, 'DriverFactory') - def test_starts_with_only_dynamic_drivers(self, df_mock, del_mock, - log_mock): - # don't load any classic drivers - driver_factory_mock = mock.MagicMock(names=[]) - df_mock.return_value = driver_factory_mock - self.service.init_host() - self.assertFalse(log_mock.error.called) - df_mock.assert_called_with() - self.assertFalse(del_mock.called) - @mock.patch.object(base_manager, 'LOG') @mock.patch.object(base_manager.BaseConductorManager, '_register_and_validate_hardware_interfaces') @@ -214,19 +201,6 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): self.assertTrue(log_mock.error.called) del_mock.assert_called_once_with() - @mock.patch.object(base_manager, 'LOG') - @mock.patch.object(driver_factory, 'HardwareTypesFactory') - @mock.patch.object(driver_factory, 'DriverFactory') - def test_start_fails_on_name_conflict(self, df_mock, ht_mock, log_mock): - driver_factory_mock = mock.MagicMock(names=['dupe-driver']) - df_mock.return_value = driver_factory_mock - ht_mock.return_value = driver_factory_mock - self.assertRaises(exception.DriverNameConflict, - self.service.init_host) - self.assertTrue(log_mock.error.called) - df_mock.assert_called_once_with() - ht_mock.assert_called_once_with() - def test_prevent_double_start(self): self._start_service() self.assertRaisesRegex(RuntimeError, 'already running', @@ -283,11 +257,6 @@ class CheckInterfacesTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): 'options enabled_boot_interfaces', base_manager._check_enabled_interfaces) - def test__check_enabled_interfaces_skip_if_no_hw_types(self): - self.config(enabled_hardware_types=[]) - self.config(enabled_boot_interfaces=[]) - base_manager._check_enabled_interfaces() - class KeepAliveTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): def test__conductor_service_record_keepalive(self): diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 2e58968438..542df0fe20 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -4824,7 +4824,7 @@ class ManagerDoSyncPowerStateTestCase(db_base.DbTestCase): def setUp(self): super(ManagerDoSyncPowerStateTestCase, self).setUp() self.service = manager.ConductorManager('hostname', 'test-topic') - self.driver = mock.Mock(spec_set=drivers_base.BaseDriver) + self.driver = mock.Mock(spec_set=drivers_base.BareDriver) self.power = self.driver.power self.node = obj_utils.create_test_node( self.context, driver='fake-hardware', maintenance=False, @@ -5341,7 +5341,7 @@ class ManagerPowerRecoveryTestCase(mgr_utils.CommonMixIn, super(ManagerPowerRecoveryTestCase, self).setUp() self.service = manager.ConductorManager('hostname', 'test-topic') self.service.dbapi = self.dbapi - self.driver = mock.Mock(spec_set=drivers_base.BaseDriver) + self.driver = mock.Mock(spec_set=drivers_base.BareDriver) self.power = self.driver.power self.task = mock.Mock(spec_set=['context', 'driver', 'node', 'upgrade_lock', 'shared']) diff --git a/ironic/tests/unit/conductor/test_rpcapi.py b/ironic/tests/unit/conductor/test_rpcapi.py index d6364aa244..28075e7c07 100644 --- a/ironic/tests/unit/conductor/test_rpcapi.py +++ b/ironic/tests/unit/conductor/test_rpcapi.py @@ -76,8 +76,10 @@ class RPCAPITestCase(db_base.DbTestCase): def test_get_topic_for_known_driver(self): CONF.set_override('host', 'fake-host') - self.dbapi.register_conductor({'hostname': 'fake-host', - 'drivers': ['fake-driver']}) + c = self.dbapi.register_conductor({'hostname': 'fake-host', + 'drivers': []}) + self.dbapi.register_conductor_hardware_interfaces( + c.id, 'fake-driver', 'deploy', ['iscsi', 'direct'], 'iscsi') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') expected_topic = 'fake-topic.fake-host' @@ -86,8 +88,10 @@ class RPCAPITestCase(db_base.DbTestCase): def test_get_topic_for_unknown_driver(self): CONF.set_override('host', 'fake-host') - self.dbapi.register_conductor({'hostname': 'fake-host', - 'drivers': ['other-driver']}) + c = self.dbapi.register_conductor({'hostname': 'fake-host', + 'drivers': []}) + self.dbapi.register_conductor_hardware_interfaces( + c.id, 'other-driver', 'deploy', ['iscsi', 'direct'], 'iscsi') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') self.assertRaises(exception.NoValidHost, @@ -102,8 +106,10 @@ class RPCAPITestCase(db_base.DbTestCase): rpcapi.get_topic_for, self.fake_node_obj) - self.dbapi.register_conductor({'hostname': 'fake-host', - 'drivers': ['fake-driver']}) + c = self.dbapi.register_conductor({'hostname': 'fake-host', + 'drivers': []}) + self.dbapi.register_conductor_hardware_interfaces( + c.id, 'fake-driver', 'deploy', ['iscsi', 'direct'], 'iscsi') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') expected_topic = 'fake-topic.fake-host' @@ -112,10 +118,12 @@ class RPCAPITestCase(db_base.DbTestCase): def test_get_topic_for_driver_known_driver(self): CONF.set_override('host', 'fake-host') - self.dbapi.register_conductor({ + c = self.dbapi.register_conductor({ 'hostname': 'fake-host', - 'drivers': ['fake-driver'], + 'drivers': [], }) + self.dbapi.register_conductor_hardware_interfaces( + c.id, 'fake-driver', 'deploy', ['iscsi', 'direct'], 'iscsi') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') self.assertEqual('fake-topic.fake-host', rpcapi.get_topic_for_driver('fake-driver')) @@ -124,7 +132,7 @@ class RPCAPITestCase(db_base.DbTestCase): CONF.set_override('host', 'fake-host') self.dbapi.register_conductor({ 'hostname': 'fake-host', - 'drivers': ['other-driver'], + 'drivers': [], }) rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') self.assertRaises(exception.DriverNotFound, @@ -138,10 +146,12 @@ class RPCAPITestCase(db_base.DbTestCase): rpcapi.get_topic_for_driver, 'fake-driver') - self.dbapi.register_conductor({ + c = self.dbapi.register_conductor({ 'hostname': 'fake-host', - 'drivers': ['fake-driver'], + 'drivers': [], }) + self.dbapi.register_conductor_hardware_interfaces( + c.id, 'fake-driver', 'deploy', ['iscsi', 'direct'], 'iscsi') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') self.assertEqual('fake-topic.fake-host', rpcapi.get_topic_for_driver('fake-driver')) diff --git a/ironic/tests/unit/db/test_api.py b/ironic/tests/unit/db/test_api.py index 104a7c1b3c..7265cc480a 100644 --- a/ironic/tests/unit/db/test_api.py +++ b/ironic/tests/unit/db/test_api.py @@ -10,12 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import mock from oslo_utils import uuidutils from testtools import matchers -from ironic.common import context -from ironic.common import driver_factory from ironic.common import exception from ironic.common import release_mappings from ironic.db import api as db_api @@ -69,38 +66,6 @@ class UpgradingTestCase(base.DbTestCase): self.assertFalse(self.dbapi.check_versions()) -@mock.patch.object(driver_factory, 'calculate_migration_delta', autospec=True) -@mock.patch.object(driver_factory, 'classic_drivers_to_migrate', autospec=True) -class MigrateToHardwareTypesTestCase(base.DbTestCase): - - def setUp(self): - super(MigrateToHardwareTypesTestCase, self).setUp() - self.context = context.get_admin_context() - self.dbapi = db_api.get_instance() - self.node = utils.create_test_node(uuid=uuidutils.generate_uuid(), - driver='classic_driver') - - def test_migrate(self, mock_drivers, mock_delta): - mock_drivers.return_value = {'classic_driver': mock.sentinel.drv1, - 'another_driver': mock.sentinel.drv2} - mock_delta.return_value = {'driver': 'new_driver', - 'inspect_interface': 'new_inspect'} - result = self.dbapi.migrate_to_hardware_types(self.context, 0) - self.assertEqual((1, 1), result) - node = self.dbapi.get_node_by_id(self.node.id) - self.assertEqual('new_driver', node.driver) - self.assertEqual('new_inspect', node.inspect_interface) - - def test_migrate_unsupported(self, mock_drivers, mock_delta): - mock_drivers.return_value = {'classic_driver': mock.sentinel.drv1, - 'another_driver': mock.sentinel.drv2} - mock_delta.return_value = None - result = self.dbapi.migrate_to_hardware_types(self.context, 0) - self.assertEqual((1, 1), result) - node = self.dbapi.get_node_by_id(self.node.id) - self.assertEqual('classic_driver', node.driver) - - class GetNotVersionsTestCase(base.DbTestCase): def setUp(self): diff --git a/ironic/tests/unit/db/test_conductor.py b/ironic/tests/unit/db/test_conductor.py index c983188a30..e044bbc11b 100644 --- a/ironic/tests/unit/db/test_conductor.py +++ b/ironic/tests/unit/db/test_conductor.py @@ -193,97 +193,6 @@ class DbConductorTestCase(base.DbTestCase): self.assertEqual('power on', node3.target_power_state) self.assertIsNone(node3.last_error) - @mock.patch.object(timeutils, 'utcnow', autospec=True) - def test_get_active_driver_dict_one_host_no_driver(self, mock_utcnow): - h = 'fake-host' - expected = {} - - mock_utcnow.return_value = datetime.datetime.utcnow() - self._create_test_cdr(hostname=h, drivers=[]) - result = self.dbapi.get_active_driver_dict() - self.assertEqual(expected, result) - - @mock.patch.object(timeutils, 'utcnow', autospec=True) - def test_get_active_driver_dict_one_host_one_driver(self, mock_utcnow): - h = 'fake-host' - d = 'fake-driver' - expected = {d: {h}} - - mock_utcnow.return_value = datetime.datetime.utcnow() - self._create_test_cdr(hostname=h, drivers=[d]) - result = self.dbapi.get_active_driver_dict() - self.assertEqual(expected, result) - - @mock.patch.object(timeutils, 'utcnow', autospec=True) - def test_get_active_driver_dict_one_host_many_drivers(self, mock_utcnow): - h = 'fake-host' - d1 = 'driver-one' - d2 = 'driver-two' - expected = {d1: {h}, d2: {h}} - - mock_utcnow.return_value = datetime.datetime.utcnow() - self._create_test_cdr(hostname=h, drivers=[d1, d2]) - result = self.dbapi.get_active_driver_dict() - self.assertEqual(expected, result) - - @mock.patch.object(timeutils, 'utcnow', autospec=True) - def test_get_active_driver_dict_many_hosts_one_driver(self, mock_utcnow): - h1 = 'host-one' - h2 = 'host-two' - d = 'fake-driver' - expected = {d: {h1, h2}} - - mock_utcnow.return_value = datetime.datetime.utcnow() - self._create_test_cdr(id=1, hostname=h1, drivers=[d]) - self._create_test_cdr(id=2, hostname=h2, drivers=[d]) - result = self.dbapi.get_active_driver_dict() - self.assertEqual(expected, result) - - @mock.patch.object(timeutils, 'utcnow', autospec=True) - def test_get_active_driver_dict_many_hosts_and_drivers(self, mock_utcnow): - h1 = 'host-one' - h2 = 'host-two' - h3 = 'host-three' - d1 = 'driver-one' - d2 = 'driver-two' - expected = {d1: {h1, h2}, d2: {h2, h3}} - - mock_utcnow.return_value = datetime.datetime.utcnow() - self._create_test_cdr(id=1, hostname=h1, drivers=[d1]) - self._create_test_cdr(id=2, hostname=h2, drivers=[d1, d2]) - self._create_test_cdr(id=3, hostname=h3, drivers=[d2]) - result = self.dbapi.get_active_driver_dict() - self.assertEqual(expected, result) - - @mock.patch.object(timeutils, 'utcnow', autospec=True) - def test_get_active_driver_dict_with_old_conductor(self, mock_utcnow): - past = datetime.datetime(2000, 1, 1, 0, 0) - present = past + datetime.timedelta(minutes=2) - - d = 'common-driver' - - h1 = 'old-host' - d1 = 'old-driver' - mock_utcnow.return_value = past - self._create_test_cdr(id=1, hostname=h1, drivers=[d, d1]) - - h2 = 'new-host' - d2 = 'new-driver' - mock_utcnow.return_value = present - self._create_test_cdr(id=2, hostname=h2, drivers=[d, d2]) - - # verify that old-host does not show up in current list - one_minute = 60 - expected = {d: {h2}, d2: {h2}} - result = self.dbapi.get_active_driver_dict(interval=one_minute) - self.assertEqual(expected, result) - - # change the interval, and verify that old-host appears - two_minute = one_minute * 2 - expected = {d: {h1, h2}, d1: {h1}, d2: {h2}} - result = self.dbapi.get_active_driver_dict(interval=two_minute) - self.assertEqual(expected, result) - @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_active_hardware_type_dict_one_host_no_ht(self, mock_utcnow): h = 'fake-host' diff --git a/ironic/tests/unit/drivers/test_base.py b/ironic/tests/unit/drivers/test_base.py index 015ba8e218..83f80ddb18 100644 --- a/ironic/tests/unit/drivers/test_base.py +++ b/ironic/tests/unit/drivers/test_base.py @@ -16,9 +16,7 @@ import json import mock -import stevedore -from ironic.common import driver_factory from ironic.common import exception from ironic.common import raid from ironic.drivers import base as driver_base @@ -500,156 +498,12 @@ class TestManagementInterface(base.TestCase): management.get_boot_mode, task_mock) -class TestBaseDriver(base.TestCase): - - def test_class_variables_immutable(self): - # Test to make sure that our *_interfaces variables in the class don't - # 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 - ) - # 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 - ) - - class TestBareDriver(base.TestCase): - def test_class_variables_immutable(self): - # Test to make sure that our *_interfaces variables in the class don't - # get modified by a child class - self.assertEqual(('deploy', 'power', 'network'), - driver_base.BareDriver.core_interfaces) + def test_class_variables(self): + self.assertEqual(['boot', 'deploy', 'management', 'network', 'power'], + driver_base.BareDriver().core_interfaces) self.assertEqual( - ('boot', 'console', 'inspect', 'management', 'raid', 'bios', - 'rescue', 'storage'), - driver_base.BareDriver.standard_interfaces + ['bios', 'console', 'inspect', 'raid', 'rescue', 'storage'], + driver_base.BareDriver().optional_interfaces ) - - -class TestToHardwareType(base.TestCase): - def setUp(self): - super(TestToHardwareType, self).setUp() - self.driver_classes = list( - driver_factory.classic_drivers_to_migrate().values()) - self.existing_ifaces = {} - for iface in driver_base.ALL_INTERFACES: - self.existing_ifaces[iface] = stevedore.ExtensionManager( - 'ironic.hardware.interfaces.%s' % iface, - invoke_on_load=False).names() - self.hardware_types = stevedore.ExtensionManager( - 'ironic.hardware.types', invoke_on_load=False).names() - # These are the interfaces that don't have a no-op version - self.mandatory_interfaces = ['boot', 'deploy', 'management', 'power'] - - def test_to_hardware_type_returns_hardware_type(self): - for driver in self.driver_classes: - try: - hw_type = driver.to_hardware_type()[0] - except NotImplementedError: - continue - except KeyError: - self.fail('%s does not return a tuple' % driver) - - self.assertIn(hw_type, self.hardware_types, - '%s returns unknown hardware type %s' % - (driver, hw_type)) - - def test_to_hardware_type_returns_existing_interfaces(self): - self.config(enabled=False, group='inspector') - # Check that all defined implementations of to_hardware_type - # contain only existing interface types - for driver in self.driver_classes: - try: - delta = driver.to_hardware_type()[1] - except Exception: - continue # covered by other tests - for iface, value in delta.items(): - self.assertIn(iface, self.existing_ifaces, - '%s returns unknown interface %s' % - (driver, iface)) - self.assertIn(value, self.existing_ifaces[iface], - '%s returns unknown %s interface %s' % - (driver, iface, value)) - - def test_to_hardware_type_returns_existing_interfaces_inspector(self): - self.config(enabled=True, group='inspector') - # Check that all defined implementations of to_hardware_type - # contain only existing interface types - for driver in self.driver_classes: - try: - delta = driver.to_hardware_type()[1] - except Exception: - continue # covered by other tests - for iface, value in delta.items(): - self.assertIn(iface, self.existing_ifaces, - '%s returns unknown interface %s' % - (driver, iface)) - self.assertIn(value, self.existing_ifaces[iface], - '%s returns unknown %s interface %s' % - (driver, iface, value)) - - def test_to_hardware_type_mandatory_interfaces(self): - for driver in self.driver_classes: - try: - delta = driver.to_hardware_type()[1] - except Exception: - continue # covered by other tests - for iface in self.mandatory_interfaces: - self.assertIn(iface, delta, - '%s does not return mandatory interface %s' % - (driver, iface)) - - def test_to_hardware_type_for_all_in_tree_drivers(self): - missing = set() - for driver in self.driver_classes: - # We don't want to test out-of-tree drivers installed locally - if not driver.__module__.startswith('ironic.'): - continue - try: - driver.to_hardware_type() - except NotImplementedError: - missing.add(driver.__name__) - - if missing: - self.fail('to_hardware_type not implemented for %s' % - ', '.join(missing)) - - def test_to_hardware_type_boot_deploy(self): - for driver in self.driver_classes: - # We don't want to test out-of-tree drivers installed locally - if not driver.__module__.startswith('ironic.'): - continue - - try: - delta = driver.to_hardware_type()[1] - boot = delta['boot'] - deploy = delta['deploy'] - except NotImplementedError: - continue # covered by other tests - - name = driver.__name__.lower() - # Try to guess the correct values for boot and deploy based on our - # naming schema - if 'pxe' in name: - self.assertIn('pxe', boot, - 'boot interface should be based on pxe for %s' % - driver) - if 'agent' in name: - self.assertIn('direct', deploy, - 'deploy interface should be direct for %s' % - driver) - elif 'iscsi' in name or 'pxe' in name: - self.assertIn('iscsi', deploy, - 'deploy interface should be iscsi for %s' % - driver) diff --git a/releasenotes/notes/no-classic-drivers-e68d8527491314c3.yaml b/releasenotes/notes/no-classic-drivers-e68d8527491314c3.yaml new file mode 100644 index 0000000000..9e645d3469 --- /dev/null +++ b/releasenotes/notes/no-classic-drivers-e68d8527491314c3.yaml @@ -0,0 +1,12 @@ +upgrade: + - | + It is no longer possible to load a classic driver. Only hardware types + are supported from now on. + - | + The ``/v1/drivers/?type=classic`` API always returns an empty list since + classic drivers can no longer be loaded. +deprecations: + - | + The ``enabled_drivers`` option is now deprecated. Since classic drivers + can no longer be loaded, setting this option to anything non-empty will + result in the conductor failing to start.