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_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) 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_drivers ""
iniset $TEMPEST_CONFIG baremetal enabled_hardware_types $IRONIC_ENABLED_HARDWARE_TYPES iniset $TEMPEST_CONFIG baremetal enabled_hardware_types $IRONIC_ENABLED_HARDWARE_TYPES
iniset $TEMPEST_CONFIG baremetal enabled_bios_interfaces $IRONIC_ENABLED_BIOS_INTERFACES 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] enabled_vendor_interfaces = [wtypes.text]
@staticmethod @staticmethod
def convert_with_links(name, hosts, driver_type, detail=False, def convert_with_links(name, hosts, detail=False, interface_info=None):
interface_info=None):
"""Convert driver/hardware type info to an API-serializable object. """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 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 :param detail: boolean, whether to include detailed info, such as
the 'type' field and default/enabled interfaces fields. the 'type' field and default/enabled interfaces fields.
:param interface_info: optional list of dicts of hardware interface :param interface_info: optional list of dicts of hardware interface
@ -169,8 +166,10 @@ class Driver(base.APIBase):
] ]
if api_utils.allow_dynamic_drivers(): if api_utils.allow_dynamic_drivers():
driver.type = driver_type # NOTE(dtantsur): only dynamic drivers (based on hardware types)
if driver_type == 'dynamic' and detail: # are supported starting with the Rocky release.
driver.type = 'dynamic'
if detail:
if interface_info is None: if interface_info is None:
# TODO(jroll) objectify this # TODO(jroll) objectify this
interface_info = (pecan.request.dbapi interface_info = (pecan.request.dbapi
@ -192,12 +191,6 @@ class Driver(base.APIBase):
setattr(driver, default_key, default) setattr(driver, default_key, default)
setattr(driver, enabled_key, list(enabled)) 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) hide_fields_in_newer_versions(driver)
return driver return driver
@ -223,10 +216,9 @@ class DriverList(base.APIBase):
"""A list containing drivers objects""" """A list containing drivers objects"""
@staticmethod @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. """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 :param hardware_types: dict mapping hardware type names to conductor
hostnames. hostnames.
:param detail: boolean, whether to include detailed info, such as :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. :returns: an API-serializable driver collection object.
""" """
collection = DriverList() collection = DriverList()
collection.drivers = [ collection.drivers = []
Driver.convert_with_links(dname, list(drivers[dname]), 'classic',
detail=detail)
for dname in drivers]
# NOTE(jroll) we return hardware types in all API versions, # NOTE(jroll) we return hardware types in all API versions,
# but restrict type/default/enabled fields to 1.30. # but restrict type/default/enabled fields to 1.30.
@ -255,7 +244,7 @@ class DriverList(base.APIBase):
collection.drivers.append( collection.drivers.append(
Driver.convert_with_links(htname, Driver.convert_with_links(htname,
list(hardware_types[htname]), list(hardware_types[htname]),
'dynamic', detail=detail, detail=detail,
interface_info=interface_info)) interface_info=interface_info))
return collection return collection
@ -392,14 +381,13 @@ class DriversController(rest.RestController):
'"type" filter must be one of "classic" or "dynamic", ' '"type" filter must be one of "classic" or "dynamic", '
'if specified.')) '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': if type is None or type == 'dynamic':
hw_type_dict = pecan.request.dbapi.get_active_hardware_type_dict() hw_type_dict = pecan.request.dbapi.get_active_hardware_type_dict()
return DriverList.convert_with_links(driver_list, hw_type_dict, else:
detail=detail) # 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') @METRICS.timer('DriversController.get_one')
@expose.expose(Driver, wtypes.text) @expose.expose(Driver, wtypes.text)
@ -412,20 +400,11 @@ class DriversController(rest.RestController):
cdict = pecan.request.context.to_policy_values() cdict = pecan.request.context.to_policy_values()
policy.authorize('baremetal:driver:get', cdict, cdict) 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() hw_type_dict = pecan.request.dbapi.get_active_hardware_type_dict()
driver = _find_driver(hw_type_dict, 'dynamic') for name, hosts in hw_type_dict.items():
if driver: if name == driver_name:
return driver return Driver.convert_with_links(name, list(hosts),
driver_dict = pecan.request.dbapi.get_active_driver_dict() detail=True)
driver = _find_driver(driver_dict, 'classic')
if driver:
return driver
raise exception.DriverNotFound(driver_name=driver_name) 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 # object, in case it is lazy loaded. The attribute will be accessed when needed
# by doing getattr on the object # by doing getattr on the object
ONLINE_MIGRATIONS = ( ONLINE_MIGRATIONS = (
# TODO(dtantsur): remove when classic drivers are removed (Rocky?)
(dbapi, 'migrate_to_hardware_types'),
# Added in Rocky # Added in Rocky
# TODO(rloo): remove in Stein # TODO(rloo): remove in Stein
(port, 'migrate_vif_port_id'), (port, 'migrate_vif_port_id'),

View File

@ -17,7 +17,6 @@ import collections
from oslo_concurrency import lockutils from oslo_concurrency import lockutils
from oslo_log import log from oslo_log import log
import stevedore
from stevedore import named from stevedore import named
from ironic.common import exception from ironic.common import exception
@ -25,7 +24,6 @@ from ironic.common.i18n import _
from ironic.conf import CONF from ironic.conf import CONF
from ironic.drivers import base as driver_base from ironic.drivers import base as driver_base
from ironic.drivers import fake_hardware from ironic.drivers import fake_hardware
from ironic.drivers import hardware_type
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -37,90 +35,58 @@ def build_driver_for_task(task):
"""Builds a composable driver for a given task. """Builds a composable driver for a given task.
Starts with a `BareDriver` object, and attaches implementations of the Starts with a `BareDriver` object, and attaches implementations of the
various driver interfaces to it. For classic drivers these all come from various driver interfaces to it. They come from separate
the monolithic driver singleton, for hardware types - from separate
driver factories and are configurable via the database. driver factories and are configurable via the database.
:param task: The task containing the node to build a driver for. :param task: The task containing the node to build a driver for.
:returns: A driver object for the task. :returns: A driver object for the task.
:raises: DriverNotFound if node.driver could not be found in either :raises: DriverNotFound if node.driver could not be found in the
"ironic.drivers" or "ironic.hardware.types" namespaces. "ironic.hardware.types" namespaces.
:raises: InterfaceNotFoundInEntrypoint if some node interfaces are set :raises: InterfaceNotFoundInEntrypoint if some node interfaces are set
to invalid or unsupported values. to invalid or unsupported values.
:raises: IncompatibleInterface if driver is a hardware type and :raises: IncompatibleInterface the requested implementation is not
the requested implementation is not compatible with it. compatible with it with the hardware type.
""" """
node = task.node node = task.node
driver_name = node.driver
driver_or_hw_type = get_driver_or_hardware_type(driver_name) hw_type = get_hardware_type(node.driver)
try: check_and_update_node_interfaces(node, hw_type=hw_type)
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)
bare_driver = driver_base.BareDriver() 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 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. """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 bare_driver: BareDriver instance to attach interfaces to
:param node: Node object :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: InterfaceNotFoundInEntrypoint if the entry point was not found.
:raises: IncompatibleInterface if driver is a hardware type and :raises: IncompatibleInterface if driver is a hardware type and
the requested implementation is not compatible with it. the requested implementation is not compatible with it.
""" """
if isinstance(driver_or_hw_type, hardware_type.AbstractHardwareType): for iface in _INTERFACE_LOADERS:
# 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:
impl_name = getattr(node, '%s_interface' % iface) 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) 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. """Get interface implementation instance.
For hardware types also validates compatibility. 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_type: name of the interface type (e.g. 'boot').
:param interface_name: name of the interface implementation from an :param interface_name: name of the interface implementation from an
appropriate entry point appropriate entry point
(ironic.hardware.interfaces.<interface type>). (ironic.hardware.interfaces.<interface type>).
:returns: instance of the requested interface implementation. :returns: instance of the requested interface implementation.
:raises: InterfaceNotFoundInEntrypoint if the entry point was not found. :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. the requested implementation is not compatible with it.
""" """
factory = _INTERFACE_LOADERS[interface_type]() factory = _INTERFACE_LOADERS[interface_type]()
@ -132,35 +98,31 @@ def get_interface(driver_or_hw_type, interface_type, interface_name):
entrypoint=factory._entrypoint_name, entrypoint=factory._entrypoint_name,
valid=factory.names) valid=factory.names)
if not isinstance(driver_or_hw_type, hardware_type.AbstractHardwareType): if isinstance(hw_type, fake_hardware.FakeHardware):
# NOTE(dtantsur): classic drivers do not have notion of compatibility
return impl_instance
if isinstance(driver_or_hw_type, fake_hardware.FakeHardware):
# NOTE(dtantsur): special-case fake hardware type to allow testing with # NOTE(dtantsur): special-case fake hardware type to allow testing with
# any combinations of interface implementations. # any combinations of interface implementations.
return impl_instance return impl_instance
supported_impls = getattr(driver_or_hw_type, supported_impls = getattr(hw_type,
'supported_%s_interfaces' % interface_type) 'supported_%s_interfaces' % interface_type)
if type(impl_instance) not in supported_impls: if type(impl_instance) not in supported_impls:
raise exception.IncompatibleInterface( raise exception.IncompatibleInterface(
interface_type=interface_type, interface_impl=impl_instance, 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 return impl_instance
def default_interface(driver_or_hw_type, interface_type, def default_interface(hw_type, interface_type,
driver_name=None, node=None): driver_name=None, node=None):
"""Calculate and return the default interface implementation. """Calculate and return the default interface implementation.
Finds the first implementation that is supported by the hardware type Finds the first implementation that is supported by the hardware type
and is enabled in the configuration. 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 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. used for exception message.
:param node: the identifier of a node. If specified, is used for exception :param node: the identifier of a node. If specified, is used for exception
message. message.
@ -169,23 +131,11 @@ def default_interface(driver_or_hw_type, interface_type,
:raises: NoValidDefaultForInterface if no default interface can be found. :raises: NoValidDefaultForInterface if no default interface can be found.
""" """
factory = _INTERFACE_LOADERS[interface_type] factory = _INTERFACE_LOADERS[interface_type]
is_hardware_type = isinstance(driver_or_hw_type,
hardware_type.AbstractHardwareType)
# Explicit interface defaults # Explicit interface defaults
additional_defaults = { additional_defaults = {
'storage': 'noop' '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 # The fallback default from the configuration
impl_name = getattr(CONF, 'default_%s_interface' % interface_type) impl_name = getattr(CONF, 'default_%s_interface' % interface_type)
if impl_name is None: if impl_name is None:
@ -193,9 +143,9 @@ def default_interface(driver_or_hw_type, interface_type,
if impl_name is not None: if impl_name is not None:
# Check that the default is correct for this type # Check that the default is correct for this type
get_interface(driver_or_hw_type, interface_type, impl_name) get_interface(hw_type, interface_type, impl_name)
elif is_hardware_type: else:
supported = getattr(driver_or_hw_type, supported = getattr(hw_type,
'supported_%s_interfaces' % interface_type) 'supported_%s_interfaces' % interface_type)
# Mapping of classes to entry points # Mapping of classes to entry points
enabled = {obj.__class__: name for (name, obj) in factory().items()} 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: if impl_name is None:
# NOTE(rloo). No i18n on driver_type_str because translating substrings # NOTE(rloo). No i18n on driver_type_str because translating substrings
# on their own may cause the final string to look odd. # on their own may cause the final string to look odd.
if is_hardware_type: driver_name = driver_name or hw_type.__class__.__name__
driver_type_str = 'hardware type'
else:
driver_type_str = 'driver'
driver_name = driver_name or driver_or_hw_type.__class__.__name__
node_info = "" node_info = ""
if node is not None: if node is not None:
node_info = _(' node %s with') % node node_info = _(' node %s with') % node
raise exception.NoValidDefaultForInterface( raise exception.NoValidDefaultForInterface(
interface_type=interface_type, driver_type=driver_type_str, interface_type=interface_type, driver=driver_name,
driver=driver_name, node_info=node_info) node_info=node_info)
return impl_name 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. """Ensure that node interfaces (e.g. for creation or updating) are valid.
Updates (but doesn't save to the database) hardware interfaces with 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. a driver instance is built for a node.
:param node: node object to check and potentially update :param node: node object to check and potentially update
:param driver_or_hw_type: classic driver or hardware type instance object; :param hw_type: hardware type instance object; will be detected from
will be detected from node.driver if missing node.driver if missing
:returns: True if any changes were made to the node, otherwise False :returns: True if any changes were made to the node, otherwise False
:raises: InterfaceNotFoundInEntrypoint on validation failure :raises: InterfaceNotFoundInEntrypoint on validation failure
:raises: NoValidDefaultForInterface if the default value cannot be :raises: NoValidDefaultForInterface if the default value cannot be
calculated and is not provided in the configuration calculated and is not provided in the configuration
:raises: DriverNotFound if the node's driver or hardware type is not found :raises: DriverNotFound if the node's hardware type is not found
:raises: MustBeNone if one or more of the node's interface
fields were specified when they should not be.
""" """
if driver_or_hw_type is None: if hw_type is None:
driver_or_hw_type = get_driver_or_hardware_type(node.driver) hw_type = get_hardware_type(node.driver)
is_hardware_type = isinstance(driver_or_hw_type,
hardware_type.AbstractHardwareType)
if is_hardware_type: factories = list(_INTERFACE_LOADERS)
factories = list(_INTERFACE_LOADERS)
else:
# Only network and storage interfaces are dynamic for classic drivers
factories = ['network', 'storage']
# 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 - whether the node object was modified
result = False 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 # Walk through all dynamic interfaces and check/update them
for iface in factories: for iface in factories:
field_name = '%s_interface' % iface 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) impl_name = getattr(node, field_name)
if impl_name is not None: if impl_name is not None:
# Check that the provided value is correct for this type # 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 # Not changing the result, proceeding with the next interface
continue continue
impl_name = default_interface(driver_or_hw_type, iface, impl_name = default_interface(hw_type, iface,
driver_name=node.driver, node=node.uuid) driver_name=node.driver, node=node.uuid)
# Set the calculated default and set result to True # 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 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): def get_hardware_type(hardware_type):
"""Get a hardware type instance by name. """Get a hardware type instance by name.
@ -339,28 +235,6 @@ def get_hardware_type(hardware_type):
raise exception.DriverNotFound(driver_name=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): def _get_all_drivers(factory):
"""Get all drivers for `factory` as a dict name -> driver object.""" """Get all drivers for `factory` as a dict name -> driver object."""
# NOTE(jroll) I don't think this needs to be ordered, but # 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) 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(): def hardware_types():
"""Get all 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) '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): class HardwareTypesFactory(BaseDriverFactory):
_entrypoint_name = 'ironic.hardware.types' _entrypoint_name = 'ironic.hardware.types'
_enabled_driver_list_config_option = 'enabled_hardware_types' _enabled_driver_list_config_option = 'enabled_hardware_types'
@ -575,101 +436,3 @@ _INTERFACE_LOADERS = {
# refactor them later to use _INTERFACE_LOADERS. # refactor them later to use _INTERFACE_LOADERS.
NetworkInterfaceFactory = _INTERFACE_LOADERS['network'] NetworkInterfaceFactory = _INTERFACE_LOADERS['network']
StorageInterfaceFactory = _INTERFACE_LOADERS['storage'] 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' # NOTE(rloo): in the line below, there is no blank space after 'For'
# because node_info could be an empty string. If node_info # because node_info could be an empty string. If node_info
# is not empty, it should start with a space. # 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.") "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): class ImageNotFound(NotFound):
_msg_fmt = _("Image %(image_id)s could not be found.") _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.") _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): class NoDriversLoaded(IronicException):
_msg_fmt = _("Conductor %(conductor)s cannot be started " _msg_fmt = _("Conductor %(conductor)s cannot be started "
"because no drivers were loaded.") "because no hardware types were loaded.")
class ConductorNotFound(NotFound): class ConductorNotFound(NotFound):

View File

@ -49,8 +49,7 @@ class HashRingManager(object):
def _load_hash_rings(self): def _load_hash_rings(self):
rings = {} rings = {}
d2c = self.dbapi.get_active_driver_dict() d2c = self.dbapi.get_active_hardware_type_dict()
d2c.update(self.dbapi.get_active_hardware_type_dict())
for driver_name, hosts in d2c.items(): for driver_name, hosts in d2c.items():
rings[driver_name] = hashring.HashRing( 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. :raises: ConfigInvalid if an enabled interfaces config option is empty.
""" """
if CONF.enabled_hardware_types: empty_confs = []
empty_confs = [] iface_types = ['enabled_%s_interfaces' % i
iface_types = ['enabled_%s_interfaces' % i for i in driver_base.ALL_INTERFACES]
for i in driver_base.ALL_INTERFACES] for iface_type in iface_types:
for iface_type in iface_types: conf_value = getattr(CONF, iface_type)
conf_value = getattr(CONF, iface_type) if not conf_value:
if not conf_value: empty_confs.append(iface_type)
empty_confs.append(iface_type) if empty_confs:
if empty_confs: msg = (_('Configuration options %s cannot be an empty list.') %
msg = (_('Configuration options %s cannot be an empty list.') % ', '.join(empty_confs))
', '.join(empty_confs)) raise exception.ConfigInvalid(error_msg=msg)
raise exception.ConfigInvalid(error_msg=msg)
class BaseConductorManager(object): class BaseConductorManager(object):
@ -111,44 +110,33 @@ class BaseConductorManager(object):
self.ring_manager = hash_ring.HashRingManager() self.ring_manager = hash_ring.HashRingManager()
"""Consistent hash ring which maps drivers to conductors.""" """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() _check_enabled_interfaces()
# NOTE(deva): these calls may raise DriverLoadError or DriverNotFound # NOTE(deva): these calls may raise DriverLoadError or DriverNotFound
# NOTE(vdrok): Instantiate network and storage interface factory on # NOTE(vdrok): Instantiate network and storage interface factory on
# startup so that all the interfaces are loaded at the very # startup so that all the interfaces are loaded at the very
# beginning, and failures prevent the conductor from starting. # beginning, and failures prevent the conductor from starting.
drivers = driver_factory.drivers()
hardware_types = driver_factory.hardware_types() hardware_types = driver_factory.hardware_types()
driver_factory.NetworkInterfaceFactory() driver_factory.NetworkInterfaceFactory()
driver_factory.StorageInterfaceFactory() driver_factory.StorageInterfaceFactory()
# NOTE(jroll) this is passed to the dbapi, which requires a list, not # NOTE(jroll) this is passed to the dbapi, which requires a list, not
# a generator (which keys() returns in py3) # a generator (which keys() returns in py3)
driver_names = list(drivers)
hardware_type_names = list(hardware_types) hardware_type_names = list(hardware_types)
# check that at least one driver is loaded, whether classic or dynamic # check that at least one driver is loaded, whether classic or dynamic
if not driver_names and not hardware_type_names: if not hardware_type_names:
msg = ("Conductor %s cannot be started because no drivers " msg = ("Conductor %s cannot be started because no hardware types "
"were loaded. This could be because no classic drivers " "were specified in the 'enabled_hardware_types' config "
"were specified in the 'enabled_drivers' config option " "option.")
"and no dynamic drivers were specified in the "
"'enabled_hardware_types' config option.")
LOG.error(msg, self.host) LOG.error(msg, self.host)
raise exception.NoDriversLoaded(conductor=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) self._collect_periodic_tasks(admin_context)
# Check for required config options if object_store_endpoint_type is # Check for required config options if object_store_endpoint_type is
@ -170,7 +158,7 @@ class BaseConductorManager(object):
try: try:
# Register this conductor with the cluster # Register this conductor with the cluster
self.conductor = objects.Conductor.register( self.conductor = objects.Conductor.register(
admin_context, self.host, driver_names) admin_context, self.host, hardware_type_names)
except exception.ConductorAlreadyRegistered: except exception.ConductorAlreadyRegistered:
# This conductor was already registered and did not shut down # This conductor was already registered and did not shut down
# properly, so log a warning and update the record. # properly, so log a warning and update the record.
@ -178,7 +166,8 @@ class BaseConductorManager(object):
"previously registered. Updating registration", "previously registered. Updating registration",
{'hostname': self.host}) {'hostname': self.host})
self.conductor = objects.Conductor.register( 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 # register hardware types and interfaces supported by this conductor
# and validate them against other conductors # and validate them against other conductors
@ -281,13 +270,6 @@ class BaseConductorManager(object):
_collect_from(iface, args=(self, admin_context)) _collect_from(iface, args=(self, admin_context))
# TODO(dtantsur): allow periodics on hardware types themselves? # 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: if len(periodic_task_callables) > CONF.conductor.workers_pool_size:
LOG.warning('This conductor has %(tasks)d periodic tasks ' LOG.warning('This conductor has %(tasks)d periodic tasks '
'enabled, but only %(workers)d task workers ' '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.conductor import utils
from ironic.conf import CONF from ironic.conf import CONF
from ironic.drivers import base as drivers_base from ironic.drivers import base as drivers_base
from ironic.drivers import hardware_type
from ironic import objects from ironic import objects
from ironic.objects import base as objects_base from ironic.objects import base as objects_base
from ironic.objects import fields from ironic.objects import fields
@ -110,7 +109,6 @@ class ConductorManager(base_manager.BaseConductorManager):
# InterfaceNotFoundInEntrypoint # InterfaceNotFoundInEntrypoint
# IncompatibleInterface, # IncompatibleInterface,
# NoValidDefaultForInterface # NoValidDefaultForInterface
# MustBeNone
@messaging.expected_exceptions(exception.InvalidParameterValue, @messaging.expected_exceptions(exception.InvalidParameterValue,
exception.DriverNotFound) exception.DriverNotFound)
def create_node(self, context, node_obj): def create_node(self, context, node_obj):
@ -126,8 +124,6 @@ class ConductorManager(base_manager.BaseConductorManager):
:raises: NoValidDefaultForInterface if no default can be calculated :raises: NoValidDefaultForInterface if no default can be calculated
for some interfaces, and explicit values must be provided. for some interfaces, and explicit values must be provided.
:raises: InvalidParameterValue if some fields fail validation. :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. :raises: DriverNotFound if the driver or hardware type is not found.
""" """
LOG.debug("RPC create_node called for node %s.", node_obj.uuid) LOG.debug("RPC create_node called for node %s.", node_obj.uuid)
@ -140,7 +136,6 @@ class ConductorManager(base_manager.BaseConductorManager):
# InterfaceNotFoundInEntrypoint # InterfaceNotFoundInEntrypoint
# IncompatibleInterface, # IncompatibleInterface,
# NoValidDefaultForInterface # NoValidDefaultForInterface
# MustBeNone
@messaging.expected_exceptions(exception.InvalidParameterValue, @messaging.expected_exceptions(exception.InvalidParameterValue,
exception.NodeLocked, exception.NodeLocked,
exception.InvalidState, exception.InvalidState,
@ -156,8 +151,6 @@ class ConductorManager(base_manager.BaseConductorManager):
:param node_obj: a changed (but not saved) node object. :param node_obj: a changed (but not saved) node object.
:raises: NoValidDefaultForInterface if no default can be calculated :raises: NoValidDefaultForInterface if no default can be calculated
for some interfaces, and explicit values must be provided. 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 node_id = node_obj.uuid
LOG.debug("RPC update_node called for node %s.", node_id) 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. # vendor_opts before starting validation.
with task_manager.acquire(context, node_id, shared=True, with task_manager.acquire(context, node_id, shared=True,
purpose='calling vendor passthru') as task: 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 vendor_iface = task.driver.vendor
try: 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 # 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. # implementation, as there is little we could reasonably lock on here.
LOG.debug("RPC driver_vendor_passthru for driver %s.", driver_name) 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 vendor = None
if isinstance(driver, hardware_type.AbstractHardwareType): vendor_name = driver_factory.default_interface(
vendor_name = driver_factory.default_interface( driver, 'vendor', driver_name=driver_name)
driver, 'vendor', driver_name=driver_name) vendor = driver_factory.get_interface(driver, 'vendor',
vendor = driver_factory.get_interface(driver, 'vendor', vendor_name)
vendor_name)
else:
vendor = getattr(driver, 'vendor', None)
if not vendor:
raise exception.UnsupportedDriverExtension(
driver=driver_name,
extension='vendor interface')
try: try:
vendor_opts = vendor.driver_routes[driver_method] vendor_opts = vendor.driver_routes[driver_method]
@ -484,11 +465,6 @@ class ConductorManager(base_manager.BaseConductorManager):
lock_purpose = 'listing vendor passthru methods' lock_purpose = 'listing vendor passthru methods'
with task_manager.acquire(context, node_id, shared=True, with task_manager.acquire(context, node_id, shared=True,
purpose=lock_purpose) as task: 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( return get_vendor_passthru_metadata(
task.driver.vendor.vendor_routes) 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. # implementation, as there is little we could reasonably lock on here.
LOG.debug("RPC get_driver_vendor_passthru_methods for driver %s", LOG.debug("RPC get_driver_vendor_passthru_methods for driver %s",
driver_name) driver_name)
driver = driver_factory.get_driver_or_hardware_type(driver_name) driver = driver_factory.get_hardware_type(driver_name)
vendor = None vendor = None
if isinstance(driver, hardware_type.AbstractHardwareType): vendor_name = driver_factory.default_interface(
vendor_name = driver_factory.default_interface( driver, 'vendor', driver_name=driver_name)
driver, 'vendor', driver_name=driver_name) vendor = driver_factory.get_interface(driver, 'vendor',
vendor = driver_factory.get_interface(driver, 'vendor', vendor_name)
vendor_name)
else:
vendor = getattr(driver, 'vendor', None)
if not vendor:
raise exception.UnsupportedDriverExtension(
driver=driver_name,
extension='vendor interface')
return get_vendor_passthru_metadata(vendor.driver_routes) return get_vendor_passthru_metadata(vendor.driver_routes)
@ -574,9 +543,6 @@ class ConductorManager(base_manager.BaseConductorManager):
raise exception.NodeInMaintenance(op=_('rescuing'), raise exception.NodeInMaintenance(op=_('rescuing'),
node=node.uuid) 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 # driver validation may check rescue_password, so save it on the
# node early # node early
instance_info = node.instance_info instance_info = node.instance_info
@ -675,9 +641,6 @@ class ConductorManager(base_manager.BaseConductorManager):
if node.maintenance: if node.maintenance:
raise exception.NodeInMaintenance(op=_('unrescuing'), raise exception.NodeInMaintenance(op=_('unrescuing'),
node=node.uuid) node=node.uuid)
if not getattr(task.driver, 'rescue', None):
raise exception.UnsupportedDriverExtension(
driver=node.driver, extension='rescue')
try: try:
task.driver.power.validate(task) task.driver.power.validate(task)
except (exception.InvalidParameterValue, except (exception.InvalidParameterValue,
@ -1192,14 +1155,13 @@ class ConductorManager(base_manager.BaseConductorManager):
# Do caching of bios settings if supported by driver, # Do caching of bios settings if supported by driver,
# this will be called for both manual and automated cleaning. # this will be called for both manual and automated cleaning.
# TODO(zshi) remove this check when classic drivers are removed # TODO(zshi) remove this check when classic drivers are removed
if getattr(task.driver, 'bios', None): try:
try: task.driver.bios.cache_bios_settings(task)
task.driver.bios.cache_bios_settings(task) except Exception as e:
except Exception as e: msg = (_('Caching of bios settings failed on node %(node)s. '
msg = (_('Caching of bios settings failed on node %(node)s. ' 'Continuing with node cleaning.')
'Continuing with node cleaning.') % {'node': node.uuid})
% {'node': node.uuid}) LOG.exception(msg)
LOG.exception(msg)
# Allow the deploy driver to set up the ramdisk again (necessary for # Allow the deploy driver to set up the ramdisk again (necessary for
# IPA cleaning) # IPA cleaning)
@ -2023,32 +1985,29 @@ class ConductorManager(base_manager.BaseConductorManager):
# interface. # interface.
if iface_name == 'bios': if iface_name == 'bios':
continue continue
iface = getattr(task.driver, iface_name, None) iface = getattr(task.driver, iface_name)
result = reason = None result = reason = None
if iface: try:
try: iface.validate(task)
iface.validate(task) if iface_name == 'deploy':
if iface_name == 'deploy': utils.validate_instance_info_traits(task.node)
utils.validate_instance_info_traits(task.node) result = True
result = True except (exception.InvalidParameterValue,
except (exception.InvalidParameterValue, exception.UnsupportedDriverExtension) as e:
exception.UnsupportedDriverExtension) as e: result = False
result = False reason = str(e)
reason = str(e) except Exception as e:
except Exception as e: result = False
result = False reason = (_('Unexpected exception, traceback saved '
reason = (_('Unexpected exception, traceback saved ' 'into log by ironic conductor service '
'into log by ironic conductor service ' 'that is running on %(host)s: %(error)s')
'that is running on %(host)s: %(error)s') % {'host': self.host, 'error': e})
% {'host': self.host, 'error': e}) LOG.exception(
LOG.exception( 'Unexpected exception occurred while validating '
'Unexpected exception occurred while validating ' '%(iface)s driver interface for driver '
'%(iface)s driver interface for driver ' '%(driver)s: %(err)s on node %(node)s.',
'%(driver)s: %(err)s on node %(node)s.', {'iface': iface_name, 'driver': task.node.driver,
{'iface': iface_name, 'driver': task.node.driver, 'err': e, 'node': task.node.uuid})
'err': e, 'node': task.node.uuid})
else:
reason = _('not supported')
ret_dict[iface_name] = {} ret_dict[iface_name] = {}
ret_dict[iface_name]['result'] = result ret_dict[iface_name]['result'] = result
@ -2256,9 +2215,6 @@ class ConductorManager(base_manager.BaseConductorManager):
purpose=lock_purpose) as task: purpose=lock_purpose) as task:
node = task.node node = task.node
if not getattr(task.driver, 'console', None):
raise exception.UnsupportedDriverExtension(driver=node.driver,
extension='console')
if not node.console_enabled: if not node.console_enabled:
raise exception.NodeConsoleNotEnabled(node=node.uuid) raise exception.NodeConsoleNotEnabled(node=node.uuid)
@ -2293,9 +2249,6 @@ class ConductorManager(base_manager.BaseConductorManager):
with task_manager.acquire(context, node_id, shared=False, with task_manager.acquire(context, node_id, shared=False,
purpose='setting console mode') as task: purpose='setting console mode') as task:
node = task.node node = task.node
if not getattr(task.driver, 'console', None):
raise exception.UnsupportedDriverExtension(driver=node.driver,
extension='console')
task.driver.console.validate(task) task.driver.console.validate(task)
@ -2632,7 +2585,7 @@ class ConductorManager(base_manager.BaseConductorManager):
""" """
LOG.debug("RPC get_driver_properties called for driver %s.", LOG.debug("RPC get_driver_properties called for driver %s.",
driver_name) driver_name)
driver = driver_factory.get_driver_or_hardware_type(driver_name) driver = driver_factory.get_hardware_type(driver_name)
return driver.get_properties() return driver.get_properties()
@METRICS.timer('ConductorManager._sensors_nodes_task') @METRICS.timer('ConductorManager._sensors_nodes_task')
@ -2656,9 +2609,6 @@ class ConductorManager(base_manager.BaseConductorManager):
node_uuid, node_uuid,
shared=True, shared=True,
purpose=lock_purpose) as task: purpose=lock_purpose) as task:
if not getattr(task.driver, 'management', None):
continue
if task.node.maintenance: if task.node.maintenance:
LOG.debug('Skipping sending sensors data for node ' LOG.debug('Skipping sending sensors data for node '
'%s as it is in maintenance mode', '%s as it is in maintenance mode',
@ -2782,10 +2732,6 @@ class ConductorManager(base_manager.BaseConductorManager):
'device %(device)s', {'node': node_id, 'device': device}) 'device %(device)s', {'node': node_id, 'device': device})
with task_manager.acquire(context, node_id, with task_manager.acquire(context, node_id,
purpose='setting boot device') as task: 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.validate(task)
task.driver.management.set_boot_device(task, device, task.driver.management.set_boot_device(task, device,
persistent=persistent) persistent=persistent)
@ -2818,9 +2764,6 @@ class ConductorManager(base_manager.BaseConductorManager):
LOG.debug('RPC get_boot_device called for node %s', node_id) LOG.debug('RPC get_boot_device called for node %s', node_id)
with task_manager.acquire(context, node_id, with task_manager.acquire(context, node_id,
purpose='getting boot device') as task: 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) task.driver.management.validate(task)
return task.driver.management.get_boot_device(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, with task_manager.acquire(context, node_id,
purpose='inject nmi') as task: 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.validate(task)
task.driver.management.inject_nmi(task) task.driver.management.inject_nmi(task)
@METRICS.timer('ConductorManager.get_supported_boot_devices') @METRICS.timer('ConductorManager.get_supported_boot_devices')
@ -2879,9 +2817,6 @@ class ConductorManager(base_manager.BaseConductorManager):
lock_purpose = 'getting supported boot devices' lock_purpose = 'getting supported boot devices'
with task_manager.acquire(context, node_id, shared=True, with task_manager.acquire(context, node_id, shared=True,
purpose=lock_purpose) as task: 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) return task.driver.management.get_supported_boot_devices(task)
@METRICS.timer('ConductorManager.inspect_hardware') @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) LOG.debug('RPC inspect_hardware called for node %s', node_id)
with task_manager.acquire(context, node_id, shared=False, with task_manager.acquire(context, node_id, shared=False,
purpose='hardware inspection') as task: 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.power.validate(task)
task.driver.inspect.validate(task) task.driver.inspect.validate(task)
@ -2983,9 +2914,6 @@ class ConductorManager(base_manager.BaseConductorManager):
context, node_id, context, node_id,
purpose='setting target RAID config') as task: purpose='setting target RAID config') as task:
node = task.node 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 # Operator may try to unset node.target_raid_config. So, try to
# validate only if it is not empty. # validate only if it is not empty.
if target_raid_config: if target_raid_config:
@ -3019,18 +2947,12 @@ class ConductorManager(base_manager.BaseConductorManager):
LOG.debug("RPC get_raid_logical_disk_properties " LOG.debug("RPC get_raid_logical_disk_properties "
"called for driver %s", driver_name) "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 raid_iface = None
if isinstance(driver, hardware_type.AbstractHardwareType): raid_iface_name = driver_factory.default_interface(
raid_iface_name = driver_factory.default_interface( driver, 'raid', driver_name=driver_name)
driver, 'raid', driver_name=driver_name) raid_iface = driver_factory.get_interface(driver, 'raid',
raid_iface = driver_factory.get_interface(driver, 'raid', raid_iface_name)
raid_iface_name)
else:
raid_iface = getattr(driver, 'raid', None)
if not raid_iface:
raise exception.UnsupportedDriverExtension(
driver=driver_name, extension='raid')
return raid_iface.get_logical_disk_properties() return raid_iface.get_logical_disk_properties()

View File

@ -60,10 +60,6 @@ def node_set_boot_device(task, device, persistent=False):
ManagementInterface fails. ManagementInterface fails.
""" """
# TODO(etingof): remove `if` once classic drivers are gone
if not getattr(task.driver, 'management', None):
return
task.driver.management.validate(task) task.driver.management.validate(task)
if task.node.provision_state != states.ADOPTING: if task.node.provision_state != states.ADOPTING:
task.driver.management.set_boot_device(task, 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` :returns: Boot mode. One of :mod:`ironic.common.boot_mode` or `None`
if boot mode can't be discovered 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) task.driver.management.validate(task)
return task.driver.management.get_boot_mode(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: if task.node.provision_state == states.ADOPTING:
return return
# TODO(etingof): remove `if` once classic drivers are gone
if not getattr(task.driver, 'management', None):
return
task.driver.management.validate(task) task.driver.management.validate(task)
boot_modes = task.driver.management.get_supported_boot_modes(task) boot_modes = task.driver.management.get_supported_boot_modes(task)

View File

@ -91,15 +91,9 @@ api_opts = [
driver_opts = [ driver_opts = [
cfg.ListOpt('enabled_drivers', cfg.ListOpt('enabled_drivers',
default=[], default=[],
help=_('Specify the list of drivers to load during service ' help=_('This option is left for a start up check only. '
'initialization. Missing drivers, or drivers which ' 'Any non-empty value will prevent the conductor '
'fail to initialize, will prevent the conductor ' 'from starting.'),
'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.'),
deprecated_for_removal=True, deprecated_for_removal=True,
deprecated_reason=_('Hardware types should be used instead ' deprecated_reason=_('Hardware types should be used instead '
'of classic drivers. They are enabled ' 'of classic drivers. They are enabled '

View File

@ -517,20 +517,6 @@ class Connection(object):
:raises: ConductorNotFound :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 @abc.abstractmethod
def get_active_hardware_type_dict(self): def get_active_hardware_type_dict(self):
"""Retrieve hardware types for the registered and active conductors. """Retrieve hardware types for the registered and active conductors.
@ -909,25 +895,6 @@ class Connection(object):
False otherwise. 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 @abc.abstractmethod
def set_node_traits(self, node_id, traits, version): def set_node_traits(self, node_id, traits, version):
"""Replace all of the node traits with specified list of traits. """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.orm import joinedload
from sqlalchemy import sql from sqlalchemy import sql
from ironic.common import driver_factory
from ironic.common import exception from ironic.common import exception
from ironic.common.i18n import _ from ironic.common.i18n import _
from ironic.common import profiler from ironic.common import profiler
@ -856,17 +855,6 @@ class Connection(api.Connection):
'powering process, their power state can be incorrect: ' 'powering process, their power state can be incorrect: '
'%(nodes)s', {'nodes': nodes}) '%(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): def get_active_hardware_type_dict(self):
query = (model_query(models.ConductorHardwareInterfaces, query = (model_query(models.ConductorHardwareInterfaces,
models.Conductor) models.Conductor)
@ -1229,82 +1217,6 @@ class Connection(api.Connection):
return False return False
return True 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 @staticmethod
def _verify_max_traits_per_node(node_id, num_traits): def _verify_max_traits_per_node(node_id, num_traits):
"""Verify that an operation would not exceed the per-node trait limit. """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') 'raid_config_schema.json')
@six.add_metaclass(abc.ABCMeta) class BareDriver(object):
class BaseDriver(object): """A bare driver object which will have interfaces attached later.
"""Base class for all drivers.
Defines the `core`, `standardized`, and `vendor-specific` interfaces for Any composable interfaces should be added as class attributes of this
drivers. Any loadable driver must implement all `core` interfaces. class, as well as appended to core_interfaces or standard_interfaces here.
Actual implementation may instantiate one or more classes, as long as
the interfaces are appropriate.
""" """
supported = False bios = None
"""Indicates if a driver is supported. """`Standard` attribute for BIOS related features.
This will be set to False for drivers which are untested in first- or A reference to an instance of :class:BIOSInterface.
third-party CI, or in the process of being deprecated.
All classic drivers are now deprecated, and thus unsupported.
""" """
# NOTE(jlvillal): These should be tuples to help prevent child classes from boot = None
# accidentally modifying the base class values. """`Standard` attribute for boot related features.
core_interfaces = ('deploy', 'power')
standard_interfaces = ('boot', 'console', 'inspect', 'management', 'raid')
power = None A reference to an instance of :class:BootInterface.
"""`Core` attribute for managing power state. """
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 deploy = None
@ -75,66 +70,71 @@ class BaseDriver(object):
A reference to an instance of :class:DeployInterface. A reference to an instance of :class:DeployInterface.
""" """
console = None inspect = None
"""`Standard` attribute for managing console access. """`Standard` attribute for inspection related features.
A reference to an instance of :class:ConsoleInterface. A reference to an instance of :class:InspectInterface.
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.
""" """
management = None management = None
"""`Standard` attribute for management related features. """`Standard` attribute for management related features.
A reference to an instance of :class:ManagementInterface. A reference to an instance of :class:ManagementInterface.
May be None, if unsupported by a driver.
""" """
boot = None network = None
"""`Standard` attribute for boot related features. """`Core` attribute for network connectivity.
A reference to an instance of :class:BootInterface. A reference to an instance of :class:NetworkInterface.
May be None, if unsupported by a driver.
""" """
vendor = None power = None
"""Attribute for accessing any vendor-specific extensions. """`Core` attribute for managing power state.
A reference to an instance of :class:VendorInterface. A reference to an instance of :class:PowerInterface.
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.
""" """
raid = None raid = None
"""`Standard` attribute for RAID related features. """`Standard` attribute for RAID related features.
A reference to an instance of :class:RaidInterface. A reference to an instance of :class:RaidInterface.
May be None, if unsupported by a driver.
""" """
def __init__(self): rescue = None
pass """`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 @property
def all_interfaces(self): def all_interfaces(self):
return (list(self.core_interfaces + self.standard_interfaces) return self.non_vendor_interfaces + ['vendor']
+ ['vendor'])
@property @property
def non_vendor_interfaces(self): 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): def get_properties(self):
"""Get the properties of the driver. """Get the properties of the driver.
@ -149,55 +149,9 @@ class BaseDriver(object):
properties.update(iface.get_properties()) properties.update(iface.get_properties())
return 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) ALL_INTERFACES = set(BareDriver().all_interfaces)
"""Constant holding all known interfaces. """Constant holding all known interfaces."""
Includes interfaces not exposed via BaseDriver.all_interfaces.
"""
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)

View File

@ -11,7 +11,7 @@
# under the License. # under the License.
""" """
Hardware types and classic drivers for IPMI (using ipmitool). Hardware type for IPMI (using ipmitool).
""" """
from ironic.drivers import generic from ironic.drivers import generic

View File

@ -97,7 +97,6 @@ class TestCase(oslo_test_base.BaseTestCase):
self.useFixture(fixtures.EnvironmentVariable('http_proxy')) self.useFixture(fixtures.EnvironmentVariable('http_proxy'))
self.policy = self.useFixture(policy_fixture.PolicyFixture()) self.policy = self.useFixture(policy_fixture.PolicyFixture())
driver_factory.DriverFactory._extension_manager = None
driver_factory.HardwareTypesFactory._extension_manager = None driver_factory.HardwareTypesFactory._extension_manager = None
for factory in driver_factory._INTERFACE_LOADERS.values(): for factory in driver_factory._INTERFACE_LOADERS.values():
factory._extension_manager = None factory._extension_manager = None
@ -129,7 +128,6 @@ class TestCase(oslo_test_base.BaseTestCase):
group='neutron') group='neutron')
self.config(rescuing_network=uuidutils.generate_uuid(), self.config(rescuing_network=uuidutils.generate_uuid(),
group='neutron') group='neutron')
self.config(enabled_drivers=[])
self.config(enabled_hardware_types=['fake-hardware', self.config(enabled_hardware_types=['fake-hardware',
'manual-management']) 'manual-management'])
for iface in drivers_base.ALL_INTERFACES: for iface in drivers_base.ALL_INTERFACES:

View File

@ -30,32 +30,30 @@ from ironic.tests.unit.api import base
class TestListDrivers(base.BaseApiTest): class TestListDrivers(base.BaseApiTest):
d1 = 'fake-driver1' hw1 = 'fake-hardware-type'
d2 = 'fake-driver2' hw2 = 'fake-hardware-type-2'
d3 = 'fake-hardware-type'
h1 = 'fake-host1' h1 = 'fake-host1'
h2 = 'fake-host2' h2 = 'fake-host2'
def register_fake_conductors(self): def register_fake_conductors(self):
c1 = self.dbapi.register_conductor({ c1 = self.dbapi.register_conductor({
'hostname': self.h1, 'hostname': self.h1, 'drivers': [],
'drivers': [self.d1, self.d2],
}) })
c2 = self.dbapi.register_conductor({ c2 = self.dbapi.register_conductor({
'hostname': self.h2, 'hostname': self.h2, 'drivers': [],
'drivers': [self.d2],
}) })
for c in (c1, c2): for c in (c1, c2):
self.dbapi.register_conductor_hardware_interfaces( 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): def _test_drivers(self, use_dynamic, detail=False, latest_if=False):
self.register_fake_conductors() self.register_fake_conductors()
headers = {} headers = {}
expected = [ expected = [
{'name': self.d1, 'hosts': [self.h1], 'type': 'classic'}, {'name': self.hw1, 'hosts': [self.h1, self.h2], 'type': 'dynamic'},
{'name': self.d2, 'hosts': [self.h1, self.h2], 'type': 'classic'}, {'name': self.hw2, 'hosts': [self.h1], 'type': 'dynamic'},
{'name': self.d3, 'hosts': [self.h1, self.h2], 'type': 'dynamic'},
] ]
expected = sorted(expected, key=lambda d: d['name']) expected = sorted(expected, key=lambda d: d['name'])
if use_dynamic: if use_dynamic:
@ -114,13 +112,13 @@ class TestListDrivers(base.BaseApiTest):
autospec=True) as mock_hw: autospec=True) as mock_hw:
mock_hw.return_value = [ mock_hw.return_value = [
{ {
'hardware_type': self.d3, 'hardware_type': self.hw1,
'interface_type': 'deploy', 'interface_type': 'deploy',
'interface_name': 'iscsi', 'interface_name': 'iscsi',
'default': False, 'default': False,
}, },
{ {
'hardware_type': self.d3, 'hardware_type': self.hw1,
'interface_type': 'deploy', 'interface_type': 'deploy',
'interface_name': 'direct', 'interface_name': 'direct',
'default': True, 'default': True,
@ -135,20 +133,20 @@ class TestListDrivers(base.BaseApiTest):
def test_drivers_with_dynamic_detailed_storage_interface(self): def test_drivers_with_dynamic_detailed_storage_interface(self):
self._test_drivers_with_dynamic_detailed(latest_if=True) 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() self.register_fake_conductors()
headers = {api_base.Version.string: '1.30'} headers = {api_base.Version.string: '1.30'}
data = self.get_json('/drivers?type=%s' % requested_type, data = self.get_json('/drivers?type=classic', headers=headers)
headers=headers) self.assertEqual([], data['drivers'])
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')
def test_drivers_type_filter_dynamic(self): 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): def test_drivers_type_filter_bad_version(self):
headers = {api_base.Version.string: '1.29'} headers = {api_base.Version.string: '1.29'}
@ -184,19 +182,14 @@ class TestListDrivers(base.BaseApiTest):
self.assertEqual([], data['drivers']) self.assertEqual([], data['drivers'])
@mock.patch.object(rpcapi.ConductorAPI, 'get_driver_properties') @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): latest_if=False):
# get_driver_properties mock is required by validate_link() # get_driver_properties mock is required by validate_link()
self.register_fake_conductors() self.register_fake_conductors()
if use_dynamic: driver = self.hw1
driver = self.d3 driver_type = 'dynamic'
driver_type = 'dynamic' hosts = [self.h1, self.h2]
hosts = [self.h1, self.h2]
else:
driver = self.d1
driver_type = 'classic'
hosts = [self.h1]
headers = {} headers = {}
if latest_if: if latest_if:
@ -213,47 +206,40 @@ class TestListDrivers(base.BaseApiTest):
self.assertIn('properties', data) self.assertIn('properties', data)
self.assertEqual(driver_type, data['type']) self.assertEqual(driver_type, data['type'])
if use_dynamic: for iface in driver_base.ALL_INTERFACES:
for iface in driver_base.ALL_INTERFACES: if iface != 'bios':
if iface != 'bios': if latest_if or iface not in ['rescue', 'storage']:
if latest_if or iface not in ['rescue', 'storage']: self.assertIn('default_%s_interface' % iface, data)
self.assertIn('default_%s_interface' % iface, data) self.assertIn('enabled_%s_interfaces' % iface, data)
self.assertIn('enabled_%s_interfaces' % iface, data)
self.assertIsNotNone(data['default_deploy_interface']) self.assertIsNotNone(data['default_deploy_interface'])
self.assertIsNotNone(data['enabled_deploy_interfaces']) self.assertIsNotNone(data['enabled_deploy_interfaces'])
else:
self.assertIsNone(data['default_deploy_interface'])
self.assertIsNone(data['enabled_deploy_interfaces'])
self.validate_link(data['links'][0]['href']) self.validate_link(data['links'][0]['href'])
self.validate_link(data['links'][1]['href']) self.validate_link(data['links'][1]['href'])
self.validate_link(data['properties'][0]['href']) self.validate_link(data['properties'][0]['href'])
self.validate_link(data['properties'][1]['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): def _test_drivers_get_one_ok_dynamic(self, latest_if=False):
with mock.patch.object(self.dbapi, 'list_hardware_type_interfaces', with mock.patch.object(self.dbapi, 'list_hardware_type_interfaces',
autospec=True) as mock_hw: autospec=True) as mock_hw:
mock_hw.return_value = [ mock_hw.return_value = [
{ {
'hardware_type': self.d3, 'hardware_type': self.hw1,
'interface_type': 'deploy', 'interface_type': 'deploy',
'interface_name': 'iscsi', 'interface_name': 'iscsi',
'default': False, 'default': False,
}, },
{ {
'hardware_type': self.d3, 'hardware_type': self.hw1,
'interface_type': 'deploy', 'interface_type': 'deploy',
'interface_name': 'direct', 'interface_name': 'direct',
'default': True, 'default': True,
}, },
] ]
self._test_drivers_get_one_ok(True, latest_if=latest_if) self._test_drivers_get_one_ok(latest_if=latest_if)
mock_hw.assert_called_once_with([self.d3]) mock_hw.assert_called_once_with([self.hw1])
def test_drivers_get_one_ok_dynamic_base_interfaces(self): def test_drivers_get_one_ok_dynamic_base_interfaces(self):
self._test_drivers_get_one_ok_dynamic() self._test_drivers_get_one_ok_dynamic()
@ -263,35 +249,35 @@ class TestListDrivers(base.BaseApiTest):
def test_driver_properties_hidden_in_lower_version(self): def test_driver_properties_hidden_in_lower_version(self):
self.register_fake_conductors() 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'}) headers={api_base.Version.string: '1.8'})
self.assertNotIn('properties', data) self.assertNotIn('properties', data)
def test_driver_type_hidden_in_lower_version(self): def test_driver_type_hidden_in_lower_version(self):
self.register_fake_conductors() 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'}) headers={api_base.Version.string: '1.14'})
self.assertNotIn('type', data) self.assertNotIn('type', data)
def test_drivers_get_one_not_found(self): 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) self.assertEqual(http_client.NOT_FOUND, response.status_int)
def _test_links(self, public_url=None): def _test_links(self, public_url=None):
cfg.CONF.set_override('public_endpoint', public_url, 'api') cfg.CONF.set_override('public_endpoint', public_url, 'api')
self.register_fake_conductors() 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.assertIn('links', data)
self.assertEqual(2, len(data['links'])) 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']: for l in data['links']:
bookmark = l['rel'] == 'bookmark' bookmark = l['rel'] == 'bookmark'
self.assertTrue(self.validate_link(l['href'], bookmark=bookmark)) self.assertTrue(self.validate_link(l['href'], bookmark=bookmark))
if public_url is not None: 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'}, 'rel': 'self'},
{'href': '%s/drivers/%s' % (public_url, self.d1), {'href': '%s/drivers/%s' % (public_url, self.hw1),
'rel': 'bookmark'}] 'rel': 'bookmark'}]
for i in expected: for i in expected:
self.assertIn(i, data['links']) self.assertIn(i, data['links'])
@ -310,7 +296,7 @@ class TestListDrivers(base.BaseApiTest):
'async': False, 'async': False,
'attach': False} 'attach': False}
response = self.post_json( response = self.post_json(
'/drivers/%s/vendor_passthru/do_test' % self.d1, '/drivers/%s/vendor_passthru/do_test' % self.hw1,
{'test_key': 'test_value'}) {'test_key': 'test_value'})
self.assertEqual(http_client.OK, response.status_int) self.assertEqual(http_client.OK, response.status_int)
self.assertEqual(mocked_driver_vendor_passthru.return_value['return'], self.assertEqual(mocked_driver_vendor_passthru.return_value['return'],
@ -323,7 +309,7 @@ class TestListDrivers(base.BaseApiTest):
'async': True, 'async': True,
'attach': False} 'attach': False}
response = self.post_json( response = self.post_json(
'/drivers/%s/vendor_passthru/do_test' % self.d1, '/drivers/%s/vendor_passthru/do_test' % self.hw1,
{'test_key': 'test_value'}) {'test_key': 'test_value'})
self.assertEqual(http_client.ACCEPTED, response.status_int) self.assertEqual(http_client.ACCEPTED, response.status_int)
self.assertIsNone(mocked_driver_vendor_passthru.return_value['return']) 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} return_value = {'return': None, 'async': True, 'attach': False}
mocked_driver_vendor_passthru.return_value = return_value mocked_driver_vendor_passthru.return_value = return_value
response = self.put_json( response = self.put_json(
'/drivers/%s/vendor_passthru/do_test' % self.d1, '/drivers/%s/vendor_passthru/do_test' % self.hw1,
{'test_key': 'test_value'}) {'test_key': 'test_value'})
self.assertEqual(http_client.ACCEPTED, response.status_int) self.assertEqual(http_client.ACCEPTED, response.status_int)
self.assertEqual(return_value['return'], response.json) self.assertEqual(return_value['return'], response.json)
@ -345,7 +331,7 @@ class TestListDrivers(base.BaseApiTest):
return_value = {'return': 'foo', 'async': False, 'attach': False} return_value = {'return': 'foo', 'async': False, 'attach': False}
mocked_driver_vendor_passthru.return_value = return_value mocked_driver_vendor_passthru.return_value = return_value
response = self.get_json( 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) self.assertEqual(return_value['return'], response)
@mock.patch.object(rpcapi.ConductorAPI, 'driver_vendor_passthru') @mock.patch.object(rpcapi.ConductorAPI, 'driver_vendor_passthru')
@ -354,7 +340,7 @@ class TestListDrivers(base.BaseApiTest):
return_value = {'return': None, 'async': True, 'attach': False} return_value = {'return': None, 'async': True, 'attach': False}
mock_driver_vendor_passthru.return_value = return_value mock_driver_vendor_passthru.return_value = return_value
response = self.delete( 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(http_client.ACCEPTED, response.status_int)
self.assertEqual(return_value['return'], response.json) self.assertEqual(return_value['return'], response.json)
@ -362,7 +348,7 @@ class TestListDrivers(base.BaseApiTest):
# tests when given driver is not found # tests when given driver is not found
# e.g. get_topic_for_driver fails to find the driver # e.g. get_topic_for_driver fails to find the driver
response = self.post_json( response = self.post_json(
'/drivers/%s/vendor_passthru/do_test' % self.d1, '/drivers/%s/vendor_passthru/do_test' % self.hw1,
{'test_key': 'test_value'}, {'test_key': 'test_value'},
expect_errors=True) expect_errors=True)
@ -370,7 +356,7 @@ class TestListDrivers(base.BaseApiTest):
def test_driver_vendor_passthru_method_not_found(self): def test_driver_vendor_passthru_method_not_found(self):
response = self.post_json( response = self.post_json(
'/drivers/%s/vendor_passthru' % self.d1, '/drivers/%s/vendor_passthru' % self.hw1,
{'test_key': 'test_value'}, {'test_key': 'test_value'},
expect_errors=True) expect_errors=True)
@ -385,11 +371,11 @@ class TestListDrivers(base.BaseApiTest):
self.register_fake_conductors() self.register_fake_conductors()
return_value = {'foo': 'bar'} return_value = {'foo': 'bar'}
get_methods_mock.return_value = return_value 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) data = self.get_json(path)
self.assertEqual(return_value, data) 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) topic=mock.ANY)
# Now let's test the cache: Reset the mock # Now let's test the cache: Reset the mock
@ -407,11 +393,11 @@ class TestListDrivers(base.BaseApiTest):
self.register_fake_conductors() self.register_fake_conductors()
properties = {'foo': 'description of foo'} properties = {'foo': 'description of foo'}
disk_prop_mock.return_value = properties 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, data = self.get_json(path,
headers={api_base.Version.string: "1.12"}) headers={api_base.Version.string: "1.12"})
self.assertEqual(properties, data) 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) topic=mock.ANY)
@mock.patch.object(rpcapi.ConductorAPI, 'get_raid_logical_disk_properties') @mock.patch.object(rpcapi.ConductorAPI, 'get_raid_logical_disk_properties')
@ -420,7 +406,7 @@ class TestListDrivers(base.BaseApiTest):
self.register_fake_conductors() self.register_fake_conductors()
properties = {'foo': 'description of foo'} properties = {'foo': 'description of foo'}
disk_prop_mock.return_value = properties 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, ret = self.get_json(path,
headers={api_base.Version.string: "1.4"}, headers={api_base.Version.string: "1.4"},
expect_errors=True) expect_errors=True)
@ -434,14 +420,14 @@ class TestListDrivers(base.BaseApiTest):
self.register_fake_conductors() self.register_fake_conductors()
properties = {'foo': 'description of foo'} properties = {'foo': 'description of foo'}
disk_prop_mock.return_value = properties 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): for i in range(3):
data = self.get_json(path, data = self.get_json(path,
headers={api_base.Version.string: "1.12"}) headers={api_base.Version.string: "1.12"})
self.assertEqual(properties, data) 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) 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') @mock.patch.object(rpcapi.ConductorAPI, 'get_raid_logical_disk_properties')
def test_raid_logical_disk_properties_iface_not_supported( def test_raid_logical_disk_properties_iface_not_supported(
@ -450,13 +436,13 @@ class TestListDrivers(base.BaseApiTest):
self.register_fake_conductors() self.register_fake_conductors()
disk_prop_mock.side_effect = exception.UnsupportedDriverExtension( disk_prop_mock.side_effect = exception.UnsupportedDriverExtension(
extension='raid', driver='fake-hardware') 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, ret = self.get_json(path,
headers={api_base.Version.string: "1.12"}, headers={api_base.Version.string: "1.12"},
expect_errors=True) expect_errors=True)
self.assertEqual(http_client.NOT_FOUND, ret.status_code) self.assertEqual(http_client.NOT_FOUND, ret.status_code)
self.assertTrue(ret.json['error_message']) 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) topic=mock.ANY)

