Add a mechanism to route vendor methods

Each vendor class had to implement a custom method
to invoke their own vendor functionalities, the commit
e129faef17 added a decorator @passthru()
to decorate those vendor methods and guarantee that the errors and
exceptions are being handled accordingly. This patch is extending that
decorator to add some metadata to each vendor method and from that be
able to generically build a way to invoke those without requiring vendor
drivers to implement a custom method for that.

The vendor_passthru() and driver_vendor_passthru() methods were removed
from the VendorInterface base class. Now vendors that wants to implement
a vendor method should now only care about implementing the method itself
and decorating them with the @passthru() or @driver_passthru decorator.

This patch also adds a backward compatibility layer for existing drivers
out of the tree that may be using the old [driver_]vendor_passthru()
methods. If the vendor class contains those methods we are going to
call them just like before, otherwise we are going to use the new
mechanism. All drivers in tree have been ported to the new mechanism.

Implements: blueprint extended-vendor-passthru
Partial-Bug: #1382457
Change-Id: I7770ccd9668d9c03e02f769aff7c54b33734a770
This commit is contained in:
Lucas Alvares Gomes 2014-10-29 11:38:44 +00:00
parent 8dba76ee7f
commit b5a531aa4d
17 changed files with 331 additions and 411 deletions

View File

@ -392,11 +392,34 @@ class ConductorManager(periodic_task.PeriodicTasks):
driver=task.node.driver,
extension='vendor passthru')
task.driver.vendor.validate(task, method=driver_method,
**info)
task.spawn_after(self._spawn_worker,
task.driver.vendor.vendor_passthru, task,
method=driver_method, **info)
vendor_iface = task.driver.vendor
# NOTE(lucasagomes): Before the vendor_passthru() method was
# a self-contained method and each driver implemented their own
# version of it, now we have a common mechanism that drivers
# should use to expose their vendor methods. If a driver still
# have their own vendor_passthru() method we call it to be
# backward compat. This code should be removed once L opens.
if hasattr(vendor_iface, 'vendor_passthru'):
LOG.warning(_LW("Drivers implementing their own version "
"of vendor_passthru() has been deprecated. "
"Please update the code to use the "
"@passthru decorator."))
vendor_iface.validate(task, method=driver_method,
**info)
task.spawn_after(self._spawn_worker,
vendor_iface.vendor_passthru, task,
method=driver_method, **info)
return
try:
vendor_func = vendor_iface.vendor_routes[driver_method]['func']
except KeyError:
raise exception.InvalidParameterValue(
_('No handler for method %s') % driver_method)
vendor_iface.validate(task, method=driver_method, **info)
task.spawn_after(self._spawn_worker, vendor_func, task, **info)
@messaging.expected_exceptions(exception.InvalidParameterValue,
exception.MissingParameterValue,
@ -432,9 +455,28 @@ class ConductorManager(periodic_task.PeriodicTasks):
driver=driver_name,
extension='vendor interface')
return driver.vendor.driver_vendor_passthru(context,
method=driver_method,
**info)
# NOTE(lucasagomes): Before the driver_vendor_passthru()
# method was a self-contained method and each driver implemented
# their own version of it, now we have a common mechanism that
# drivers should use to expose their vendor methods. If a driver
# still have their own driver_vendor_passthru() method we call
# it to be backward compat. This code should be removed
# once L opens.
if hasattr(driver.vendor, 'driver_vendor_passthru'):
LOG.warning(_LW("Drivers implementing their own version "
"of driver_vendor_passthru() has been "
"deprecated. Please update the code to use "
"the @driver_passthru decorator."))
return driver.vendor.driver_vendor_passthru(
context, method=driver_method, **info)
try:
vendor_func = driver.vendor.driver_routes[driver_method]['func']
except KeyError:
raise exception.InvalidParameterValue(
_('No handler for method %s') % driver_method)
return vendor_func(context, **info)
def _provisioning_error_handler(self, e, node, provision_state,
target_provision_state):

View File

