Add pxe_ucs and agent_ucs drivers to manage Cisco UCS servers
This commit adds pxe_ucs, agent_ucs driver changes. Adds support for the following: - Power driver changes - Management interface changes - UCS Helper code, which can be reused with other parts of the driver, ex: to support vendor apis. Change-Id: I02211da5fad039dc7e6b509d547e473e9b57009b Implements: blueprint cisco-ucs-pxe-driver
This commit is contained in:
parent
102b85bd31
commit
8c26a8c33b
@ -119,7 +119,6 @@ iRMC
|
|||||||
The iRMC driver enables PXE Deploy to control power via ServerView Common
|
The iRMC driver enables PXE Deploy to control power via ServerView Common
|
||||||
Command Interface (SCCI).
|
Command Interface (SCCI).
|
||||||
|
|
||||||
|
|
||||||
Software Requirements
|
Software Requirements
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@ -162,3 +161,12 @@ VirtualBox drivers
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
../drivers/vbox
|
../drivers/vbox
|
||||||
|
|
||||||
|
|
||||||
|
Cisco UCS Driver
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
../drivers/ucs
|
||||||
|
84
doc/source/drivers/ucs.rst
Normal file
84
doc/source/drivers/ucs.rst
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
.. _UCS:
|
||||||
|
|
||||||
|
===========
|
||||||
|
UCS drivers
|
||||||
|
===========
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
The UCS driver is targeted for UCS Manager managed Cisco UCS B/C series
|
||||||
|
servers. The pxe_ucs, agent_ucs drivers enables you to take advantage of
|
||||||
|
UCS Manager by using the python SDK.
|
||||||
|
|
||||||
|
``pxe_ucs`` driver uses PXE/iSCSI (just like ``pxe_ipmitool`` driver) to
|
||||||
|
deploy the image and uses UCS to do all management operations on the
|
||||||
|
baremetal node (instead of using IPMI).
|
||||||
|
|
||||||
|
``agent_ucs`` driver uses IPA ramdisk (just like ``agent_ipmitool`` and
|
||||||
|
``agent_ipminative`` drivers.) to deploy the image and uses UCS to do all
|
||||||
|
management operations on the baremetal node (instead of using IPMI).
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
=============
|
||||||
|
|
||||||
|
* ``UcsSdk`` is a python package version of XML API sdk available to
|
||||||
|
to manage Cisco UCS Managed B/C-series servers.
|
||||||
|
|
||||||
|
Install ``UcsSdk`` [1]_ module on the Ironic conductor node.
|
||||||
|
Required version is 0.8.1.6::
|
||||||
|
|
||||||
|
$ pip install "UcsSdk==0.8.1.6"
|
||||||
|
|
||||||
|
Tested Platforms
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
This driver works on Cisco UCS Manager Managed B/C-series servers.
|
||||||
|
It has been tested with the following servers:
|
||||||
|
|
||||||
|
UCS Manager version: 2.2(1b), 2.2(3d).
|
||||||
|
|
||||||
|
* UCS B22M, B200M3
|
||||||
|
* UCS C220M3.
|
||||||
|
|
||||||
|
All the Cisco UCS B/C-series servers managed by UCSM 2.1 or later are supported
|
||||||
|
by this driver.
|
||||||
|
|
||||||
|
Configuring and Enabling the driver
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
1. Add ``pxe_ucs`` and/or ``agent_ucs`` to the list of ``enabled_drivers`` in
|
||||||
|
``/etc/ironic/ironic.conf``. For example::
|
||||||
|
|
||||||
|
enabled_drivers = pxe_ipmitool,pxe_ucs,agent_ucs
|
||||||
|
|
||||||
|
2. Restart the Ironic conductor service::
|
||||||
|
|
||||||
|
service ironic-conductor restart
|
||||||
|
|
||||||
|
Registering UCS node in Ironic
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Nodes configured for UCS driver should have the ``driver`` property set to
|
||||||
|
``pxe_ucs/agent_ucs``. The following configuration values are also required in
|
||||||
|
``driver_info``:
|
||||||
|
|
||||||
|
- ``ucs_hostname``: IP address or hostname of the UCS Manager
|
||||||
|
- ``ucs_username``: UCS Manager login user name with administrator or
|
||||||
|
server_profile privileges.
|
||||||
|
- ``ucs_password``: UCS Manager login password for the above UCS Manager user.
|
||||||
|
- ``deploy_kernel``: The Glance UUID of the deployment kernel.
|
||||||
|
- ``deploy_ramdisk``: The Glance UUID of the deployment ramdisk.
|
||||||
|
- ``ucs_service_profile``: Distinguished name(DN) of service_profile being enrolled.
|
||||||
|
|
||||||
|
The following sequence of commands can be used to enroll a UCS node.
|
||||||
|
|
||||||
|
Create Node::
|
||||||
|
|
||||||
|
ironic node-create -d <pxe_ucs/agent_ucs> -i ucs_hostname=<UCS Manager hostname/ip-address> -i ucs_username=<ucsm_username> -i ucs_password=<ucsm_password> -i ucs_service_profile=<serivce_profile_dn_being_enrolled> -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>
|
||||||
|
|
||||||
|
References
|
||||||
|
==========
|
||||||
|
.. [1] UcsSdk - https://pypi.python.org/pypi/UcsSdk
|
@ -10,6 +10,7 @@ pyghmi
|
|||||||
pysnmp
|
pysnmp
|
||||||
python-scciclient
|
python-scciclient
|
||||||
python-seamicroclient>=0.4.0
|
python-seamicroclient>=0.4.0
|
||||||
|
UcsSdk==0.8.1.6
|
||||||
|
|
||||||
# The drac and amt driver import a python module called "pywsman", however,
|
# The drac and amt driver import a python module called "pywsman", however,
|
||||||
# this does not exist on pypi.
|
# this does not exist on pypi.
|
||||||
|
@ -1600,3 +1600,14 @@
|
|||||||
#port=18083
|
#port=18083
|
||||||
|
|
||||||
|
|
||||||
|
[cisco_ucs]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Options defined in ironic.drivers.modules.ucs.power
|
||||||
|
#
|
||||||
|
|
||||||
|
# Number of times a power operation needs to be retried.
|
||||||
|
#max_retry=6
|
||||||
|
|
||||||
|
# Amount of time in seconds to wait in between power operations.
|
||||||
|
#action_interval=5
|
||||||
|
@ -572,3 +572,13 @@ class PathNotFound(IronicException):
|
|||||||
|
|
||||||
class DirectoryNotWritable(IronicException):
|
class DirectoryNotWritable(IronicException):
|
||||||
message = _("Directory %(dir)s is not writable.")
|
message = _("Directory %(dir)s is not writable.")
|
||||||
|
|
||||||
|
|
||||||
|
class UcsOperationError(IronicException):
|
||||||
|
message = _("Cisco UCS client: operation %(operation)s failed for node"
|
||||||
|
" %(node)s. Reason: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class UcsConnectionError(IronicException):
|
||||||
|
message = _("Cisco UCS client: connection failed for node "
|
||||||
|
"%(node)s. Reason: %(error)s")
|
||||||
|
@ -21,6 +21,8 @@ from ironic.drivers.modules import agent
|
|||||||
from ironic.drivers.modules import ipminative
|
from ironic.drivers.modules import ipminative
|
||||||
from ironic.drivers.modules import ipmitool
|
from ironic.drivers.modules import ipmitool
|
||||||
from ironic.drivers.modules import ssh
|
from ironic.drivers.modules import ssh
|
||||||
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
|
|
||||||
|
|
||||||
@ -105,3 +107,25 @@ class AgentAndVirtualBoxDriver(base.BaseDriver):
|
|||||||
self.deploy = agent.AgentDeploy()
|
self.deploy = agent.AgentDeploy()
|
||||||
self.management = virtualbox.VirtualBoxManagement()
|
self.management = virtualbox.VirtualBoxManagement()
|
||||||
self.vendor = agent.AgentVendorInterface()
|
self.vendor = agent.AgentVendorInterface()
|
||||||
|
|
||||||
|
|
||||||
|
class AgentAndUcsDriver(base.BaseDriver):
|
||||||
|
"""Agent + Cisco UCSM driver.
|
||||||
|
|
||||||
|
This driver implements the `core` functionality, combining
|
||||||
|
:class:ironic.drivers.modules.ucs.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('UcsSdk'):
|
||||||
|
raise exception.DriverLoadError(
|
||||||
|
driver=self.__class__.__name__,
|
||||||
|
reason=_("Unable to import UcsSdk library"))
|
||||||
|
self.power = ucs_power.Power()
|
||||||
|
self.deploy = agent.AgentDeploy()
|
||||||
|
self.management = ucs_mgmt.UcsManagement()
|
||||||
|
self.vendor = agent.AgentVendorInterface()
|
||||||
|
@ -43,6 +43,8 @@ from ironic.drivers.modules import pxe
|
|||||||
from ironic.drivers.modules import seamicro
|
from ironic.drivers.modules import seamicro
|
||||||
from ironic.drivers.modules import snmp
|
from ironic.drivers.modules import snmp
|
||||||
from ironic.drivers.modules import ssh
|
from ironic.drivers.modules import ssh
|
||||||
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
from ironic.drivers import utils
|
from ironic.drivers import utils
|
||||||
|
|
||||||
@ -245,3 +247,16 @@ class FakeMSFTOCSDriver(base.BaseDriver):
|
|||||||
self.power = msftocs_power.MSFTOCSPower()
|
self.power = msftocs_power.MSFTOCSPower()
|
||||||
self.deploy = fake.FakeDeploy()
|
self.deploy = fake.FakeDeploy()
|
||||||
self.management = msftocs_management.MSFTOCSManagement()
|
self.management = msftocs_management.MSFTOCSManagement()
|
||||||
|
|
||||||
|
|
||||||
|
class FakeUcsDriver(base.BaseDriver):
|
||||||
|
"""Fake UCS driver."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not importutils.try_import('UcsSdk'):
|
||||||
|
raise exception.DriverLoadError(
|
||||||
|
driver=self.__class__.__name__,
|
||||||
|
reason=_("Unable to import UcsSdk library"))
|
||||||
|
self.power = ucs_power.Power()
|
||||||
|
self.deploy = fake.FakeDeploy()
|
||||||
|
self.management = ucs_mgmt.UcsManagement()
|
||||||
|
0
ironic/drivers/modules/ucs/__init__.py
Normal file
0
ironic/drivers/modules/ucs/__init__.py
Normal file
126
ironic/drivers/modules/ucs/helper.py
Normal file
126
ironic/drivers/modules/ucs/helper.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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ironic Cisco UCSM helper functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import importutils
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
from ironic.common.i18n import _LE
|
||||||
|
from ironic.drivers.modules import deploy_utils
|
||||||
|
|
||||||
|
ucs_helper = importutils.try_import('UcsSdk.utils.helper')
|
||||||
|
ucs_error = importutils.try_import('UcsSdk.utils.exception')
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIRED_PROPERTIES = {
|
||||||
|
'ucs_address': _('IP or Hostname of the UCS Manager. Required.'),
|
||||||
|
'ucs_username': _('UCS Manager admin/server-profile username. Required.'),
|
||||||
|
'ucs_password': _('UCS Manager password. Required.'),
|
||||||
|
'ucs_service_profile': _('UCS Manager service-profile name. Required.')
|
||||||
|
}
|
||||||
|
|
||||||
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES
|
||||||
|
|
||||||
|
|
||||||
|
def requires_ucs_client(func):
|
||||||
|
"""Creates handle to connect to UCS Manager.
|
||||||
|
|
||||||
|
This method is being used as a decorator method. It establishes connection
|
||||||
|
with UCS Manager. And creates a session. Any method that has to perform
|
||||||
|
operation on UCS Manager, requries this session, which can use this method
|
||||||
|
as decorator method. Use this method as decorator method requires having
|
||||||
|
helper keyword argument in the definition.
|
||||||
|
|
||||||
|
:param func: function using this as a decorator.
|
||||||
|
:returns: a wrapper function that performs the required tasks
|
||||||
|
mentioned above before and after calling the actual function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(self, task, *args, **kwargs):
|
||||||
|
if kwargs.get('helper') is None:
|
||||||
|
kwargs['helper'] = CiscoUcsHelper(task)
|
||||||
|
try:
|
||||||
|
kwargs['helper'].connect_ucsm()
|
||||||
|
return func(self, task, *args, **kwargs)
|
||||||
|
finally:
|
||||||
|
kwargs['helper'].logout()
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def parse_driver_info(node):
|
||||||
|
"""Parses and creates Cisco driver info
|
||||||
|
|
||||||
|
:param node: An Ironic node object.
|
||||||
|
:returns: dictonary 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 = _("cisco driver requries these parameter to be set.")
|
||||||
|
deploy_utils.check_for_missing_params(info, error_msg)
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
class CiscoUcsHelper(object):
|
||||||
|
"""Cisco UCS helper. Performs session managemnt."""
|
||||||
|
|
||||||
|
def __init__(self, task):
|
||||||
|
"""Initialize with UCS Manager details.
|
||||||
|
|
||||||
|
:param task: instance of `ironic.manager.task_manager.TaskManager`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
info = parse_driver_info(task.node)
|
||||||
|
self.address = info['ucs_address']
|
||||||
|
self.username = info['ucs_username']
|
||||||
|
self.password = info['ucs_password']
|
||||||
|
# service_profile is used by the utilities functions in UcsSdk.utils.*.
|
||||||
|
self.service_profile = info['ucs_service_profile']
|
||||||
|
self.handle = None
|
||||||
|
self.uuid = task.node.uuid
|
||||||
|
|
||||||
|
def connect_ucsm(self):
|
||||||
|
"""Creates the UcsHandle
|
||||||
|
|
||||||
|
:raises: UcsConnectionError, if ucs helper failes to establish session
|
||||||
|
with UCS Manager.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
success, self.handle = ucs_helper.generate_ucsm_handle(
|
||||||
|
self.address,
|
||||||
|
self.username,
|
||||||
|
self.password)
|
||||||
|
except ucs_error.UcsConnectionError as ucs_exception:
|
||||||
|
LOG.error(_LE("Cisco client: service unavailable for node "
|
||||||
|
"%(uuid)s."), {'uuid': self.uuid})
|
||||||
|
raise exception.UcsConnectionError(error=ucs_exception,
|
||||||
|
node=self.uuid)
|
||||||
|
|
||||||
|
def logout(self):
|
||||||
|
"""Logouts the current active session."""
|
||||||
|
|
||||||
|
if self.handle:
|
||||||
|
self.handle.Logout()
|
146
ironic/drivers/modules/ucs/management.py
Normal file
146
ironic/drivers/modules/ucs/management.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ironic Cisco UCSM interfaces.
|
||||||
|
Provides Management interface operations of servers managed by Cisco UCSM using
|
||||||
|
PyUcs Sdk.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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.common.i18n import _
|
||||||
|
from ironic.common.i18n import _LE
|
||||||
|
from ironic.drivers import base
|
||||||
|
from ironic.drivers.modules.ucs import helper as ucs_helper
|
||||||
|
|
||||||
|
ucs_error = importutils.try_import('UcsSdk.utils.exception')
|
||||||
|
ucs_mgmt = importutils.try_import('UcsSdk.utils.management')
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
UCS_TO_IRONIC_BOOT_DEVICE = {
|
||||||
|
'storage': boot_devices.DISK,
|
||||||
|
'pxe': boot_devices.PXE,
|
||||||
|
'read-only-vm': boot_devices.CDROM
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UcsManagement(base.ManagementInterface):
|
||||||
|
|
||||||
|
def get_properties(self):
|
||||||
|
return ucs_helper.COMMON_PROPERTIES
|
||||||
|
|
||||||
|
def validate(self, task):
|
||||||
|
"""Check that 'driver_info' contains UCSM login credentials.
|
||||||
|
|
||||||
|
Validates whether the 'driver_info' property of the supplied
|
||||||
|
task's node contains the required credentials information.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:raises: MissingParameterValue if a required parameter is missing
|
||||||
|
"""
|
||||||
|
|
||||||
|
ucs_helper.parse_driver_info(task.node)
|
||||||
|
|
||||||
|
def get_supported_boot_devices(self):
|
||||||
|
"""Get a list of the supported boot devices.
|
||||||
|
|
||||||
|
:returns: A list with the supported boot devices defined
|
||||||
|
in :mod:`ironic.common.boot_devices`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return list(UCS_TO_IRONIC_BOOT_DEVICE.values())
|
||||||
|
|
||||||
|
@ucs_helper.requires_ucs_client
|
||||||
|
def set_boot_device(self, task, device, persistent=False, helper=None):
|
||||||
|
"""Set the boot device for the task's 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 'PXE, DISK or CDROM'.
|
||||||
|
:param persistent: Boolean value. True if the boot device will
|
||||||
|
persist to all future boots, False if not.
|
||||||
|
Default: False. Ignored by this driver.
|
||||||
|
:param helper: ucs helper instance.
|
||||||
|
:raises: MissingParameterValue if required CiscoDriver parameters
|
||||||
|
are missing.
|
||||||
|
:raises: UcsOperationError on error from UCS client.
|
||||||
|
setting the boot device.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
mgmt_handle = ucs_mgmt.BootDeviceHelper(helper)
|
||||||
|
mgmt_handle.set_boot_device(device, persistent)
|
||||||
|
except ucs_error.UcsOperationError as ucs_exception:
|
||||||
|
LOG.error(_LE("%(driver)s: client failed to set boot device "
|
||||||
|
"%(device)s for node %(uuid)s."),
|
||||||
|
{'driver': task.node.driver, 'device': device,
|
||||||
|
'uuid': task.node.uuid})
|
||||||
|
operation = _('setting boot device')
|
||||||
|
raise exception.UcsOperationError(operation=operation,
|
||||||
|
error=ucs_exception,
|
||||||
|
node=task.node.uuid)
|
||||||
|
LOG.debug("Node %(uuid)s set to boot from %(device)s.",
|
||||||
|
{'uuid': task.node.uuid, 'device': device})
|
||||||
|
|
||||||
|
@ucs_helper.requires_ucs_client
|
||||||
|
def get_boot_device(self, task, helper=None):
|
||||||
|
"""Get the current boot device for the task's node.
|
||||||
|
|
||||||
|
Provides the current boot device of the node.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:param helper: ucs helper instance.
|
||||||
|
:returns: a dictionary containing:
|
||||||
|
|
||||||
|
:boot_device: the boot device, one of
|
||||||
|
:mod:`ironic.common.boot_devices` [PXE, DISK, CDROM] or
|
||||||
|
None if it is unknown.
|
||||||
|
:persistent: Whether the boot device will persist to all
|
||||||
|
future boots or not, None if it is unknown.
|
||||||
|
:raises: MissingParameterValue if a required UCS parameter is missing.
|
||||||
|
:raises: UcsOperationError on error from UCS client, while setting the
|
||||||
|
boot device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
mgmt_handle = ucs_mgmt.BootDeviceHelper(helper)
|
||||||
|
boot_device = mgmt_handle.get_boot_device()
|
||||||
|
except ucs_error.UcsOperationError as ucs_exception:
|
||||||
|
LOG.error(_LE("%(driver)s: client failed to get boot device for "
|
||||||
|
"node %(uuid)s."),
|
||||||
|
{'driver': task.node.driver, 'uuid': task.node.uuid})
|
||||||
|
operation = _('getting boot device')
|
||||||
|
raise exception.UcsOperationError(operation=operation,
|
||||||
|
error=ucs_exception,
|
||||||
|
node=task.node.uuid)
|
||||||
|
boot_device['boot_device'] = (
|
||||||
|
UCS_TO_IRONIC_BOOT_DEVICE[boot_device['boot_device']])
|
||||||
|
return boot_device
|
||||||
|
|
||||||
|
def get_sensors_data(self, task):
|
||||||
|
"""Get sensors data.
|
||||||
|
|
||||||
|
Not implemented by this driver.
|
||||||
|
:param task: a TaskManager instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError()
|
212
ironic/drivers/modules/ucs/power.py
Normal file
212
ironic/drivers/modules/ucs/power.py
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ironic Cisco UCSM interfaces.
|
||||||
|
Provides basic power control of servers managed by Cisco UCSM using PyUcs Sdk.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import importutils
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
from ironic.common.i18n import _
|
||||||
|
from ironic.common.i18n import _LE
|
||||||
|
from ironic.common import states
|
||||||
|
from ironic.conductor import task_manager
|
||||||
|
from ironic.drivers import base
|
||||||
|
from ironic.drivers.modules.ucs import helper as ucs_helper
|
||||||
|
from ironic.openstack.common import loopingcall
|
||||||
|
|
||||||
|
ucs_power = importutils.try_import('UcsSdk.utils.power')
|
||||||
|
ucs_error = importutils.try_import('UcsSdk.utils.exception')
|
||||||
|
|
||||||
|
opts = [
|
||||||
|
cfg.IntOpt('max_retry',
|
||||||
|
default=6,
|
||||||
|
help='Number of times a power operation needs to be retried'),
|
||||||
|
cfg.IntOpt('action_interval',
|
||||||
|
default=5,
|
||||||
|
help='Amount of time in seconds to wait in between power '
|
||||||
|
'operations'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(opts, group='cisco_ucs')
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
UCS_TO_IRONIC_POWER_STATE = {
|
||||||
|
'up': states.POWER_ON,
|
||||||
|
'down': states.POWER_OFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
IRONIC_TO_UCS_POWER_STATE = {
|
||||||
|
states.POWER_ON: 'up',
|
||||||
|
states.POWER_OFF: 'down',
|
||||||
|
states.REBOOT: 'hard-reset-immediate'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_for_state_change(target_state, ucs_power_handle):
|
||||||
|
"""Wait and check for the power state change."""
|
||||||
|
state = [None]
|
||||||
|
retries = [0]
|
||||||
|
|
||||||
|
def _wait(state, retries):
|
||||||
|
state[0] = ucs_power_handle.get_power_state()
|
||||||
|
if ((retries[0] != 0) and (
|
||||||
|
UCS_TO_IRONIC_POWER_STATE.get(state[0]) == target_state)):
|
||||||
|
raise loopingcall.LoopingCallDone()
|
||||||
|
|
||||||
|
if retries[0] > CONF.cisco_ucs.max_retry:
|
||||||
|
state[0] = states.ERROR
|
||||||
|
raise loopingcall.LoopingCallDone()
|
||||||
|
|
||||||
|
retries[0] += 1
|
||||||
|
|
||||||
|
timer = loopingcall.FixedIntervalLoopingCall(_wait, state, retries)
|
||||||
|
timer.start(interval=CONF.cisco_ucs.action_interval).wait()
|
||||||
|
return UCS_TO_IRONIC_POWER_STATE.get(state[0], states.ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
class Power(base.PowerInterface):
|
||||||
|
"""Cisco Power Interface.
|
||||||
|
|
||||||
|
This PowerInterface class provides a mechanism for controlling the
|
||||||
|
power state of servers managed by Cisco UCS Manager.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_properties(self):
|
||||||
|
"""Returns common properties of the driver."""
|
||||||
|
return ucs_helper.COMMON_PROPERTIES
|
||||||
|
|
||||||
|
def validate(self, task):
|
||||||
|
"""Check that node 'driver_info' is valid.
|
||||||
|
|
||||||
|
Check that node 'driver_info' contains the required fields.
|
||||||
|
|
||||||
|
:param task: instance of `ironic.manager.task_manager.TaskManager`.
|
||||||
|
:raises: MissingParameterValue if required CiscoDriver parameters
|
||||||
|
are missing.
|
||||||
|
"""
|
||||||
|
ucs_helper.parse_driver_info(task.node)
|
||||||
|
|
||||||
|
@ucs_helper.requires_ucs_client
|
||||||
|
def get_power_state(self, task, helper=None):
|
||||||
|
"""Get the current power state.
|
||||||
|
|
||||||
|
Poll the host for the current power state of the node.
|
||||||
|
|
||||||
|
:param task: instance of `ironic.manager.task_manager.TaskManager`.
|
||||||
|
:param helper: ucs helper instance
|
||||||
|
:raises: MissingParameterValue if required CiscoDriver parameters
|
||||||
|
are missing.
|
||||||
|
:raises: UcsOperationError on error from UCS Client.
|
||||||
|
:returns: power state. One of :class:`ironic.common.states`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
power_handle = ucs_power.UcsPower(helper)
|
||||||
|
power_status = power_handle.get_power_state()
|
||||||
|
except ucs_error.UcsOperationError as ucs_exception:
|
||||||
|
LOG.error(_LE("%(driver)s: get_power_status operation failed for "
|
||||||
|
"node %(uuid)s with error."),
|
||||||
|
{'driver': task.node.driver, 'uuid': task.node.uuid})
|
||||||
|
operation = _('getting power status')
|
||||||
|
raise exception.UcsOperationError(operation=operation,
|
||||||
|
error=ucs_exception,
|
||||||
|
node=task.node.uuid)
|
||||||
|
return UCS_TO_IRONIC_POWER_STATE.get(power_status, states.ERROR)
|
||||||
|
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
@ucs_helper.requires_ucs_client
|
||||||
|
def set_power_state(self, task, pstate, helper=None):
|
||||||
|
"""Turn the power on or off.
|
||||||
|
|
||||||
|
Set the power state of a node.
|
||||||
|
|
||||||
|
:param task: instance of `ironic.manager.task_manager.TaskManager`.
|
||||||
|
:param pstate: Either POWER_ON or POWER_OFF from :class:
|
||||||
|
`ironic.common.states`.
|
||||||
|
:param helper: ucs helper instance
|
||||||
|
:raises: InvalidParameterValue if an invalid power state was specified.
|
||||||
|
:raises: MissingParameterValue if required CiscoDriver parameters
|
||||||
|
are missing.
|
||||||
|
:raises: UcsOperationError on error from UCS Client.
|
||||||
|
:raises: PowerStateFailure if the desired power state couldn't be set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if pstate not in (states.POWER_ON, states.POWER_OFF):
|
||||||
|
msg = _("set_power_state called with invalid power state "
|
||||||
|
"'%s'") % pstate
|
||||||
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ucs_power_handle = ucs_power.UcsPower(helper)
|
||||||
|
power_status = ucs_power_handle.get_power_state()
|
||||||
|
if UCS_TO_IRONIC_POWER_STATE.get(power_status) != pstate:
|
||||||
|
ucs_power_handle.set_power_state(
|
||||||
|
IRONIC_TO_UCS_POWER_STATE.get(pstate))
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
except ucs_error.UcsOperationError as ucs_exception:
|
||||||
|
LOG.error(_LE("Cisco client exception %(msg)s for node %(uuid)s"),
|
||||||
|
{'msg': ucs_exception, 'uuid': task.node.uuid})
|
||||||
|
operation = _("setting power status")
|
||||||
|
raise exception.UcsOperationError(operation=operation,
|
||||||
|
error=ucs_exception,
|
||||||
|
node=task.node.uuid)
|
||||||
|
state = _wait_for_state_change(pstate, ucs_power_handle)
|
||||||
|
if state != pstate:
|
||||||
|
timeout = CONF.cisco_ucs.action_interval * CONF.cisco_ucs.max_retry
|
||||||
|
LOG.error(_LE("%(driver)s: driver failed to change node %(uuid)s "
|
||||||
|
"power state to %(state)s within %(timeout)s "
|
||||||
|
"seconds."),
|
||||||
|
{'driver': task.node.driver, 'uuid': task.node.uuid,
|
||||||
|
'state': pstate, 'timeout': timeout})
|
||||||
|
raise exception.PowerStateFailure(pstate=pstate)
|
||||||
|
|
||||||
|
@task_manager.require_exclusive_lock
|
||||||
|
@ucs_helper.requires_ucs_client
|
||||||
|
def reboot(self, task, helper=None):
|
||||||
|
"""Cycles the power to a node.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance.
|
||||||
|
:param helper: ucs helper instance.
|
||||||
|
:raises: UcsOperationError on error from UCS Client.
|
||||||
|
:raises: PowerStateFailure if the final state of the node is not
|
||||||
|
POWER_ON.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ucs_power_handle = ucs_power.UcsPower(helper)
|
||||||
|
ucs_power_handle.reboot()
|
||||||
|
except ucs_error.UcsOperationError as ucs_exception:
|
||||||
|
LOG.error(_LE("%(driver)s: driver failed to reset node %(uuid)s "
|
||||||
|
"power state."),
|
||||||
|
{'driver': task.node.driver, 'uuid': task.node.uuid})
|
||||||
|
operation = _("rebooting")
|
||||||
|
raise exception.UcsOperationError(operation=operation,
|
||||||
|
error=ucs_exception,
|
||||||
|
node=task.node.uuid)
|
||||||
|
|
||||||
|
state = _wait_for_state_change(states.POWER_ON, ucs_power_handle)
|
||||||
|
if state != states.POWER_ON:
|
||||||
|
timeout = CONF.cisco_ucs.action_interval * CONF.cisco_ucs.max_retry
|
||||||
|
LOG.error(_LE("%(driver)s: driver failed to reboot node %(uuid)s "
|
||||||
|
"within %(timeout)s seconds."),
|
||||||
|
{'driver': task.node.driver,
|
||||||
|
'uuid': task.node.uuid, 'timeout': timeout})
|
||||||
|
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
@ -41,6 +41,8 @@ from ironic.drivers.modules import pxe
|
|||||||
from ironic.drivers.modules import seamicro
|
from ironic.drivers.modules import seamicro
|
||||||
from ironic.drivers.modules import snmp
|
from ironic.drivers.modules import snmp
|
||||||
from ironic.drivers.modules import ssh
|
from ironic.drivers.modules import ssh
|
||||||
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
from ironic.drivers.modules import virtualbox
|
from ironic.drivers.modules import virtualbox
|
||||||
from ironic.drivers import utils
|
from ironic.drivers import utils
|
||||||
|
|
||||||
@ -285,3 +287,25 @@ class PXEAndMSFTOCSDriver(base.BaseDriver):
|
|||||||
self.deploy = pxe.PXEDeploy()
|
self.deploy = pxe.PXEDeploy()
|
||||||
self.management = msftocs_management.MSFTOCSManagement()
|
self.management = msftocs_management.MSFTOCSManagement()
|
||||||
self.vendor = pxe.VendorPassthru()
|
self.vendor = pxe.VendorPassthru()
|
||||||
|
|
||||||
|
|
||||||
|
class PXEAndUcsDriver(base.BaseDriver):
|
||||||
|
"""PXE + Cisco UCSM driver.
|
||||||
|
|
||||||
|
This driver implements the `core` functionality, combining
|
||||||
|
:class:ironic.drivers.modules.ucs.power.Power for power
|
||||||
|
on/off and reboot with
|
||||||
|
:class:ironic.driver.modules.pxe.PXE 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('UcsSdk'):
|
||||||
|
raise exception.DriverLoadError(
|
||||||
|
driver=self.__class__.__name__,
|
||||||
|
reason=_("Unable to import UcsSdk library"))
|
||||||
|
self.power = ucs_power.Power()
|
||||||
|
self.deploy = pxe.PXEDeploy()
|
||||||
|
self.management = ucs_mgmt.UcsManagement()
|
||||||
|
self.vendor = pxe.VendorPassthru()
|
||||||
|
@ -307,3 +307,12 @@ def get_test_conductor(**kw):
|
|||||||
'created_at': kw.get('created_at', timeutils.utcnow()),
|
'created_at': kw.get('created_at', timeutils.utcnow()),
|
||||||
'updated_at': kw.get('updated_at', timeutils.utcnow()),
|
'updated_at': kw.get('updated_at', timeutils.utcnow()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_ucs_info():
|
||||||
|
return {
|
||||||
|
"ucs_username": "admin",
|
||||||
|
"ucs_password": "password",
|
||||||
|
"ucs_service_profile": "org-root/ls-devstack",
|
||||||
|
"ucs_address": "ucs-b",
|
||||||
|
}
|
||||||
|
@ -197,3 +197,26 @@ if not ironic_inspector:
|
|||||||
if 'ironic.drivers.modules.inspector' in sys.modules:
|
if 'ironic.drivers.modules.inspector' in sys.modules:
|
||||||
six.moves.reload_module(
|
six.moves.reload_module(
|
||||||
sys.modules['ironic.drivers.modules.inspector'])
|
sys.modules['ironic.drivers.modules.inspector'])
|
||||||
|
|
||||||
|
|
||||||
|
class MockKwargsException(Exception):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(MockKwargsException, self).__init__(*args)
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
|
||||||
|
ucssdk = importutils.try_import('UcsSdk')
|
||||||
|
if not ucssdk:
|
||||||
|
ucssdk = mock.MagicMock()
|
||||||
|
sys.modules['UcsSdk'] = ucssdk
|
||||||
|
sys.modules['UcsSdk.utils'] = ucssdk.utils
|
||||||
|
sys.modules['UcsSdk.utils.power'] = ucssdk.utils.power
|
||||||
|
sys.modules['UcsSdk.utils.management'] = ucssdk.utils.management
|
||||||
|
sys.modules['UcsSdk.utils.exception'] = ucssdk.utils.exception
|
||||||
|
ucssdk.utils.exception.UcsOperationError = (
|
||||||
|
type('UcsOperationError', (MockKwargsException,), {}))
|
||||||
|
ucssdk.utils.exception.UcsConnectionError = (
|
||||||
|
type('UcsConnectionError', (MockKwargsException,), {}))
|
||||||
|
if 'ironic.drivers.modules.ucs' in sys.modules:
|
||||||
|
six.moves.reload_module(
|
||||||
|
sys.modules['ironic.drivers.modules.ucs'])
|
||||||
|
0
ironic/tests/drivers/ucs/__init__.py
Normal file
0
ironic/tests/drivers/ucs/__init__.py
Normal file
161
ironic/tests/drivers/ucs/test_helper.py
Normal file
161
ironic/tests/drivers/ucs/test_helper.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Test class for common methods used by UCS modules."""
|
||||||
|
|
||||||
|
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.db import api as dbapi
|
||||||
|
from ironic.drivers.modules.ucs import helper as ucs_helper
|
||||||
|
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
|
||||||
|
|
||||||
|
ucs_error = importutils.try_import('UcsSdk.utils.exception')
|
||||||
|
|
||||||
|
INFO_DICT = db_utils.get_test_ucs_info()
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class UcsValidateParametersTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(UcsValidateParametersTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver="fake_ucs")
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_ucs',
|
||||||
|
driver_info=INFO_DICT)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.helper = ucs_helper.CiscoUcsHelper(task)
|
||||||
|
|
||||||
|
def test_parse_driver_info(self):
|
||||||
|
info = ucs_helper.parse_driver_info(self.node)
|
||||||
|
|
||||||
|
self.assertIsNotNone(info.get('ucs_address'))
|
||||||
|
self.assertIsNotNone(info.get('ucs_username'))
|
||||||
|
self.assertIsNotNone(info.get('ucs_password'))
|
||||||
|
self.assertIsNotNone(info.get('ucs_service_profile'))
|
||||||
|
|
||||||
|
def test_parse_driver_info_missing_address(self):
|
||||||
|
|
||||||
|
del self.node.driver_info['ucs_address']
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
ucs_helper.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_missing_username(self):
|
||||||
|
del self.node.driver_info['ucs_username']
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
ucs_helper.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_missing_password(self):
|
||||||
|
del self.node.driver_info['ucs_password']
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
ucs_helper.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_missing_service_profile(self):
|
||||||
|
del self.node.driver_info['ucs_service_profile']
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
ucs_helper.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_connect_ucsm(self, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.helper.connect_ucsm()
|
||||||
|
|
||||||
|
mock_helper.generate_ucsm_handle.assert_called_once_with(
|
||||||
|
task.node.driver_info['ucs_address'],
|
||||||
|
task.node.driver_info['ucs_username'],
|
||||||
|
task.node.driver_info['ucs_password']
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_connect_ucsm_fail(self, mock_helper):
|
||||||
|
side_effect = ucs_error.UcsConnectionError(
|
||||||
|
message='connecting to ucsm',
|
||||||
|
error='failed')
|
||||||
|
mock_helper.generate_ucsm_handle.side_effect = side_effect
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.UcsConnectionError,
|
||||||
|
self.helper.connect_ucsm
|
||||||
|
)
|
||||||
|
mock_helper.generate_ucsm_handle.assert_called_once_with(
|
||||||
|
task.node.driver_info['ucs_address'],
|
||||||
|
task.node.driver_info['ucs_username'],
|
||||||
|
task.node.driver_info['ucs_password']
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper',
|
||||||
|
autospec=True)
|
||||||
|
def test_logout(self, mock_helper):
|
||||||
|
self.helper.logout()
|
||||||
|
|
||||||
|
|
||||||
|
class UcsCommonMethodsTestcase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(UcsCommonMethodsTestcase, self).setUp()
|
||||||
|
self.dbapi = dbapi.get_instance()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver="fake_ucs")
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_ucs',
|
||||||
|
driver_info=INFO_DICT.copy())
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.helper = ucs_helper.CiscoUcsHelper(task)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper', autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.CiscoUcsHelper',
|
||||||
|
autospec=True)
|
||||||
|
def test_requires_ucs_client_ok_logout(self, mc_helper, mock_ucs_helper):
|
||||||
|
mock_helper = mc_helper.return_value
|
||||||
|
mock_helper.logout.return_value = None
|
||||||
|
mock_working_function = mock.Mock()
|
||||||
|
mock_working_function.__name__ = "Working"
|
||||||
|
mock_working_function.return_valure = "Success"
|
||||||
|
mock_ucs_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
wont_error = ucs_helper.requires_ucs_client(
|
||||||
|
mock_working_function)
|
||||||
|
wont_error(wont_error, task)
|
||||||
|
mock_helper.logout.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper', autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.CiscoUcsHelper',
|
||||||
|
autospec=True)
|
||||||
|
def test_requires_ucs_client_fail_logout(self, mc_helper, mock_ucs_helper):
|
||||||
|
mock_helper = mc_helper.return_value
|
||||||
|
mock_helper.logout.return_value = None
|
||||||
|
mock_broken_function = mock.Mock()
|
||||||
|
mock_broken_function.__name__ = "Broken"
|
||||||
|
mock_broken_function.side_effect = exception.IronicException()
|
||||||
|
mock_ucs_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
|
||||||
|
will_error = ucs_helper.requires_ucs_client(mock_broken_function)
|
||||||
|
self.assertRaises(exception.IronicException,
|
||||||
|
will_error, will_error, task)
|
||||||
|
mock_helper.logout.assert_called_once_with()
|
139
ironic/tests/drivers/ucs/test_management.py
Normal file
139
ironic/tests/drivers/ucs/test_management.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test class for UCS ManagementInterface
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
|
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.ucs import helper as ucs_helper
|
||||||
|
from ironic.drivers.modules.ucs import management as ucs_mgmt
|
||||||
|
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
|
||||||
|
|
||||||
|
ucs_error = importutils.try_import('UcsSdk.utils.exception')
|
||||||
|
|
||||||
|
INFO_DICT = db_utils.get_test_ucs_info()
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class UcsManagementTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(UcsManagementTestCase, self).setUp()
|
||||||
|
mgr_utils.mock_the_extension_manager(driver='fake_ucs')
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_ucs',
|
||||||
|
driver_info=INFO_DICT)
|
||||||
|
self.interface = ucs_mgmt.UcsManagement()
|
||||||
|
self.task = mock.Mock()
|
||||||
|
self.task.node = self.node
|
||||||
|
|
||||||
|
def test_get_properties(self):
|
||||||
|
expected = ucs_helper.COMMON_PROPERTIES
|
||||||
|
self.assertEqual(expected, self.interface.get_properties())
|
||||||
|
|
||||||
|
def test_get_supported_boot_devices(self):
|
||||||
|
expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM]
|
||||||
|
self.assertEqual(sorted(expected),
|
||||||
|
sorted(self.interface.get_supported_boot_devices()))
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch(
|
||||||
|
'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_get_boot_device(self, mock_ucs_mgmt, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_mgmt = mock_ucs_mgmt.return_value
|
||||||
|
mock_mgmt.get_boot_device.return_value = {
|
||||||
|
'boot_device': 'storage',
|
||||||
|
'persistent': False
|
||||||
|
}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
expected_device = boot_devices.DISK
|
||||||
|
expected_response = {'boot_device': expected_device,
|
||||||
|
'persistent': False}
|
||||||
|
self.assertEqual(expected_response,
|
||||||
|
self.interface.get_boot_device(task))
|
||||||
|
mock_mgmt.get_boot_device.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch(
|
||||||
|
'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_get_boot_device_fail(self, mock_ucs_mgmt, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_mgmt = mock_ucs_mgmt.return_value
|
||||||
|
side_effect = ucs_error.UcsOperationError(
|
||||||
|
operation='getting boot device',
|
||||||
|
error='failed',
|
||||||
|
node=self.node.uuid
|
||||||
|
)
|
||||||
|
mock_mgmt.get_boot_device.side_effect = side_effect
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertRaises(exception.UcsOperationError,
|
||||||
|
self.interface.get_boot_device,
|
||||||
|
task)
|
||||||
|
mock_mgmt.get_boot_device.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch(
|
||||||
|
'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_set_boot_device(self, mock_mgmt, mock_helper):
|
||||||
|
mc_mgmt = mock_mgmt.return_value
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.interface.set_boot_device(task, boot_devices.CDROM)
|
||||||
|
|
||||||
|
mc_mgmt.set_boot_device.assert_called_once_with('cdrom', False)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch(
|
||||||
|
'ironic.drivers.modules.ucs.management.ucs_mgmt.BootDeviceHelper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_set_boot_device_fail(self, mock_mgmt, mock_helper):
|
||||||
|
mc_mgmt = mock_mgmt.return_value
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
side_effect = exception.UcsOperationError(
|
||||||
|
operation='setting boot device',
|
||||||
|
error='failed',
|
||||||
|
node=self.node.uuid)
|
||||||
|
mc_mgmt.set_boot_device.side_effect = side_effect
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertRaises(exception.IronicException,
|
||||||
|
self.interface.set_boot_device,
|
||||||
|
task, boot_devices.PXE)
|
||||||
|
mc_mgmt.set_boot_device.assert_called_once_with(
|
||||||
|
boot_devices.PXE, False)
|
||||||
|
|
||||||
|
def test_get_sensors_data(self):
|
||||||
|
self.assertRaises(NotImplementedError,
|
||||||
|
self.interface.get_sensors_data, self.task)
|
259
ironic/tests/drivers/ucs/test_power.py
Normal file
259
ironic/tests/drivers/ucs/test_power.py
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Test class for UcsPower module."""
|
||||||
|
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.ucs import helper as ucs_helper
|
||||||
|
from ironic.drivers.modules.ucs import power as ucs_power
|
||||||
|
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
|
||||||
|
|
||||||
|
ucs_error = importutils.try_import('UcsSdk.utils.exception')
|
||||||
|
|
||||||
|
INFO_DICT = db_utils.get_test_ucs_info()
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class UcsPowerTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(UcsPowerTestCase, self).setUp()
|
||||||
|
driver_info = INFO_DICT
|
||||||
|
mgr_utils.mock_the_extension_manager(driver="fake_ucs")
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='fake_ucs',
|
||||||
|
driver_info=driver_info)
|
||||||
|
CONF.set_override('max_retry', 2, 'cisco_ucs')
|
||||||
|
CONF.set_override('action_interval', 1, 'cisco_ucs')
|
||||||
|
self.interface = ucs_power.Power()
|
||||||
|
|
||||||
|
def test_get_properties(self):
|
||||||
|
expected = ucs_helper.COMMON_PROPERTIES
|
||||||
|
expected.update(ucs_helper.COMMON_PROPERTIES)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertEqual(expected, task.driver.get_properties())
|
||||||
|
|
||||||
|
@mock.patch.object(ucs_helper, 'parse_driver_info',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_validate(self, mock_parse_driver_info):
|
||||||
|
mock_parse_driver_info.return_value = {}
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.interface.validate(task)
|
||||||
|
mock_parse_driver_info.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch.object(ucs_helper, 'parse_driver_info',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_validate_fail(self, mock_parse_driver_info):
|
||||||
|
side_effect = exception.InvalidParameterValue('Invalid Input')
|
||||||
|
mock_parse_driver_info.side_effect = side_effect
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
self.interface.validate,
|
||||||
|
task)
|
||||||
|
mock_parse_driver_info.assert_called_once_with(task.node)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_get_power_state_up(self, mock_power_helper, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
mock_power.get_power_state.return_value = 'up'
|
||||||
|
self.assertEqual(states.POWER_ON,
|
||||||
|
self.interface.get_power_state(task))
|
||||||
|
mock_power.get_power_state.assert_called_once_with()
|
||||||
|
mock_power.get_power_state.reset_mock()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_get_power_state_down(self, mock_power_helper, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
mock_power.get_power_state.return_value = 'down'
|
||||||
|
self.assertEqual(states.POWER_OFF,
|
||||||
|
self.interface.get_power_state(task))
|
||||||
|
mock_power.get_power_state.assert_called_once_with()
|
||||||
|
mock_power.get_power_state.reset_mock()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_get_power_state_error(self, mock_power_helper, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
mock_power.get_power_state.return_value = states.ERROR
|
||||||
|
self.assertEqual(states.ERROR,
|
||||||
|
self.interface.get_power_state(task))
|
||||||
|
mock_power.get_power_state.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_get_power_state_fail(self,
|
||||||
|
mock_ucs_power,
|
||||||
|
mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
power = mock_ucs_power.return_value
|
||||||
|
power.get_power_state.side_effect = (
|
||||||
|
ucs_error.UcsOperationError(operation='getting power state',
|
||||||
|
error='failed'))
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
self.assertRaises(exception.UcsOperationError,
|
||||||
|
self.interface.get_power_state,
|
||||||
|
task)
|
||||||
|
power.get_power_state.assert_called_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power._wait_for_state_change',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_set_power_state(self, mock_power_helper, mock__wait, mock_helper):
|
||||||
|
target_state = states.POWER_ON
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
mock_power.get_power_state.side_effect = ['down', 'up']
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock__wait.return_value = target_state
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertIsNone(self.interface.set_power_state(task,
|
||||||
|
target_state))
|
||||||
|
|
||||||
|
mock_power.set_power_state.assert_called_once_with('up')
|
||||||
|
mock_power.get_power_state.assert_called_once_with()
|
||||||
|
mock__wait.assert_called_once_with(target_state, mock_power)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_set_power_state_fail(self, mock_power_helper, mock_helper):
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
mock_power.set_power_state.side_effect = (
|
||||||
|
ucs_error.UcsOperationError(operation='setting power state',
|
||||||
|
error='failed'))
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertRaises(exception.UcsOperationError,
|
||||||
|
self.interface.set_power_state,
|
||||||
|
task, states.POWER_OFF)
|
||||||
|
mock_power.set_power_state.assert_called_once_with('down')
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_set_power_state_invalid_state(self, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
self.interface.set_power_state,
|
||||||
|
task, states.ERROR)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test__set_and_wait_for_state_change_already_target_state(
|
||||||
|
self,
|
||||||
|
mock_ucs_power,
|
||||||
|
mock_helper):
|
||||||
|
mock_power = mock_ucs_power.return_value
|
||||||
|
target_state = states.POWER_ON
|
||||||
|
mock_power.get_power_state.return_value = 'up'
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
self.assertEqual(states.POWER_ON,
|
||||||
|
ucs_power._wait_for_state_change(
|
||||||
|
target_state, mock_power))
|
||||||
|
mock_power.get_power_state.assert_called_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test__set_and_wait_for_state_change_exceed_iterations(
|
||||||
|
self,
|
||||||
|
mock_power_helper,
|
||||||
|
mock_helper):
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
target_state = states.POWER_ON
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_power.get_power_state.side_effect = (
|
||||||
|
['down', 'down', 'down', 'down'])
|
||||||
|
self.assertEqual(states.ERROR,
|
||||||
|
ucs_power._wait_for_state_change(
|
||||||
|
target_state, mock_power)
|
||||||
|
)
|
||||||
|
mock_power.get_power_state.assert_called_with()
|
||||||
|
self.assertEqual(4, mock_power.get_power_state.call_count)
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power._wait_for_state_change',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_reboot(self, mock_power_helper, mock__wait, mock_helper):
|
||||||
|
mock_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
mock__wait.return_value = states.POWER_ON
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertIsNone(self.interface.reboot(task))
|
||||||
|
mock_power.reboot.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.helper.ucs_helper',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power._wait_for_state_change',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
@mock.patch('ironic.drivers.modules.ucs.power.ucs_power.UcsPower',
|
||||||
|
spec_set=True, autospec=True)
|
||||||
|
def test_reboot_fail(self, mock_power_helper, mock__wait,
|
||||||
|
mock_ucs_helper):
|
||||||
|
mock_ucs_helper.generate_ucsm_handle.return_value = (True, mock.Mock())
|
||||||
|
mock_power = mock_power_helper.return_value
|
||||||
|
mock_power.reboot.side_effect = (
|
||||||
|
ucs_error.UcsOperationError(operation='rebooting', error='failed'))
|
||||||
|
mock__wait.return_value = states.ERROR
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertRaises(exception.UcsOperationError,
|
||||||
|
self.interface.reboot,
|
||||||
|
task
|
||||||
|
)
|
||||||
|
mock_power.reboot.assert_called_once_with()
|
@ -38,6 +38,7 @@ ironic.drivers =
|
|||||||
agent_pyghmi = ironic.drivers.agent:AgentAndIPMINativeDriver
|
agent_pyghmi = ironic.drivers.agent:AgentAndIPMINativeDriver
|
||||||
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
|
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver
|
||||||
agent_vbox = ironic.drivers.agent:AgentAndVirtualBoxDriver
|
agent_vbox = ironic.drivers.agent:AgentAndVirtualBoxDriver
|
||||||
|
agent_ucs = ironic.drivers.agent:AgentAndUcsDriver
|
||||||
fake = ironic.drivers.fake:FakeDriver
|
fake = ironic.drivers.fake:FakeDriver
|
||||||
fake_agent = ironic.drivers.fake:FakeAgentDriver
|
fake_agent = ironic.drivers.fake:FakeAgentDriver
|
||||||
fake_inspector = ironic.drivers.fake:FakeIPMIToolInspectorDriver
|
fake_inspector = ironic.drivers.fake:FakeIPMIToolInspectorDriver
|
||||||
@ -54,6 +55,7 @@ ironic.drivers =
|
|||||||
fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
|
fake_vbox = ironic.drivers.fake:FakeVirtualBoxDriver
|
||||||
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
fake_amt = ironic.drivers.fake:FakeAMTDriver
|
||||||
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
fake_msftocs = ironic.drivers.fake:FakeMSFTOCSDriver
|
||||||
|
fake_ucs = ironic.drivers.fake:FakeUcsDriver
|
||||||
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
iscsi_ilo = ironic.drivers.ilo:IloVirtualMediaIscsiDriver
|
||||||
pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver
|
pxe_ipmitool = ironic.drivers.pxe:PXEAndIPMIToolDriver
|
||||||
pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
|
pxe_ipminative = ironic.drivers.pxe:PXEAndIPMINativeDriver
|
||||||
@ -67,6 +69,7 @@ ironic.drivers =
|
|||||||
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
|
pxe_irmc = ironic.drivers.pxe:PXEAndIRMCDriver
|
||||||
pxe_amt = ironic.drivers.pxe:PXEAndAMTDriver
|
pxe_amt = ironic.drivers.pxe:PXEAndAMTDriver
|
||||||
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
pxe_msftocs = ironic.drivers.pxe:PXEAndMSFTOCSDriver
|
||||||
|
pxe_ucs = ironic.drivers.pxe:PXEAndUcsDriver
|
||||||
|
|
||||||
ironic.database.migration_backend =
|
ironic.database.migration_backend =
|
||||||
sqlalchemy = ironic.db.sqlalchemy.migration
|
sqlalchemy = ironic.db.sqlalchemy.migration
|
||||||
|
Loading…
Reference in New Issue
Block a user