Add Cisco IMC PXE Driver
Current drivers only allow for control of UCS servers via either IPMI or UCSM, the Cisco UCS C-Series operating in standalone mode can also be controlled via CIMC using its http/s XML API. This provides finer control over the server than IPMI can, and doesn't require the extra infrastructure that UCSM needs. Change-Id: Ibd39040e3d7e82a87960d33150750433beb2453b Implements: blueprint cisco-imc-pxe-driver
This commit is contained in:
parent
4b5d69ffcf
commit
363c9c38df
@ -105,3 +105,12 @@ iBoot driver
|
||||
:maxdepth: 1
|
||||
|
||||
../drivers/iboot
|
||||
|
||||
|
||||
CIMC driver
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../drivers/cimc
|
||||
|
95
doc/source/drivers/cimc.rst
Normal file
95
doc/source/drivers/cimc.rst
Normal file
@ -0,0 +1,95 @@
|
||||
.. _CIMC:
|
||||
|
||||
============
|
||||
CIMC drivers
|
||||
============
|
||||
|
||||
Overview
|
||||
========
|
||||
The CIMC drivers are targeted for standalone Cisco UCS C series servers.
|
||||
These drivers enable you to take advantage of CIMC by using the
|
||||
python SDK.
|
||||
|
||||
``pxe_iscsi_cimc`` driver uses PXE boot + iSCSI deploy (just like ``pxe_ipmitool``
|
||||
driver) to deploy the image and uses CIMC to do all management operations on
|
||||
the baremetal node (instead of using IPMI).
|
||||
|
||||
``pxe_agent_cimc`` driver uses PXE boot + Agent deploy (just like ``agent_ipmitool``
|
||||
and ``agent_ipminative`` drivers.) to deploy the image and uses CIMC to do all
|
||||
management operations on the baremetal node (instead of using IPMI). Unlike with
|
||||
iSCSI deploy in Agent deploy, the ramdisk is responsible for writing the image to
|
||||
the disk, instead of the conductor.
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
||||
* ``ImcSdk`` is a python SDK for the CIMC HTTP/HTTPS XML API used to control
|
||||
CIMC.
|
||||
|
||||
Install the ``ImcSdk`` module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
Install the ``ImcSdk`` module on the Ironic conductor node. Required version is
|
||||
0.7.1.
|
||||
|
||||
#. Download the tar.gz from: https://communities.cisco.com/docs/DOC-56257
|
||||
|
||||
#. Unpack it::
|
||||
|
||||
$ tar xvf ImcSdk-0.7.1.tar.gz
|
||||
|
||||
#. Install it::
|
||||
|
||||
$ cd ImcSdk-0.7.1
|
||||
$ python setup.py install
|
||||
|
||||
Tested Platforms
|
||||
~~~~~~~~~~~~~~~~
|
||||
This driver works with UCS C-Series servers and has been tested with:
|
||||
|
||||
* UCS C240M3S
|
||||
|
||||
Configuring and Enabling the driver
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
1. Add ``pxe_cimc`` and/or ``agent_cimc`` to the list of ``enabled_drivers`` in
|
||||
``/etc/ironic/ironic.conf``. For example::
|
||||
|
||||
enabled_drivers = pxe_ipmitool,pxe_cimc,agent_cimc
|
||||
|
||||
2. Restart the Ironic conductor service:
|
||||
|
||||
For Ubuntu/Debian systems::
|
||||
|
||||
$ sudo service ironic-conductor restart
|
||||
|
||||
or for RHEL/CentOS/Fedora::
|
||||
|
||||
$ sudo systemctl restart openstack-ironic-conductor
|
||||
|
||||
Registering Standalone UCS node in Ironic
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Nodes configured for CIMC driver should have the ``driver`` property set to
|
||||
``pxe_iscsi_cimc`` or ``pxe_agent_cimc``. The following configuration values are
|
||||
also required in ``driver_info``:
|
||||
|
||||
- ``cimc_address``: IP address or hostname for CIMC
|
||||
- ``cimc_username``: CIMC login user name
|
||||
- ``cimc_password``: CIMC login password for the above CIMC user.
|
||||
- ``deploy_kernel``: The Glance UUID of the deployment kernel.
|
||||
- ``deploy_ramdisk``: The Glance UUID of the deployment ramdisk.
|
||||
|
||||
The following sequence of commands can be used to enroll a UCS Standalone node.
|
||||
|
||||
Create Node::
|
||||
|
||||
ironic node-create -d <pxe_cimc/agent_cimc> -i cimc_address=<CIMC hostname/ip-address> -i cimc_username=<cimc_username> -i cimc_password=<cimc_password> -i deploy_kernel=<glance_uuid_of_deploy_kernel> -i deploy_ramdisk=<glance_uuid_of_deploy_ramdisk> -p cpus=<number_of_cpus> -p memory_mb=<memory_size_in_MB> -p local_gb=<local_disk_size_in_GB> -p cpu_arch=<cpu_arch>
|
||||
|
||||
The above command 'ironic node-create' will return UUID of the node, which is the value of $NODE in the following command.
|
||||
|
||||
Associate port with the node created::
|
||||
|
||||
ironic port-create -n $NODE -a <MAC_address_of_Ucs_server's_NIC>
|
||||
|
||||
For more information about enrolling nodes see "Enrolling a node" in the :ref:`install-guide`
|
@ -26,3 +26,6 @@ UcsSdk==0.8.2.2
|
||||
# Refer documentation on how to install and configure this:
|
||||
# http://docs.openstack.org/developer/ironic/drivers/vbox.html
|
||||
pyremotevbox>=0.5.0
|
||||
|
||||
# The CIMC drivers use the Cisco IMC SDK version 0.7.1, which is avaliable from
|
||||
# https://communities.cisco.com/docs/DOC-37174
|
||||
|
@ -453,6 +453,21 @@
|
||||
#public_endpoint=<None>
|
||||
|
||||
|
||||
[cimc]
|
||||
|
||||
#
|
||||
# Options defined in ironic.drivers.modules.cimc.power
|
||||
#
|
||||
|
||||
# Number of times a power operation needs to be retried
|
||||
# (integer value)
|
||||
#max_retry=6
|
||||
|
||||
# Amount of time in seconds to wait in between power
|
||||
# operations (integer value)
|
||||
#action_interval=10
|
||||
|
||||
|
||||
[cisco_ucs]
|
||||
|
||||
#
|
||||
|
@ -590,3 +590,7 @@ class WolOperationError(IronicException):
|
||||
class ImageUploadFailed(IronicException):
|
||||
message = _("Failed to upload %(image_name)s image to web server "
|
||||
"%(web_server)s, reason: %(reason)s")
|
||||
|
||||
|
||||
class CIMCException(IronicException):
|
||||
message = _("Cisco IMC exception occured for node %(node)s: %(error)s")
|
||||
|
@ -18,6 +18,8 @@ from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
||||
from ironic.drivers.modules.cimc import power as cimc_power
|
||||
from ironic.drivers.modules import ipminative
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules import pxe
|
||||
@ -157,3 +159,26 @@ class AgentAndUcsDriver(base.BaseDriver):
|
||||
self.deploy = agent.AgentDeploy()
|
||||
self.management = ucs_mgmt.UcsManagement()
|
||||
self.vendor = agent.AgentVendorInterface()
|
||||
|
||||
|
||||
class AgentAndCIMCDriver(base.BaseDriver):
|
||||
"""Agent + Cisco CIMC driver.
|
||||
|
||||
This driver implements the `core` functionality, combining
|
||||
:class:ironic.drivers.modules.cimc.power.Power for power
|
||||
on/off and reboot with
|
||||
:class:'ironic.driver.modules.agent.AgentDeploy' (for image deployment.)
|
||||
Implementations are in those respective classes;
|
||||
this class is merely the glue between them.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
if not importutils.try_import('ImcSdk'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import ImcSdk library"))
|
||||
self.power = cimc_power.Power()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = agent.AgentDeploy()
|
||||
self.management = cimc_mgmt.CIMCManagement()
|
||||
self.vendor = agent.AgentVendorInterface()
|
||||
|
@ -25,6 +25,8 @@ from ironic.drivers import base
|
||||
from ironic.drivers.modules import agent
|
||||
from ironic.drivers.modules.amt import management as amt_mgmt
|
||||
from ironic.drivers.modules.amt import power as amt_power
|
||||
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
||||
from ironic.drivers.modules.cimc import power as cimc_power
|
||||
from ironic.drivers.modules.drac import management as drac_mgmt
|
||||
from ironic.drivers.modules.drac import power as drac_power
|
||||
from ironic.drivers.modules import fake
|
||||
@ -270,6 +272,19 @@ class FakeUcsDriver(base.BaseDriver):
|
||||
self.management = ucs_mgmt.UcsManagement()
|
||||
|
||||
|
||||
class FakeCIMCDriver(base.BaseDriver):
|
||||
"""Fake CIMC driver."""
|
||||
|
||||
def __init__(self):
|
||||
if not importutils.try_import('ImcSdk'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import ImcSdk library"))
|
||||
self.power = cimc_power.Power()
|
||||
self.deploy = fake.FakeDeploy()
|
||||
self.management = cimc_mgmt.CIMCManagement()
|
||||
|
||||
|
||||
class FakeWakeOnLanDriver(base.BaseDriver):
|
||||
"""Fake Wake-On-Lan driver."""
|
||||
|
||||
|
0
ironic/drivers/modules/cimc/__init__.py
Normal file
0
ironic/drivers/modules/cimc/__init__.py
Normal file
87
ironic/drivers/modules/cimc/common.py
Normal file
87
ironic/drivers/modules/cimc/common.py
Normal file
@ -0,0 +1,87 @@
|
||||
# Copyright 2015, Cisco Systems.
|
||||
#
|
||||
# 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 contextlib import contextmanager
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.drivers.modules import deploy_utils
|
||||
|
||||
REQUIRED_PROPERTIES = {
|
||||
'cimc_address': _('IP or Hostname of the CIMC. Required.'),
|
||||
'cimc_username': _('CIMC Manager admin username. Required.'),
|
||||
'cimc_password': _('CIMC Manager password. Required.'),
|
||||
}
|
||||
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES
|
||||
|
||||
imcsdk = importutils.try_import('ImcSdk')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_driver_info(node):
|
||||
"""Parses and creates Cisco driver info
|
||||
|
||||
:param node: An Ironic node object.
|
||||
:returns: dictionary that contains node.driver_info parameter/values.
|
||||
:raises: MissingParameterValue if any required parameters are missing.
|
||||
"""
|
||||
|
||||
info = {}
|
||||
for param in REQUIRED_PROPERTIES:
|
||||
info[param] = node.driver_info.get(param)
|
||||
error_msg = (_("%s driver requires these parameters to be set in the "
|
||||
"node's driver_info.") %
|
||||
node.driver)
|
||||
deploy_utils.check_for_missing_params(info, error_msg)
|
||||
return info
|
||||
|
||||
|
||||
def handle_login(task, handle, info):
|
||||
"""Login to the CIMC handle.
|
||||
|
||||
Run login on the CIMC handle, catching any ImcException and reraising
|
||||
it as an ironic CIMCException.
|
||||
|
||||
:param handle: A CIMC handle.
|
||||
:param info: A list of driver info as produced by parse_driver_info.
|
||||
:raises: CIMCException if there error logging in.
|
||||
"""
|
||||
try:
|
||||
handle.login(info['cimc_address'],
|
||||
info['cimc_username'],
|
||||
info['cimc_password'])
|
||||
except imcsdk.ImcException as e:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=e)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cimc_handle(task):
|
||||
"""Context manager for creating a CIMC handle and logging into it
|
||||
|
||||
:param task: The current task object.
|
||||
:raises: CIMCException if login fails
|
||||
:yields: A CIMC Handle for the node in the task.
|
||||
"""
|
||||
info = parse_driver_info(task.node)
|
||||
handle = imcsdk.ImcHandle()
|
||||
|
||||
handle_login(task, handle, info)
|
||||
try:
|
||||
yield handle
|
||||
finally:
|
||||
handle.logout()
|
166
ironic/drivers/modules/cimc/management.py
Normal file
166
ironic/drivers/modules/cimc/management.py
Normal file
@ -0,0 +1,166 @@
|
||||
# Copyright 2015, Cisco Systems.
|
||||
#
|
||||
# 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 oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules.cimc import common
|
||||
|
||||
imcsdk = importutils.try_import('ImcSdk')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CIMC_TO_IRONIC_BOOT_DEVICE = {
|
||||
'storage-read-write': boot_devices.DISK,
|
||||
'lan-read-only': boot_devices.PXE,
|
||||
'vm-read-only': boot_devices.CDROM
|
||||
}
|
||||
|
||||
IRONIC_TO_CIMC_BOOT_DEVICE = {
|
||||
boot_devices.DISK: ('lsbootStorage', 'storage-read-write',
|
||||
'storage', 'read-write'),
|
||||
boot_devices.PXE: ('lsbootLan', 'lan-read-only',
|
||||
'lan', 'read-only'),
|
||||
boot_devices.CDROM: ('lsbootVirtualMedia', 'vm-read-only',
|
||||
'virtual-media', 'read-only')
|
||||
}
|
||||
|
||||
|
||||
class CIMCManagement(base.ManagementInterface):
|
||||
|
||||
def get_properties(self):
|
||||
"""Return the properties of the interface.
|
||||
|
||||
:returns: dictionary of <property name>:<property description> entries.
|
||||
"""
|
||||
return common.COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check if node.driver_info contains the required CIMC credentials.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: InvalidParameterValue if required CIMC credentials are
|
||||
missing.
|
||||
"""
|
||||
common.parse_driver_info(task.node)
|
||||
|
||||
def get_supported_boot_devices(self, task):
|
||||
"""Get a list of the supported boot devices.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:returns: A list with the supported boot devices defined
|
||||
in :mod:`ironic.common.boot_devices`.
|
||||
"""
|
||||
return list(CIMC_TO_IRONIC_BOOT_DEVICE.values())
|
||||
|
||||
def get_boot_device(self, task):
|
||||
"""Get the current boot device for a node.
|
||||
|
||||
Provides the current boot device of the node. Be aware that not
|
||||
all drivers support this.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: MissingParameterValue if a required parameter is missing
|
||||
:raises: CIMCException if there is an error from CIMC
|
||||
:returns: a dictionary containing:
|
||||
|
||||
:boot_device:
|
||||
the boot device, one of :mod:`ironic.common.boot_devices` or
|
||||
None if it is unknown.
|
||||
:persistent:
|
||||
Whether the boot device will persist to all future boots or
|
||||
not, None if it is unknown.
|
||||
"""
|
||||
|
||||
with common.cimc_handle(task) as handle:
|
||||
method = imcsdk.ImcCore.ExternalMethod("ConfigResolveClass")
|
||||
method.Cookie = handle.cookie
|
||||
method.InDn = "sys/rack-unit-1"
|
||||
method.InHierarchical = "true"
|
||||
method.ClassId = "lsbootDef"
|
||||
|
||||
try:
|
||||
resp = handle.xml_query(method, imcsdk.WriteXmlOption.DIRTY)
|
||||
except imcsdk.ImcException as e:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=e)
|
||||
error = getattr(resp, 'error_code', None)
|
||||
if error:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=error)
|
||||
|
||||
bootDevs = resp.OutConfigs.child[0].child
|
||||
|
||||
first_device = None
|
||||
for dev in bootDevs:
|
||||
try:
|
||||
if int(dev.Order) == 1:
|
||||
first_device = dev
|
||||
break
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
boot_device = (CIMC_TO_IRONIC_BOOT_DEVICE.get(
|
||||
first_device.Rn) if first_device else None)
|
||||
|
||||
# Every boot device in CIMC is persistent right now
|
||||
persistent = True if boot_device else None
|
||||
return {'boot_device': boot_device, 'persistent': persistent}
|
||||
|
||||
def set_boot_device(self, task, device, persistent=True):
|
||||
"""Set the boot device for a node.
|
||||
|
||||
Set the boot device to use on next reboot of the node.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:param device: the boot device, one of
|
||||
:mod:`ironic.common.boot_devices`.
|
||||
:param persistent: Every boot device in CIMC is persistent right now,
|
||||
so this value is ignored.
|
||||
:raises: InvalidParameterValue if an invalid boot device is
|
||||
specified.
|
||||
:raises: MissingParameterValue if a required parameter is missing
|
||||
:raises: CIMCException if there is an error from CIMC
|
||||
"""
|
||||
|
||||
with common.cimc_handle(task) as handle:
|
||||
dev = IRONIC_TO_CIMC_BOOT_DEVICE[device]
|
||||
|
||||
method = imcsdk.ImcCore.ExternalMethod("ConfigConfMo")
|
||||
method.Cookie = handle.cookie
|
||||
method.Dn = "sys/rack-unit-1/boot-policy"
|
||||
method.InHierarchical = "true"
|
||||
|
||||
config = imcsdk.Imc.ConfigConfig()
|
||||
|
||||
bootMode = imcsdk.ImcCore.ManagedObject(dev[0])
|
||||
bootMode.set_attr("access", dev[3])
|
||||
bootMode.set_attr("type", dev[2])
|
||||
bootMode.set_attr("Rn", dev[1])
|
||||
bootMode.set_attr("order", "1")
|
||||
|
||||
config.add_child(bootMode)
|
||||
method.InConfig = config
|
||||
|
||||
try:
|
||||
resp = handle.xml_query(method, imcsdk.WriteXmlOption.DIRTY)
|
||||
except imcsdk.ImcException as e:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=e)
|
||||
error = getattr(resp, 'error_code')
|
||||
if error:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=error)
|
||||
|
||||
def get_sensors_data(self, task):
|
||||
raise NotImplementedError()
|
184
ironic/drivers/modules/cimc/power.py
Normal file
184
ironic/drivers/modules/cimc/power.py
Normal file
@ -0,0 +1,184 @@
|
||||
# Copyright 2015, Cisco Systems.
|
||||
#
|
||||
# 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 oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules.cimc import common
|
||||
|
||||
imcsdk = importutils.try_import('ImcSdk')
|
||||
|
||||
opts = [
|
||||
cfg.IntOpt('max_retry',
|
||||
default=6,
|
||||
help=_('Number of times a power operation needs to be '
|
||||
'retried')),
|
||||
cfg.IntOpt('action_interval',
|
||||
default=10,
|
||||
help=_('Amount of time in seconds to wait in between power '
|
||||
'operations')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(opts, group='cimc')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
if imcsdk:
|
||||
CIMC_TO_IRONIC_POWER_STATE = {
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON: states.POWER_ON,
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF: states.POWER_OFF,
|
||||
}
|
||||
|
||||
IRONIC_TO_CIMC_POWER_STATE = {
|
||||
states.POWER_ON: imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_UP,
|
||||
states.POWER_OFF: imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_DOWN,
|
||||
states.REBOOT:
|
||||
imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_HARD_RESET_IMMEDIATE
|
||||
}
|
||||
|
||||
|
||||
def _wait_for_state_change(target_state, task):
|
||||
"""Wait and check for the power state change
|
||||
|
||||
:param target_state: The target state we are waiting for.
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: CIMCException if there is an error communicating with CIMC
|
||||
"""
|
||||
store = {'state': None, 'retries': CONF.cimc.max_retry}
|
||||
|
||||
def _wait(store):
|
||||
|
||||
current_power_state = None
|
||||
with common.cimc_handle(task) as handle:
|
||||
try:
|
||||
rack_unit = handle.get_imc_managedobject(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"}
|
||||
)
|
||||
except imcsdk.ImcException as e:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=e)
|
||||
else:
|
||||
current_power_state = rack_unit[0].get_attr("OperPower")
|
||||
store['state'] = CIMC_TO_IRONIC_POWER_STATE.get(current_power_state)
|
||||
|
||||
if store['state'] == target_state:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
store['retries'] -= 1
|
||||
if store['retries'] <= 0:
|
||||
store['state'] = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait, store)
|
||||
timer.start(interval=CONF.cimc.action_interval).wait()
|
||||
return store['state']
|
||||
|
||||
|
||||
class Power(base.PowerInterface):
|
||||
|
||||
def get_properties(self):
|
||||
"""Return the properties of the interface.
|
||||
|
||||
:returns: dictionary of <property name>:<property description> entries.
|
||||
"""
|
||||
return common.COMMON_PROPERTIES
|
||||
|
||||
def validate(self, task):
|
||||
"""Check if node.driver_info contains the required CIMC credentials.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: InvalidParameterValue if required CIMC credentials are
|
||||
missing.
|
||||
"""
|
||||
common.parse_driver_info(task.node)
|
||||
|
||||
def get_power_state(self, task):
|
||||
"""Return the power state of the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: MissingParameterValue if a required parameter is missing.
|
||||
:returns: a power state. One of :mod:`ironic.common.states`.
|
||||
:raises: CIMCException if there is an error communicating with CIMC
|
||||
"""
|
||||
current_power_state = None
|
||||
with common.cimc_handle(task) as handle:
|
||||
try:
|
||||
rack_unit = handle.get_imc_managedobject(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"}
|
||||
)
|
||||
except imcsdk.ImcException as e:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=e)
|
||||
else:
|
||||
current_power_state = rack_unit[0].get_attr("OperPower")
|
||||
return CIMC_TO_IRONIC_POWER_STATE.get(current_power_state,
|
||||
states.ERROR)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, pstate):
|
||||
"""Set the power state of the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param pstate: Any power state from :mod:`ironic.common.states`.
|
||||
:raises: MissingParameterValue if a required parameter is missing.
|
||||
:raises: InvalidParameterValue if an invalid power state is passed
|
||||
:raises: CIMCException if there is an error communicating with CIMC
|
||||
"""
|
||||
if pstate not in IRONIC_TO_CIMC_POWER_STATE:
|
||||
msg = _("set_power_state called for %(node)s with "
|
||||
"invalid state %(state)s")
|
||||
raise exception.InvalidParameterValue(
|
||||
msg % {"node": task.node.uuid, "state": pstate})
|
||||
with common.cimc_handle(task) as handle:
|
||||
try:
|
||||
handle.set_imc_managedobject(
|
||||
None, class_id="ComputeRackUnit",
|
||||
params={
|
||||
imcsdk.ComputeRackUnit.ADMIN_POWER:
|
||||
IRONIC_TO_CIMC_POWER_STATE[pstate],
|
||||
imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
|
||||
})
|
||||
except imcsdk.ImcException as e:
|
||||
raise exception.CIMCException(node=task.node.uuid, error=e)
|
||||
|
||||
if pstate is states.REBOOT:
|
||||
pstate = states.POWER_ON
|
||||
|
||||
state = _wait_for_state_change(pstate, task)
|
||||
if state != pstate:
|
||||
raise exception.PowerStateFailure(pstate=pstate)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task):
|
||||
"""Perform a hard reboot of the task's node.
|
||||
|
||||
If the node is already powered on then it shall reboot the node, if
|
||||
its off then the node will just be turned on.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: MissingParameterValue if a required parameter is missing.
|
||||
:raises: CIMCException if there is an error communicating with CIMC
|
||||
"""
|
||||
current_power_state = self.get_power_state(task)
|
||||
|
||||
if current_power_state == states.POWER_ON:
|
||||
self.set_power_state(task, states.REBOOT)
|
||||
elif current_power_state == states.POWER_OFF:
|
||||
self.set_power_state(task, states.POWER_ON)
|
@ -25,6 +25,8 @@ from ironic.drivers import base
|
||||
from ironic.drivers.modules.amt import management as amt_management
|
||||
from ironic.drivers.modules.amt import power as amt_power
|
||||
from ironic.drivers.modules.amt import vendor as amt_vendor
|
||||
from ironic.drivers.modules.cimc import management as cimc_mgmt
|
||||
from ironic.drivers.modules.cimc import power as cimc_power
|
||||
from ironic.drivers.modules import iboot
|
||||
from ironic.drivers.modules.ilo import deploy as ilo_deploy
|
||||
from ironic.drivers.modules.ilo import inspect as ilo_inspect
|
||||
@ -340,6 +342,28 @@ class PXEAndUcsDriver(base.BaseDriver):
|
||||
self.vendor = iscsi_deploy.VendorPassthru()
|
||||
|
||||
|
||||
class PXEAndCIMCDriver(base.BaseDriver):
|
||||
"""PXE + Cisco IMC driver.
|
||||
|
||||
This driver implements the 'core' functionality, combining
|
||||
:class:`ironic.drivers.modules.cimc.Power` for power on/off and reboot with
|
||||
:class:`ironic.drivers.modules.pxe.PXEBoot` for booting the node and
|
||||
:class:`ironic.drivers.modules.iscsi_deploy.ISCSIDeploy` for image
|
||||
deployment. Implentations are in those respective classes; this
|
||||
class is merely the glue between them.
|
||||
"""
|
||||
def __init__(self):
|
||||
if not importutils.try_import('ImcSdk'):
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason=_("Unable to import ImcSdk library"))
|
||||
self.power = cimc_power.Power()
|
||||
self.boot = pxe.PXEBoot()
|
||||
self.deploy = iscsi_deploy.ISCSIDeploy()
|
||||
self.management = cimc_mgmt.CIMCManagement()
|
||||
self.vendor = iscsi_deploy.VendorPassthru()
|
||||
|
||||
|
||||
class PXEAndWakeOnLanDriver(base.BaseDriver):
|
||||
"""PXE + WakeOnLan driver.
|
||||
|
||||
|
@ -318,3 +318,11 @@ def get_test_ucs_info():
|
||||
"ucs_service_profile": "org-root/ls-devstack",
|
||||
"ucs_address": "ucs-b",
|
||||
}
|
||||
|
||||
|
||||
def get_test_cimc_info():
|
||||
return {
|
||||
"cimc_username": "admin",
|
||||
"cimc_password": "password",
|
||||
"cimc_address": "1.2.3.4",
|
||||
}
|
||||
|
0
ironic/tests/drivers/cimc/__init__.py
Normal file
0
ironic/tests/drivers/cimc/__init__.py
Normal file
125
ironic/tests/drivers/cimc/test_common.py
Normal file
125
ironic/tests/drivers/cimc/test_common.py
Normal file
@ -0,0 +1,125 @@
|
||||
# Copyright 2015, Cisco Systems.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.cimc import common as cimc_common
|
||||
from ironic.tests.conductor import utils as mgr_utils
|
||||
from ironic.tests.db import base as db_base
|
||||
from ironic.tests.db import utils as db_utils
|
||||
from ironic.tests.objects import utils as obj_utils
|
||||
|
||||
imcsdk = importutils.try_import('ImcSdk')
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class CIMCBaseTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CIMCBaseTestCase, self).setUp()
|
||||
mgr_utils.mock_the_extension_manager(driver="fake_cimc")
|
||||
self.node = obj_utils.create_test_node(
|
||||
self.context,
|
||||
driver='fake_cimc',
|
||||
driver_info=db_utils.get_test_cimc_info(),
|
||||
instance_uuid="fake_uuid")
|
||||
CONF.set_override('max_retry', 2, 'cimc')
|
||||
CONF.set_override('action_interval', 0, 'cimc')
|
||||
|
||||
|
||||
class ParseDriverInfoTestCase(CIMCBaseTestCase):
|
||||
|
||||
def test_parse_driver_info(self):
|
||||
info = cimc_common.parse_driver_info(self.node)
|
||||
|
||||
self.assertIsNotNone(info.get('cimc_address'))
|
||||
self.assertIsNotNone(info.get('cimc_username'))
|
||||
self.assertIsNotNone(info.get('cimc_password'))
|
||||
|
||||
def test_parse_driver_info_missing_address(self):
|
||||
del self.node.driver_info['cimc_address']
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
cimc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_missing_username(self):
|
||||
del self.node.driver_info['cimc_username']
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
cimc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_missing_password(self):
|
||||
del self.node.driver_info['cimc_password']
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
cimc_common.parse_driver_info, self.node)
|
||||
|
||||
|
||||
@mock.patch.object(cimc_common, 'cimc_handle', autospec=True)
|
||||
class CIMCHandleLogin(CIMCBaseTestCase):
|
||||
|
||||
def test_cimc_handle_login(self, mock_handle):
|
||||
info = cimc_common.parse_driver_info(self.node)
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
cimc_common.handle_login(task, handle, info)
|
||||
|
||||
handle.login.assert_called_once_with(
|
||||
self.node.driver_info['cimc_address'],
|
||||
self.node.driver_info['cimc_username'],
|
||||
self.node.driver_info['cimc_password'])
|
||||
|
||||
def test_cimc_handle_login_exception(self, mock_handle):
|
||||
info = cimc_common.parse_driver_info(self.node)
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.login.side_effect = imcsdk.ImcException('Boom')
|
||||
|
||||
self.assertRaises(exception.CIMCException,
|
||||
cimc_common.handle_login,
|
||||
task, handle, info)
|
||||
|
||||
handle.login.assert_called_once_with(
|
||||
self.node.driver_info['cimc_address'],
|
||||
self.node.driver_info['cimc_username'],
|
||||
self.node.driver_info['cimc_password'])
|
||||
|
||||
|
||||
class CIMCHandleTestCase(CIMCBaseTestCase):
|
||||
|
||||
@mock.patch.object(imcsdk, 'ImcHandle', autospec=True)
|
||||
@mock.patch.object(cimc_common, 'handle_login', autospec=True)
|
||||
def test_cimc_handle(self, mock_login, mock_handle):
|
||||
mo_hand = mock.MagicMock()
|
||||
mo_hand.username = self.node.driver_info.get('cimc_username')
|
||||
mo_hand.password = self.node.driver_info.get('cimc_password')
|
||||
mo_hand.name = self.node.driver_info.get('cimc_address')
|
||||
mock_handle.return_value = mo_hand
|
||||
info = cimc_common.parse_driver_info(self.node)
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with cimc_common.cimc_handle(task) as handle:
|
||||
self.assertEqual(handle, mock_handle.return_value)
|
||||
|
||||
mock_login.assert_called_once_with(task, mock_handle.return_value,
|
||||
info)
|
||||
mock_handle.return_value.logout.assert_called_once_with()
|
126
ironic/tests/drivers/cimc/test_management.py
Normal file
126
ironic/tests/drivers/cimc/test_management.py
Normal file
@ -0,0 +1,126 @@
|
||||
# Copyright 2015, Cisco Systems.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import boot_devices
|
||||
from ironic.common import exception
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.cimc import common
|
||||
from ironic.tests.drivers.cimc import test_common
|
||||
|
||||
imcsdk = importutils.try_import('ImcSdk')
|
||||
|
||||
|
||||
@mock.patch.object(common, 'cimc_handle', autospec=True)
|
||||
class CIMCManagementTestCase(test_common.CIMCBaseTestCase):
|
||||
|
||||
def test_get_properties(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertEqual(common.COMMON_PROPERTIES,
|
||||
task.driver.management.get_properties())
|
||||
|
||||
@mock.patch.object(common, "parse_driver_info", autospec=True)
|
||||
def test_validate(self, mock_driver_info, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.management.validate(task)
|
||||
mock_driver_info.assert_called_once_with(task.node)
|
||||
|
||||
def test_get_supported_boot_devices(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
expected = [boot_devices.PXE, boot_devices.DISK,
|
||||
boot_devices.CDROM]
|
||||
result = task.driver.management.get_supported_boot_devices(task)
|
||||
self.assertEqual(sorted(expected), sorted(result))
|
||||
|
||||
def test_get_boot_device(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.xml_query.return_value.error_code = None
|
||||
mock_dev = mock.MagicMock()
|
||||
mock_dev.Order = 1
|
||||
mock_dev.Rn = 'storage-read-write'
|
||||
handle.xml_query().OutConfigs.child[0].child = [mock_dev]
|
||||
|
||||
device = task.driver.management.get_boot_device(task)
|
||||
self.assertEqual(
|
||||
{'boot_device': boot_devices.DISK, 'persistent': True},
|
||||
device)
|
||||
|
||||
def test_get_boot_device_fail(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.xml_query.return_value.error_code = None
|
||||
mock_dev = mock.MagicMock()
|
||||
mock_dev.Order = 1
|
||||
mock_dev.Rn = 'storage-read-write'
|
||||
handle.xml_query().OutConfigs.child[0].child = [mock_dev]
|
||||
|
||||
device = task.driver.management.get_boot_device(task)
|
||||
|
||||
self.assertEqual(
|
||||
{'boot_device': boot_devices.DISK, 'persistent': True},
|
||||
device)
|
||||
|
||||
def test_set_boot_device(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.xml_query.return_value.error_code = None
|
||||
task.driver.management.set_boot_device(task, boot_devices.DISK)
|
||||
method = imcsdk.ImcCore.ExternalMethod("ConfigConfMo")
|
||||
method.Cookie = handle.cookie
|
||||
method.Dn = "sys/rack-unit-1/boot-policy"
|
||||
method.InHierarchical = "true"
|
||||
|
||||
config = imcsdk.Imc.ConfigConfig()
|
||||
|
||||
bootMode = imcsdk.ImcCore.ManagedObject('lsbootStorage')
|
||||
bootMode.set_attr("access", 'read-write')
|
||||
bootMode.set_attr("type", 'storage')
|
||||
bootMode.set_attr("Rn", 'storage-read-write')
|
||||
bootMode.set_attr("order", "1")
|
||||
|
||||
config.add_child(bootMode)
|
||||
method.InConfig = config
|
||||
|
||||
handle.xml_query.assert_called_once_with(
|
||||
method, imcsdk.WriteXmlOption.DIRTY)
|
||||
|
||||
def test_set_boot_device_fail(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
method = imcsdk.ImcCore.ExternalMethod("ConfigConfMo")
|
||||
handle.xml_query.return_value.error_code = "404"
|
||||
|
||||
self.assertRaises(exception.CIMCException,
|
||||
task.driver.management.set_boot_device,
|
||||
task, boot_devices.DISK)
|
||||
|
||||
handle.xml_query.assert_called_once_with(
|
||||
method, imcsdk.WriteXmlOption.DIRTY)
|
||||
|
||||
def test_get_sensors_data(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(NotImplementedError,
|
||||
task.driver.management.get_sensors_data, task)
|
302
ironic/tests/drivers/cimc/test_power.py
Normal file
302
ironic/tests/drivers/cimc/test_power.py
Normal file
@ -0,0 +1,302 @@
|
||||
# Copyright 2015, Cisco Systems.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.cimc import common
|
||||
from ironic.drivers.modules.cimc import power
|
||||
from ironic.tests.drivers.cimc import test_common
|
||||
|
||||
imcsdk = importutils.try_import('ImcSdk')
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@mock.patch.object(common, 'cimc_handle', autospec=True)
|
||||
class WaitForStateChangeTestCase(test_common.CIMCBaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(WaitForStateChangeTestCase, self).setUp()
|
||||
CONF.set_override('max_retry', 2, 'cimc')
|
||||
CONF.set_override('action_interval', 0, 'cimc')
|
||||
|
||||
def test__wait_for_state_change(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
mock_rack_unit = mock.MagicMock()
|
||||
mock_rack_unit.get_attr.return_value = (
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON)
|
||||
|
||||
handle.get_imc_managedobject.return_value = [mock_rack_unit]
|
||||
|
||||
state = power._wait_for_state_change(states.POWER_ON, task)
|
||||
|
||||
handle.get_imc_managedobject.assert_called_once_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
self.assertEqual(state, states.POWER_ON)
|
||||
|
||||
def test__wait_for_state_change_fail(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
mock_rack_unit = mock.MagicMock()
|
||||
mock_rack_unit.get_attr.return_value = (
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF)
|
||||
|
||||
handle.get_imc_managedobject.return_value = [mock_rack_unit]
|
||||
|
||||
state = power._wait_for_state_change(states.POWER_ON, task)
|
||||
|
||||
calls = [
|
||||
mock.call(None, None, params={"Dn": "sys/rack-unit-1"}),
|
||||
mock.call(None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
]
|
||||
handle.get_imc_managedobject.assert_has_calls(calls)
|
||||
self.assertEqual(state, states.ERROR)
|
||||
|
||||
def test__wait_for_state_change_imc_exception(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.get_imc_managedobject.side_effect = (
|
||||
imcsdk.ImcException('Boom'))
|
||||
|
||||
self.assertRaises(
|
||||
exception.CIMCException,
|
||||
power._wait_for_state_change, states.POWER_ON, task)
|
||||
|
||||
handle.get_imc_managedobject.assert_called_once_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
|
||||
@mock.patch.object(common, 'cimc_handle', autospec=True)
|
||||
class PowerTestCase(test_common.CIMCBaseTestCase):
|
||||
|
||||
def test_get_properties(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertEqual(common.COMMON_PROPERTIES,
|
||||
task.driver.power.get_properties())
|
||||
|
||||
@mock.patch.object(common, "parse_driver_info", autospec=True)
|
||||
def test_validate(self, mock_driver_info, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
task.driver.power.validate(task)
|
||||
mock_driver_info.assert_called_once_with(task.node)
|
||||
|
||||
def test_get_power_state(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
mock_rack_unit = mock.MagicMock()
|
||||
mock_rack_unit.get_attr.return_value = (
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON)
|
||||
|
||||
handle.get_imc_managedobject.return_value = [mock_rack_unit]
|
||||
|
||||
state = task.driver.power.get_power_state(task)
|
||||
|
||||
handle.get_imc_managedobject.assert_called_once_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
self.assertEqual(states.POWER_ON, state)
|
||||
|
||||
def test_get_power_state_fail(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
mock_rack_unit = mock.MagicMock()
|
||||
mock_rack_unit.get_attr.return_value = (
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON)
|
||||
|
||||
handle.get_imc_managedobject.side_effect = (
|
||||
imcsdk.ImcException("boom"))
|
||||
|
||||
self.assertRaises(exception.CIMCException,
|
||||
task.driver.power.get_power_state, task)
|
||||
|
||||
handle.get_imc_managedobject.assert_called_once_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
def test_set_power_state_invalid_state(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
task.driver.power.set_power_state,
|
||||
task, states.ERROR)
|
||||
|
||||
def test_set_power_state_reboot_ok(self, mock_handle):
|
||||
hri = imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_HARD_RESET_IMMEDIATE
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
mock_rack_unit = mock.MagicMock()
|
||||
mock_rack_unit.get_attr.side_effect = [
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF,
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON
|
||||
]
|
||||
handle.get_imc_managedobject.return_value = [mock_rack_unit]
|
||||
|
||||
task.driver.power.set_power_state(task, states.REBOOT)
|
||||
|
||||
handle.set_imc_managedobject.assert_called_once_with(
|
||||
None, class_id="ComputeRackUnit",
|
||||
params={
|
||||
imcsdk.ComputeRackUnit.ADMIN_POWER: hri,
|
||||
imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
|
||||
})
|
||||
|
||||
handle.get_imc_managedobject.assert_called_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
def test_set_power_state_reboot_fail(self, mock_handle):
|
||||
hri = imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_HARD_RESET_IMMEDIATE
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.get_imc_managedobject.side_effect = (
|
||||
imcsdk.ImcException("boom"))
|
||||
|
||||
self.assertRaises(exception.CIMCException,
|
||||
task.driver.power.set_power_state,
|
||||
task, states.REBOOT)
|
||||
|
||||
handle.set_imc_managedobject.assert_called_once_with(
|
||||
None, class_id="ComputeRackUnit",
|
||||
params={
|
||||
imcsdk.ComputeRackUnit.ADMIN_POWER: hri,
|
||||
imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
|
||||
})
|
||||
|
||||
handle.get_imc_managedobject.assert_called_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
def test_set_power_state_on_ok(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
mock_rack_unit = mock.MagicMock()
|
||||
mock_rack_unit.get_attr.side_effect = [
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF,
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON
|
||||
]
|
||||
handle.get_imc_managedobject.return_value = [mock_rack_unit]
|
||||
|
||||
task.driver.power.set_power_state(task, states.POWER_ON)
|
||||
|
||||
handle.set_imc_managedobject.assert_called_once_with(
|
||||
None, class_id="ComputeRackUnit",
|
||||
params={
|
||||
imcsdk.ComputeRackUnit.ADMIN_POWER:
|
||||
imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_UP,
|
||||
imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
|
||||
})
|
||||
|
||||
handle.get_imc_managedobject.assert_called_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
def test_set_power_state_on_fail(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.get_imc_managedobject.side_effect = (
|
||||
imcsdk.ImcException("boom"))
|
||||
|
||||
self.assertRaises(exception.CIMCException,
|
||||
task.driver.power.set_power_state,
|
||||
task, states.POWER_ON)
|
||||
|
||||
handle.set_imc_managedobject.assert_called_once_with(
|
||||
None, class_id="ComputeRackUnit",
|
||||
params={
|
||||
imcsdk.ComputeRackUnit.ADMIN_POWER:
|
||||
imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_UP,
|
||||
imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
|
||||
})
|
||||
|
||||
handle.get_imc_managedobject.assert_called_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
def test_set_power_state_off_ok(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
mock_rack_unit = mock.MagicMock()
|
||||
mock_rack_unit.get_attr.side_effect = [
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_ON,
|
||||
imcsdk.ComputeRackUnit.CONST_OPER_POWER_OFF
|
||||
]
|
||||
handle.get_imc_managedobject.return_value = [mock_rack_unit]
|
||||
|
||||
task.driver.power.set_power_state(task, states.POWER_OFF)
|
||||
|
||||
handle.set_imc_managedobject.assert_called_once_with(
|
||||
None, class_id="ComputeRackUnit",
|
||||
params={
|
||||
imcsdk.ComputeRackUnit.ADMIN_POWER:
|
||||
imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_DOWN,
|
||||
imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
|
||||
})
|
||||
|
||||
handle.get_imc_managedobject.assert_called_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
def test_set_power_state_off_fail(self, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
with mock_handle(task) as handle:
|
||||
handle.get_imc_managedobject.side_effect = (
|
||||
imcsdk.ImcException("boom"))
|
||||
|
||||
self.assertRaises(exception.CIMCException,
|
||||
task.driver.power.set_power_state,
|
||||
task, states.POWER_OFF)
|
||||
|
||||
handle.set_imc_managedobject.assert_called_once_with(
|
||||
None, class_id="ComputeRackUnit",
|
||||
params={
|
||||
imcsdk.ComputeRackUnit.ADMIN_POWER:
|
||||
imcsdk.ComputeRackUnit.CONST_ADMIN_POWER_DOWN,
|
||||
imcsdk.ComputeRackUnit.DN: "sys/rack-unit-1"
|
||||
})
|
||||
|
||||
handle.get_imc_managedobject.assert_called_with(
|
||||
None, None, params={"Dn": "sys/rack-unit-1"})
|
||||
|
||||
@mock.patch.object(power.Power, "set_power_state", autospec=True)
|
||||
@mock.patch.object(power.Power, "get_power_state", autospec=True)
|
||||
def test_reboot_on(self, mock_get_state, mock_set_state, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_get_state.return_value = states.POWER_ON
|
||||
task.driver.power.reboot(task)
|
||||
mock_set_state.assert_called_with(mock.ANY, task, states.REBOOT)
|
||||
|
||||
@mock.patch.object(power.Power, "set_power_state", autospec=True)
|
||||
@mock.patch.object(power.Power, "get_power_state", autospec=True)
|
||||
def test_reboot_off(self, mock_get_state, mock_set_state, mock_handle):
|
||||
with task_manager.acquire(self.context, self.node.uuid,
|
||||
shared=False) as task:
|
||||
mock_get_state.return_value = states.POWER_OFF
|
||||
task.driver.power.reboot(task)
|
||||
mock_set_state.assert_called_with(mock.ANY, task, states.POWER_ON)
|
@ -232,3 +232,12 @@ if not ucssdk:
|
||||
if 'ironic.drivers.modules.ucs' in sys.modules:
|
||||
six.moves.reload_module(
|
||||
sys.modules['ironic.drivers.modules.ucs'])
|
||||
|
||||
imcsdk = importutils.try_import('ImcSdk')
|
||||
if not imcsdk:
|
||||
imcsdk = mock.MagicMock()
|
||||
imcsdk.ImcException = Exception
|
||||
sys.modules['ImcSdk'] = imcsdk
|
||||
if 'ironic.drivers.modules.cimc' in sys.modules:
|
||||
six.moves.reload_module(
|
||||
sys.modules['ironic.drivers.modules.cimc'])
|
||||
|
@ -56,6 +56,7 @@ ironic.drivers =
|
||||
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
||||
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
||||
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
||||
fake_cimc = ironic.drivers.fake:FakeCIMCDriver
|
||||
fake_wol = ironic.drivers.fake:FakeWakeOnLanDriver
|
||||
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
||||
iscsi_irmc = ironic.drivers.irmc:IRMCVirtualMediaIscsiDriver
|
||||
@ -73,6 +74,8 @@ ironic.drivers =
|
||||
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
||||
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
||||
pxe_wol = ironic.drivers.pxe:PXEAndWakeOnLanDriver
|
||||
pxe_iscsi_cimc = ironic.drivers.pxe:PXEAndCIMCDriver
|
||||
pxe_agent_cimc = ironic.drivers.agent:AgentAndCIMCDriver
|
||||
|
||||
ironic.database.migration_backend =
|
||||
sqlalchemy = ironic.db.sqlalchemy.migration
|
||||
|
Loading…
Reference in New Issue
Block a user