@ -18,13 +18,14 @@ Abstract base classes for drivers.
"""
import abc
import collections
import functools
import inspect
from oslo.utils import excutils
import six
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common.i18n import _LE
from ironic.openstack.common import log as logging
@ -355,7 +356,12 @@ class RescueInterface(object):
"""
def passthru(method=None):
# Representation of a single vendor method metadata
VendorMetadata = collections.namedtuple('VendorMetadata', ['method',
'metadata'])
def _passthru(method=None, driver_passthru=False):
"""A decorator for registering a function as a passthru function.
Decorator ensures function is ready to catch any ironic exceptions
@ -367,11 +373,26 @@ def passthru(method=None):
reraised, it won't be handled if it is an async. call.
:param method: an arbitrary string describing the action to be taken.
:param driver_passthru: Boolean value. Whether this is a driver
vendor passthru or a node vendor passthru
method.
"""
def handle_passthru(func):
api_method = method
if api_method is None:
api_method = func.__name__
# NOTE(lucasagomes): It's adding an empty dictionary for now but
# in the following patches this is going to have more metadata
# about the vendor method. For e.g the supported HTTP methods and
# whether it should run asynchrounously or not.
metadata = VendorMetadata(api_method, {})
if driver_passthru:
func._driver_metadata = metadata
else:
func._vendor_metadata = metadata
passthru_logmessage = _LE('vendor_passthru failed with method %s')
@functools.wraps(func)
@ -389,18 +410,46 @@ def passthru(method=None):
return handle_passthru
def passthru(method=None):
return _passthru(method, driver_passthru=False)
def driver_passthru(method=None):
return _passthru(method, driver_passthru=True)
@six.add_metaclass(abc.ABCMeta)
class VendorInterface(object):
"""Interface for all vendor passthru functionality.
Additional vendor- or driver-specific capabilities should be implemented as
private methods and invoked from vendor_passthru() or
driver_vendor_passthru().
Additional vendor- or driver-specific capabilities should be
implemented as a method in the class inheriting from this class and
use the @passthru or @driver_passthru decorators.
driver_vendor_passthru() is a blocking call - methods implemented here
should be short-lived.
Methods decorated with @driver_passthru should be short-lived because
it is a blocking call.
"""
def __new__(cls, *args, **kwargs):
inst = super(VendorInterface, cls).__new__(cls, *args, **kwargs)
inst.vendor_routes = {}
inst.driver_routes = {}
for name, ref in inspect.getmembers(inst, predicate=inspect.ismethod):
vmeta = getattr(ref, '_vendor_metadata', None)
dmeta = getattr(ref, '_driver_metadata', None)
if vmeta is not None:
vmeta.metadata['func'] = ref
inst.vendor_routes.update({vmeta.method: vmeta.metadata})
if dmeta is not None:
dmeta.metadata['func'] = ref
inst.driver_routes.update({dmeta.method: dmeta.metadata})
return inst
@abc.abstractmethod
def get_properties(self):
"""Return the properties of the interface.
@ -422,37 +471,6 @@ class VendorInterface(object):
:raises: MissingParameterValue
"""
@abc.abstractmethod
def vendor_passthru(self, task, **kwargs):
"""Receive requests for vendor-specific actions.
:param task: a task from TaskManager.
:param kwargs: info for action.
:raises: UnsupportedDriverExtension if 'method' can not be mapped to
the supported interfaces.
:raises: InvalidParameterValue if kwargs does not contain 'method'.
:raises: MissingParameterValue when a required parameter is missing
"""
def driver_vendor_passthru(self, context, method, **kwargs):
"""Handle top-level (ie, no node is specified) vendor actions.
These allow a vendor interface to expose additional cross-node API
functionality.
VendorInterface subclasses are explicitly not required to implement
this in order to maintain backwards compatibility with existing
drivers.
:param context: a context for this action.
:param method: an arbitrary string describing the action to be taken.
:param kwargs: arbitrary parameters to the passthru method.
"""
raise exception.UnsupportedDriverExtension(
_('Vendor interface does not support driver vendor_passthru '
'method: %s') % method)
@six.add_metaclass(abc.ABCMeta)
class ManagementInterface(object):

View File

@ -305,13 +305,8 @@ class AgentDeploy(base.DeployInterface):
class AgentVendorInterface(base.VendorInterface):
def __init__(self):
self.vendor_routes = {
'heartbeat': self.heartbeat
}
self.driver_routes = {
'lookup': self._lookup,
}
self.supported_payload_versions = ['2']
self._client = _get_client()
@ -333,31 +328,6 @@ class AgentVendorInterface(base.VendorInterface):
"""
pass
def driver_vendor_passthru(self, task, method, **kwargs):
"""Handle top-level vendor actions.
A node that does not know its UUID should POST to this method.
Given method, route the command to the appropriate private function.
"""
if method not in self.driver_routes:
raise exception.InvalidParameterValue(_('No handler for method %s')
% method)
func = self.driver_routes[method]
return func(task, **kwargs)
def vendor_passthru(self, task, **kwargs):
"""A node that knows its UUID should heartbeat to this passthru.
It will get its node object back, with what Ironic thinks its provision
state is and the target provision state is.
"""
method = kwargs['method'] # Existence checked in mixin
if method not in self.vendor_routes:
raise exception.InvalidParameterValue(_('No handler for method '
'%s') % method)
func = self.vendor_routes[method]
return func(task, **kwargs)
@base.passthru()
def heartbeat(self, task, **kwargs):
"""Method for agent to periodically check in.
@ -465,7 +435,8 @@ class AgentVendorInterface(base.VendorInterface):
node.target_provision_state = states.NOSTATE
node.save()
def _lookup(self, context, **kwargs):
@base.driver_passthru()
def lookup(self, context, **kwargs):
"""Find a matching node for the agent.
Method to be called the first time a ramdisk agent checks in. This

View File

@ -112,13 +112,6 @@ class FakeVendorA(base.VendorInterface):
def first_method(self, task, bar):
return True if bar == 'baz' else False
def vendor_passthru(self, task, **kwargs):
method = kwargs.get('method')
if method == 'first_method':
bar = kwargs.get('bar')
return self._private_method(task, bar)
_raise_unsupported_error(method)
class FakeVendorB(base.VendorInterface):
"""Example implementation of a secondary vendor passthru."""
@ -141,13 +134,6 @@ class FakeVendorB(base.VendorInterface):
def second_method(self, task, bar):
return True if bar == 'kazoo' else False
def vendor_passthru(self, task, **kwargs):
method = kwargs.get('method')
if method == 'second_method':
bar = kwargs.get('bar')
return self._private_method(task, bar)
_raise_unsupported_error(method)
class FakeConsole(base.ConsoleInterface):
"""Example implementation of a simple console interface."""

View File

@ -565,22 +565,10 @@ class IloConsoleInterface(ipmitool.IPMIShellinaboxConsole):
class IloPXEVendorPassthru(pxe.VendorPassthru):
@base.passthru()
def pass_deploy_info(self, *args, **kwargs):
ilo_common.set_boot_device(*args, **kwargs)
def vendor_passthru(self, task, **kwargs):
"""Calls a valid vendor passthru method.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs containing the vendor passthru method and its
parameters.
"""
method = kwargs['method']
if method == 'pass_deploy_info':
self.pass_deploy_info(task.node, 'NETWORK', True)
return super(IloPXEVendorPassthru, self).vendor_passthru(task,
**kwargs)
@base.passthru(method='pass_deploy_info')
def _continue_deploy(self, task, **kwargs):
ilo_common.set_boot_device(task.node, 'NETWORK', True)
super(IloPXEVendorPassthru, self)._continue_deploy(task, **kwargs)
class VendorPassthru(base.VendorInterface):
@ -611,7 +599,7 @@ class VendorPassthru(base.VendorInterface):
"Unsupported method (%s) passed to iLO driver.")
% method)
@base.passthru('pass_deploy_info')
@base.passthru(method='pass_deploy_info')
@task_manager.require_exclusive_lock
def _continue_deploy(self, task, **kwargs):
"""Continues the iSCSI deployment from where ramdisk left off.
@ -660,14 +648,3 @@ class VendorPassthru(base.VendorInterface):
{'instance': node.instance_uuid, 'error': e})
msg = _('Failed to continue iSCSI deployment.')
iscsi_deploy.set_failed_state(task, msg)
def vendor_passthru(self, task, **kwargs):
"""Calls a valid vendor passthru method.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs containing the vendor passthru method and its
parameters.
"""
method = kwargs['method']
if method == 'pass_deploy_info':
self._continue_deploy(task, **kwargs)