View File

@ -2325,22 +2325,6 @@ class TestPatch(test_api_base.BaseApiTest):
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code) 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): def test_update_storage_interface(self):
node = obj_utils.create_test_node(self.context, node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid()) uuid=uuidutils.generate_uuid())

View File

@ -14,7 +14,6 @@
import mock import mock
from oslo_utils import uuidutils from oslo_utils import uuidutils
import stevedore
from stevedore import named from stevedore import named
from ironic.common import driver_factory from ironic.common import driver_factory
@ -386,8 +385,8 @@ class TestFakeHardware(hardware_type.AbstractHardwareType):
return [fake.FakeVendorB, fake.FakeVendorA] return [fake.FakeVendorB, fake.FakeVendorA]
OPTIONAL_INTERFACES = (set(drivers_base.BareDriver().standard_interfaces) OPTIONAL_INTERFACES = (drivers_base.BareDriver().optional_interfaces +
- {'management', 'boot'}) | {'vendor'} ['vendor'])
class HardwareTypeLoadTestCase(db_base.DbTestCase): class HardwareTypeLoadTestCase(db_base.DbTestCase):
@ -421,11 +420,6 @@ class HardwareTypeLoadTestCase(db_base.DbTestCase):
self.assertRaises(exception.DriverNotFound, self.assertRaises(exception.DriverNotFound,
driver_factory.get_hardware_type, 'fake_agent') 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): def test_build_driver_for_task(self):
node = obj_utils.create_test_node(self.context, driver='fake-hardware', node = obj_utils.create_test_node(self.context, driver='fake-hardware',
**self.node_kwargs) **self.node_kwargs)
@ -577,80 +571,3 @@ class HardwareTypeLoadTestCase(db_base.DbTestCase):
def test_enabled_supported_interfaces_non_default(self): def test_enabled_supported_interfaces_non_default(self):
self._test_enabled_supported_interfaces(True) 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( self.dbapi.register_conductor_hardware_interfaces(
c.id, 'hardware-type', 'deploy', ['iscsi', 'direct'], 'iscsi') 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): def test_hash_ring_manager_hardware_type_success(self):
self.register_conductors() self.register_conductors()
ring = self.ring_manager['hardware-type'] ring = self.ring_manager['hardware-type']
@ -65,11 +60,11 @@ class HashRingManagerTestCase(db_base.DbTestCase):
# undesirable, but today is the intended behavior. # undesirable, but today is the intended behavior.
self.assertRaises(exception.DriverNotFound, self.assertRaises(exception.DriverNotFound,
self.ring_manager.__getitem__, self.ring_manager.__getitem__,
'driver1') 'hardware-type')
self.register_conductors() self.register_conductors()
self.assertRaises(exception.DriverNotFound, self.assertRaises(exception.DriverNotFound,
self.ring_manager.__getitem__, self.ring_manager.__getitem__,
'driver1') 'hardware-type')
def test_hash_ring_manager_refresh(self): def test_hash_ring_manager_refresh(self):
CONF.set_override('hash_ring_reset_interval', 30) 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. # hash ring will refresh only when time interval exceeded.
self.assertRaises(exception.DriverNotFound, self.assertRaises(exception.DriverNotFound,
self.ring_manager.__getitem__, self.ring_manager.__getitem__,
'driver1') 'hardware-type')
self.register_conductors() self.register_conductors()
self.ring_manager.updated_at = time.time() - 31 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._start_service()
self.service.del_host() self.service.del_host()
@mock.patch.object(driver_factory.DriverFactory, '__getitem__', @mock.patch.object(driver_factory.HardwareTypesFactory, '__getitem__',
lambda *args: mock.MagicMock()) lambda *args: mock.MagicMock())
@mock.patch.object(driver_factory, 'StorageInterfaceFactory') @mock.patch.object(driver_factory, 'default_interface', autospec=True)
@mock.patch.object(driver_factory, 'NetworkInterfaceFactory') def test_start_registers_driver_names(self, mock_def_iface):
def test_start_registers_driver_names(self, net_factory,
storage_factory):
init_names = ['fake1', 'fake2'] init_names = ['fake1', 'fake2']
restart_names = ['fake3', 'fake4'] 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: with mock.patch.object(df._extension_manager, 'names') as mock_names:
# verify driver names are registered # verify driver names are registered
self.config(enabled_drivers=init_names) self.config(enabled_hardware_types=init_names)
mock_names.return_value = init_names mock_names.return_value = init_names
self._start_service() self._start_service()
res = objects.Conductor.get_by_hostname(self.context, res = objects.Conductor.get_by_hostname(self.context,
@ -103,20 +103,36 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
self._stop_service() self._stop_service()
# verify that restart registers new driver names # 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 mock_names.return_value = restart_names
self._start_service() self._start_service()
res = objects.Conductor.get_by_hostname(self.context, res = objects.Conductor.get_by_hostname(self.context,
self.hostname) self.hostname)
self.assertEqual(restart_names, res['drivers']) 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, 'all_interfaces', autospec=True)
@mock.patch.object(driver_factory, 'hardware_types', 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,
def test_start_registers_driver_specific_tasks(self, mock_drivers, mock_hw_types, mock_ifaces,
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): class TestInterface(object):
@periodics.periodic(spacing=100500) @periodics.periodic(spacing=100500)
def iface(self): def iface(self):
@ -127,23 +143,12 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
def iface(self): def iface(self):
pass pass
class Driver(object): hw_type = TestHwType()
core_interfaces = []
standard_interfaces = ['iface']
all_interfaces = core_interfaces + standard_interfaces
iface = TestInterface()
@periodics.periodic(spacing=42)
def task(self, context):
pass
driver = Driver()
iface1 = TestInterface() iface1 = TestInterface()
iface2 = TestInterface2() iface2 = TestInterface2()
expected = [iface1.iface, iface2.iface] expected = [iface1.iface, iface2.iface]
mock_drivers.return_value = {'fake1': driver} mock_hw_types.return_value = {'fake1': hw_type}
mock_ifaces.return_value = { mock_ifaces.return_value = {
'management': {'fake1': iface1}, 'management': {'fake1': iface1},
'power': {'fake2': iface2} 'power': {'fake2': iface2}
@ -156,11 +161,11 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
self.assertTrue(periodics.is_periodic(item)) self.assertTrue(periodics.is_periodic(item))
self.assertIn(item, tasks) self.assertIn(item, tasks)
# no periodic tasks from the Driver object # no periodic tasks from the hardware type
self.assertTrue(periodics.is_periodic(driver.task)) self.assertTrue(periodics.is_periodic(hw_type.task))
self.assertNotIn(driver.task, tasks) 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): def test_start_fails_on_missing_driver(self, mock_df):
mock_df.side_effect = exception.DriverNotFound('test') mock_df.side_effect = exception.DriverNotFound('test')
with mock.patch.object(self.dbapi, 'register_conductor') as mock_reg: 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(base_manager, 'LOG')
@mock.patch.object(driver_factory, 'HardwareTypesFactory') @mock.patch.object(driver_factory, 'HardwareTypesFactory')
@mock.patch.object(driver_factory, 'DriverFactory') def test_start_fails_on_hw_types(self, ht_mock, log_mock):
def test_start_fails_on_no_driver_or_hw_types(self, df_mock, ht_mock,
log_mock):
driver_factory_mock = mock.MagicMock(names=[]) driver_factory_mock = mock.MagicMock(names=[])
df_mock.return_value = driver_factory_mock
ht_mock.return_value = driver_factory_mock ht_mock.return_value = driver_factory_mock
self.assertRaises(exception.NoDriversLoaded, self.assertRaises(exception.NoDriversLoaded,
self.service.init_host) self.service.init_host)
self.assertTrue(log_mock.error.called) self.assertTrue(log_mock.error.called)
df_mock.assert_called_once_with()
ht_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, 'LOG')
@mock.patch.object(base_manager.BaseConductorManager, @mock.patch.object(base_manager.BaseConductorManager,
'_register_and_validate_hardware_interfaces') '_register_and_validate_hardware_interfaces')
@ -214,19 +201,6 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
self.assertTrue(log_mock.error.called) self.assertTrue(log_mock.error.called)
del_mock.assert_called_once_with() 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): def test_prevent_double_start(self):
self._start_service() self._start_service()
self.assertRaisesRegex(RuntimeError, 'already running', self.assertRaisesRegex(RuntimeError, 'already running',
@ -283,11 +257,6 @@ class CheckInterfacesTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
'options enabled_boot_interfaces', 'options enabled_boot_interfaces',
base_manager._check_enabled_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): class KeepAliveTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
def test__conductor_service_record_keepalive(self): def test__conductor_service_record_keepalive(self):

View File

@ -4824,7 +4824,7 @@ class ManagerDoSyncPowerStateTestCase(db_base.DbTestCase):
def setUp(self): def setUp(self):
super(ManagerDoSyncPowerStateTestCase, self).setUp() super(ManagerDoSyncPowerStateTestCase, self).setUp()
self.service = manager.ConductorManager('hostname', 'test-topic') 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.power = self.driver.power
self.node = obj_utils.create_test_node( self.node = obj_utils.create_test_node(
self.context, driver='fake-hardware', maintenance=False, self.context, driver='fake-hardware', maintenance=False,
@ -5341,7 +5341,7 @@ class ManagerPowerRecoveryTestCase(mgr_utils.CommonMixIn,
super(ManagerPowerRecoveryTestCase, self).setUp() super(ManagerPowerRecoveryTestCase, self).setUp()
self.service = manager.ConductorManager('hostname', 'test-topic') self.service = manager.ConductorManager('hostname', 'test-topic')
self.service.dbapi = self.dbapi 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.power = self.driver.power
self.task = mock.Mock(spec_set=['context', 'driver', 'node', self.task = mock.Mock(spec_set=['context', 'driver', 'node',
'upgrade_lock', 'shared']) 'upgrade_lock', 'shared'])

View File

@ -76,8 +76,10 @@ class RPCAPITestCase(db_base.DbTestCase):
def test_get_topic_for_known_driver(self): def test_get_topic_for_known_driver(self):
CONF.set_override('host', 'fake-host') CONF.set_override('host', 'fake-host')
self.dbapi.register_conductor({'hostname': 'fake-host', 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') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic')
expected_topic = 'fake-topic.fake-host' expected_topic = 'fake-topic.fake-host'
@ -86,8 +88,10 @@ class RPCAPITestCase(db_base.DbTestCase):
def test_get_topic_for_unknown_driver(self): def test_get_topic_for_unknown_driver(self):
CONF.set_override('host', 'fake-host') CONF.set_override('host', 'fake-host')
self.dbapi.register_conductor({'hostname': 'fake-host', c = self.dbapi.register_conductor({'hostname': 'fake-host',
'drivers': ['other-driver']}) 'drivers': []})
self.dbapi.register_conductor_hardware_interfaces(
c.id, 'other-driver', 'deploy', ['iscsi', 'direct'], 'iscsi')
rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic')
self.assertRaises(exception.NoValidHost, self.assertRaises(exception.NoValidHost,
@ -102,8 +106,10 @@ class RPCAPITestCase(db_base.DbTestCase):
rpcapi.get_topic_for, rpcapi.get_topic_for,
self.fake_node_obj) self.fake_node_obj)
self.dbapi.register_conductor({'hostname': 'fake-host', 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') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic')
expected_topic = 'fake-topic.fake-host' expected_topic = 'fake-topic.fake-host'
@ -112,10 +118,12 @@ class RPCAPITestCase(db_base.DbTestCase):
def test_get_topic_for_driver_known_driver(self): def test_get_topic_for_driver_known_driver(self):
CONF.set_override('host', 'fake-host') CONF.set_override('host', 'fake-host')
self.dbapi.register_conductor({ c = self.dbapi.register_conductor({
'hostname': 'fake-host', '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') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic')
self.assertEqual('fake-topic.fake-host', self.assertEqual('fake-topic.fake-host',
rpcapi.get_topic_for_driver('fake-driver')) rpcapi.get_topic_for_driver('fake-driver'))
@ -124,7 +132,7 @@ class RPCAPITestCase(db_base.DbTestCase):
CONF.set_override('host', 'fake-host') CONF.set_override('host', 'fake-host')
self.dbapi.register_conductor({ self.dbapi.register_conductor({
'hostname': 'fake-host', 'hostname': 'fake-host',
'drivers': ['other-driver'], 'drivers': [],
}) })
rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic')
self.assertRaises(exception.DriverNotFound, self.assertRaises(exception.DriverNotFound,
@ -138,10 +146,12 @@ class RPCAPITestCase(db_base.DbTestCase):
rpcapi.get_topic_for_driver, rpcapi.get_topic_for_driver,
'fake-driver') 'fake-driver')
self.dbapi.register_conductor({ c = self.dbapi.register_conductor({
'hostname': 'fake-host', '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') rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic')
self.assertEqual('fake-topic.fake-host', self.assertEqual('fake-topic.fake-host',
rpcapi.get_topic_for_driver('fake-driver')) rpcapi.get_topic_for_driver('fake-driver'))

View File

@ -10,12 +10,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
from oslo_utils import uuidutils from oslo_utils import uuidutils
from testtools import matchers from testtools import matchers
from ironic.common import context
from ironic.common import driver_factory
from ironic.common import exception from ironic.common import exception
from ironic.common import release_mappings from ironic.common import release_mappings
from ironic.db import api as db_api from ironic.db import api as db_api
@ -69,38 +66,6 @@ class UpgradingTestCase(base.DbTestCase):
self.assertFalse(self.dbapi.check_versions()) 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): class GetNotVersionsTestCase(base.DbTestCase):
def setUp(self): def setUp(self):

View File

@ -193,97 +193,6 @@ class DbConductorTestCase(base.DbTestCase):
self.assertEqual('power on', node3.target_power_state) self.assertEqual('power on', node3.target_power_state)
self.assertIsNone(node3.last_error) 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) @mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_hardware_type_dict_one_host_no_ht(self, mock_utcnow): def test_get_active_hardware_type_dict_one_host_no_ht(self, mock_utcnow):
h = 'fake-host' h = 'fake-host'

View File

@ -16,9 +16,7 @@
import json import json
import mock import mock
import stevedore
from ironic.common import driver_factory
from ironic.common import exception from ironic.common import exception
from ironic.common import raid from ironic.common import raid
from ironic.drivers import base as driver_base from ironic.drivers import base as driver_base
@ -500,156 +498,12 @@ class TestManagementInterface(base.TestCase):
management.get_boot_mode, task_mock) 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): class TestBareDriver(base.TestCase):
def test_class_variables_immutable(self): def test_class_variables(self):
# Test to make sure that our *_interfaces variables in the class don't self.assertEqual(['boot', 'deploy', 'management', 'network', 'power'],
# get modified by a child class driver_base.BareDriver().core_interfaces)
self.assertEqual(('deploy', 'power', 'network'),
driver_base.BareDriver.core_interfaces)
self.assertEqual( self.assertEqual(
('boot', 'console', 'inspect', 'management', 'raid', 'bios', ['bios', 'console', 'inspect', 'raid', 'rescue', 'storage'],
'rescue', 'storage'), driver_base.BareDriver().optional_interfaces
driver_base.BareDriver.standard_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.