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