View File

@ -891,29 +891,6 @@ class VendorPassthru(base.VendorInterface):
% method)
_parse_driver_info(task.node)
def vendor_passthru(self, task, **kwargs):
"""Receive requests for vendor-specific actions.
Valid methods:
* send_raw
* bmc_reset
:param task: a task from TaskManager.
:param kwargs: info for action.
:raises: InvalidParameterValue if required IPMI credentials
are missing.
:raises: IPMIFailure if ipmitool fails for any method.
:raises: MissingParameterValue when a required parameter is missing
"""
method = kwargs['method']
if method == 'send_raw':
return self.send_raw(task, kwargs.get('raw_bytes'))
elif method == 'bmc_reset':
return self.bmc_reset(task, warm=kwargs.get('warm', True))
class IPMIShellinaboxConsole(base.ConsoleInterface):
"""A ConsoleInterface that uses ipmitool and shellinabox."""

View File

@ -452,7 +452,7 @@ class VendorPassthru(base.VendorInterface):
"Unsupported method (%s) passed to PXE driver.")
% method)
@base.passthru('pass_deploy_info')
@base.passthru(method='pass_deploy_info')
@task_manager.require_exclusive_lock
def _continue_deploy(self, task, **kwargs):
"""Continues the deployment of baremetal node over iSCSI.
@ -494,13 +494,3 @@ class VendorPassthru(base.VendorInterface):
{'instance': node.instance_uuid, 'error': e})
msg = _('Failed to continue iSCSI deployment.')
iscsi_deploy.set_failed_state(task, msg)
def vendor_passthru(self, task, **kwargs):
"""Invokes a vendor passthru method.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: kwargs containins the method name and its parameters.
"""
method = kwargs['method']
if method == 'pass_deploy_info':
self._continue_deploy(task, **kwargs)

View File

@ -55,8 +55,6 @@ CONF.register_opts(opts, opt_group)
LOG = logging.getLogger(__name__)
VENDOR_PASSTHRU_METHODS = ['attach_volume', 'set_node_vlan_id']
_BOOT_DEVICES_MAP = {
boot_devices.DISK: 'hd0',
boot_devices.PXE: 'pxe',
@ -416,19 +414,8 @@ class VendorPassthru(base.VendorInterface):
return COMMON_PROPERTIES
def validate(self, task, **kwargs):
method = kwargs['method']
if method not in VENDOR_PASSTHRU_METHODS:
raise exception.InvalidParameterValue(_(
"Unsupported method (%s) passed to SeaMicro driver.")
% method)
_parse_driver_info(task.node)
def vendor_passthru(self, task, **kwargs):
"""Dispatch vendor specific method calls."""
method = kwargs['method']
if method in VENDOR_PASSTHRU_METHODS:
return getattr(self, method)(task, **kwargs)
@base.passthru()
def set_node_vlan_id(self, task, **kwargs):
"""Sets a untagged vlan id for NIC 0 of node.

View File

