Merge "Support defining and loading hardware types"
This commit is contained in:
commit
000697716f
@ -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
|
||||
|
@ -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(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)
|
||||
def _attach_interfaces_to_driver(bare_driver, node, driver_or_hw_type):
|
||||
"""Attach interface implementations to a bare driver object.
|
||||
|
||||
network_iface = node.network_interface
|
||||
network_factory = NetworkInterfaceFactory()
|
||||
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).
|
||||
|
||||
For hardware types, load all interface implementations dynamically.
|
||||
|
||||
:param bare_driver: BareDriver instance to attach interfaces to
|
||||
:param node: Node object
|
||||
:param driver_or_hw_type: classic driver or hardware type instance
|
||||
: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__)
|
||||
|
||||
return impl_instance
|
||||
|
||||
|
||||
def check_and_update_node_interfaces(node):
|
||||
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']}
|
||||
|
||||
return False
|
||||
# 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.
|
||||
|
||||
: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']
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 = [
|
||||
|
72
ironic/drivers/fake_hardware.py
Normal file
72
ironic/drivers/fake_hardware.py
Normal file
@ -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]
|
86
ironic/drivers/hardware_type.py
Normal file
86
ironic/drivers/hardware_type.py
Normal file
@ -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]
|
@ -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://",
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
19
setup.cfg
19
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user