Restructuring driver API and inheritance.
Based on discussions during and after the Ironic team meeting on June 03, regarding support for substantially different driver work flows, this is a re-working of the internal driver API. tl;dr: The strict separation of "control" and "deploy" driver was an artefact of the ipmi + pxe implementation used in nova-baremetal, and does not map on to all drivers. Furthermore, the prior implementation did not accurately represent the separation of "core", "standard", and "vendor-specific" driver functionality. These changes impact the v1 API structure, but since that is largely not implemented yet, this change does not attempt to affect the public API itself. Highlights: - No more deploy + control driver; nodes have one and only one driver. This drops the deploy_driver and deploy_info parameters, and renames control_driver -> driver, and control_info -> driver_info. - Interfaces for core, standard, and vendor functionality now clearly defined in the driver API. - Improve Fake driver to demonstrate use of interfaces. - Convert IPMI and SSH driver classes into interfaces, and move to drivers/modules/ directory. - Stub for the pxe interfaces. - Stub implementations of pxe+ipmi and pxe+ssh drivers. - driver_info field uses more standard names, but requires driver-specific data to be in a nested object. Examples in tests/db/utils.py as before. A separate doc change will follow this to update the API v1 spec. Also includes some cosmetic cleanup of test_ssh.py and test_ipmi.py. Change-Id: I057ede8e07b1b57010e81ef58415debe0ba8b934
This commit is contained in:
parent
9d80a7171b
commit
59d5bea14a
@ -92,11 +92,10 @@ class Connection(object):
|
||||
'uuid': uuidutils.generate_uuid(),
|
||||
'instance_uuid': None,
|
||||
'task_state': states.NOSTATE,
|
||||
'control_driver': 'ipmi',
|
||||
'control_info': { ... },
|
||||
'deploy_driver': 'pxe',
|
||||
'deploy_info': { ... },
|
||||
'driver': 'pxe_ipmi',
|
||||
'driver_info': { ... },
|
||||
'properties': { ... },
|
||||
'extra': { ... },
|
||||
}
|
||||
:returns: A node.
|
||||
"""
|
||||
@ -131,11 +130,10 @@ class Connection(object):
|
||||
:param node: The id or uuid of a node.
|
||||
:param values: Dict of values to update.
|
||||
May be a partial list, eg. when setting the
|
||||
properties for a single driver. For example:
|
||||
properties for a driver. For example:
|
||||
|
||||
{
|
||||
'deploy_driver': 'my-vendor-driver',
|
||||
'deploy_info':
|
||||
'driver_info':
|
||||
{
|
||||
'my-field-1': val1,
|
||||
'my-field-2': val2,
|
||||
|
@ -0,0 +1,34 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from sqlalchemy import Table, MetaData
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
nodes = Table('nodes', meta, autoload=True)
|
||||
|
||||
nodes.c.deploy_driver.drop()
|
||||
nodes.c.deploy_info.drop()
|
||||
nodes.c.control_driver.alter(name='driver')
|
||||
nodes.c.control_info.alter(name='driver_info')
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
raise NotImplementedError('Downgrade from version 005 is unsupported.')
|
@ -98,10 +98,8 @@ class Node(Base):
|
||||
task_start = Column(DateTime, nullable=True)
|
||||
task_state = Column(String(15))
|
||||
properties = Column(JSONEncodedDict)
|
||||
control_driver = Column(String(15))
|
||||
control_info = Column(JSONEncodedDict)
|
||||
deploy_driver = Column(String(15))
|
||||
deploy_info = Column(JSONEncodedDict)
|
||||
driver = Column(String(15))
|
||||
driver_info = Column(JSONEncodedDict)
|
||||
reservation = Column(String(255), nullable=True)
|
||||
extra = Column(JSONEncodedDict)
|
||||
|
||||
|
@ -15,87 +15,226 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
Base classes for drivers.
|
||||
|
||||
All methods take, at minimum, a TaskManager resource and a single Node.
|
||||
Abstract base classes for drivers.
|
||||
"""
|
||||
|
||||
import abc
|
||||
|
||||
|
||||
class DeployDriver(object):
|
||||
"""Base class for image deployment drivers."""
|
||||
class BaseDriver(object):
|
||||
"""Base class for all drivers.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
power = None
|
||||
"""`Core` attribute for managing power state.
|
||||
|
||||
A reference to an instance of :class:PowerInterface.
|
||||
"""
|
||||
|
||||
deploy = None
|
||||
"""`Core` attribute for managing deployments.
|
||||
|
||||
A reference to an instance of :class:DeployInterface.
|
||||
"""
|
||||
|
||||
console = None
|
||||
"""`Standard` attribute for managing console access.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
vendor = None
|
||||
"""Attribute for accessing any vendor-specific extensions.
|
||||
|
||||
A reference to an instance of :class:VendorInterface.
|
||||
May be None, if the driver does not implement any vendor extensions.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class DeployInterface(object):
|
||||
"""Interface for deploy-related actions."""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self):
|
||||
"""Constructor."""
|
||||
def validate(self, node):
|
||||
"""Validate the driver-specific Node deployment info.
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate_driver_info(cls, node):
|
||||
"""Validate the driver-specific Node info.
|
||||
|
||||
This method validates whether the 'deploy_info' property of the
|
||||
supplied nodes contains the required information for this driver to
|
||||
manage the nodes.
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver to
|
||||
deploy images to the node.
|
||||
|
||||
:param node: a single Node to validate.
|
||||
:returns: True or False, depending on capabilities.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def activate_bootloader(self, task, node):
|
||||
"""Prepare the bootloader for this deployment."""
|
||||
def deploy(self, task, node):
|
||||
"""Perform a deployment to a node.
|
||||
|
||||
Given a node with complete metadata, deploy the indicated image
|
||||
to the node.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param node: the Node to act upon.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def deactivate_bootloader(self, task, node):
|
||||
"""Tear down the bootloader for this deployment."""
|
||||
def tear_down(self, task, node):
|
||||
"""Tear down a previous deployment.
|
||||
|
||||
@abc.abstractmethod
|
||||
def activate_node(self, task, node):
|
||||
"""Perform post-power-on operations for this deployment."""
|
||||
Given a node that has been previously deployed to,
|
||||
do all cleanup and tear down necessary to "un-deploy" that node.
|
||||
|
||||
@abc.abstractmethod
|
||||
def deactivate_node(self, task, node):
|
||||
"""Perform pre-power-off operations for this deployment."""
|
||||
:param task: a TaskManager instance.
|
||||
:param node: the Node to act upon.
|
||||
"""
|
||||
|
||||
|
||||
class ControlDriver(object):
|
||||
"""Base class for node control drivers."""
|
||||
class PowerInterface(object):
|
||||
"""Interface for power-related actions."""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self):
|
||||
"""Constructor."""
|
||||
def validate(self, node):
|
||||
"""Validate the driver-specific Node power info.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver to
|
||||
manage the power state of the node.
|
||||
|
||||
:param node: a single Node to validate.
|
||||
:returns: True or False, depending on capabilities.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate_driver_info(cls, node):
|
||||
"""Validate the driver-specific Node info.
|
||||
def get_power_state(self, task, node):
|
||||
"""Return the power state of the node.
|
||||
|
||||
This method validates whether the 'control_info' property of the
|
||||
supplied nodes contains the required information for this driver to
|
||||
manage the nodes.
|
||||
TODO
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_power_state(self, task, node, power_state):
|
||||
"""Set the power state of the node.
|
||||
|
||||
TODO
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def reboot(self, task, node):
|
||||
"""Perform a hard reboot of the node.
|
||||
|
||||
TODO
|
||||
"""
|
||||
|
||||
|
||||
class ConsoleInterface(object):
|
||||
"""Interface for console-related actions."""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate(self, node):
|
||||
"""Validate the driver-specific Node console info.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver to
|
||||
provide console access to the Node.
|
||||
|
||||
:param node: a single Node to validate.
|
||||
:returns: True or False, depending on capabilities.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def start_console(self, task, node):
|
||||
"""Start a remote console for the nodes."""
|
||||
"""Start a remote console for the node.
|
||||
|
||||
TODO
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def stop_console(self, task, node):
|
||||
"""Stop the remote console session for the nodes."""
|
||||
"""Stop the remote console session for the node.
|
||||
|
||||
TODO
|
||||
"""
|
||||
|
||||
|
||||
class RescueInterface(object):
|
||||
"""Interface for rescue-related actions."""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_power_state(self, task, node):
|
||||
"""Return the power state of the nodes."""
|
||||
def validate(self, node):
|
||||
"""Validate the rescue info stored in the node' properties."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_power_state(self, task, node):
|
||||
"""Set the power state of the nodes."""
|
||||
def rescue(self, task, node):
|
||||
"""Boot the node into a rescue environment.
|
||||
|
||||
TODO
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def reboot(self, task, node):
|
||||
"""Perform a hard reboot of the nodes."""
|
||||
def unrescue(self, task, node):
|
||||
"""Tear down the rescue environment, and return to normal.
|
||||
|
||||
TODO
|
||||
"""
|
||||
|
||||
|
||||
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().
|
||||
"""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate(self, node):
|
||||
"""Validate vendor-specific info stored in the node' properties."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def vendor_passthru(self, task, node, *args, **kwargs):
|
||||
"""Receive RPC requests for vendor-specific actions.
|
||||
|
||||
TODO: Mechanism for vendor_passthru and, in particular, where the
|
||||
the interpretation of API requests will live, is still being
|
||||
discussed.
|
||||
|
||||
One possible implementation of this method might look like::
|
||||
|
||||
method = args.get('method')
|
||||
if not method:
|
||||
raise ...
|
||||
|
||||
if hasattr(self, method):
|
||||
callable = getattr(self, method)
|
||||
self.callable(task, node, *args, **kwargs)
|
||||
else:
|
||||
raise ...
|
||||
"""
|
||||
|
@ -19,49 +19,42 @@ Fake drivers used in testing.
|
||||
"""
|
||||
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import fake
|
||||
from ironic.drivers.modules import ipmi
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import ssh
|
||||
|
||||
|
||||
class FakeDeployDriver(base.DeployDriver):
|
||||
class FakeDriver(base.BaseDriver):
|
||||
"""Example implementation of a Driver."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def validate_driver_info(self, node):
|
||||
return True
|
||||
|
||||
def activate_bootloader(self, task, node):
|
||||
pass
|
||||
|
||||
def deactivate_bootloader(self, task, node):
|
||||
pass
|
||||
|
||||
def activate_node(self, task, node):
|
||||
pass
|
||||
|
||||
def deactivate_node(self, task, node):
|
||||
pass
|
||||
self.power = fake.FakePower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.vendor = fake.FakeVendor()
|
||||
|
||||
|
||||
class FakeControlDriver(base.ControlDriver):
|
||||
class FakeIPMIDriver(base.BaseDriver):
|
||||
"""Example implementation of a Driver."""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
self.power = ipmi.IPMIPower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.vendor = self.power
|
||||
|
||||
def validate_driver_info(self, node):
|
||||
return True
|
||||
|
||||
def start_console(self, task, node):
|
||||
pass
|
||||
class FakePXEDriver(base.BaseDriver):
|
||||
"""Example implementation of a Driver."""
|
||||
|
||||
def stop_console(self, task, node):
|
||||
pass
|
||||
def __init__(self):
|
||||
self.power = fake.FakePower()
|
||||
self.deploy = pxe.PXEDeploy()
|
||||
self.rescue = self.deploy
|
||||
|
||||
def attach_console(self, task, node):
|
||||
pass
|
||||
|
||||
def get_power_state(self, task, node):
|
||||
pass
|
||||
class FakeSSHDriver(base.BaseDriver):
|
||||
"""Example implementation of a Driver."""
|
||||
|
||||
def set_power_state(self, task, node):
|
||||
pass
|
||||
|
||||
def reboot(self, task, node):
|
||||
pass
|
||||
def __init__(self):
|
||||
self.power = ssh.SSHPower()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
|
16
ironic/drivers/modules/__init__.py
Normal file
16
ironic/drivers/modules/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
78
ironic/drivers/modules/fake.py
Normal file
78
ironic/drivers/modules/fake.py
Normal file
@ -0,0 +1,78 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
Fake driver interfaces used in testing.
|
||||
"""
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.drivers import base
|
||||
|
||||
|
||||
class FakePower(base.PowerInterface):
|
||||
"""Example implementation of a simple power interface."""
|
||||
|
||||
def validate(self, node):
|
||||
return True
|
||||
|
||||
def get_power_state(self, task, node):
|
||||
return states.NOSTATE
|
||||
|
||||
def set_power_state(self, task, node, power_state):
|
||||
pass
|
||||
|
||||
def reboot(self, task, node):
|
||||
pass
|
||||
|
||||
|
||||
class FakeDeploy(base.DeployInterface):
|
||||
"""Example imlementation of a deploy interface that uses a
|
||||
separate power interface.
|
||||
"""
|
||||
|
||||
def validate(self, node):
|
||||
return True
|
||||
|
||||
def deploy(self, task, node):
|
||||
pass
|
||||
|
||||
def tear_down(self, task, node):
|
||||
pass
|
||||
|
||||
|
||||
class FakeVendor(base.VendorInterface):
|
||||
"""Example implementation of a vendor passthru interface."""
|
||||
|
||||
def validate(self, node):
|
||||
return True
|
||||
|
||||
def _foo(self, task, node, bar):
|
||||
return True if bar else False
|
||||
|
||||
def vendor_passthru(self, task, node, *args, **kwargs):
|
||||
method = kwargs.get('method')
|
||||
if not method:
|
||||
raise exception.IronicException(_(
|
||||
"Invalid vendor passthru, no 'method' specified."))
|
||||
|
||||
if method == 'foo':
|
||||
bar = kwargs.get('bar')
|
||||
return self._foo(task, node, bar)
|
||||
else:
|
||||
raise exception.IronicException(_(
|
||||
"Unsupported method (%s) passed through to vendor extension.")
|
||||
% method)
|
@ -60,7 +60,8 @@ LOG = logging.getLogger(__name__)
|
||||
VALID_BOOT_DEVICES = ['pxe', 'disk', 'safe', 'cdrom', 'bios']
|
||||
|
||||
|
||||
# TODO(deva): use a contextmanager for this, and port it to nova.
|
||||
# TODO(deva): replace this with remove_path_on_error once that is available
|
||||
# https://review.openstack.org/#/c/31030
|
||||
def _make_password_file(password):
|
||||
fd, path = tempfile.mkstemp()
|
||||
os.fchmod(fd, stat.S_IRUSR | stat.S_IWUSR)
|
||||
@ -69,37 +70,38 @@ def _make_password_file(password):
|
||||
return path
|
||||
|
||||
|
||||
def _parse_control_info(node):
|
||||
info = json.loads(node.get('control_info', ''))
|
||||
address = info.get('ipmi_address', None)
|
||||
user = info.get('ipmi_username', None)
|
||||
password = info.get('ipmi_password', None)
|
||||
port = info.get('ipmi_terminal_port', None)
|
||||
def _parse_driver_info(node):
|
||||
driver_info = json.loads(node.get('driver_info', ''))
|
||||
ipmi_info = driver_info.get('ipmi')
|
||||
address = ipmi_info.get('address', None)
|
||||
username = ipmi_info.get('username', None)
|
||||
password = ipmi_info.get('password', None)
|
||||
port = ipmi_info.get('terminal_port', None)
|
||||
|
||||
if not address or not user or not password:
|
||||
if not address or not username or not password:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"IPMI credentials not supplied to IPMI driver."))
|
||||
|
||||
return {
|
||||
'address': address,
|
||||
'user': user,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'port': port,
|
||||
'uuid': node.get('uuid')
|
||||
}
|
||||
|
||||
|
||||
def _exec_ipmitool(c_info, command):
|
||||
def _exec_ipmitool(driver_info, command):
|
||||
args = ['ipmitool',
|
||||
'-I',
|
||||
'lanplus',
|
||||
'-H',
|
||||
c_info['address'],
|
||||
driver_info['address'],
|
||||
'-U',
|
||||
c_info['user'],
|
||||
driver_info['username'],
|
||||
'-f']
|
||||
try:
|
||||
pwfile = _make_password_file(c_info['password'])
|
||||
pwfile = _make_password_file(driver_info['password'])
|
||||
args.append(pwfile)
|
||||
args.extend(command.split(" "))
|
||||
out, err = utils.execute(*args, attempts=3)
|
||||
@ -110,7 +112,7 @@ def _exec_ipmitool(c_info, command):
|
||||
utils.delete_if_exists(pwfile)
|
||||
|
||||
|
||||
def _power_on(c_info):
|
||||
def _power_on(driver_info):
|
||||
"""Turn the power to this node ON."""
|
||||
|
||||
# use mutable objects so the looped method can change them
|
||||
@ -120,7 +122,7 @@ def _power_on(c_info):
|
||||
def _wait_for_power_on(state, retries):
|
||||
"""Called at an interval until the node's power is on."""
|
||||
|
||||
state[0] = _power_status(c_info)
|
||||
state[0] = _power_status(driver_info)
|
||||
if state[0] == states.POWER_ON:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
@ -129,11 +131,11 @@ def _power_on(c_info):
|
||||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
retries[0] += 1
|
||||
_exec_ipmitool(c_info, "power on")
|
||||
_exec_ipmitool(driver_info, "power on")
|
||||
except Exception:
|
||||
# Log failures but keep trying
|
||||
LOG.warning(_("IPMI power on failed for node %s.")
|
||||
% c_info['uuid'])
|
||||
% driver_info['uuid'])
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_on,
|
||||
state, retries)
|
||||
@ -141,7 +143,7 @@ def _power_on(c_info):
|
||||
return state[0]
|
||||
|
||||
|
||||
def _power_off(c_info):
|
||||
def _power_off(driver_info):
|
||||
"""Turn the power to this node OFF."""
|
||||
|
||||
# use mutable objects so the looped method can change them
|
||||
@ -151,7 +153,7 @@ def _power_off(c_info):
|
||||
def _wait_for_power_off(state, retries):
|
||||
"""Called at an interval until the node's power is off."""
|
||||
|
||||
state[0] = _power_status(c_info)
|
||||
state[0] = _power_status(driver_info)
|
||||
if state[0] == states.POWER_OFF:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
@ -160,11 +162,11 @@ def _power_off(c_info):
|
||||
raise loopingcall.LoopingCallDone()
|
||||
try:
|
||||
retries[0] += 1
|
||||
_exec_ipmitool(c_info, "power off")
|
||||
_exec_ipmitool(driver_info, "power off")
|
||||
except Exception:
|
||||
# Log failures but keep trying
|
||||
LOG.warning(_("IPMI power off failed for node %s.")
|
||||
% c_info['uuid'])
|
||||
% driver_info['uuid'])
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_power_off,
|
||||
state=state, retries=retries)
|
||||
@ -172,8 +174,8 @@ def _power_off(c_info):
|
||||
return state[0]
|
||||
|
||||
|
||||
def _power_status(c_info):
|
||||
out_err = _exec_ipmitool(c_info, "power status")
|
||||
def _power_status(driver_info):
|
||||
out_err = _exec_ipmitool(driver_info, "power status")
|
||||
if out_err[0] == "Chassis Power is on\n":
|
||||
return states.POWER_ON
|
||||
elif out_err[0] == "Chassis Power is off\n":
|
||||
@ -182,41 +184,30 @@ def _power_status(c_info):
|
||||
return states.ERROR
|
||||
|
||||
|
||||
class IPMIPowerDriver(base.ControlDriver):
|
||||
"""Generic IPMI Power Driver
|
||||
class IPMIPower(base.PowerInterface):
|
||||
|
||||
This ControlDriver class provides mechanism for controlling the power state
|
||||
of physical hardware via IPMI calls. It also provides console access for
|
||||
some supported hardware.
|
||||
|
||||
NOTE: This driver does not currently support multi-node operations.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def validate_driver_info(self, node):
|
||||
"""Check that node['control_info'] contains the requisite fields."""
|
||||
def validate(self, node):
|
||||
"""Check that node['driver_info'] contains IPMI credentials."""
|
||||
try:
|
||||
_parse_control_info(node)
|
||||
_parse_driver_info(node)
|
||||
except exception.InvalidParameterValue:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_power_state(self, task, node):
|
||||
"""Get the current power state."""
|
||||
c_info = _parse_control_info(node)
|
||||
return _power_status(c_info)
|
||||
driver_info = _parse_driver_info(node)
|
||||
return _power_status(driver_info)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, node, pstate):
|
||||
"""Turn the power on or off."""
|
||||
c_info = _parse_control_info(node)
|
||||
driver_info = _parse_driver_info(node)
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(c_info)
|
||||
state = _power_on(driver_info)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(c_info)
|
||||
state = _power_off(driver_info)
|
||||
else:
|
||||
raise exception.IronicException(_(
|
||||
"set_power_state called with invalid power state."))
|
||||
@ -227,18 +218,18 @@ class IPMIPowerDriver(base.ControlDriver):
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task, node):
|
||||
"""Cycles the power to a node."""
|
||||
c_info = _parse_control_info(node)
|
||||
_power_off(c_info)
|
||||
state = _power_on(c_info)
|
||||
driver_info = _parse_driver_info(node)
|
||||
_power_off(driver_info)
|
||||
state = _power_on(driver_info)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_boot_device(self, task, node, device, persistent=False):
|
||||
def _set_boot_device(self, task, node, device, persistent=False):
|
||||
"""Set the boot device for a node.
|
||||
|
||||
:param task: TaskManager context.
|
||||
:param task: a TaskManager instance.
|
||||
:param node: The Node.
|
||||
:param device: Boot device. One of [pxe, disk, cdrom, safe, bios].
|
||||
:param persistent: Whether to set next-boot, or make the change
|
||||
@ -253,19 +244,9 @@ class IPMIPowerDriver(base.ControlDriver):
|
||||
cmd = "chassis bootdev %s" % device
|
||||
if persistent:
|
||||
cmd = cmd + " options=persistent"
|
||||
c_info = _parse_control_info(node)
|
||||
driver_info = _parse_driver_info(node)
|
||||
try:
|
||||
out, err = _exec_ipmitool(c_info, cmd)
|
||||
out, err = _exec_ipmitool(driver_info, cmd)
|
||||
# TODO(deva): validate (out, err) and add unit test for failure
|
||||
except Exception:
|
||||
raise exception.IPMIFailure(cmd=cmd)
|
||||
|
||||
# TODO(deva): port start_console
|
||||
def start_console(self, task, node):
|
||||
raise exception.IronicException(_(
|
||||
"start_console is not supported by IPMIPowerDriver."))
|
||||
|
||||
# TODO(deva): port stop_console
|
||||
def stop_console(self, task, node):
|
||||
raise exception.IronicException(_(
|
||||
"stop_console is not supported by IPMIPowerDriver."))
|
64
ironic/drivers/modules/pxe.py
Normal file
64
ironic/drivers/modules/pxe.py
Normal file
@ -0,0 +1,64 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
PXE Driver and supporting meta-classes.
|
||||
"""
|
||||
|
||||
from ironic.drivers import base
|
||||
|
||||
|
||||
class PXEDeploy(base.DeployInterface):
|
||||
"""PXE Deploy Interface: just a stub until the real driver is ported."""
|
||||
|
||||
def validate(self, nodes):
|
||||
pass
|
||||
|
||||
def deploy(self, task, nodes):
|
||||
pass
|
||||
|
||||
def tear_down(self, task, nodes):
|
||||
pass
|
||||
|
||||
|
||||
class PXERescue(base.RescueInterface):
|
||||
"""PXE Rescue Interface: just a stub until the real driver is ported."""
|
||||
|
||||
def validate(self, nodes):
|
||||
pass
|
||||
|
||||
def rescue(self, task, nodes):
|
||||
pass
|
||||
|
||||
def unrescue(self, task, nodes):
|
||||
pass
|
||||
|
||||
|
||||
class IPMIVendorPassthru(base.VendorInterface):
|
||||
"""Interface to mix IPMI and PXE vendor-specific interfaces."""
|
||||
|
||||
def validate(self, node):
|
||||
pass
|
||||
|
||||
def vendor_passthru(self, task, node, *args, **kwargs):
|
||||
method = kwargs.get('method')
|
||||
if method == 'set_boot_device':
|
||||
return node.driver.vendor._set_boot_device(
|
||||
task, node,
|
||||
args.get('device'),
|
||||
args.get('persistent'))
|
||||
else:
|
||||
return
|
@ -25,7 +25,6 @@ For use in dev and test environments.
|
||||
Currently supported environments are:
|
||||
Virtual Box (vbox)
|
||||
Virsh (virsh)
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -109,17 +108,19 @@ def _exec_ssh_command(ssh_obj, command):
|
||||
return (stdout, stderr)
|
||||
|
||||
|
||||
def _parse_control_info(node):
|
||||
info = json.loads(node.get('control_info', ''))
|
||||
host = info.get('ssh_host', None)
|
||||
username = info.get('ssh_user', None)
|
||||
password = info.get('ssh_pass', None)
|
||||
port = info.get('ssh_port', 22)
|
||||
key_filename = info.get('ssh_key', None)
|
||||
virt_type = info.get('virt_type', None)
|
||||
def _parse_driver_info(node):
|
||||
driver_info = json.loads(node.get('driver_info', ''))
|
||||
ssh_info = driver_info.get('ssh')
|
||||
address = ssh_info.get('address', None)
|
||||
username = ssh_info.get('username', None)
|
||||
password = ssh_info.get('password', None)
|
||||
port = ssh_info.get('port', 22)
|
||||
key_filename = ssh_info.get('key_filename', None)
|
||||
virt_type = ssh_info.get('virt_type', None)
|
||||
|
||||
# NOTE(deva): we map 'address' from API to 'host' for common utils
|
||||
res = {
|
||||
'host': host,
|
||||
'host': address,
|
||||
'username': username,
|
||||
'port': port,
|
||||
'virt_type': virt_type,
|
||||
@ -136,15 +137,16 @@ def _parse_control_info(node):
|
||||
"SSHPowerDriver unknown virt_type (%s).") % cmd_set)
|
||||
res['cmd_set'] = cmd_set
|
||||
|
||||
if not host or not username:
|
||||
if not address or not username:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver requires both ssh_host and ssh_user be set."))
|
||||
"SSHPowerDriver requires both address and username be set."))
|
||||
if password:
|
||||
res['password'] = password
|
||||
else:
|
||||
if not key_filename:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"SSHPowerDriver requires either ssh_pass or ssh_key be set."))
|
||||
"SSHPowerDriver requires either password or "
|
||||
"key_filename be set."))
|
||||
if not os.path.isfile(key_filename):
|
||||
raise exception.FileNotFound(file_path=key_filename)
|
||||
res['key_filename'] = key_filename
|
||||
@ -152,15 +154,15 @@ def _parse_control_info(node):
|
||||
return res
|
||||
|
||||
|
||||
def _get_power_status(ssh_obj, c_info):
|
||||
def _get_power_status(ssh_obj, driver_info):
|
||||
"""Returns a node's current power state."""
|
||||
|
||||
power_state = None
|
||||
cmd_to_exec = c_info['cmd_set']['list_running']
|
||||
cmd_to_exec = driver_info['cmd_set']['list_running']
|
||||
running_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
|
||||
# Command should return a list of running vms. If the current node is
|
||||
# not listed then we can assume it is not powered on.
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
if node_name:
|
||||
for node in running_list:
|
||||
if node_name in node:
|
||||
@ -175,23 +177,23 @@ def _get_power_status(ssh_obj, c_info):
|
||||
|
||||
|
||||
def _get_connection(node):
|
||||
return utils.ssh_connect(_parse_control_info(node))
|
||||
return utils.ssh_connect(_parse_driver_info(node))
|
||||
|
||||
|
||||
def _get_hosts_name_for_node(ssh_obj, c_info):
|
||||
def _get_hosts_name_for_node(ssh_obj, driver_info):
|
||||
"""Get the name the host uses to reference the node."""
|
||||
|
||||
matched_name = None
|
||||
cmd_to_exec = c_info['cmd_set']['list_all']
|
||||
cmd_to_exec = driver_info['cmd_set']['list_all']
|
||||
full_node_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
|
||||
# for each node check Mac Addresses
|
||||
for node in full_node_list:
|
||||
cmd_to_exec = c_info['cmd_set']['get_node_macs']
|
||||
cmd_to_exec = driver_info['cmd_set']['get_node_macs']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', node)
|
||||
hosts_node_mac_list = _exec_ssh_command(ssh_obj, cmd_to_exec)
|
||||
|
||||
for host_mac in hosts_node_mac_list:
|
||||
for node_mac in c_info['macs']:
|
||||
for node_mac in driver_info['macs']:
|
||||
if _normalize_mac(host_mac) in _normalize_mac(node_mac):
|
||||
matched_name = node
|
||||
break
|
||||
@ -204,40 +206,40 @@ def _get_hosts_name_for_node(ssh_obj, c_info):
|
||||
return matched_name
|
||||
|
||||
|
||||
def _power_on(ssh_obj, c_info):
|
||||
def _power_on(ssh_obj, driver_info):
|
||||
"""Power ON this node."""
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
_power_off(ssh_obj, c_info)
|
||||
_power_off(ssh_obj, driver_info)
|
||||
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
|
||||
cmd_to_power_on = c_info['cmd_set']['start_cmd']
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
cmd_to_power_on = driver_info['cmd_set']['start_cmd']
|
||||
cmd_to_power_on = cmd_to_power_on.replace('{_NodeName_}', node_name)
|
||||
|
||||
_exec_ssh_command(ssh_obj, cmd_to_power_on)
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
return current_pstate
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
def _power_off(ssh_obj, c_info):
|
||||
def _power_off(ssh_obj, driver_info):
|
||||
"""Power OFF this node."""
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, c_info)
|
||||
cmd_to_power_off = c_info['cmd_set']['stop_cmd']
|
||||
node_name = _get_hosts_name_for_node(ssh_obj, driver_info)
|
||||
cmd_to_power_off = driver_info['cmd_set']['stop_cmd']
|
||||
cmd_to_power_off = cmd_to_power_off.replace('{_NodeName_}', node_name)
|
||||
|
||||
_exec_ssh_command(ssh_obj, cmd_to_power_off)
|
||||
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_OFF:
|
||||
return current_pstate
|
||||
else:
|
||||
@ -251,30 +253,25 @@ def _get_nodes_mac_addresses(task, node):
|
||||
return macs
|
||||
|
||||
|
||||
class SSHPowerDriver(base.ControlDriver):
|
||||
"""SSH Power Driver.
|
||||
class SSHPower(base.PowerInterface):
|
||||
"""SSH Power Interface.
|
||||
|
||||
This ControlDriver class provides a mechanism for controlling the power
|
||||
This PowerInterface class provides a mechanism for controlling the power
|
||||
state of virtual machines via SSH.
|
||||
|
||||
NOTE: This driver supports VirtualBox and Virsh commands.
|
||||
NOTE: This driver does not currently support multi-node operations.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def validate_driver_info(self, node):
|
||||
"""Check that node['control_info'] contains the requisite fields.
|
||||
def validate(self, node):
|
||||
"""Check that node['driver_info'] contains the requisite fields.
|
||||
|
||||
:param node: Single node object.
|
||||
|
||||
:returns: True / False.
|
||||
|
||||
"""
|
||||
try:
|
||||
_parse_control_info(node)
|
||||
_parse_driver_info(node)
|
||||
except exception.InvalidParameterValue:
|
||||
return False
|
||||
return True
|
||||
@ -288,12 +285,11 @@ class SSHPowerDriver(base.ControlDriver):
|
||||
:param node: A single node.
|
||||
|
||||
:returns: power state. One of :class:`ironic.common.states`.
|
||||
|
||||
"""
|
||||
c_info = _parse_control_info(node)
|
||||
c_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
driver_info = _parse_driver_info(node)
|
||||
driver_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
ssh_obj = _get_connection(node)
|
||||
return _get_power_status(ssh_obj, c_info)
|
||||
return _get_power_status(ssh_obj, driver_info)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, node, pstate):
|
||||
@ -307,18 +303,16 @@ class SSHPowerDriver(base.ControlDriver):
|
||||
`ironic.common.states`.
|
||||
|
||||
:returns NOTHING:
|
||||
|
||||
Can raise exception.IronicException or exception.PowerStateFailure.
|
||||
|
||||
:raises: exception.IronicException or exception.PowerStateFailure.
|
||||
"""
|
||||
c_info = _parse_control_info(node)
|
||||
c_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
driver_info = _parse_driver_info(node)
|
||||
driver_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
ssh_obj = _get_connection(node)
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(ssh_obj, c_info)
|
||||
state = _power_on(ssh_obj, driver_info)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(ssh_obj, c_info)
|
||||
state = _power_off(ssh_obj, driver_info)
|
||||
else:
|
||||
raise exception.IronicException(_(
|
||||
"set_power_state called with invalid power state."))
|
||||
@ -336,47 +330,16 @@ class SSHPowerDriver(base.ControlDriver):
|
||||
:param node: A single node.
|
||||
|
||||
:returns NOTHING:
|
||||
|
||||
Can raise exception.PowerStateFailure.
|
||||
|
||||
:raises: exception.PowerStateFailure.
|
||||
"""
|
||||
c_info = _parse_control_info(node)
|
||||
c_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
driver_info = _parse_driver_info(node)
|
||||
driver_info['macs'] = _get_nodes_mac_addresses(task, node)
|
||||
ssh_obj = _get_connection(node)
|
||||
current_pstate = _get_power_status(ssh_obj, c_info)
|
||||
current_pstate = _get_power_status(ssh_obj, driver_info)
|
||||
if current_pstate == states.POWER_ON:
|
||||
_power_off(ssh_obj, c_info)
|
||||
_power_off(ssh_obj, driver_info)
|
||||
|
||||
state = _power_on(ssh_obj, c_info)
|
||||
state = _power_on(ssh_obj, driver_info)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
def start_console(self, task, node):
|
||||
"""Starts a console connection to a node.
|
||||
|
||||
CURRENTLY NOT SUPPORTED.
|
||||
|
||||
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
|
||||
:param node: A single node.
|
||||
|
||||
Will raise raise exception.IronicException.
|
||||
|
||||
"""
|
||||
raise exception.IronicException(_(
|
||||
"start_console is not supported by SSHPowerDriver."))
|
||||
|
||||
def stop_console(self, task, node):
|
||||
"""Stops a console connection to a node.
|
||||
|
||||
CURRENTLY NOT SUPPORTED.
|
||||
|
||||
:param task: A instance of `ironic.manager.task_manager.TaskManager`.
|
||||
:param node: A single node.
|
||||
|
||||
Will raise raise exception.IronicException.
|
||||
|
||||
"""
|
||||
|
||||
raise exception.IronicException(_(
|
||||
"stop_console is not supported by SSHPowerDriver."))
|
59
ironic/drivers/pxe.py
Normal file
59
ironic/drivers/pxe.py
Normal file
@ -0,0 +1,59 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
PXE Driver and supporting meta-classes.
|
||||
"""
|
||||
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import ipmi
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import ssh
|
||||
|
||||
|
||||
class PXEAndIPMIDriver(base.BaseDriver):
|
||||
"""PXE + IPMI driver.
|
||||
|
||||
This driver implements the `core` functionality, combinding
|
||||
:class:ironic.drivers.ipmi.IPMI for power on/off and reboot with
|
||||
:class:ironic.driver.pxe.PXE for image deployment. Implementations are in
|
||||
those respective classes; this class is merely the glue between them.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.power = ipmi.IPMIPower()
|
||||
self.deploy = pxe.PXEDeploy()
|
||||
self.rescue = self.deploy
|
||||
self.vendor = pxe.IPMIVendorPassthru()
|
||||
|
||||
|
||||
class PXEAndSSHDriver(base.BaseDriver):
|
||||
"""PXE + SSH driver.
|
||||
|
||||
NOTE: This driver is meant only for testing environments.
|
||||
|
||||
This driver implements the `core` functionality, combinding
|
||||
:class:ironic.drivers.ssh.SSH for power on/off and reboot of virtual
|
||||
machines tunneled over SSH, with :class:ironic.driver.pxe.PXE for image
|
||||
deployment. Implementations are in those respective classes; this class is
|
||||
merely the glue between them.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.power = ssh.SSHPower()
|
||||
self.deploy = pxe.PXEDeploy()
|
||||
self.rescue = self.deploy
|
||||
self.vendor = None
|
@ -75,8 +75,8 @@ class ManagerService(service.PeriodicService):
|
||||
|
||||
with task_manager.acquire([node_id], shared=True) as task:
|
||||
node = task.resources[0].node
|
||||
driver = task.resources[0].controller
|
||||
state = driver.get_power_state(task, node)
|
||||
driver = task.resources[0].driver
|
||||
state = driver.power.get_power_state(task, node)
|
||||
return state
|
||||
|
||||
# TODO(deva)
|
||||
|
@ -46,12 +46,8 @@ class NodeManager(object):
|
||||
|
||||
_nodes = {}
|
||||
|
||||
_control_factory = dispatch.NameDispatchExtensionManager(
|
||||
namespace='ironic.controllers',
|
||||
check_func=lambda x: True,
|
||||
invoke_on_load=True)
|
||||
_deploy_factory = dispatch.NameDispatchExtensionManager(
|
||||
namespace='ironic.deployers',
|
||||
_driver_factory = dispatch.NameDispatchExtensionManager(
|
||||
namespace='ironic.drivers',
|
||||
check_func=lambda x: True,
|
||||
invoke_on_load=True)
|
||||
|
||||
@ -69,22 +65,13 @@ class NodeManager(object):
|
||||
# NOTE(deva): Driver loading here may get refactored, depend on:
|
||||
# https://github.com/dreamhost/stevedore/issues/15
|
||||
try:
|
||||
ref = NodeManager._control_factory.map(
|
||||
[self.node.get('control_driver')], _get_instance)
|
||||
self.controller = ref[0]
|
||||
ref = NodeManager._driver_factory.map(
|
||||
[self.node.get('driver')], _get_instance)
|
||||
self.driver = ref[0]
|
||||
except KeyError:
|
||||
raise exception.IronicException(_(
|
||||
"Failed to load Control driver %s.") %
|
||||
self.node.get('control_driver'))
|
||||
|
||||
try:
|
||||
ref = NodeManager._deploy_factory.map(
|
||||
[self.node.get('deploy_driver')], _get_instance)
|
||||
self.deployer = ref[0]
|
||||
except KeyError:
|
||||
raise exception.IronicException(_(
|
||||
"Failed to load Deploy driver %s.") %
|
||||
self.node.get('deploy_driver'))
|
||||
"Failed to load driver %s.") %
|
||||
self.node.get('driver'))
|
||||
|
||||
@classmethod
|
||||
@lockutils.synchronized(RESOURCE_MANAGER_SEMAPHORE, 'ironic-')
|
||||
|
@ -23,7 +23,7 @@ A context manager to peform a series of tasks on a set of resources.
|
||||
locking and simplify operations across a set of
|
||||
:class:`ironic.manager.resource_manager.NodeManager` instances. Each
|
||||
NodeManager holds the data model for a node, as well as references to the
|
||||
controller and deployer driver singletons appropriate for that node.
|
||||
driver singleton appropriate for that node.
|
||||
|
||||
The :class:`TaskManager` will acquire either a shared or exclusive lock, as
|
||||
indicated. Multiple shared locks for the same resource may coexist with an
|
||||
@ -56,11 +56,10 @@ driver function, you can access the drivers directly in this way::
|
||||
|
||||
with task_manager.acquire(node_ids) as task:
|
||||
states = []
|
||||
for node, control, deploy in [r.node, r.controller, r.deployer
|
||||
for node, driver in [r.node, r.driver
|
||||
for r in task.resources]:
|
||||
# control and deploy are driver singletons,
|
||||
# loaded based on that node's configuration.
|
||||
states.append(control.get_power_state(task, node)
|
||||
# the driver is loaded based on that node's configuration.
|
||||
states.append(driver.power.get_power_state(task, node)
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
|
@ -20,30 +20,45 @@ from ironic.db.sqlalchemy import models
|
||||
from ironic.openstack.common import jsonutils as json
|
||||
|
||||
|
||||
_ipmi_control_info = json.dumps(
|
||||
fake_info = json.dumps({"foo": "bar"})
|
||||
|
||||
ipmi_info = json.dumps(
|
||||
{
|
||||
"ipmi_address": "1.2.3.4",
|
||||
"ipmi_username": "admin",
|
||||
"ipmi_password": "fake",
|
||||
'ipmi': {
|
||||
"address": "1.2.3.4",
|
||||
"username": "admin",
|
||||
"password": "fake",
|
||||
}
|
||||
})
|
||||
|
||||
_ssh_control_info = json.dumps(
|
||||
ssh_info = json.dumps(
|
||||
{
|
||||
"ssh_host": "1.2.3.4",
|
||||
"ssh_user": "admin",
|
||||
"ssh_pass": "fake",
|
||||
"ssh_port": 22,
|
||||
"virt_type": "vbox",
|
||||
'ssh': {
|
||||
"address": "1.2.3.4",
|
||||
"username": "admin",
|
||||
"password": "fake",
|
||||
"port": 22,
|
||||
"virt_type": "vbox",
|
||||
"key_filename": "/not/real/file",
|
||||
}
|
||||
})
|
||||
|
||||
_deploy_info = json.dumps(
|
||||
pxe_info = json.dumps(
|
||||
{
|
||||
"image_path": "/path/to/image.qcow2",
|
||||
"image_source": "glance://image-uuid",
|
||||
"deploy_image_source": "glance://deploy-image-uuid",
|
||||
'pxe': {
|
||||
"image_path": "/path/to/image.qcow2",
|
||||
"image_source": "glance://image-uuid",
|
||||
"deploy_image_source": "glance://deploy-image-uuid",
|
||||
}
|
||||
})
|
||||
|
||||
_properties = json.dumps(
|
||||
pxe_ssh_info = json.dumps(
|
||||
dict(json.loads(pxe_info), **json.loads(ssh_info)))
|
||||
|
||||
pxe_ipmi_info = json.dumps(
|
||||
dict(json.loads(pxe_info), **json.loads(ipmi_info)))
|
||||
|
||||
properties = json.dumps(
|
||||
{
|
||||
"cpu_arch": "x86_64",
|
||||
"cpu_num": 8,
|
||||
@ -61,18 +76,11 @@ def get_test_node(**kw):
|
||||
node.instance_uuid = kw.get('instance_uuid',
|
||||
'8227348d-5f1d-4488-aad1-7c92b2d42504')
|
||||
|
||||
node.control_driver = kw.get('control_driver', 'ipmi')
|
||||
node.control_info = kw.get('control_info', None)
|
||||
node.driver = kw.get('driver', 'fake')
|
||||
node.driver_info = kw.get('driver_info', fake_info)
|
||||
|
||||
if node.control_driver == 'ipmi' and not node.control_info:
|
||||
node.control_info = _ipmi_control_info
|
||||
elif node.control_driver == 'ssh' and not node.control_info:
|
||||
node.control_info = _ssh_control_info
|
||||
|
||||
node.deploy_driver = kw.get('deploy_driver', 'pxe')
|
||||
node.deploy_info = kw.get('deploy_info', _deploy_info)
|
||||
|
||||
node.properties = kw.get('properties', _properties)
|
||||
node.properties = kw.get('properties', properties)
|
||||
node.extra = kw.get('extra', '{}')
|
||||
|
||||
return node
|
||||
|
||||
|
61
ironic/tests/drivers/test_fake.py
Normal file
61
ironic/tests/drivers/test_fake.py
Normal file
@ -0,0 +1,61 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Test class for Fake driver."""
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.drivers import base as driver_base
|
||||
from ironic.drivers import fake
|
||||
from ironic.tests import base
|
||||
from ironic.tests.db import utils as db_utils
|
||||
|
||||
|
||||
class FakeDriverTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FakeDriverTestCase, self).setUp()
|
||||
self.driver = fake.FakeDriver()
|
||||
self.node = db_utils.get_test_node()
|
||||
|
||||
def test_driver_interfaces(self):
|
||||
# fake driver implements only 3 out of 5 interfaces
|
||||
self.assertIsInstance(self.driver.power, driver_base.PowerInterface)
|
||||
self.assertIsInstance(self.driver.deploy, driver_base.DeployInterface)
|
||||
self.assertIsInstance(self.driver.vendor, driver_base.VendorInterface)
|
||||
self.assertIsNone(self.driver.rescue)
|
||||
self.assertIsNone(self.driver.console)
|
||||
|
||||
def test_power_interface(self):
|
||||
self.driver.power.validate(self.node)
|
||||
self.driver.power.get_power_state(None, self.node)
|
||||
self.driver.power.set_power_state(None, None, states.NOSTATE)
|
||||
self.driver.power.reboot(None, None)
|
||||
|
||||
def test_deploy_interface(self):
|
||||
self.driver.deploy.validate(self.node)
|
||||
self.driver.deploy.deploy(None, None)
|
||||
self.driver.deploy.tear_down(None, None)
|
||||
|
||||
def test_vendor_interface(self):
|
||||
self.driver.vendor.validate(self.node)
|
||||
self.driver.vendor.vendor_passthru(None, self.node,
|
||||
method='foo', bar='baz')
|
||||
self.assertRaises(exception.IronicException,
|
||||
self.driver.vendor.vendor_passthru,
|
||||
None, self.node, method='fake')
|
@ -17,7 +17,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Test class for baremetal IPMI power manager."""
|
||||
"""Test class for IPMI driver module."""
|
||||
|
||||
import os
|
||||
import stat
|
||||
@ -30,26 +30,24 @@ from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.db import api as db_api
|
||||
from ironic.drivers import ipmi
|
||||
from ironic.drivers.modules import ipmi
|
||||
from ironic.manager import task_manager
|
||||
from ironic.tests import base
|
||||
from ironic.tests.db import base as db_base
|
||||
from ironic.tests.db import utils as db_utils
|
||||
from ironic.tests.manager import utils as mgr_utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class BareMetalIPMITestCase(base.TestCase):
|
||||
class IPMIPrivateMethodTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BareMetalIPMITestCase, self).setUp()
|
||||
self.dbapi = db_api.get_instance()
|
||||
(self.controller, self.deployer) = mgr_utils.get_mocked_node_manager(
|
||||
control_driver='ipmi')
|
||||
|
||||
self.node = db_utils.get_test_node(control_driver='ipmi',
|
||||
deploy_driver='fake')
|
||||
self.dbapi.create_node(self.node)
|
||||
super(IPMIPrivateMethodTestCase, self).setUp()
|
||||
self.node = db_utils.get_test_node(
|
||||
driver='fake_ipmi',
|
||||
driver_info=db_utils.ipmi_info)
|
||||
self.info = ipmi._parse_driver_info(self.node)
|
||||
|
||||
def test__make_password_file(self):
|
||||
fakepass = 'this is a fake password'
|
||||
@ -63,39 +61,38 @@ class BareMetalIPMITestCase(base.TestCase):
|
||||
finally:
|
||||
os.unlink(pw_file)
|
||||
|
||||
def test__parse_control_info(self):
|
||||
def test__parse_driver_info(self):
|
||||
# make sure we get back the expected things
|
||||
node = db_utils.get_test_node()
|
||||
info = ipmi._parse_control_info(node)
|
||||
self.assertIsNotNone(info.get('address'))
|
||||
self.assertIsNotNone(info.get('user'))
|
||||
self.assertIsNotNone(info.get('password'))
|
||||
self.assertIsNotNone(info.get('uuid'))
|
||||
self.assertIsNotNone(self.info.get('address'))
|
||||
self.assertIsNotNone(self.info.get('username'))
|
||||
self.assertIsNotNone(self.info.get('password'))
|
||||
self.assertIsNotNone(self.info.get('uuid'))
|
||||
|
||||
# make sure error is raised when info, eg. username, is missing
|
||||
_control_info = json.dumps(
|
||||
_driver_info = json.dumps(
|
||||
{
|
||||
"ipmi_address": "1.2.3.4",
|
||||
"ipmi_password": "fake",
|
||||
'ipmi': {
|
||||
"address": "1.2.3.4",
|
||||
"password": "fake",
|
||||
}
|
||||
})
|
||||
node = db_utils.get_test_node(control_info=_control_info)
|
||||
node = db_utils.get_test_node(driver_info=_driver_info)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
ipmi._parse_control_info,
|
||||
ipmi._parse_driver_info,
|
||||
node)
|
||||
|
||||
def test__exec_ipmitool(self):
|
||||
pw_file = '/tmp/password_file'
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
|
||||
self.mox.StubOutWithMock(ipmi, '_make_password_file')
|
||||
self.mox.StubOutWithMock(utils, 'execute')
|
||||
self.mox.StubOutWithMock(utils, 'delete_if_exists')
|
||||
ipmi._make_password_file(info['password']).AndReturn(pw_file)
|
||||
ipmi._make_password_file(self.info['password']).AndReturn(pw_file)
|
||||
args = [
|
||||
'ipmitool',
|
||||
'-I', 'lanplus',
|
||||
'-H', info['address'],
|
||||
'-U', info['user'],
|
||||
'-H', self.info['address'],
|
||||
'-U', self.info['username'],
|
||||
'-f', pw_file,
|
||||
'A', 'B', 'C',
|
||||
]
|
||||
@ -103,125 +100,133 @@ class BareMetalIPMITestCase(base.TestCase):
|
||||
utils.delete_if_exists(pw_file).AndReturn(None)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ipmi._exec_ipmitool(info, 'A B C')
|
||||
ipmi._exec_ipmitool(self.info, 'A B C')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__power_status_on(self):
|
||||
self.mox.StubOutWithMock(ipmi, '_exec_ipmitool')
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is on\n", None])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
state = ipmi._power_status(info)
|
||||
state = ipmi._power_status(self.info)
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(state, states.POWER_ON)
|
||||
|
||||
def test__power_status_off(self):
|
||||
self.mox.StubOutWithMock(ipmi, '_exec_ipmitool')
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is off\n", None])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
state = ipmi._power_status(info)
|
||||
state = ipmi._power_status(self.info)
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(state, states.POWER_OFF)
|
||||
|
||||
def test__power_status_error(self):
|
||||
self.mox.StubOutWithMock(ipmi, '_exec_ipmitool')
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is badstate\n", None])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
state = ipmi._power_status(info)
|
||||
state = ipmi._power_status(self.info)
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(state, states.ERROR)
|
||||
|
||||
def test__power_on_max_retries(self):
|
||||
self.config(ipmi_power_retry=2)
|
||||
self.mox.StubOutWithMock(ipmi, '_exec_ipmitool')
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is off\n", None])
|
||||
ipmi._exec_ipmitool(info, "power on").AndReturn([None, None])
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power on").AndReturn([None, None])
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is off\n", None])
|
||||
ipmi._exec_ipmitool(info, "power on").AndReturn([None, None])
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power on").AndReturn([None, None])
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is off\n", None])
|
||||
ipmi._exec_ipmitool(info, "power on").AndReturn([None, None])
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power on").AndReturn([None, None])
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is off\n", None])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
state = ipmi._power_on(info)
|
||||
state = ipmi._power_on(self.info)
|
||||
self.mox.VerifyAll()
|
||||
self.assertEqual(state, states.ERROR)
|
||||
|
||||
|
||||
class IPMIDriverTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IPMIDriverTestCase, self).setUp()
|
||||
self.dbapi = db_api.get_instance()
|
||||
self.driver = mgr_utils.get_mocked_node_manager(driver='fake_ipmi')
|
||||
|
||||
self.node = db_utils.get_test_node(
|
||||
driver='fake_ipmi',
|
||||
driver_info=db_utils.ipmi_info)
|
||||
self.info = ipmi._parse_driver_info(self.node)
|
||||
self.dbapi.create_node(self.node)
|
||||
|
||||
def test_get_power_state(self):
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
self.mox.StubOutWithMock(ipmi, '_exec_ipmitool')
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is off\n", None])
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["Chassis Power is on\n", None])
|
||||
ipmi._exec_ipmitool(info, "power status").AndReturn(
|
||||
ipmi._exec_ipmitool(self.info, "power status").AndReturn(
|
||||
["\n", None])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
pstate = self.controller.get_power_state(None, self.node)
|
||||
pstate = self.driver.power.get_power_state(None, self.node)
|
||||
self.assertEqual(pstate, states.POWER_OFF)
|
||||
|
||||
pstate = self.controller.get_power_state(None, self.node)
|
||||
pstate = self.driver.power.get_power_state(None, self.node)
|
||||
self.assertEqual(pstate, states.POWER_ON)
|
||||
|
||||
pstate = self.controller.get_power_state(None, self.node)
|
||||
pstate = self.driver.power.get_power_state(None, self.node)
|
||||
self.assertEqual(pstate, states.ERROR)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_on_ok(self):
|
||||
self.config(ipmi_power_retry=0)
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
self.mox.StubOutWithMock(ipmi, '_power_on')
|
||||
self.mox.StubOutWithMock(ipmi, '_power_off')
|
||||
|
||||
ipmi._power_on(info).AndReturn(states.POWER_ON)
|
||||
ipmi._power_on(self.info).AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.controller.set_power_state(task, self.node, states.POWER_ON)
|
||||
self.driver.power.set_power_state(
|
||||
task, self.node, states.POWER_ON)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_off_ok(self):
|
||||
self.config(ipmi_power_retry=0)
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
self.mox.StubOutWithMock(ipmi, '_power_on')
|
||||
self.mox.StubOutWithMock(ipmi, '_power_off')
|
||||
|
||||
ipmi._power_off(info).AndReturn(states.POWER_OFF)
|
||||
ipmi._power_off(self.info).AndReturn(states.POWER_OFF)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.controller.set_power_state(task, self.node, states.POWER_OFF)
|
||||
self.driver.power.set_power_state(
|
||||
task, self.node, states.POWER_OFF)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_on_fail(self):
|
||||
self.config(ipmi_power_retry=0)
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
|
||||
self.mox.StubOutWithMock(ipmi, '_power_on')
|
||||
self.mox.StubOutWithMock(ipmi, '_power_off')
|
||||
|
||||
ipmi._power_on(info).AndReturn(states.ERROR)
|
||||
ipmi._power_on(self.info).AndReturn(states.ERROR)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
self.controller.set_power_state,
|
||||
self.driver.power.set_power_state,
|
||||
task,
|
||||
self.node,
|
||||
states.POWER_ON)
|
||||
@ -230,57 +235,54 @@ class BareMetalIPMITestCase(base.TestCase):
|
||||
def test_set_power_invalid_state(self):
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.assertRaises(exception.IronicException,
|
||||
self.controller.set_power_state,
|
||||
self.driver.power.set_power_state,
|
||||
task,
|
||||
self.node,
|
||||
"fake state")
|
||||
|
||||
def test_set_boot_device_ok(self):
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
self.mox.StubOutWithMock(ipmi, '_exec_ipmitool')
|
||||
|
||||
ipmi._exec_ipmitool(info, "chassis bootdev pxe").\
|
||||
ipmi._exec_ipmitool(self.info, "chassis bootdev pxe").\
|
||||
AndReturn([None, None])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.controller.set_boot_device(task, self.node, 'pxe')
|
||||
self.driver.power._set_boot_device(task, self.node, 'pxe')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_boot_device_bad_device(self):
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self.controller.set_boot_device,
|
||||
self.driver.power._set_boot_device,
|
||||
task,
|
||||
self.node,
|
||||
'fake-device')
|
||||
|
||||
def test_reboot_ok(self):
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
self.mox.StubOutWithMock(ipmi, '_power_off')
|
||||
self.mox.StubOutWithMock(ipmi, '_power_on')
|
||||
|
||||
ipmi._power_off(info)
|
||||
ipmi._power_on(info).AndReturn(states.POWER_ON)
|
||||
ipmi._power_off(self.info)
|
||||
ipmi._power_on(self.info).AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.controller.reboot(task, self.node)
|
||||
self.driver.power.reboot(task, self.node)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_reboot_fail(self):
|
||||
info = ipmi._parse_control_info(self.node)
|
||||
self.mox.StubOutWithMock(ipmi, '_power_off')
|
||||
self.mox.StubOutWithMock(ipmi, '_power_on')
|
||||
|
||||
ipmi._power_off(info)
|
||||
ipmi._power_on(info).AndReturn(states.ERROR)
|
||||
ipmi._power_off(self.info)
|
||||
ipmi._power_on(self.info).AndReturn(states.ERROR)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([self.node.uuid]) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
self.controller.reboot,
|
||||
self.driver.power.reboot,
|
||||
task,
|
||||
self.node)
|
||||
|
||||
|
22
ironic/tests/drivers/test_pxe.py
Normal file
22
ironic/tests/drivers/test_pxe.py
Normal file
@ -0,0 +1,22 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# coding=utf-8
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Test class for PXE driver.
|
||||
|
||||
TODO
|
||||
"""
|
@ -18,49 +18,29 @@
|
||||
import mox
|
||||
import paramiko
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from ironic.openstack.common import jsonutils as json
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.db import api as dbapi
|
||||
from ironic.drivers import ssh
|
||||
from ironic.drivers.modules import ssh
|
||||
from ironic.manager import task_manager
|
||||
from ironic.tests import base
|
||||
from ironic.tests.db import base as db_base
|
||||
from ironic.tests.db import utils as db_utils
|
||||
from ironic.tests.manager import utils as mgr_utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
INFO_DICT = json.loads(db_utils.ssh_info).get('ssh')
|
||||
|
||||
|
||||
class IronicSSHTestCase(base.TestCase):
|
||||
class SSHValidateParametersTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(IronicSSHTestCase, self).setUp()
|
||||
(self.controller, self.deployer) = mgr_utils.get_mocked_node_manager(
|
||||
control_driver="ssh")
|
||||
self.node = db_utils.get_test_node(**{'control_driver': 'ssh',
|
||||
'deploy_driver': 'fake'})
|
||||
self.dbapi = dbapi.get_instance()
|
||||
self.dbapi.create_node(self.node)
|
||||
self.ifports = [db_utils.get_test_port(id=6, address='aa:bb:cc'),
|
||||
db_utils.get_test_port(id=7, address='dd:ee:ff')]
|
||||
self.dbapi.create_port(self.ifports[0])
|
||||
self.dbapi.create_port(self.ifports[1])
|
||||
self.ssh = ssh.SSHPowerDriver()
|
||||
self.test_control_info_dict = {
|
||||
"ssh_host": "1.2.3.4",
|
||||
"ssh_user": "admin",
|
||||
"ssh_pass": "fake",
|
||||
"ssh_port": 22,
|
||||
"virt_type": "vbox",
|
||||
"ssh_key": "/not/real/file"}
|
||||
|
||||
def test__parse_control_info_good(self):
|
||||
def test__parse_driver_info_good(self):
|
||||
# make sure we get back the expected things
|
||||
info = ssh._parse_control_info(self.node)
|
||||
node = db_utils.get_test_node(
|
||||
driver='fake_ssh',
|
||||
driver_info=db_utils.ssh_info)
|
||||
info = ssh._parse_driver_info(node)
|
||||
self.assertIsNotNone(info.get('host'))
|
||||
self.assertIsNotNone(info.get('username'))
|
||||
self.assertIsNotNone(info.get('password'))
|
||||
@ -70,62 +50,62 @@ class IronicSSHTestCase(base.TestCase):
|
||||
self.assertIsNotNone(info.get('uuid'))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__parse_control_info_missing_host(self):
|
||||
def test__parse_driver_info_missing_host(self):
|
||||
# make sure error is raised when info is missing
|
||||
tmp_dict = self.test_control_info_dict
|
||||
del tmp_dict['ssh_host']
|
||||
del tmp_dict['ssh_key']
|
||||
_ssh_control_info = json.dumps(tmp_dict)
|
||||
node = db_utils.get_test_node(control_info=_ssh_control_info)
|
||||
tmp_dict = dict(INFO_DICT)
|
||||
del tmp_dict['address']
|
||||
del tmp_dict['key_filename']
|
||||
info = json.dumps({'ssh': tmp_dict})
|
||||
node = db_utils.get_test_node(driver_info=info)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
ssh._parse_control_info,
|
||||
ssh._parse_driver_info,
|
||||
node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__parse_control_info_missing_user(self):
|
||||
def test__parse_driver_info_missing_user(self):
|
||||
# make sure error is raised when info is missing
|
||||
tmp_dict = self.test_control_info_dict
|
||||
del tmp_dict['ssh_user']
|
||||
del tmp_dict['ssh_key']
|
||||
_ssh_control_info = json.dumps(tmp_dict)
|
||||
node = db_utils.get_test_node(control_info=_ssh_control_info)
|
||||
tmp_dict = dict(INFO_DICT)
|
||||
del tmp_dict['username']
|
||||
del tmp_dict['key_filename']
|
||||
info = json.dumps({'ssh': tmp_dict})
|
||||
node = db_utils.get_test_node(driver_info=info)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
ssh._parse_control_info,
|
||||
ssh._parse_driver_info,
|
||||
node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__parse_control_info_missing_pass(self):
|
||||
def test__parse_driver_info_missing_pass(self):
|
||||
# make sure error is raised when info is missing
|
||||
tmp_dict = self.test_control_info_dict
|
||||
del tmp_dict['ssh_pass']
|
||||
del tmp_dict['ssh_key']
|
||||
_ssh_control_info = json.dumps(tmp_dict)
|
||||
node = db_utils.get_test_node(control_info=_ssh_control_info)
|
||||
tmp_dict = dict(INFO_DICT)
|
||||
del tmp_dict['password']
|
||||
del tmp_dict['key_filename']
|
||||
info = json.dumps({'ssh': tmp_dict})
|
||||
node = db_utils.get_test_node(driver_info=info)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
ssh._parse_control_info,
|
||||
ssh._parse_driver_info,
|
||||
node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__parse_control_info_missing_virt_type(self):
|
||||
def test__parse_driver_info_missing_virt_type(self):
|
||||
# make sure error is raised when info is missing
|
||||
tmp_dict = self.test_control_info_dict
|
||||
tmp_dict = dict(INFO_DICT)
|
||||
del tmp_dict['virt_type']
|
||||
del tmp_dict['ssh_key']
|
||||
_ssh_control_info = json.dumps(tmp_dict)
|
||||
node = db_utils.get_test_node(control_info=_ssh_control_info)
|
||||
del tmp_dict['key_filename']
|
||||
info = json.dumps({'ssh': tmp_dict})
|
||||
node = db_utils.get_test_node(driver_info=info)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
ssh._parse_control_info,
|
||||
ssh._parse_driver_info,
|
||||
node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__parse_control_info_missing_key(self):
|
||||
def test__parse_driver_info_missing_key(self):
|
||||
# make sure error is raised when info is missing
|
||||
tmp_dict = self.test_control_info_dict
|
||||
del tmp_dict['ssh_pass']
|
||||
_ssh_control_info = json.dumps(tmp_dict)
|
||||
node = db_utils.get_test_node(control_info=_ssh_control_info)
|
||||
tmp_dict = dict(INFO_DICT)
|
||||
del tmp_dict['password']
|
||||
info = json.dumps({'ssh': tmp_dict})
|
||||
node = db_utils.get_test_node(driver_info=info)
|
||||
self.assertRaises(exception.FileNotFound,
|
||||
ssh._parse_control_info,
|
||||
ssh._parse_driver_info,
|
||||
node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
@ -135,219 +115,191 @@ class IronicSSHTestCase(base.TestCase):
|
||||
self.assertEqual(mac_clean, "0a1b2c3d4f")
|
||||
self.mox.VerifyAll()
|
||||
|
||||
|
||||
class SSHPrivateMethodsTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SSHPrivateMethodsTestCase, self).setUp()
|
||||
self.node = db_utils.get_test_node(
|
||||
driver='fake_ssh',
|
||||
driver_info=db_utils.ssh_info)
|
||||
self.sshclient = paramiko.SSHClient()
|
||||
|
||||
def test__get_power_status_on(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
|
||||
|
||||
ssh._exec_ssh_command(
|
||||
ssh_obj, info['cmd_set']['list_running']).AndReturn(
|
||||
self.sshclient, info['cmd_set']['list_running']).AndReturn(
|
||||
['"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}'])
|
||||
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
|
||||
|
||||
ssh._get_hosts_name_for_node(self.sshclient, info).\
|
||||
AndReturn("NodeName")
|
||||
self.mox.ReplayAll()
|
||||
pstate = ssh._get_power_status(ssh_obj, info)
|
||||
|
||||
pstate = ssh._get_power_status(self.sshclient, info)
|
||||
self.assertEqual(pstate, states.POWER_ON)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__get_power_status_off(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
|
||||
|
||||
ssh._exec_ssh_command(
|
||||
ssh_obj, info['cmd_set']['list_running']).AndReturn(
|
||||
self.sshclient, info['cmd_set']['list_running']).AndReturn(
|
||||
['"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}'])
|
||||
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NotNodeName")
|
||||
|
||||
ssh._get_hosts_name_for_node(self.sshclient, info).\
|
||||
AndReturn("NotNodeName")
|
||||
self.mox.ReplayAll()
|
||||
pstate = ssh._get_power_status(ssh_obj, info)
|
||||
|
||||
pstate = ssh._get_power_status(self.sshclient, info)
|
||||
self.assertEqual(pstate, states.POWER_OFF)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__get_power_status_error(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
|
||||
|
||||
ssh._exec_ssh_command(
|
||||
ssh_obj, info['cmd_set']['list_running']).AndReturn(
|
||||
self.sshclient, info['cmd_set']['list_running']).AndReturn(
|
||||
['"NodeName" {b43c4982-110c-4c29-9325-d5f41b053513}'])
|
||||
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn(None)
|
||||
|
||||
ssh._get_hosts_name_for_node(self.sshclient, info).\
|
||||
AndReturn(None)
|
||||
self.mox.ReplayAll()
|
||||
pstate = ssh._get_power_status(ssh_obj, info)
|
||||
|
||||
pstate = ssh._get_power_status(self.sshclient, info)
|
||||
self.assertEqual(pstate, states.ERROR)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__get_hosts_name_for_node_match(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
ssh._exec_ssh_command(ssh_obj, info['cmd_set']['list_all']).AndReturn(
|
||||
['NodeName'])
|
||||
cmd_to_exec = info['cmd_set']['get_node_macs']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
|
||||
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(
|
||||
['52:54:00:cf:2d:31'])
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
ssh._exec_ssh_command(self.sshclient, info['cmd_set']['list_all']).\
|
||||
AndReturn(['NodeName'])
|
||||
ssh._exec_ssh_command(self.sshclient, cmd_to_exec).\
|
||||
AndReturn(['52:54:00:cf:2d:31'])
|
||||
self.mox.ReplayAll()
|
||||
found_name = ssh._get_hosts_name_for_node(ssh_obj, info)
|
||||
|
||||
found_name = ssh._get_hosts_name_for_node(self.sshclient, info)
|
||||
self.assertEqual(found_name, 'NodeName')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__get_hosts_name_for_node_no_match(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "22:22:22:22:22:22"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
ssh._exec_ssh_command(ssh_obj, info['cmd_set']['list_all']).AndReturn(
|
||||
['NodeName'])
|
||||
ssh._exec_ssh_command(self.sshclient, info['cmd_set']['list_all']).\
|
||||
AndReturn(['NodeName'])
|
||||
cmd_to_exec = info['cmd_set']['get_node_macs']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
|
||||
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(
|
||||
['52:54:00:cf:2d:31'])
|
||||
|
||||
ssh._exec_ssh_command(self.sshclient, cmd_to_exec).\
|
||||
AndReturn(['52:54:00:cf:2d:31'])
|
||||
self.mox.ReplayAll()
|
||||
found_name = ssh._get_hosts_name_for_node(ssh_obj, info)
|
||||
|
||||
found_name = ssh._get_hosts_name_for_node(self.sshclient, info)
|
||||
self.assertEqual(found_name, None)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__power_on_good(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
|
||||
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
ssh._get_hosts_name_for_node(self.sshclient, info).\
|
||||
AndReturn("NodeName")
|
||||
cmd_to_exec = info['cmd_set']['start_cmd']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
|
||||
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._exec_ssh_command(self.sshclient, cmd_to_exec).\
|
||||
AndReturn(None)
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
current_state = ssh._power_on(ssh_obj, info)
|
||||
|
||||
current_state = ssh._power_on(self.sshclient, info)
|
||||
self.assertEqual(current_state, states.POWER_ON)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__power_on_fail(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
|
||||
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
ssh._get_hosts_name_for_node(self.sshclient, info).\
|
||||
AndReturn("NodeName")
|
||||
cmd_to_exec = info['cmd_set']['start_cmd']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
|
||||
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
|
||||
ssh._exec_ssh_command(self.sshclient, cmd_to_exec).\
|
||||
AndReturn(None)
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
self.mox.ReplayAll()
|
||||
current_state = ssh._power_on(ssh_obj, info)
|
||||
|
||||
current_state = ssh._power_on(self.sshclient, info)
|
||||
self.assertEqual(current_state, states.ERROR)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__power_off_good(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
ssh._get_hosts_name_for_node(self.sshclient, info).\
|
||||
AndReturn("NodeName")
|
||||
cmd_to_exec = info['cmd_set']['stop_cmd']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
|
||||
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_OFF)
|
||||
ssh._exec_ssh_command(self.sshclient, cmd_to_exec).\
|
||||
AndReturn(None)
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
self.mox.ReplayAll()
|
||||
current_state = ssh._power_off(ssh_obj, info)
|
||||
|
||||
current_state = ssh._power_off(self.sshclient, info)
|
||||
self.assertEqual(current_state, states.POWER_OFF)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__power_off_fail(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_get_hosts_name_for_node')
|
||||
self.mox.StubOutWithMock(ssh, '_exec_ssh_command')
|
||||
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._get_hosts_name_for_node(ssh_obj, info).AndReturn("NodeName")
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
ssh._get_hosts_name_for_node(self.sshclient, info).\
|
||||
AndReturn("NodeName")
|
||||
cmd_to_exec = info['cmd_set']['stop_cmd']
|
||||
cmd_to_exec = cmd_to_exec.replace('{_NodeName_}', 'NodeName')
|
||||
ssh._exec_ssh_command(ssh_obj, cmd_to_exec).AndReturn(None)
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._exec_ssh_command(self.sshclient, cmd_to_exec).\
|
||||
AndReturn(None)
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
current_state = ssh._power_off(ssh_obj, info)
|
||||
|
||||
current_state = ssh._power_off(self.sshclient, info)
|
||||
self.assertEqual(current_state, states.ERROR)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_reboot_good(self):
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_control_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_power_off')
|
||||
self.mox.StubOutWithMock(ssh, '_power_on')
|
||||
ssh._parse_control_info(self.node).AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
|
||||
info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(ssh_obj)
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._power_off(ssh_obj, info).AndReturn(None)
|
||||
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
task.resources[0].controller.reboot(task, self.node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_reboot_fail(self):
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_control_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_power_off')
|
||||
self.mox.StubOutWithMock(ssh, '_power_on')
|
||||
ssh._parse_control_info(self.node).AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
|
||||
info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(ssh_obj)
|
||||
ssh._get_power_status(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._power_off(ssh_obj, info).AndReturn(None)
|
||||
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_OFF)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.resources[0].controller.reboot,
|
||||
task,
|
||||
self.node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_exec_ssh_command_good(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh_obj, 'exec_command')
|
||||
self.mox.StubOutWithMock(self.sshclient, 'exec_command')
|
||||
|
||||
class Channel(object):
|
||||
def recv_exit_status(self):
|
||||
@ -364,18 +316,16 @@ class IronicSSHTestCase(base.TestCase):
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
ssh_obj.exec_command("command").AndReturn(
|
||||
self.sshclient.exec_command("command").AndReturn(
|
||||
(Stream(), Stream('hello'), Stream()))
|
||||
self.mox.ReplayAll()
|
||||
stdout, stderr = ssh._exec_ssh_command(ssh_obj, "command")
|
||||
|
||||
stdout, stderr = ssh._exec_ssh_command(self.sshclient, "command")
|
||||
self.assertEqual(stdout, 'hello')
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_exec_ssh_command_fail(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
self.mox.StubOutWithMock(ssh_obj, 'exec_command')
|
||||
self.mox.StubOutWithMock(self.sshclient, 'exec_command')
|
||||
|
||||
class Channel(object):
|
||||
def recv_exit_status(self):
|
||||
@ -392,69 +342,148 @@ class IronicSSHTestCase(base.TestCase):
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
ssh_obj.exec_command("command").AndReturn(
|
||||
self.sshclient.exec_command("command").AndReturn(
|
||||
(Stream(), Stream('hello'), Stream()))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
ssh._exec_ssh_command,
|
||||
ssh_obj,
|
||||
self.sshclient,
|
||||
"command")
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_start_console(self):
|
||||
self.assertRaises(exception.IronicException,
|
||||
self.ssh.start_console,
|
||||
None,
|
||||
self.node)
|
||||
|
||||
class SSHDriverTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SSHDriverTestCase, self).setUp()
|
||||
self.driver = mgr_utils.get_mocked_node_manager(driver='fake_ssh')
|
||||
self.node = db_utils.get_test_node(
|
||||
driver='fake_ssh',
|
||||
driver_info=db_utils.ssh_info)
|
||||
self.dbapi = dbapi.get_instance()
|
||||
self.dbapi.create_node(self.node)
|
||||
self.sshclient = paramiko.SSHClient()
|
||||
|
||||
def test__get_nodes_mac_addresses(self):
|
||||
class task(object):
|
||||
dbapi = self.dbapi
|
||||
|
||||
ports = [db_utils.get_test_port(id=6, address='aa:bb:cc'),
|
||||
db_utils.get_test_port(id=7, address='dd:ee:ff')]
|
||||
self.dbapi.create_port(ports[0])
|
||||
self.dbapi.create_port(ports[1])
|
||||
|
||||
self.mox.StubOutWithMock(self.dbapi, 'get_ports_by_node')
|
||||
self.dbapi.get_ports_by_node(self.node.get('id')).\
|
||||
AndReturn(ports)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
node_macs = ssh._get_nodes_mac_addresses(task(), self.node)
|
||||
self.assertEqual(node_macs, ['aa:bb:cc', 'dd:ee:ff'])
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_stop_console(self):
|
||||
self.assertRaises(exception.IronicException,
|
||||
self.ssh.start_console,
|
||||
None,
|
||||
self.node)
|
||||
def test_reboot_good(self):
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_driver_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_power_off')
|
||||
self.mox.StubOutWithMock(ssh, '_power_on')
|
||||
ssh._parse_driver_info(self.node).\
|
||||
AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).\
|
||||
AndReturn(info['macs'])
|
||||
ssh._get_connection(self.node).\
|
||||
AndReturn(self.sshclient)
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
ssh._power_off(self.sshclient, info).\
|
||||
AndReturn(None)
|
||||
ssh._power_on(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
task.resources[0].driver.power.reboot(task, self.node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_reboot_fail(self):
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_driver_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_get_power_status')
|
||||
self.mox.StubOutWithMock(ssh, '_power_off')
|
||||
self.mox.StubOutWithMock(ssh, '_power_on')
|
||||
ssh._parse_driver_info(self.node).\
|
||||
AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).\
|
||||
AndReturn(info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(self.sshclient)
|
||||
ssh._get_power_status(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
ssh._power_off(self.sshclient, info).\
|
||||
AndReturn(None)
|
||||
ssh._power_on(self.sshclient, info).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.resources[0].driver.power.reboot,
|
||||
task,
|
||||
self.node)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_state_bad_state(self):
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_control_info')
|
||||
self.mox.StubOutWithMock(ssh, '_parse_driver_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
ssh._parse_control_info(self.node).AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
|
||||
info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(ssh_obj)
|
||||
ssh._parse_driver_info(self.node).\
|
||||
AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).\
|
||||
AndReturn(info['macs'])
|
||||
ssh._get_connection(self.node).\
|
||||
AndReturn(self.sshclient)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
self.assertRaises(exception.IronicException,
|
||||
task.resources[0].controller.set_power_state,
|
||||
task.resources[0].driver.power.set_power_state,
|
||||
task,
|
||||
self.node,
|
||||
"BAD_PSTATE")
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_state_on_good(self):
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_control_info')
|
||||
self.mox.StubOutWithMock(ssh, '_parse_driver_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_power_on')
|
||||
ssh._parse_control_info(self.node).AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
|
||||
info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(ssh_obj)
|
||||
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._parse_driver_info(self.node).\
|
||||
AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).\
|
||||
AndReturn(info['macs'])
|
||||
ssh._get_connection(self.node).\
|
||||
AndReturn(self.sshclient)
|
||||
ssh._power_on(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
task.resources[0].controller.set_power_state(
|
||||
task.resources[0].driver.power.set_power_state(
|
||||
task,
|
||||
self.node,
|
||||
states.POWER_ON)
|
||||
@ -462,47 +491,51 @@ class IronicSSHTestCase(base.TestCase):
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_state_on_fail(self):
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_control_info')
|
||||
self.mox.StubOutWithMock(ssh, '_parse_driver_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_power_on')
|
||||
ssh._parse_control_info(self.node).AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
|
||||
info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(ssh_obj)
|
||||
ssh._power_on(ssh_obj, info).AndReturn(states.POWER_OFF)
|
||||
ssh._parse_driver_info(self.node).\
|
||||
AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).\
|
||||
AndReturn(info['macs'])
|
||||
ssh._get_connection(self.node).\
|
||||
AndReturn(self.sshclient)
|
||||
ssh._power_on(self.sshclient, info).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.resources[0].controller.set_power_state,
|
||||
task.resources[0].driver.power.set_power_state,
|
||||
task,
|
||||
self.node,
|
||||
states.POWER_ON)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_state_off_good(self):
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_control_info')
|
||||
self.mox.StubOutWithMock(ssh, '_parse_driver_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_power_off')
|
||||
ssh._parse_control_info(self.node).AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
|
||||
info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(ssh_obj)
|
||||
ssh._power_off(ssh_obj, info).AndReturn(states.POWER_OFF)
|
||||
ssh._parse_driver_info(self.node).\
|
||||
AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).\
|
||||
AndReturn(info['macs'])
|
||||
ssh._get_connection(self.node).\
|
||||
AndReturn(self.sshclient)
|
||||
ssh._power_off(self.sshclient, info).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
task.resources[0].controller.set_power_state(
|
||||
task.resources[0].driver.power.set_power_state(
|
||||
task,
|
||||
self.node,
|
||||
states.POWER_OFF)
|
||||
@ -510,40 +543,27 @@ class IronicSSHTestCase(base.TestCase):
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_set_power_state_off_fail(self):
|
||||
ssh_obj = paramiko.SSHClient()
|
||||
info = ssh._parse_control_info(self.node)
|
||||
info = ssh._parse_driver_info(self.node)
|
||||
info['macs'] = ["11:11:11:11:11:11", "52:54:00:cf:2d:31"]
|
||||
|
||||
self.mox.StubOutWithMock(ssh, '_parse_control_info')
|
||||
self.mox.StubOutWithMock(ssh, '_parse_driver_info')
|
||||
self.mox.StubOutWithMock(ssh, '_get_nodes_mac_addresses')
|
||||
self.mox.StubOutWithMock(ssh, '_get_connection')
|
||||
self.mox.StubOutWithMock(ssh, '_power_off')
|
||||
ssh._parse_control_info(self.node).AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).AndReturn(
|
||||
info['macs'])
|
||||
ssh._get_connection(self.node).AndReturn(ssh_obj)
|
||||
ssh._power_off(ssh_obj, info).AndReturn(states.POWER_ON)
|
||||
ssh._parse_driver_info(self.node).\
|
||||
AndReturn(info)
|
||||
ssh._get_nodes_mac_addresses(mox.IgnoreArg(), self.node).\
|
||||
AndReturn(info['macs'])
|
||||
ssh._get_connection(self.node).\
|
||||
AndReturn(self.sshclient)
|
||||
ssh._power_off(self.sshclient, info).\
|
||||
AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']], shared=False) as task:
|
||||
self.assertRaises(exception.PowerStateFailure,
|
||||
task.resources[0].controller.set_power_state,
|
||||
task.resources[0].driver.power.set_power_state,
|
||||
task,
|
||||
self.node,
|
||||
states.POWER_OFF)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test__get_nodes_mac_addresses(self):
|
||||
info = ssh._parse_control_info(self.node)
|
||||
|
||||
self.mox.StubOutWithMock(self.dbapi, 'get_ports_by_node')
|
||||
self.dbapi.get_ports_by_node(self.node.get('uuid')).\
|
||||
AndReturn(self.ifports)
|
||||
self.dbapi.get_ports_by_node(self.node.get('id')).\
|
||||
AndReturn(self.ifports)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
with task_manager.acquire([info['uuid']]) as task:
|
||||
node_macs = ssh._get_nodes_mac_addresses(task, self.node)
|
||||
self.assertEqual(node_macs, ['aa:bb:cc', 'dd:ee:ff'])
|
||||
self.mox.VerifyAll()
|
||||
|
@ -36,11 +36,10 @@ class ManagerTestCase(base.DbTestCase):
|
||||
self.service = manager.ManagerService('test-host', 'test-topic')
|
||||
self.context = context.get_admin_context()
|
||||
self.dbapi = dbapi.get_instance()
|
||||
(self.controller, self.deployer) = mgr_utils.get_mocked_node_manager()
|
||||
self.driver = mgr_utils.get_mocked_node_manager()
|
||||
|
||||
def test_get_power_state(self):
|
||||
n = utils.get_test_node(control_driver='fake',
|
||||
deploy_driver='fake')
|
||||
n = utils.get_test_node(driver='fake')
|
||||
self.dbapi.create_node(n)
|
||||
|
||||
# FakeControlDriver.get_power_state will "pass"
|
||||
@ -49,15 +48,14 @@ class ManagerTestCase(base.DbTestCase):
|
||||
self.assertEqual(state, states.NOSTATE)
|
||||
|
||||
def test_get_power_state_with_mock(self):
|
||||
n = utils.get_test_node(control_driver='fake',
|
||||
deploy_driver='fake')
|
||||
n = utils.get_test_node(driver='fake')
|
||||
self.dbapi.create_node(n)
|
||||
|
||||
self.mox.StubOutWithMock(self.controller, 'get_power_state')
|
||||
self.mox.StubOutWithMock(self.driver.power, 'get_power_state')
|
||||
|
||||
self.controller.get_power_state(mox.IgnoreArg(), mox.IgnoreArg()).\
|
||||
self.driver.power.get_power_state(mox.IgnoreArg(), mox.IgnoreArg()).\
|
||||
AndReturn(states.POWER_OFF)
|
||||
self.controller.get_power_state(mox.IgnoreArg(), mox.IgnoreArg()).\
|
||||
self.driver.power.get_power_state(mox.IgnoreArg(), mox.IgnoreArg()).\
|
||||
AndReturn(states.POWER_ON)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -51,7 +51,7 @@ class TaskManagerTestCase(base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(TaskManagerTestCase, self).setUp()
|
||||
self.dbapi = dbapi.get_instance()
|
||||
(self.controller, self.deployer) = mgr_utils.get_mocked_node_manager()
|
||||
self.driver = mgr_utils.get_mocked_node_manager()
|
||||
|
||||
self.uuids = [create_fake_node(i) for i in xrange(1, 6)]
|
||||
self.uuids.sort()
|
||||
@ -119,7 +119,7 @@ class ExclusiveLockDecoratorTestCase(base.DbTestCase):
|
||||
def setUp(self):
|
||||
super(ExclusiveLockDecoratorTestCase, self).setUp()
|
||||
self.dbapi = dbapi.get_instance()
|
||||
(self.controller, self.deployer) = mgr_utils.get_mocked_node_manager()
|
||||
self.driver = mgr_utils.get_mocked_node_manager()
|
||||
self.uuids = [create_fake_node(123)]
|
||||
|
||||
def test_require_exclusive_lock(self):
|
||||
|
@ -27,19 +27,29 @@ class UtilsTestCase(base.TestCase):
|
||||
def setUp(self):
|
||||
super(UtilsTestCase, self).setUp()
|
||||
|
||||
def test_fails_to_load_extension(self):
|
||||
self.assertRaises(AttributeError,
|
||||
utils.get_mockable_extension_manager,
|
||||
'fake',
|
||||
'bad.namespace')
|
||||
self.assertRaises(AttributeError,
|
||||
utils.get_mockable_extension_manager,
|
||||
'no-such-driver',
|
||||
'ironic.drivers')
|
||||
|
||||
def test_get_mockable_ext_mgr(self):
|
||||
(mgr, ext) = utils.get_mockable_extension_manager("fake",
|
||||
'ironic.controllers')
|
||||
(mgr, ext) = utils.get_mockable_extension_manager('fake',
|
||||
'ironic.drivers')
|
||||
|
||||
# confirm that stevedore did not scan the actual entrypoints
|
||||
self.assertNotEqual(mgr.namespace, 'ironic.controllers')
|
||||
self.assertNotEqual(mgr.namespace, 'ironic.drivers')
|
||||
# confirm mgr has only one extension
|
||||
self.assertEqual(len(mgr.extensions), 1)
|
||||
# confirm that we got a reference to the extension in this manager
|
||||
self.assertEqual(mgr.extensions[0], ext)
|
||||
# confirm that it is the "fake" driver we asked for
|
||||
self.assertEqual("%s" % ext.entry_point,
|
||||
"fake = ironic.drivers.fake:FakeControlDriver")
|
||||
"fake = ironic.drivers.fake:FakeDriver")
|
||||
|
||||
def test_get_mocked_node_mgr(self):
|
||||
self.mox.StubOutWithMock(utils, 'get_mockable_extension_manager')
|
||||
@ -48,19 +58,14 @@ class UtilsTestCase(base.TestCase):
|
||||
def __init__(self, name):
|
||||
self.obj = name
|
||||
|
||||
utils.get_mockable_extension_manager('foo', 'ironic.controllers').\
|
||||
utils.get_mockable_extension_manager('foo', 'ironic.drivers').\
|
||||
AndReturn(('foo-manager', ext('foo-extension')))
|
||||
utils.get_mockable_extension_manager('bar', 'ironic.deployers').\
|
||||
AndReturn(('bar-manager', ext('bar-extension')))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
(control_ext, deploy_ext) = utils.get_mocked_node_manager('foo', 'bar')
|
||||
driver = utils.get_mocked_node_manager('foo')
|
||||
|
||||
self.assertEqual(resource_manager.NodeManager._control_factory,
|
||||
self.assertEqual(resource_manager.NodeManager._driver_factory,
|
||||
'foo-manager')
|
||||
self.assertEqual(control_ext, 'foo-extension')
|
||||
self.assertEqual(resource_manager.NodeManager._deploy_factory,
|
||||
'bar-manager')
|
||||
self.assertEqual(deploy_ext, 'bar-extension')
|
||||
self.assertEqual(driver, 'foo-extension')
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
@ -35,10 +35,13 @@ def get_mockable_extension_manager(driver, namespace):
|
||||
namespace.
|
||||
|
||||
"""
|
||||
for entry_point in list(pkg_resources.iter_entry_points(namespace)):
|
||||
s = "%s" % entry_point
|
||||
if s.startswith(driver):
|
||||
entry_point = None
|
||||
for ep in list(pkg_resources.iter_entry_points(namespace)):
|
||||
s = "%s" % ep
|
||||
if driver == s[:s.index(' =')]:
|
||||
entry_point = ep
|
||||
break
|
||||
|
||||
mock_ext_mgr = dispatch.NameDispatchExtensionManager(
|
||||
'ironic.no-such-namespace',
|
||||
lambda x: True)
|
||||
@ -48,25 +51,18 @@ def get_mockable_extension_manager(driver, namespace):
|
||||
return (mock_ext_mgr, mock_ext)
|
||||
|
||||
|
||||
def get_mocked_node_manager(control_driver="fake", deploy_driver="fake"):
|
||||
"""Get a mockable :class:NodeManager instance.
|
||||
def get_mocked_node_manager(driver="fake"):
|
||||
"""Mock :class:NodeManager and get a ref to the driver inside..
|
||||
|
||||
To enable testing of NodeManagers, we need to control what plugins
|
||||
stevedore loads under the hood. To do that, we fake the plugin loading,
|
||||
substitute NodeManager's _control_factory and _deploy_factory with the
|
||||
fake managers, and then return handles to the actual objects.
|
||||
substituting NodeManager's _driver_factory with an instance of the
|
||||
specified driver only, and return a reference directly to that driver
|
||||
instance.
|
||||
|
||||
:returns: A tuple of (control, deploy) drivers.
|
||||
:returns: A driver instance.
|
||||
"""
|
||||
|
||||
(mgr, ext) = get_mockable_extension_manager(control_driver,
|
||||
'ironic.controllers')
|
||||
resource_manager.NodeManager._control_factory = mgr
|
||||
c = ext.obj
|
||||
|
||||
(mgr, ext) = get_mockable_extension_manager(deploy_driver,
|
||||
'ironic.deployers')
|
||||
resource_manager.NodeManager._deploy_factory = mgr
|
||||
d = ext.obj
|
||||
|
||||
return (c, d)
|
||||
(mgr, ext) = get_mockable_extension_manager(driver, 'ironic.drivers')
|
||||
resource_manager.NodeManager._driver_factory = mgr
|
||||
return ext.obj
|
||||
|
15
setup.cfg
15
setup.cfg
@ -33,14 +33,13 @@ console_scripts =
|
||||
ironic-manager = ironic.cmd.manager:main
|
||||
ironic-rootwrap = ironic.openstack.common.rootwrap.cmd:main
|
||||
|
||||
ironic.controllers =
|
||||
fake = ironic.drivers.fake:FakeControlDriver
|
||||
ipmi = ironic.drivers.ipmi:IPMIPowerDriver
|
||||
ssh = ironic.drivers.ssh:SSHPowerDriver
|
||||
|
||||
ironic.deployers =
|
||||
fake = ironic.drivers.fake:FakeDeployDriver
|
||||
# pxe = ironic.drivers.pxe.PXEDeployDriver
|
||||
ironic.drivers =
|
||||
fake = ironic.drivers.fake:FakeDriver
|
||||
fake_ipmi = ironic.drivers.fake:FakeIPMIDriver
|
||||
fake_ssh = ironic.drivers.fake:FakeSSHDriver
|
||||
fake_pxe = ironic.drivers.fake:FakePXEDriver
|
||||
pxe_ipmi = ironic.drivers.pxe:PXEAndIPMIDriver
|
||||
pxe_ssh = ironic.drivers.pxe:PXEAndSSHDriver
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
|
Loading…
x
Reference in New Issue
Block a user