@ -22,15 +22,6 @@ from ironic.openstack.common import log as logging
LOG = logging.getLogger(__name__)
def _raise_unsupported_error(method=None):
if method:
raise exception.UnsupportedDriverExtension(_(
"Unsupported method (%s) passed through to vendor extension.")
% method)
raise exception.MissingParameterValue(_(
"Method not specified when calling vendor extension."))
class MixinVendorInterface(base.VendorInterface):
"""Wrapper around multiple VendorInterfaces."""
@ -47,10 +38,53 @@ class MixinVendorInterface(base.VendorInterface):
"""
self.mapping = mapping
self.driver_level_mapping = driver_passthru_mapping or {}
self.vendor_routes = self._build_routes(self.mapping)
self.driver_routes = self._build_routes(self.driver_level_mapping,
driver_passthru=True)
def _map(self, **kwargs):
def _build_routes(self, map_dict, driver_passthru=False):
"""Build the mapping for the vendor calls.
Build the mapping between the given methods and the corresponding
method metadata.
:param map_dict: dict of {'method': interface} specifying how
to map multiple vendor calls to interfaces.
:param driver_passthru: Boolean value. Whether build the mapping
to the node vendor passthru or driver
vendor passthru.
"""
d = {}
for method_name in map_dict:
iface = map_dict[method_name]
if driver_passthru:
driver_methods = iface.driver_routes
else:
driver_methods = iface.vendor_routes
try:
d.update({method_name: driver_methods[method_name]})
except KeyError:
pass
return d
def _get_route(self, **kwargs):
"""Return the driver interface which contains the given method.
:param method: The name of the vendor method.
"""
method = kwargs.get('method')
return self.mapping.get(method) or _raise_unsupported_error(method)
if not method:
raise exception.MissingParameterValue(
_("Method not specified when calling vendor extension."))
try:
route = self.mapping[method]
except KeyError:
raise exception.InvalidParameterValue(
_('No handler for method %s') % method)
return route
def get_properties(self):
"""Return the properties from all the VendorInterfaces.
@ -73,39 +107,9 @@ class MixinVendorInterface(base.VendorInterface):
:raisee: MissingParameterValue if missing parameters in kwargs.
"""
route = self._map(**kwargs)
route = self._get_route(**kwargs)
route.validate(*args, **kwargs)
def vendor_passthru(self, task, **kwargs):
"""Call vendor_passthru on the appropriate interface only.
Returns or raises according to the requested vendor_passthru method.
:raises: UnsupportedDriverExtension if 'method' can not be mapped to
the supported interfaces.
:raises: MissingParameterValue if kwargs does not contain 'method'.
"""
route = self._map(**kwargs)
return route.vendor_passthru(task, **kwargs)
def driver_vendor_passthru(self, context, method, **kwargs):
"""Handle top-level vendor actions.
Call driver_vendor_passthru on a mapped interface based on the
specified method.
Returns or raises according to the requested driver_vendor_passthru
:raises: UnsupportedDriverExtension if 'method' cannot be mapped to
a supported interface.
"""
iface = self.driver_level_mapping.get(method)
if iface is None:
_raise_unsupported_error(method)
return iface.driver_vendor_passthru(context, method, **kwargs)
def get_node_mac_addresses(task):
"""Get all MAC addresses for the ports belonging to this task's node.

View File

@ -538,7 +538,7 @@ class VendorPassthruTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase):
self.context,
node.uuid, 'unsupported_method', info)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.UnsupportedDriverExtension,
self.assertEqual(exception.InvalidParameterValue,
exc.exc_info[0])
node.refresh()
@ -604,20 +604,45 @@ class VendorPassthruTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase):
# Verify reservation has been cleared.
self.assertIsNone(node.reservation)
@mock.patch.object(task_manager, 'acquire')
def test_vendor_passthru_backwards_compat(self, acquire_mock):
node = obj_utils.create_test_node(self.context, driver='fake')
vendor_passthru_ref = mock.Mock()
self._start_service()
driver = mock.Mock()
driver.vendor.vendor_routes = {}
driver.vendor.vendor_passthru = vendor_passthru_ref
task = mock.Mock()
task.node = node
task.driver = driver
acquire_mock.return_value.__enter__.return_value = task
self.service.vendor_passthru(
self.context, node.uuid, 'test_method', {'bar': 'baz'})
task.spawn_after.assert_called_once_with(mock.ANY, vendor_passthru_ref,
task, bar='baz', method='test_method')
def test_driver_vendor_passthru_success(self):
expected = {'foo': 'bar'}
self.driver.vendor = vendor = mock.Mock()
vendor.driver_vendor_passthru.return_value = expected
self.driver.vendor = mock.Mock(spec=drivers_base.VendorInterface)
test_method = mock.MagicMock(return_value=expected)
self.driver.vendor.driver_routes = {'test_method':
{'func': test_method}}
self.service.init_host()
got = self.service.driver_vendor_passthru(self.context,
'fake',
'test_method',
{'test': 'arg'})
# Assert that the vendor interface has no custom
# driver_vendor_passthru()
self.assertFalse(hasattr(self.driver.vendor, 'driver_vendor_passthru'))
self.assertEqual(expected, got)
vendor.driver_vendor_passthru.assert_called_once_with(
mock.ANY,
method='test_method',
test='arg')
test_method.assert_called_once_with(mock.ANY, test='arg')
def test_driver_vendor_passthru_vendor_interface_not_supported(self):
# Test for when no vendor interface is set at all
@ -633,7 +658,7 @@ class VendorPassthruTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase):
self.assertEqual(exception.UnsupportedDriverExtension,
exc.exc_info[0])
def test_driver_vendor_passthru_not_supported(self):
def test_driver_vendor_passthru_method_not_supported(self):
# Test for when the vendor interface is set, but hasn't passed a
# driver_passthru_mapping to MixinVendorInterface
self.service.init_host()
@ -644,7 +669,7 @@ class VendorPassthruTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase):
'test_method',
{})
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.UnsupportedDriverExtension,
self.assertEqual(exception.InvalidParameterValue,
exc.exc_info[0])
def test_driver_vendor_passthru_driver_not_found(self):

