Merge "Remove support for creating and loading classic drivers"

This commit is contained in:
Zuul 2018-07-03 19:01:59 +00:00 committed by Gerrit Code Review
commit eede3bf6ce
26 changed files with 327 additions and 1278 deletions

View File

@ -2512,6 +2512,8 @@ function ironic_configure_tempest {
iniset $TEMPEST_CONFIG baremetal whole_disk_image_url "http://$IRONIC_HTTP_SERVER:$IRONIC_HTTP_PORT/${IRONIC_WHOLEDISK_IMAGE_NAME}.img"
iniset $TEMPEST_CONFIG baremetal whole_disk_image_checksum $(md5sum $FILES/${IRONIC_WHOLEDISK_IMAGE_NAME}.img)
# 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

View File

@ -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)

View File

@ -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'),

View File

@ -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.<interface type>).
: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-<interface name>,
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}

View File

@ -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):

View File

@ -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(

View File

@ -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 '

View File

@ -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()

View File

@ -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)

View File

@ -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 '

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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())

View File

@ -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())

View File

@ -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')

View File

@ -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):

View File

@ -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'])

View File

@ -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'))

View File

@ -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):

View File

@ -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'

View File

@ -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)

View File

@ -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.