summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/ironic/ironic.conf.sample224
-rw-r--r--ironic/common/driver_factory.py278
-rw-r--r--ironic/common/exception.py25
-rw-r--r--ironic/conductor/manager.py33
-rw-r--r--ironic/conf/default.py112
-rw-r--r--ironic/drivers/fake_hardware.py72
-rw-r--r--ironic/drivers/hardware_type.py86
-rw-r--r--ironic/tests/base.py11
-rw-r--r--ironic/tests/unit/__init__.py4
-rw-r--r--ironic/tests/unit/api/v1/test_nodes.py1
-rw-r--r--ironic/tests/unit/common/test_driver_factory.py172
-rw-r--r--ironic/tests/unit/conductor/test_manager.py8
-rw-r--r--setup.cfg19
13 files changed, 953 insertions, 92 deletions
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample
index ab0d66354..144112b72 100644
--- a/etc/ironic/ironic.conf.sample
+++ b/etc/ironic/ironic.conf.sample
@@ -30,18 +30,152 @@
# developer documentation online. (list value)
#enabled_drivers = pxe_ipmitool
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of hardware types to load during
+# service initialization. Missing hardware types, or hardware
+# types which fail to initialize, will prevent the conductor
+# service from starting. No hardware types are enabled by
+# default now, but in the future this option will default to a
+# recommended set of production-oriented hardware types. A
+# complete list of hardware types present on your system may
+# be found by enumerating the "ironic.hardware.types"
+# entrypoint. (list value)
+#enabled_hardware_types =
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of boot interfaces to load during
+# service initialization. Missing boot interfaces, or boot
+# interfaces which fail to initialize, will prevent the
+# ironic-conductor service from starting. The default value is
+# a recommended set of production-oriented boot interfaces. A
+# complete list of boot interfaces present on your system may
+# be found by enumerating the
+# "ironic.hardware.interfaces.boot" entrypoint. When setting
+# this value, please make sure that every enabled hardware
+# type will have the same set of enabled boot interfaces on
+# every ironic-conductor service. (list value)
+#enabled_boot_interfaces =
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default boot interface to be used for nodes that do
+# not have boot_interface field set. A complete list of boot
+# interfaces present on your system may be found by
+# enumerating the "ironic.hardware.interfaces.boot"
+# entrypoint. (string value)
+#default_boot_interface = <None>
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of console interfaces to load
+# during service initialization. Missing console interfaces,
+# or console interfaces which fail to initialize, will prevent
+# the ironic-conductor service from starting. The default
+# value is a recommended set of production-oriented console
+# interfaces. A complete list of console interfaces present on
+# your system may be found by enumerating the
+# "ironic.hardware.interfaces.console" entrypoint. When
+# setting this value, please make sure that every enabled
+# hardware type will have the same set of enabled console
+# interfaces on every ironic-conductor service. (list value)
+#enabled_console_interfaces = no-console
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default console interface to be used for nodes that
+# do not have console_interface field set. A complete list of
+# console interfaces present on your system may be found by
+# enumerating the "ironic.hardware.interfaces.console"
+# entrypoint. (string value)
+#default_console_interface = <None>
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of deploy interfaces to load during
+# service initialization. Missing deploy interfaces, or deploy
+# interfaces which fail to initialize, will prevent the
+# ironic-conductor service from starting. The default value is
+# a recommended set of production-oriented deploy interfaces.
+# A complete list of deploy interfaces present on your system
+# may be found by enumerating the
+# "ironic.hardware.interfaces.deploy" entrypoint. When setting
+# this value, please make sure that every enabled hardware
+# type will have the same set of enabled deploy interfaces on
+# every ironic-conductor service. (list value)
+#enabled_deploy_interfaces =
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default deploy interface to be used for nodes that
+# do not have deploy_interface field set. A complete list of
+# deploy interfaces present on your system may be found by
+# enumerating the "ironic.hardware.interfaces.deploy"
+# entrypoint. (string value)
+#default_deploy_interface = <None>
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of inspect interfaces to load
+# during service initialization. Missing inspect interfaces,
+# or inspect interfaces which fail to initialize, will prevent
+# the ironic-conductor service from starting. The default
+# value is a recommended set of production-oriented inspect
+# interfaces. A complete list of inspect interfaces present on
+# your system may be found by enumerating the
+# "ironic.hardware.interfaces.inspect" entrypoint. When
+# setting this value, please make sure that every enabled
+# hardware type will have the same set of enabled inspect
+# interfaces on every ironic-conductor service. (list value)
+#enabled_inspect_interfaces = no-inspect
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default inspect interface to be used for nodes that
+# do not have inspect_interface field set. A complete list of
+# inspect interfaces present on your system may be found by
+# enumerating the "ironic.hardware.interfaces.inspect"
+# entrypoint. (string value)
+#default_inspect_interface = <None>
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of management interfaces to load
+# during service initialization. Missing management
+# interfaces, or management interfaces which fail to
+# initialize, will prevent the ironic-conductor service from
+# starting. The default value is a recommended set of
+# production-oriented management interfaces. A complete list
+# of management interfaces present on your system may be found
+# by enumerating the "ironic.hardware.interfaces.management"
+# entrypoint. When setting this value, please make sure that
+# every enabled hardware type will have the same set of
+# enabled management interfaces on every ironic-conductor
+# service. (list value)
+#enabled_management_interfaces =
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default management interface to be used for nodes
+# that do not have management_interface field set. A complete
+# list of management interfaces present on your system may be
+# found by enumerating the
+# "ironic.hardware.interfaces.management" entrypoint. (string
+# value)
+#default_management_interface = <None>
+
# Specify the list of network interfaces to load during
# service initialization. Missing network interfaces, or
# network interfaces which fail to initialize, will prevent
-# the conductor service from starting. The option default is a
-# recommended set of production-oriented network interfaces. A
-# complete list of network interfaces present on your system
-# may be found by enumerating the
-# "ironic.hardware.interfaces.network" entrypoint. This value
-# must be the same on all ironic-conductor and ironic-api
-# services, because it is used by ironic-api service to
-# validate a new or updated node's network_interface value.
-# (list value)
+# the ironic-conductor service from starting. The default
+# value is a recommended set of production-oriented network
+# interfaces. A complete list of network interfaces present on
+# your system may be found by enumerating the
+# "ironic.hardware.interfaces.network" entrypoint. When
+# setting this value, please make sure that every enabled
+# hardware type will have the same set of enabled network
+# interfaces on every ironic-conductor service. (list value)
#enabled_network_interfaces = flat,noop
# Default network interface to be used for nodes that do not
@@ -51,6 +185,78 @@
# entrypoint. (string value)
#default_network_interface = <None>
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of power interfaces to load during
+# service initialization. Missing power interfaces, or power
+# interfaces which fail to initialize, will prevent the
+# ironic-conductor service from starting. The default value is
+# a recommended set of production-oriented power interfaces. A
+# complete list of power interfaces present on your system may
+# be found by enumerating the
+# "ironic.hardware.interfaces.power" entrypoint. When setting
+# this value, please make sure that every enabled hardware
+# type will have the same set of enabled power interfaces on
+# every ironic-conductor service. (list value)
+#enabled_power_interfaces =
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default power interface to be used for nodes that do
+# not have power_interface field set. A complete list of power
+# interfaces present on your system may be found by
+# enumerating the "ironic.hardware.interfaces.power"
+# entrypoint. (string value)
+#default_power_interface = <None>
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of raid interfaces to load during
+# service initialization. Missing raid interfaces, or raid
+# interfaces which fail to initialize, will prevent the
+# ironic-conductor service from starting. The default value is
+# a recommended set of production-oriented raid interfaces. A
+# complete list of raid interfaces present on your system may
+# be found by enumerating the
+# "ironic.hardware.interfaces.raid" entrypoint. When setting
+# this value, please make sure that every enabled hardware
+# type will have the same set of enabled raid interfaces on
+# every ironic-conductor service. (list value)
+#enabled_raid_interfaces = no-raid
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default raid interface to be used for nodes that do
+# not have raid_interface field set. A complete list of raid
+# interfaces present on your system may be found by
+# enumerating the "ironic.hardware.interfaces.raid"
+# entrypoint. (string value)
+#default_raid_interface = <None>
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Specify the list of vendor interfaces to load during
+# service initialization. Missing vendor interfaces, or vendor
+# interfaces which fail to initialize, will prevent the
+# ironic-conductor service from starting. The default value is
+# a recommended set of production-oriented vendor interfaces.
+# A complete list of vendor interfaces present on your system
+# may be found by enumerating the
+# "ironic.hardware.interfaces.vendor" entrypoint. When setting
+# this value, please make sure that every enabled hardware
+# type will have the same set of enabled vendor interfaces on
+# every ironic-conductor service. (list value)
+#enabled_vendor_interfaces = no-vendor
+
+# WARNING: This configuration option is part of the incomplete
+# driver composition work, changing it's setting has no
+# effect. Default vendor interface to be used for nodes that
+# do not have vendor_interface field set. A complete list of
+# vendor interfaces present on your system may be found by
+# enumerating the "ironic.hardware.interfaces.vendor"
+# entrypoint. (string value)
+#default_vendor_interface = <None>
+
# Used if there is a formatting error when generating an
# exception message (a programming error). If True, raise an
# exception; if False, use the unformatted message. (boolean
diff --git a/ironic/common/driver_factory.py b/ironic/common/driver_factory.py
index 64ffc377d..fead4ef54 100644
--- a/ironic/common/driver_factory.py
+++ b/ironic/common/driver_factory.py
@@ -23,6 +23,8 @@ from ironic.common import exception
from ironic.common.i18n import _LI, _LW
from ironic.conf import CONF
from ironic.drivers import base as driver_base
+from ironic.drivers import fake_hardware
+from ironic.drivers import hardware_type
LOG = log.getLogger(__name__)
@@ -34,71 +36,240 @@ def build_driver_for_task(task, driver_name=None):
"""Builds a composable driver for a given task.
Starts with a `BareDriver` object, and attaches implementations of the
- various driver interfaces to it. Currently these all come from the
- monolithic driver singleton, but later will come from separate
- driver factories and configurable via the database.
+ various driver interfaces to it. For classic drivers these all come from
+ the monolithic driver singleton, for hardware types - from separate
+ driver factories and are configurable via the database.
:param task: The task containing the node to build a driver for.
- :param driver_name: The name of the monolithic driver to use as a base,
- if different than task.node.driver.
+ :param driver_name: The name of the classic driver or hardware type to use
+ as a base, if different than task.node.driver.
:returns: A driver object for the task.
- :raises: DriverNotFound if node.driver could not be
- found in the "ironic.drivers" namespace.
+ :raises: DriverNotFound if node.driver could not be found in either
+ "ironic.drivers" or "ironic.hardware.types" namespaces.
:raises: InterfaceNotFoundInEntrypoint if some node interfaces are set
to invalid or unsupported values.
+ :raises: IncompatibleInterface if driver is a hardware type and
+ the requested implementation is not compatible with it.
"""
node = task.node
- check_and_update_node_interfaces(node)
- driver = driver_base.BareDriver()
- _attach_interfaces_to_driver(driver, node, driver_name=driver_name)
- return driver
+ driver_name = driver_name or node.driver
+
+ driver_or_hw_type = get_driver_or_hardware_type(driver_name)
+ check_and_update_node_interfaces(node, driver_or_hw_type=driver_or_hw_type)
+
+ bare_driver = driver_base.BareDriver()
+ _attach_interfaces_to_driver(bare_driver, node, driver_or_hw_type)
+
+ return bare_driver
+
+
+def _attach_interfaces_to_driver(bare_driver, node, driver_or_hw_type):
+ """Attach interface implementations to a bare driver object.
+ For classic drivers, copies implementations from the singleton driver
+ object, then attaches the dynamic interfaces (network_interface for classic
+ drivers, all interfaces for dynamic drivers made of hardware types).
-def _attach_interfaces_to_driver(driver, node, driver_name=None):
- driver_singleton = get_driver(driver_name or node.driver)
- for iface in driver_singleton.all_interfaces:
- impl = getattr(driver_singleton, iface, None)
- setattr(driver, iface, impl)
+ For hardware types, load all interface implementations dynamically.
- network_iface = node.network_interface
- network_factory = NetworkInterfaceFactory()
+ :param bare_driver: BareDriver instance to attach interfaces to
+ :param node: Node object
+ :param driver_or_hw_type: classic driver or hardware type instance
+ :raises: InterfaceNotFoundInEntrypoint if the entry point was not found.
+ :raises: IncompatibleInterface if driver is a hardware type and
+ the requested implementation is not compatible with it.
+ """
+ if isinstance(driver_or_hw_type, hardware_type.AbstractHardwareType):
+ # For hardware types all interfaces are dynamic
+ dynamic_interfaces = _INTERFACE_LOADERS
+ else:
+ # Copy implementations from the classic driver singleton
+ for iface in driver_or_hw_type.all_interfaces:
+ impl = getattr(driver_or_hw_type, iface, None)
+ setattr(bare_driver, iface, impl)
+
+ # NOTE(dtantsur): only network interface is dynamic for classic
+ # drivers, thus it requires separate treatment.
+ dynamic_interfaces = ['network']
+
+ for iface in dynamic_interfaces:
+ impl_name = getattr(node, '%s_interface' % iface)
+ impl = _get_interface(driver_or_hw_type, iface, impl_name)
+ setattr(bare_driver, iface, impl)
+
+
+def _get_interface(driver_or_hw_type, interface_type, interface_name):
+ """Get interface implementation instance.
+
+ For hardware types also validates compatibility.
+
+ :param driver_or_hw_type: a hardware type or classic driver instance.
+ :param interface_type: name of the interface type (e.g. 'boot').
+ :param interface_name: name of the interface implementation from an
+ appropriate entry point
+ (ironic.hardware.interfaces.<interface type>).
+ :returns: instance of the requested interface implementation.
+ :raises: InterfaceNotFoundInEntrypoint if the entry point was not found.
+ :raises: IncompatibleInterface if driver_or_hw_type is a hardware type and
+ the requested implementation is not compatible with it.
+ """
+ factory = _INTERFACE_LOADERS[interface_type]()
try:
- net_driver = network_factory.get_driver(network_iface)
+ impl_instance = factory.get_driver(interface_name)
except KeyError:
raise exception.InterfaceNotFoundInEntrypoint(
- iface=network_iface,
- entrypoint=network_factory._entrypoint_name,
- valid=network_factory.names)
- driver.network = net_driver
+ iface=interface_name,
+ entrypoint=factory._entrypoint_name,
+ valid=factory.names)
+
+ if not isinstance(driver_or_hw_type, hardware_type.AbstractHardwareType):
+ # NOTE(dtantsur): classic drivers do not have notion of compatibility
+ return impl_instance
+
+ if isinstance(driver_or_hw_type, fake_hardware.FakeHardware):
+ # NOTE(dtantsur): special-case fake hardware type to allow testing with
+ # any combinations of interface implementations.
+ return impl_instance
+ supported_impls = getattr(driver_or_hw_type,
+ 'supported_%s_interfaces' % interface_type)
+ if type(impl_instance) not in supported_impls:
+ raise exception.IncompatibleInterface(
+ interface_type=interface_type, interface_impl=impl_instance,
+ hardware_type=driver_or_hw_type.__class__.__name__)
-def check_and_update_node_interfaces(node):
+ return impl_instance
+
+
+def _default_interface(hardware_type, interface_type, factory):
+ """Calculate and return the default interface implementation.
+
+ Finds the first implementation that is supported by the hardware type
+ and is enabled in the configuration.
+
+ :param hardware_type: hardware type instance.
+ :param interface_type: type of the interface (e.g. 'boot').
+ :param factory: interface factory class to use for loading implementations.
+ :returns: an entrypoint name of the calculated default implementation
+ or None if no default implementation can be found.
+ :raises: InterfaceNotFoundInEntrypoint if the entry point was not found.
+ """
+ supported = getattr(hardware_type,
+ 'supported_%s_interfaces' % interface_type)
+ # Mapping of classes to entry points
+ enabled = {obj.__class__: name for (name, obj) in factory().items()}
+
+ # Order of the supported list matters
+ for impl_class in supported:
+ try:
+ return enabled[impl_class]
+ except KeyError:
+ pass
+
+
+def check_and_update_node_interfaces(node, driver_or_hw_type=None):
"""Ensure that node interfaces (e.g. for creation or updating) are valid.
- Updates interfaces with calculated defaults, if they are not provided.
+ Updates (but doesn't save to the database) hardware interfaces with
+ calculated defaults, if they are not provided.
+
+ This function is run on node updating and creation, as well as each time
+ a driver instance is built for a node.
:param node: node object to check and potentially update
- :raises: InterfaceNotFoundInEntrypoint on validation failure
+ :param driver_or_hw_type: classic driver or hardware type instance object;
+ will be detected from node.driver if missing
:returns: True if any changes were made to the node, otherwise False
+ :raises: InterfaceNotFoundInEntrypoint on validation failure
+ :raises: NoValidDefaultForInterface if the default value cannot be
+ calculated and is not provided in the configuration
+ :raises: DriverNotFound if the node's driver or hardware type is not found
"""
- # NOTE(dtantsur): objects raise NotImplementedError on accessing fields
- # that are known, but missing from an object. Thus, we cannot just use
- # getattr(node, 'network_interface', None) here.
- if 'network_interface' in node and node.network_interface is not None:
- if node.network_interface not in CONF.enabled_network_interfaces:
- raise exception.InterfaceNotFoundInEntrypoint(
- iface=node.network_interface,
- entrypoint=NetworkInterfaceFactory._entrypoint_name,
- valid=NetworkInterfaceFactory().names)
+ if driver_or_hw_type is None:
+ driver_or_hw_type = get_driver_or_hardware_type(node.driver)
+ is_hardware_type = isinstance(driver_or_hw_type,
+ hardware_type.AbstractHardwareType)
+
+ # Legacy network interface defaults
+ additional_defaults = {
+ 'network': 'flat' if CONF.dhcp.dhcp_provider == 'neutron' else 'noop'
+ }
+
+ if is_hardware_type:
+ factories = _INTERFACE_LOADERS
else:
- node.network_interface = (
- CONF.default_network_interface or
- ('flat' if CONF.dhcp.dhcp_provider == 'neutron' else 'noop'))
- return True
+ # Only network interface is dynamic for classic drivers
+ factories = {'network': _INTERFACE_LOADERS['network']}
+
+ # Result - whether the node object was modified
+ result = False
+
+ # Walk through all dynamic interfaces and check/update them
+ for iface, factory in factories.items():
+ field_name = '%s_interface' % iface
+ # 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.
+ if field_name in node:
+ impl_name = getattr(node, field_name)
+ if impl_name is not None:
+ # Check that the provided value is correct for this type
+ _get_interface(driver_or_hw_type, iface, impl_name)
+ # Not changing the result, proceeding with the next interface
+ continue
+
+ # The fallback default from the configuration
+ impl_name = getattr(CONF, 'default_%s_interface' % iface)
+ if impl_name is None:
+ impl_name = additional_defaults.get(iface)
+
+ if impl_name is not None:
+ # Check that the default is correct for this type
+ _get_interface(driver_or_hw_type, iface, impl_name)
+ elif is_hardware_type:
+ impl_name = _default_interface(driver_or_hw_type, iface, factory)
+
+ if impl_name is None:
+ raise exception.NoValidDefaultForInterface(
+ interface_type=iface, node=node.uuid, driver=node.driver)
+
+ # Set the calculated default and set result to True
+ setattr(node, field_name, impl_name)
+ result = True
+
+ return result
+
+
+def get_driver_or_hardware_type(name):
+ """Get driver or hardware type by its entry point name.
+
+ First, checks the hardware types namespace, then checks the classic
+ drivers namespace. The first object found is returned.
+
+ :param name: entry point name.
+ :returns: An instance of a hardware type or a classic driver.
+ :raises: DriverNotFound if neither hardware type nor classic driver found.
+ """
+ try:
+ return get_hardware_type(name)
+ except exception.DriverNotFound:
+ return get_driver(name)
+
+
+def get_hardware_type(hardware_type):
+ """Get a hardware type instance by name.
- return False
+ :param hardware_type: the name of the hardware type to find
+ :returns: An instance of ironic.drivers.hardware_type.AbstractHardwareType
+ :raises: DriverNotFound if requested hardware type cannot be found
+ """
+ try:
+ return HardwareTypesFactory().get_driver(hardware_type)
+ except KeyError:
+ 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.
@@ -234,7 +405,7 @@ class BaseDriverFactory(object):
# just in case more than one could not be found ...
names = ', '.join(names)
raise exception.DriverNotFoundInEntrypoint(
- driver_name=names, entrypoint=cls._entrypoint_name)
+ names=names, entrypoint=cls._entrypoint_name)
# warn for any untested/unsupported/deprecated drivers or interfaces
cls._extension_manager.map(cls._extension_manager.names(),
@@ -248,6 +419,10 @@ class BaseDriverFactory(object):
"""The list of driver names available."""
return self._extension_manager.names()
+ def items(self):
+ """Iterator over pairs (name, instance)."""
+ return ((ext.name, ext.obj) for ext in self._extension_manager)
+
def _warn_if_unsupported(ext):
if not ext.obj.supported:
@@ -260,6 +435,21 @@ class DriverFactory(BaseDriverFactory):
_enabled_driver_list_config_option = 'enabled_drivers'
-class NetworkInterfaceFactory(BaseDriverFactory):
- _entrypoint_name = 'ironic.hardware.interfaces.network'
- _enabled_driver_list_config_option = 'enabled_network_interfaces'
+class HardwareTypesFactory(BaseDriverFactory):
+ _entrypoint_name = 'ironic.hardware.types'
+ _enabled_driver_list_config_option = 'enabled_hardware_types'
+
+
+_INTERFACE_LOADERS = {
+ name: type('%sInterfaceFactory' % name.capitalize(),
+ (BaseDriverFactory,),
+ {'_entrypoint_name': 'ironic.hardware.interfaces.%s' % name,
+ '_enabled_driver_list_config_option':
+ 'enabled_%s_interfaces' % name})
+ for name in driver_base.ALL_INTERFACES
+}
+
+
+# TODO(dtantsur): This factory is still used explicitly in many places,
+# refactor them later to use _INTERFACE_LOADERS.
+NetworkInterfaceFactory = _INTERFACE_LOADERS['network']
diff --git a/ironic/common/exception.py b/ironic/common/exception.py
index b0e0c3d04..cfdb6a40e 100644
--- a/ironic/common/exception.py
+++ b/ironic/common/exception.py
@@ -301,13 +301,18 @@ class DHCPLoadError(IronicException):
"reason: %(reason)s")
+# TODO(dtantsur): word "driver" is overused in class names here, and generally
+# means stevedore driver, not ironic driver. Rename them in the future.
+
+
class DriverNotFound(NotFound):
- _msg_fmt = _("Could not find the following driver(s): %(driver_name)s.")
+ _msg_fmt = _("Could not find the following driver(s) or hardware type(s): "
+ "%(driver_name)s.")
class DriverNotFoundInEntrypoint(DriverNotFound):
- _msg_fmt = _("Could not find the following driver(s) in the "
- "'%(entrypoint)s' entrypoint: %(driver_name)s.")
+ _msg_fmt = _("Could not find the following items in the "
+ "'%(entrypoint)s' entrypoint: %(names)s.")
class InterfaceNotFoundInEntrypoint(InvalidParameterValue):
@@ -316,6 +321,17 @@ class InterfaceNotFoundInEntrypoint(InvalidParameterValue):
"are %(valid)s.")
+class IncompatibleInterface(InvalidParameterValue):
+ _msg_fmt = _("%(interface_type)s interface implementation "
+ "'%(interface_impl)s' is not supported by hardware type "
+ "%(hardware_type)s.")
+
+
+class NoValidDefaultForInterface(InvalidParameterValue):
+ _msg_fmt = _("No default value found for %(interface_type)s interface "
+ "for node %(node)s with driver or hardware type %(driver)s.")
+
+
class ImageNotFound(NotFound):
_msg_fmt = _("Image %(image_id)s could not be found.")
@@ -551,7 +567,8 @@ class ConfigInvalid(IronicException):
class DriverLoadError(IronicException):
- _msg_fmt = _("Driver %(driver)s could not be loaded. Reason: %(reason)s.")
+ _msg_fmt = _("Driver, hardware type or interface %(driver)s could not be "
+ "loaded. Reason: %(reason)s.")
class ConsoleError(IronicException):
diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py
index 81931727e..d288de3cb 100644
--- a/ironic/conductor/manager.py
+++ b/ironic/conductor/manager.py
@@ -66,6 +66,7 @@ from ironic.conductor import notification_utils as notify_utils
from ironic.conductor import task_manager
from ironic.conductor import utils
from ironic.conf import CONF
+from ironic.drivers import base as drivers_base
from ironic import objects
from ironic.objects import base as objects_base
@@ -92,7 +93,10 @@ class ConductorManager(base_manager.BaseConductorManager):
@METRICS.timer('ConductorManager.create_node')
@messaging.expected_exceptions(exception.InvalidParameterValue,
- exception.InterfaceNotFoundInEntrypoint)
+ exception.InterfaceNotFoundInEntrypoint,
+ exception.IncompatibleInterface,
+ exception.NoValidDefaultForInterface,
+ exception.DriverNotFound)
def create_node(self, context, node_obj):
"""Create a node in database.
@@ -101,7 +105,12 @@ class ConductorManager(base_manager.BaseConductorManager):
:returns: created node object.
:raises: InterfaceNotFoundInEntrypoint if validation fails for any
dynamic interfaces (e.g. network_interface).
+ :raises: IncompatibleInterface if one or more of the requested
+ interfaces are not compatible with the hardware type.
+ :raises: NoValidDefaultForInterface if no default can be calculated
+ for some interfaces, and explicit values must be provided.
:raises: InvalidParameterValue if some fields fail validation.
+ :raises: DriverNotFound if the driver or hardware type is not found.
"""
LOG.debug("RPC create_node called for node %s.", node_obj.uuid)
driver_factory.check_and_update_node_interfaces(node_obj)
@@ -112,7 +121,10 @@ class ConductorManager(base_manager.BaseConductorManager):
@messaging.expected_exceptions(exception.InvalidParameterValue,
exception.NodeLocked,
exception.InvalidState,
- exception.InterfaceNotFoundInEntrypoint)
+ exception.InterfaceNotFoundInEntrypoint,
+ exception.IncompatibleInterface,
+ exception.NoValidDefaultForInterface,
+ exception.DriverNotFound)
def update_node(self, context, node_obj):
"""Update a node with the supplied data.
@@ -134,17 +146,24 @@ class ConductorManager(base_manager.BaseConductorManager):
if 'maintenance' in delta and not node_obj.maintenance:
node_obj.maintenance_reason = None
- if 'network_interface' in delta:
- allowed_update_states = [states.ENROLL, states.INSPECTING,
- states.MANAGEABLE]
+ # TODO(dtantsur): reconsider allowing changing some (but not all)
+ # interfaces for active nodes in the future.
+ allowed_update_states = [states.ENROLL, states.INSPECTING,
+ states.MANAGEABLE]
+ for iface in drivers_base.ALL_INTERFACES:
+ interface_field = '%s_interface' % iface
+ if interface_field not in delta:
+ continue
+
if not (node_obj.provision_state in allowed_update_states or
node_obj.maintenance):
- action = _("Node %(node)s can not have network_interface "
+ action = _("Node %(node)s can not have %(iface)s "
"updated unless it is in one of allowed "
"(%(allowed)s) states or in maintenance mode.")
raise exception.InvalidState(
action % {'node': node_obj.uuid,
- 'allowed': ', '.join(allowed_update_states)})
+ 'allowed': ', '.join(allowed_update_states),
+ 'iface': interface_field})
driver_factory.check_and_update_node_interfaces(node_obj)
diff --git a/ironic/conf/default.py b/ironic/conf/default.py
index b3f2f07ee..6df090ad0 100644
--- a/ironic/conf/default.py
+++ b/ironic/conf/default.py
@@ -26,6 +26,42 @@ from oslo_utils import netutils
from ironic.common.i18n import _
+
+# TODO(dtantsur): remove the variants with warnings as soon as we support
+# actually creating nodes with hardware types.
+
+_ENABLED_IFACE_HELP = _('Specify the list of {0} interfaces to load during '
+ 'service initialization. Missing {0} interfaces, '
+ 'or {0} interfaces which fail to initialize, will '
+ 'prevent the ironic-conductor service from starting. '
+ 'The default value is a recommended set of '
+ 'production-oriented {0} interfaces. A complete '
+ 'list of {0} interfaces present on your system may '
+ 'be found by enumerating the '
+ '"ironic.hardware.interfaces.{0}" entrypoint. '
+ 'When setting this value, please make sure that '
+ 'every enabled hardware type will have the same '
+ 'set of enabled {0} interfaces on every '
+ 'ironic-conductor service.')
+
+_ENABLED_IFACE_HELP_WITH_WARNING = (
+ _('WARNING: This configuration option is part of the incomplete driver '
+ 'composition work, changing it\'s setting has no effect. ') +
+ _ENABLED_IFACE_HELP
+)
+
+_DEFAULT_IFACE_HELP = _('Default {0} interface to be used for nodes that '
+ 'do not have {0}_interface field set. A complete '
+ 'list of {0} interfaces present on your system may '
+ 'be found by enumerating the '
+ '"ironic.hardware.interfaces.{0}" entrypoint.')
+
+_DEFAULT_IFACE_HELP_WITH_WARNING = (
+ _('WARNING: This configuration option is part of the incomplete driver '
+ 'composition work, changing it\'s setting has no effect. ') +
+ _DEFAULT_IFACE_HELP
+)
+
api_opts = [
cfg.StrOpt(
'auth_strategy',
@@ -57,27 +93,67 @@ driver_opts = [
'be found by enumerating the "ironic.drivers" '
'entrypoint. An example may be found in the '
'developer documentation online.')),
+ cfg.ListOpt('enabled_hardware_types',
+ default=[],
+ help=_('WARNING: This configuration option is part of the '
+ 'incomplete driver composition work, changing it\'s '
+ 'setting has no effect. '
+ 'Specify the list of hardware types to load during '
+ 'service initialization. Missing hardware types, or '
+ 'hardware types which fail to initialize, will prevent '
+ 'the conductor service from starting. No hardware '
+ 'types are enabled by default now, but in the future '
+ 'this option will default to a recommended set of '
+ 'production-oriented hardware types. '
+ 'A complete list of hardware types present on your '
+ 'system may be found by enumerating the '
+ '"ironic.hardware.types" entrypoint.')),
+ # TODO(dtantsur): populate with production-ready values
+ cfg.ListOpt('enabled_boot_interfaces',
+ default=[],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('boot')),
+ cfg.StrOpt('default_boot_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('boot')),
+ cfg.ListOpt('enabled_console_interfaces',
+ default=['no-console'],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('console')),
+ cfg.StrOpt('default_console_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('console')),
+ cfg.ListOpt('enabled_deploy_interfaces',
+ default=[],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('deploy')),
+ cfg.StrOpt('default_deploy_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('deploy')),
+ cfg.ListOpt('enabled_inspect_interfaces',
+ default=['no-inspect'],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('inspect')),
+ cfg.StrOpt('default_inspect_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('inspect')),
+ cfg.ListOpt('enabled_management_interfaces',
+ default=[],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('management')),
+ cfg.StrOpt('default_management_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('management')),
cfg.ListOpt('enabled_network_interfaces',
default=['flat', 'noop'],
- help=_('Specify the list of network interfaces to load during '
- 'service initialization. Missing network interfaces, '
- 'or network interfaces which fail to initialize, will '
- 'prevent the conductor service from starting. The '
- 'option default is a recommended set of '
- 'production-oriented network interfaces. A complete '
- 'list of network interfaces present on your system may '
- 'be found by enumerating the '
- '"ironic.hardware.interfaces.network" entrypoint. '
- 'This value must be the same on all ironic-conductor '
- 'and ironic-api services, because it is used by '
- 'ironic-api service to validate a new or updated '
- 'node\'s network_interface value.')),
+ help=_ENABLED_IFACE_HELP.format('network')),
cfg.StrOpt('default_network_interface',
- help=_('Default network interface to be used for nodes that '
- 'do not have network_interface field set. A complete '
- 'list of network interfaces present on your system may '
- 'be found by enumerating the '
- '"ironic.hardware.interfaces.network" entrypoint.'))
+ help=_DEFAULT_IFACE_HELP.format('network')),
+ cfg.ListOpt('enabled_power_interfaces',
+ default=[],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('power')),
+ cfg.StrOpt('default_power_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('power')),
+ cfg.ListOpt('enabled_raid_interfaces',
+ default=['no-raid'],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('raid')),
+ cfg.StrOpt('default_raid_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('raid')),
+ cfg.ListOpt('enabled_vendor_interfaces',
+ default=['no-vendor'],
+ help=_ENABLED_IFACE_HELP_WITH_WARNING.format('vendor')),
+ cfg.StrOpt('default_vendor_interface',
+ help=_DEFAULT_IFACE_HELP_WITH_WARNING.format('vendor')),
]
exc_log_opts = [
diff --git a/ironic/drivers/fake_hardware.py b/ironic/drivers/fake_hardware.py
new file mode 100644
index 000000000..769a6664d
--- /dev/null
+++ b/ironic/drivers/fake_hardware.py
@@ -0,0 +1,72 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Fake hardware type.
+"""
+
+from ironic.drivers import hardware_type
+from ironic.drivers.modules import fake
+
+
+class FakeHardware(hardware_type.AbstractHardwareType):
+ """Fake hardware type.
+
+ This hardware type is special-cased in the driver factory to bypass
+ compatibility verification. Thus, supported_* methods here are only
+ for calculating the defaults, not for actual check.
+
+ All fake implementations are still expected to be enabled in the
+ configuration.
+ """
+
+ @property
+ def supported_boot_interfaces(self):
+ """List of classes of supported boot interfaces."""
+ return [fake.FakeBoot]
+
+ @property
+ def supported_console_interfaces(self):
+ """List of classes of supported console interfaces."""
+ return [fake.FakeConsole]
+
+ @property
+ def supported_deploy_interfaces(self):
+ """List of classes of supported deploy interfaces."""
+ return [fake.FakeDeploy]
+
+ @property
+ def supported_inspect_interfaces(self):
+ """List of classes of supported inspect interfaces."""
+ return [fake.FakeInspect]
+
+ @property
+ def supported_management_interfaces(self):
+ """List of classes of supported management interfaces."""
+ return [fake.FakeManagement]
+
+ @property
+ def supported_power_interfaces(self):
+ """List of classes of supported power interfaces."""
+ return [fake.FakePower]
+
+ @property
+ def supported_raid_interfaces(self):
+ """List of classes of supported raid interfaces."""
+ return [fake.FakeRAID]
+
+ @property
+ def supported_vendor_interfaces(self):
+ """List of classes of supported rescue interfaces."""
+ return [fake.FakeVendorB, fake.FakeVendorA]
diff --git a/ironic/drivers/hardware_type.py b/ironic/drivers/hardware_type.py
new file mode 100644
index 000000000..71287235a
--- /dev/null
+++ b/ironic/drivers/hardware_type.py
@@ -0,0 +1,86 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Abstract base class for all hardware types.
+"""
+
+import abc
+
+import six
+
+from ironic.drivers.modules.network import noop as noop_net
+from ironic.drivers.modules import noop
+
+
+@six.add_metaclass(abc.ABCMeta)
+class AbstractHardwareType(object):
+ """Abstract base class for all hardware types.
+
+ Hardware type is a family of hardware supporting the same set of interfaces
+ from the ironic standpoint. This can be as wide as all hardware supporting
+ the IPMI protocol or as narrow as several hardware models supporting some
+ specific interfaces.
+
+ A hardware type defines an ordered list of supported implementations for
+ each driver interface (power, deploy, etc).
+ """
+
+ supported = True
+ """Whether hardware is supported by the community."""
+
+ # Required hardware interfaces
+
+ @abc.abstractproperty
+ def supported_boot_interfaces(self):
+ """List of supported boot interfaces."""
+
+ @abc.abstractproperty
+ def supported_deploy_interfaces(self):
+ """List of supported deploy interfaces."""
+
+ @abc.abstractproperty
+ def supported_management_interfaces(self):
+ """List of supported management interfaces."""
+
+ @abc.abstractproperty
+ def supported_power_interfaces(self):
+ """List of supported power interfaces."""
+
+ # Optional hardware interfaces
+
+ @property
+ def supported_console_interfaces(self):
+ """List of supported console interfaces."""
+ return [noop.NoConsole]
+
+ @property
+ def supported_inspect_interfaces(self):
+ """List of supported inspect interfaces."""
+ return [noop.NoInspect]
+
+ @property
+ def supported_network_interfaces(self):
+ """List of supported network interfaces."""
+ return [noop_net.NoopNetwork]
+
+ @property
+ def supported_raid_interfaces(self):
+ """List of supported raid interfaces."""
+ return [noop.NoRAID]
+
+ @property
+ def supported_vendor_interfaces(self):
+ """List of supported vendor interfaces."""
+ return [noop.NoVendor]
diff --git a/ironic/tests/base.py b/ironic/tests/base.py
index f0afb5f20..3f5f2ed41 100644
--- a/ironic/tests/base.py
+++ b/ironic/tests/base.py
@@ -40,6 +40,7 @@ from ironic.common import context as ironic_context
from ironic.common import driver_factory
from ironic.common import hash_ring
from ironic.conf import CONF
+from ironic.drivers import base as drivers_base
from ironic.objects import base as objects_base
from ironic.tests.unit import policy_fixture
@@ -112,7 +113,9 @@ class TestCase(testtools.TestCase):
self.policy = self.useFixture(policy_fixture.PolicyFixture())
driver_factory.DriverFactory._extension_manager = None
- driver_factory.NetworkInterfaceFactory._extension_manager = None
+ driver_factory.HardwareTypesFactory._extension_manager = None
+ for factory in driver_factory._INTERFACE_LOADERS.values():
+ factory._extension_manager = None
def _set_config(self):
self.cfg_fixture = self.useFixture(config_fixture.Config(CONF))
@@ -124,8 +127,10 @@ class TestCase(testtools.TestCase):
self.config(provisioning_network=uuidutils.generate_uuid(),
group='neutron')
self.config(enabled_drivers=['fake'])
- self.config(enabled_network_interfaces=['flat', 'noop', 'neutron'],
- default_network_interface=None)
+ self.config(enabled_hardware_types=['fake-hardware'])
+ self.config(enabled_network_interfaces=['flat', 'noop', 'neutron'])
+ for iface in drivers_base.ALL_INTERFACES:
+ self.config(**{'default_%s_interface' % iface: None})
self.set_defaults(host='fake-mini',
debug=True)
self.set_defaults(connection="sqlite://",
diff --git a/ironic/tests/unit/__init__.py b/ironic/tests/unit/__init__.py
index dcc2f0d68..d1cd870f2 100644
--- a/ironic/tests/unit/__init__.py
+++ b/ironic/tests/unit/__init__.py
@@ -34,3 +34,7 @@ eventlet.monkey_patch(os=False)
# at module import time, because we may be using mock decorators in our
# tests that run at import time.
objects.register_all()
+
+# NOTE(dtantsur): this module creates mocks which may be used at random points
+# of time, so it must be imported as early as possible.
+from ironic.tests.unit.drivers import third_party_driver_mocks # noqa
diff --git a/ironic/tests/unit/api/v1/test_nodes.py b/ironic/tests/unit/api/v1/test_nodes.py
index c7a1fe71e..513e0a505 100644
--- a/ironic/tests/unit/api/v1/test_nodes.py
+++ b/ironic/tests/unit/api/v1/test_nodes.py
@@ -1732,6 +1732,7 @@ class TestPost(test_api_base.BaseApiTest):
def setUp(self):
super(TestPost, self).setUp()
+ self.config(enabled_drivers=['fake'])
self.chassis = obj_utils.create_test_chassis(self.context)
p = mock.patch.object(rpcapi.ConductorAPI, 'get_topic_for')
self.mock_gtf = p.start()
diff --git a/ironic/tests/unit/common/test_driver_factory.py b/ironic/tests/unit/common/test_driver_factory.py
index b15f74646..5a0c23dd2 100644
--- a/ironic/tests/unit/common/test_driver_factory.py
+++ b/ironic/tests/unit/common/test_driver_factory.py
@@ -19,6 +19,10 @@ from ironic.common import driver_factory
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers import base as drivers_base
+from ironic.drivers import fake_hardware
+from ironic.drivers import hardware_type
+from ironic.drivers.modules import fake
+from ironic.drivers.modules import noop
from ironic.tests import base
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
@@ -135,9 +139,10 @@ class NetworkInterfaceFactoryTestCase(db_base.DbTestCase):
factory._entrypoint_name)
self.assertEqual(['flat', 'neutron', 'noop'],
sorted(factory._enabled_driver_list))
- # NOTE(jroll) 4 checks, one for the driver we're building and
- # one for each of the 3 network interfaces
- self.assertEqual(4, mock_warn.call_count)
+ # NOTE(jroll) 5 checks, one for the driver we're building and
+ # one for each of the 3 network interfaces, the last - for the fake
+ # hardware type.
+ self.assertEqual(5, mock_warn.call_count)
def test_build_driver_for_task_default_is_none(self):
# flat, neutron, and noop network interfaces are enabled in base test
@@ -232,3 +237,164 @@ class CheckAndUpdateNodeInterfacesTestCase(db_base.DbTestCase):
self.assertRaises(exception.InterfaceNotFoundInEntrypoint,
driver_factory.check_and_update_node_interfaces,
node)
+
+
+class TestFakeHardware(hardware_type.AbstractHardwareType):
+ @property
+ def supported_boot_interfaces(self):
+ """List of supported boot interfaces."""
+ return [fake.FakeBoot]
+
+ @property
+ def supported_console_interfaces(self):
+ """List of supported console interfaces."""
+ return [fake.FakeConsole]
+
+ @property
+ def supported_deploy_interfaces(self):
+ """List of supported deploy interfaces."""
+ return [fake.FakeDeploy]
+
+ @property
+ def supported_inspect_interfaces(self):
+ """List of supported inspect interfaces."""
+ return [fake.FakeInspect]
+
+ @property
+ def supported_management_interfaces(self):
+ """List of supported management interfaces."""
+ return [fake.FakeManagement]
+
+ @property
+ def supported_power_interfaces(self):
+ """List of supported power interfaces."""
+ return [fake.FakePower]
+
+ @property
+ def supported_raid_interfaces(self):
+ """List of supported raid interfaces."""
+ return [fake.FakeRAID]
+
+ @property
+ def supported_vendor_interfaces(self):
+ """List of supported rescue interfaces."""
+ return [fake.FakeVendorB, fake.FakeVendorA]
+
+
+OPTIONAL_INTERFACES = set(drivers_base.BareDriver().standard_interfaces) - {
+ 'management', 'boot'}
+
+
+class HardwareTypeLoadTestCase(db_base.DbTestCase):
+
+ def setUp(self):
+ super(HardwareTypeLoadTestCase, self).setUp()
+ self.config(dhcp_provider=None, group='dhcp')
+ self.ifaces = {}
+ self.node_kwargs = {}
+ for iface in drivers_base.ALL_INTERFACES:
+ if iface == 'network':
+ self.ifaces[iface] = 'noop'
+ enabled = ['noop']
+ else:
+ self.ifaces[iface] = 'fake'
+ enabled = ['fake']
+ if iface in OPTIONAL_INTERFACES:
+ enabled.append('no-%s' % iface)
+
+ self.config(**{'enabled_%s_interfaces' % iface: enabled})
+ self.node_kwargs['%s_interface' % iface] = self.ifaces[iface]
+
+ def test_get_hardware_type_existing(self):
+ hw_type = driver_factory.get_hardware_type('fake-hardware')
+ self.assertIsInstance(hw_type, fake_hardware.FakeHardware)
+
+ def test_get_hardware_type_missing(self):
+ self.assertRaises(exception.DriverNotFound,
+ # "fake" is a classic driver
+ driver_factory.get_hardware_type, 'fake')
+
+ def test_get_driver_or_hardware_type(self):
+ hw_type = driver_factory.get_driver_or_hardware_type('fake-hardware')
+ self.assertIsInstance(hw_type, fake_hardware.FakeHardware)
+ driver = driver_factory.get_driver_or_hardware_type('fake')
+ self.assertNotIsInstance(driver, fake_hardware.FakeHardware)
+
+ def test_get_driver_or_hardware_type_missing(self):
+ self.assertRaises(exception.DriverNotFound,
+ driver_factory.get_driver_or_hardware_type,
+ 'banana')
+
+ def test_build_driver_for_task(self):
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ **self.node_kwargs)
+ with task_manager.acquire(self.context, node.id) as task:
+ for iface in drivers_base.ALL_INTERFACES:
+ impl = getattr(task.driver, iface)
+ self.assertIsNotNone(impl)
+
+ def test_build_driver_for_task_incorrect(self):
+ self.node_kwargs['power_interface'] = 'foobar'
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ **self.node_kwargs)
+ self.assertRaises(exception.InterfaceNotFoundInEntrypoint,
+ task_manager.acquire, self.context, node.id)
+
+ def test_build_driver_for_task_fake(self):
+ # Checks that fake driver is compatible with any interfaces, even those
+ # which are not declared in supported_<INTERFACE>_interfaces result.
+ self.node_kwargs['raid_interface'] = 'no-raid'
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware',
+ **self.node_kwargs)
+ with task_manager.acquire(self.context, node.id) as task:
+ for iface in drivers_base.ALL_INTERFACES:
+ impl = getattr(task.driver, iface)
+ self.assertIsNotNone(impl)
+ self.assertIsInstance(task.driver.raid, noop.NoRAID)
+
+ @mock.patch.object(driver_factory, 'get_hardware_type', autospec=True,
+ return_value=TestFakeHardware())
+ def test_build_driver_for_task_not_fake(self, mock_get_hw_type):
+ # Checks that other hardware types do check compatibility.
+ self.node_kwargs['raid_interface'] = 'no-raid'
+ node = obj_utils.create_test_node(self.context, driver='fake-2',
+ **self.node_kwargs)
+ self.assertRaises(exception.IncompatibleInterface,
+ task_manager.acquire, self.context, node.id)
+ mock_get_hw_type.assert_called_once_with('fake-2')
+
+ def test_build_driver_for_task_no_defaults(self):
+ self.config(dhcp_provider=None, group='dhcp')
+ for iface in drivers_base.ALL_INTERFACES:
+ if iface != 'network':
+ self.config(**{'enabled_%s_interfaces' % iface: []})
+ self.config(**{'default_%s_interface' % iface: None})
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware')
+ self.assertRaises(exception.NoValidDefaultForInterface,
+ task_manager.acquire, self.context, node.id)
+
+ def test_build_driver_for_task_calculated_defaults(self):
+ self.config(dhcp_provider=None, group='dhcp')
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware')
+ with task_manager.acquire(self.context, node.id) as task:
+ for iface in drivers_base.ALL_INTERFACES:
+ impl = getattr(task.driver, iface)
+ self.assertIsNotNone(impl)
+
+ def test_build_driver_for_task_configured_defaults(self):
+ for iface in drivers_base.ALL_INTERFACES:
+ self.config(**{'default_%s_interface' % iface: self.ifaces[iface]})
+
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware')
+ with task_manager.acquire(self.context, node.id) as task:
+ for iface in drivers_base.ALL_INTERFACES:
+ impl = getattr(task.driver, iface)
+ self.assertIsNotNone(impl)
+ self.assertEqual(self.ifaces[iface],
+ getattr(task.node, '%s_interface' % iface))
+
+ def test_build_driver_for_task_bad_default(self):
+ self.config(default_power_interface='foobar')
+ node = obj_utils.create_test_node(self.context, driver='fake-hardware')
+ self.assertRaises(exception.InterfaceNotFoundInEntrypoint,
+ task_manager.acquire, self.context, node.id)
diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py
index 5b6394801..c3e2b75a7 100644
--- a/ironic/tests/unit/conductor/test_manager.py
+++ b/ironic/tests/unit/conductor/test_manager.py
@@ -485,10 +485,10 @@ class UpdateNodeTestCase(mgr_utils.ServiceSetUpMixin,
# check that it fails because driver not found
node.driver = wrong_driver
node.driver_info = {}
- self.assertRaises(exception.DriverNotFound,
- self.service.update_node,
- self.context,
- node)
+ exc = self.assertRaises(messaging.rpc.ExpectedException,
+ self.service.update_node,
+ self.context, node)
+ self.assertEqual(exception.DriverNotFound, exc.exc_info[0])
# verify change did not happen
node.refresh()
diff --git a/setup.cfg b/setup.cfg
index 5369d86a4..085e41940 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -97,26 +97,45 @@ ironic.drivers =
pxe_iscsi_cimc = ironic.drivers.pxe:PXEAndCIMCDriver
pxe_agent_cimc = ironic.drivers.agent:AgentAndCIMCDriver
+ironic.hardware.interfaces.boot =
+ fake = ironic.drivers.modules.fake:FakeBoot
+
ironic.hardware.interfaces.console =
+ fake = ironic.drivers.modules.fake:FakeConsole
no-console = ironic.drivers.modules.noop:NoConsole
+ironic.hardware.interfaces.deploy =
+ fake = ironic.drivers.modules.fake:FakeDeploy
+
ironic.hardware.interfaces.inspect =
+ fake = ironic.drivers.modules.fake:FakeInspect
no-inspect = ironic.drivers.modules.noop:NoInspect
+ironic.hardware.interfaces.management =
+ fake = ironic.drivers.modules.fake:FakeManagement
+
ironic.hardware.interfaces.network =
flat = ironic.drivers.modules.network.flat:FlatNetwork
noop = ironic.drivers.modules.network.noop:NoopNetwork
neutron = ironic.drivers.modules.network.neutron:NeutronNetwork
+ironic.hardware.interfaces.power =
+ fake = ironic.drivers.modules.fake:FakePower
+
ironic.hardware.interfaces.raid =
+ fake = ironic.drivers.modules.fake:FakeRAID
no-raid = ironic.drivers.modules.noop:NoRAID
ironic.hardware.interfaces.rescue =
no-rescue = ironic.drivers.modules.noop:NoRescue
ironic.hardware.interfaces.vendor =
+ fake = ironic.drivers.modules.fake:FakeVendorB
no-vendor = ironic.drivers.modules.noop:NoVendor
+ironic.hardware.types =
+ fake-hardware = ironic.drivers.fake_hardware:FakeHardware
+
ironic.database.migration_backend =
sqlalchemy = ironic.db.sqlalchemy.migration