View File

@ -473,14 +473,29 @@ class IloPXEVendorPassthruTestCase(db_base.DbTestCase):
self.node = obj_utils.create_test_node(self.context,
driver='pxe_ilo', driver_info=INFO_DICT)
@mock.patch.object(pxe.VendorPassthru, 'vendor_passthru')
def test_vendor_routes(self):
expected = ['pass_deploy_info']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
self.assertIsInstance(vendor_routes, dict)
self.assertEqual(expected, list(vendor_routes))
def test_driver_routes(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
driver_routes = task.driver.vendor.driver_routes
self.assertIsInstance(driver_routes, dict)
self.assertEqual({}, driver_routes)
@mock.patch.object(pxe.VendorPassthru, '_continue_deploy')
@mock.patch.object(ilo_common, 'set_boot_device')
def test_vendorpassthru(self, set_persistent_mock,
pxe_vendorpassthru_mock):
kwargs = {'method': 'pass_deploy_info', 'address': '123456'}
kwargs = {'address': '123456'}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.node.provision_state = states.DEPLOYWAIT
task.driver.vendor.vendor_passthru(task, **kwargs)
task.driver.vendor._continue_deploy(task, **kwargs)
set_persistent_mock.assert_called_with(task.node, 'NETWORK', True)
pxe_vendorpassthru_mock.assert_called_once_with(task, **kwargs)

View File

@ -21,7 +21,6 @@ from ironic.common import keystone
from ironic.common import pxe_utils
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers import base as driver_base
from ironic.drivers.modules import agent
from ironic import objects
from ironic.tests.conductor import utils as mgr_utils
@ -168,7 +167,7 @@ class TestAgentVendor(db_base.DbTestCase):
}
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.InvalidParameterValue,
self.passthru._lookup,
self.passthru.lookup,
task.context,
**kwargs)
@ -193,26 +192,26 @@ class TestAgentVendor(db_base.DbTestCase):
}
find_mock.return_value = self.node
with task_manager.acquire(self.context, self.node.uuid) as task:
node = self.passthru._lookup(task.context, **kwargs)
node = self.passthru.lookup(task.context, **kwargs)
self.assertEqual(self.node, node['node'])
def test_lookup_v2_missing_inventory(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.InvalidParameterValue,
self.passthru._lookup,
self.passthru.lookup,
task.context)
def test_lookup_v2_empty_inventory(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.InvalidParameterValue,
self.passthru._lookup,
self.passthru.lookup,
task.context,
inventory={})
def test_lookup_v2_empty_interfaces(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.NodeNotFound,
self.passthru._lookup,
self.passthru.lookup,
task.context,
version='2',
inventory={'interfaces': []})
@ -348,45 +347,18 @@ class TestAgentVendor(db_base.DbTestCase):
self.passthru.heartbeat(task, **kwargs)
failed_mock.assert_called_once_with(task, mock.ANY)
@mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
'.heartbeat', autospec=True)
def test_vendor_passthru_heartbeat(self, mock_heartbeat):
kwargs = {
'method': 'heartbeat',
}
self.passthru.vendor_routes['heartbeat'] = (
driver_base.passthru('heartbeat')(mock_heartbeat))
with task_manager.acquire(
self.context, self.node['uuid'], shared=True) as task:
self.passthru.vendor_passthru(task, **kwargs)
mock_heartbeat.assert_called_once_with(task, **kwargs)
def test_vendor_passthru_vendor_routes(self):
expected = ['heartbeat']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
self.assertIsInstance(vendor_routes, dict)
self.assertEqual(expected, list(vendor_routes))
@mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
'.heartbeat', autospec=True)
def test_vendor_passthru_heartbeat_ironic_exc(self, mock_heartbeat):
mock_heartbeat.side_effect = exception.IronicException()
kwargs = {
'method': 'heartbeat',
}
self.passthru.vendor_routes['heartbeat'] = (
driver_base.passthru('heartbeat')(mock_heartbeat))
with task_manager.acquire(
self.context, self.node['uuid'], shared=True) as task:
self.assertRaises(exception.IronicException,
self.passthru.vendor_passthru, task, **kwargs)
mock_heartbeat.assert_called_once_with(task, **kwargs)
@mock.patch('ironic.drivers.modules.agent.AgentVendorInterface'
'.heartbeat', autospec=True)
def test_vendor_passthru_heartbeat_exception(self, mock_heartbeat):
mock_heartbeat.side_effect = KeyError()
kwargs = {
'method': 'heartbeat',
}
self.passthru.vendor_routes['heartbeat'] = (
driver_base.passthru('heartbeat')(mock_heartbeat))
with task_manager.acquire(
self.context, self.node['uuid'], shared=True) as task:
self.assertRaises(exception.VendorPassthruException,
self.passthru.vendor_passthru, task, **kwargs)
mock_heartbeat.assert_called_once_with(task, **kwargs)
def test_vendor_passthru_driver_routes(self):
expected = ['lookup']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
driver_routes = task.driver.vendor.driver_routes
self.assertIsInstance(driver_routes, dict)
self.assertEqual(expected, list(driver_routes))

View File

@ -24,30 +24,21 @@ class FakeVendorInterface(driver_base.VendorInterface):
def get_properties(self):
pass
@driver_base.passthru('noexception')
def _noexception(self):
@driver_base.passthru()
def noexception(self):
return "Fake"
@driver_base.passthru('ironicexception')
def _ironicexception(self):
@driver_base.passthru()
def ironicexception(self):
raise exception.IronicException("Fake!")
@driver_base.passthru('normalexception')
def _normalexception(self):
@driver_base.passthru()
def normalexception(self):
raise Exception("Fake!")
def validate(self, task, **kwargs):
pass
def vendor_passthru(self, task, **kwargs):
method = kwargs['method']
if method == "noexception":
self._noexception()
elif method == "ironicexception":
self._ironicexception()
elif method == "normalexception":
self._normalexception()
class PassthruDecoratorTestCase(base.TestCase):
@ -57,17 +48,17 @@ class PassthruDecoratorTestCase(base.TestCase):
driver_base.LOG = mock.Mock()
def test_passthru_noexception(self):
result = self.fvi._noexception()
result = self.fvi.noexception()
self.assertEqual("Fake", result)
def test_passthru_ironicexception(self):
self.assertRaises(exception.IronicException,
self.fvi.vendor_passthru, mock.ANY, method="ironicexception")
self.fvi.ironicexception, mock.ANY)
driver_base.LOG.exception.assert_called_with(
mock.ANY, 'ironicexception')
def test_passthru_nonironicexception(self):
self.assertRaises(exception.VendorPassthruException,
self.fvi.vendor_passthru, mock.ANY, method="normalexception")
self.fvi.normalexception, mock.ANY)
driver_base.LOG.exception.assert_called_with(
mock.ANY, 'normalexception')

View File

@ -1074,10 +1074,8 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
def test_vendor_passthru_call_send_raw_bytes(self, raw_bytes_mock):
with task_manager.acquire(self.context, self.node['uuid'],
shared=False) as task:
self.driver.vendor.vendor_passthru(task,
method='send_raw',
raw_bytes='0x00 0x01')
raw_bytes_mock.assert_called_once_with(task, '0x00 0x01')
self.driver.vendor.send_raw(task, raw_bytes='0x00 0x01')
raw_bytes_mock.assert_called_once_with(task, raw_bytes='0x00 0x01')
def test_vendor_passthru_validate__bmc_reset_good(self):
with task_manager.acquire(self.context, self.node['uuid']) as task:
@ -1096,32 +1094,35 @@ class IPMIToolDriverTestCase(db_base.DbTestCase):
method='bmc_reset',
warm=False)
@mock.patch.object(ipmi.VendorPassthru, 'bmc_reset')
def test_vendor_passthru_call_bmc_reset(self, bmc_mock):
with task_manager.acquire(self.context, self.node['uuid'],
shared=False) as task:
self.driver.vendor.vendor_passthru(task,
method='bmc_reset')
bmc_mock.assert_called_once_with(task, warm=True)
@mock.patch.object(ipmi.VendorPassthru, 'bmc_reset')
def test_vendor_passthru_call_bmc_reset_warm(self, bmc_mock):
with task_manager.acquire(self.context, self.node['uuid'],
shared=False) as task:
self.driver.vendor.vendor_passthru(task,
method='bmc_reset',
warm=True)
self.driver.vendor.bmc_reset(task, warm=True)
bmc_mock.assert_called_once_with(task, warm=True)
@mock.patch.object(ipmi.VendorPassthru, 'bmc_reset')
def test_vendor_passthru_call_bmc_reset_cold(self, bmc_mock):
with task_manager.acquire(self.context, self.node['uuid'],
shared=False) as task:
self.driver.vendor.vendor_passthru(task,
method='bmc_reset',
warm=False)
self.driver.vendor.bmc_reset(task, warm=False)
bmc_mock.assert_called_once_with(task, warm=False)
def test_vendor_passthru_vendor_routes(self):
expected = ['send_raw', 'bmc_reset']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
self.assertIsInstance(vendor_routes, dict)
self.assertEqual(sorted(expected), sorted(vendor_routes))
def test_vendor_passthru_driver_routes(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
driver_routes = task.driver.vendor.driver_routes
self.assertIsInstance(driver_routes, dict)
self.assertEqual({}, driver_routes)
@mock.patch.object(console_utils, 'start_shellinabox_console',
autospec=True)
def test_start_console(self, mock_exec):

View File

@ -606,9 +606,9 @@ class PXEDriverTestCase(db_base.DbTestCase):
fake_deploy))
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.vendor.vendor_passthru(
task, method='pass_deploy_info', address='123456',
iqn='aaa-bbb', key='fake-56789')
task.driver.vendor._continue_deploy(
task, address='123456', iqn='aaa-bbb', key='fake-56789')
self.node.refresh()
self.assertEqual(states.ACTIVE, self.node.provision_state)
self.assertEqual(states.POWER_ON, self.node.power_state)
@ -636,9 +636,9 @@ class PXEDriverTestCase(db_base.DbTestCase):
fake_deploy))
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.vendor.vendor_passthru(
task, method='pass_deploy_info', address='123456',
iqn='aaa-bbb', key='fake-56789')
task.driver.vendor._continue_deploy(
task, address='123456', iqn='aaa-bbb', key='fake-56789')
self.node.refresh()
self.assertEqual(states.DEPLOYFAIL, self.node.provision_state)
self.assertEqual(states.POWER_OFF, self.node.power_state)
@ -662,10 +662,10 @@ class PXEDriverTestCase(db_base.DbTestCase):
fake_deploy))
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.vendor.vendor_passthru(
task, method='pass_deploy_info', address='123456',
iqn='aaa-bbb', key='fake-56789',
error='test ramdisk error')
task.driver.vendor._continue_deploy(
task, address='123456', iqn='aaa-bbb',
key='fake-56789', error='test ramdisk error')
self.node.refresh()
self.assertEqual(states.DEPLOYFAIL, self.node.provision_state)
self.assertEqual(states.POWER_OFF, self.node.power_state)
@ -680,10 +680,10 @@ class PXEDriverTestCase(db_base.DbTestCase):
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.vendor.vendor_passthru(
task, method='pass_deploy_info', address='123456',
iqn='aaa-bbb', key='fake-56789',
error='test ramdisk error')
task.driver.vendor._continue_deploy(
task, address='123456', iqn='aaa-bbb',
key='fake-56789', error='test ramdisk error')
self.node.refresh()
self.assertEqual('FAKE', self.node.provision_state)
self.assertEqual(states.POWER_ON, self.node.power_state)
@ -692,13 +692,28 @@ class PXEDriverTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid) as task:
with mock.patch.object(task.driver.vendor,
'_continue_deploy') as _cont_deploy_mock:
task.driver.vendor.vendor_passthru(task,
method='pass_deploy_info', address='123456', iqn='aaa-bbb',
key='fake-56789')
task.driver.vendor._continue_deploy(
task, address='123456', iqn='aaa-bbb', key='fake-56789')
# lock elevated w/o exception
self.assertEqual(1, _cont_deploy_mock.call_count,
"_continue_deploy was not called once.")
def test_vendor_routes(self):
expected = ['pass_deploy_info']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
self.assertIsInstance(vendor_routes, dict)
self.assertEqual(expected, list(vendor_routes))
def test_driver_routes(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
driver_routes = task.driver.vendor.driver_routes
self.assertIsInstance(driver_routes, dict)
self.assertEqual({}, driver_routes)
@mock.patch.object(utils, 'unlink_without_raise')
@mock.patch.object(iscsi_deploy, 'destroy_images')

View File

@ -293,6 +293,21 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
shared=True) as task:
self.assertEqual(expected, task.driver.get_properties())
def test_vendor_routes(self):
expected = ['set_node_vlan_id', 'attach_volume']
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
vendor_routes = task.driver.vendor.vendor_routes
self.assertIsInstance(vendor_routes, dict)
self.assertEqual(sorted(expected), sorted(vendor_routes))
def test_driver_routes(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
driver_routes = task.driver.vendor.driver_routes
self.assertIsInstance(driver_routes, dict)
self.assertEqual({}, driver_routes)
@mock.patch.object(seamicro, '_parse_driver_info')
def test_power_interface_validate_good(self, parse_drv_info_mock):
with task_manager.acquire(self.context, self.node['uuid'],
@ -390,26 +405,17 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
def test_vendor_passthru_validate_good(self, mock_info):
with task_manager.acquire(self.context, self.node['uuid'],
shared=True) as task:
for method in seamicro.VENDOR_PASSTHRU_METHODS:
for method in task.driver.vendor.vendor_routes:
task.driver.vendor.validate(task, **{'method': method})
self.assertEqual(len(seamicro.VENDOR_PASSTHRU_METHODS),
self.assertEqual(len(task.driver.vendor.vendor_routes),
mock_info.call_count)
@mock.patch.object(seamicro, '_parse_driver_info')
def test_vendor_passthru_validate_fail(self, mock_info):
with task_manager.acquire(self.context, self.node['uuid'],
shared=True) as task:
self.assertRaises(exception.InvalidParameterValue,
task.driver.vendor.validate,
task, **{'method': 'invalid_method'})
self.assertFalse(mock_info.called)
@mock.patch.object(seamicro, '_parse_driver_info')
def test_vendor_passthru_validate_parse_driver_info_fail(self, mock_info):
mock_info.side_effect = exception.InvalidParameterValue("bad")
with task_manager.acquire(self.context, self.node['uuid'],
shared=True) as task:
method = seamicro.VENDOR_PASSTHRU_METHODS[0]
method = list(task.driver.vendor.vendor_routes)[0]
self.assertRaises(exception.InvalidParameterValue,
task.driver.vendor.validate,
task, **{'method': method})
@ -422,8 +428,8 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
kwargs = {'vlan_id': vlan_id, 'method': 'set_node_vlan_id'}
task.driver.vendor.vendor_passthru(task, **kwargs)
kwargs = {'vlan_id': vlan_id}
task.driver.vendor.set_node_vlan_id(task, **kwargs)
mock_get_server.assert_called_once_with(info)
def test_set_node_vlan_id_no_input(self):
@ -431,9 +437,8 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
self.assertRaises(exception.InvalidParameterValue,
task.driver.vendor.vendor_passthru,
task,
**{'method': 'set_node_vlan_id'})
task.driver.vendor.set_node_vlan_id,
task, **{})
@mock.patch.object(seamicro, '_get_server')
def test_set_node_vlan_id_fail(self, mock_get_server):
@ -447,11 +452,10 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
mock_get_server.return_value = server
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
kwargs = {'vlan_id': vlan_id, 'method': 'set_node_vlan_id'}
kwargs = {'vlan_id': vlan_id}
self.assertRaises(exception.IronicException,
task.driver.vendor.vendor_passthru,
task,
**kwargs)
task.driver.vendor.set_node_vlan_id,
task, **kwargs)
mock_get_server.assert_called_once_with(info)
@ -465,8 +469,8 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
kwargs = {'volume_id': volume_id, 'method': 'attach_volume'}
task.driver.vendor.vendor_passthru(task, **kwargs)
kwargs = {'volume_id': volume_id}
task.driver.vendor.attach_volume(task, **kwargs)
mock_get_server.assert_called_once_with(info)
@mock.patch.object(seamicro, '_get_server')
@ -480,11 +484,10 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
kwargs = {'volume_id': volume_id, 'method': 'attach_volume'}
kwargs = {'volume_id': volume_id}
self.assertRaises(exception.InvalidParameterValue,
task.driver.vendor.vendor_passthru,
task,
**kwargs)
task.driver.vendor.attach_volume,
task, **kwargs)
@mock.patch.object(seamicro, '_get_server')
@mock.patch.object(seamicro, '_validate_volume')
@ -501,11 +504,10 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
mock_get_server.return_value = server
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
kwargs = {'volume_id': volume_id, 'method': 'attach_volume'}
kwargs = {'volume_id': volume_id}
self.assertRaises(exception.IronicException,
task.driver.vendor.vendor_passthru,
task,
**kwargs)
task.driver.vendor.attach_volume,
task, **kwargs)
mock_get_server.assert_called_once_with(info)
@ -523,8 +525,8 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
kwargs = {'volume_size': volume_size, 'method': "attach_volume"}
task.driver.vendor.vendor_passthru(task, **kwargs)
kwargs = {'volume_size': volume_size}
task.driver.vendor.attach_volume(task, **kwargs)
mock_get_server.assert_called_once_with(info)
mock_create_volume.assert_called_once_with(info, volume_size)
@ -533,8 +535,8 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, info['uuid'],
shared=False) as task:
self.assertRaises(exception.InvalidParameterValue,
task.driver.vendor.vendor_passthru, task,
**{'method': 'attach_volume'})
task.driver.vendor.attach_volume, task,
**{})
@mock.patch.object(seamicro, '_get_server')
def test_set_boot_device_good(self, mock_get_server):

View File

@ -48,66 +48,13 @@ class UtilsTestCase(db_base.DbTestCase):
mock_fakea_validate.assert_called_once_with(method='first_method')
def test_vendor_interface_validate_bad_method(self):
self.assertRaises(exception.UnsupportedDriverExtension,
self.assertRaises(exception.InvalidParameterValue,
self.driver.vendor.validate, method='fake_method')
def test_vendor_interface_validate_none_method(self):
self.assertRaises(exception.InvalidParameterValue,
self.assertRaises(exception.MissingParameterValue,
self.driver.vendor.validate)
@mock.patch.object(fake.FakeVendorA, 'vendor_passthru')
@mock.patch.object(fake.FakeVendorB, 'vendor_passthru')
def test_vendor_interface_route_valid_method(self, mock_fakeb_vendor,
mock_fakea_vendor):
self.driver.vendor.vendor_passthru('task',
method='first_method',
param1='fake1', param2='fake2')
mock_fakea_vendor.assert_called_once_with('task',
method='first_method',
param1='fake1', param2='fake2')
self.driver.vendor.vendor_passthru('task',
method='second_method',
param1='fake1', param2='fake2')
mock_fakeb_vendor.assert_called_once_with('task',
method='second_method',
param1='fake1', param2='fake2')
def test_driver_passthru_mixin_success(self):
vendor_a = fake.FakeVendorA()
vendor_a.driver_vendor_passthru = mock.Mock()
vendor_b = fake.FakeVendorB()
vendor_b.driver_vendor_passthru = mock.Mock()
driver_vendor_mapping = {
'method_a': vendor_a,
'method_b': vendor_b,
}
mixed_vendor = driver_utils.MixinVendorInterface(
{},
driver_vendor_mapping)
mixed_vendor.driver_vendor_passthru('context',
'method_a',
param1='p1')
vendor_a.driver_vendor_passthru.assert_called_once_with(
'context',
'method_a',
param1='p1')
def test_driver_passthru_mixin_unsupported(self):
mixed_vendor = driver_utils.MixinVendorInterface({}, {})
self.assertRaises(exception.UnsupportedDriverExtension,
mixed_vendor.driver_vendor_passthru,
'context',
'fake_method',
param='p1')
def test_driver_passthru_mixin_unspecified(self):
mixed_vendor = driver_utils.MixinVendorInterface({})
self.assertRaises(exception.UnsupportedDriverExtension,
mixed_vendor.driver_vendor_passthru,
'context',
'fake_method',
param='p1')
def test_get_node_mac_addresses(self):
ports = []
ports.append(