Remove deprecated idrac wsman driver interfaces

Change-Id: I70738db25fdf9902575ac92195c3a40f1d7a0976
This commit is contained in:
Julia Kreger 2024-06-19 09:46:50 -07:00
parent f14794ca2e
commit 578f24bf18
26 changed files with 64 additions and 8952 deletions

View File

@ -5,17 +5,12 @@ iDRAC driver
Overview
========
.. warning::
The ``-wsman`` driver interfaces have been deprecated and are anticipated
to be removed from Ironic at some point during or after the 2024.2
development cycle. The anticipated forward management path is to migrate
to the ``-redfish`` driver interfaces or the ``redfish`` hardware type.
The integrated Dell Remote Access Controller (iDRAC_) is an out-of-band
management platform on Dell EMC servers, and is supported directly by
the ``idrac`` hardware type. This driver uses the Dell Web Services for
Management (WSMAN) protocol and the standard Distributed Management Task
Force (DMTF) Redfish protocol to perform all of its functions.
the ``idrac`` hardware type. This driver utilizes the Distributed
Management Task Force (DMTF) Redfish protocol to perform all of it's
functions. In older versions of Ironic, this driver leveraged
Web Services for Management (WSMAN) protocol.
iDRAC_ hardware is also supported by the generic ``ipmi`` and ``redfish``
hardware types, though with smaller feature sets.
@ -38,19 +33,12 @@ The ``idrac`` hardware type supports the following Ironic interfaces:
* `Management Interface`_: Boot device and firmware management
* Power Interface: Power management
* `RAID Interface`_: RAID controller and disk management
* `Vendor Interface`_: BIOS management (WSMAN) and eject virtual media
(Redfish)
* `Vendor Interface`_: eject virtual media (Redfish)
Prerequisites
-------------
The ``idrac`` hardware type requires the ``python-dracclient`` library
to be installed on the ironic conductor node(s) if an Ironic node is
configured to use an ``idrac-wsman`` interface implementation, for example::
sudo pip install 'python-dracclient>=3.1.0'
Additionally, the ``idrac`` hardware type requires the ``sushy`` library
The ``idrac`` hardware type requires the ``sushy`` library
to be installed on the ironic conductor node(s) if an Ironic node is
configured to use an ``idrac-redfish`` interface implementation, for example::
@ -59,28 +47,24 @@ configured to use an ``idrac-redfish`` interface implementation, for example::
Enabling
--------
The iDRAC driver supports WSMAN for the bios, inspect, management, power,
raid, and vendor interfaces. In addition, it supports Redfish for
the bios, inspect, management, power, and raid interfaces. The iDRAC driver
allows you to mix and match WSMAN and Redfish interfaces.
The iDRAC driver supports Redfish for the bios, inspect, management, power,
and raid interfaces.
The ``idrac-wsman`` implementation must be enabled to use WSMAN for
an interface. The ``idrac-redfish`` implementation must be enabled
The ``idrac-redfish`` implementation must be enabled
to use Redfish for an interface.
To enable the ``idrac`` hardware type with the minimum interfaces,
all using WSMAN, add the following to your ``/etc/ironic/ironic.conf``:
To enable the ``idrac`` hardware type, add the following to your
``/etc/ironic/ironic.conf``:
.. code-block:: ini
[DEFAULT]
enabled_hardware_types=idrac
enabled_management_interfaces=idrac-wsman
enabled_power_interfaces=idrac-wsman
enabled_management_interfaces=idrac-redfish
enabled_power_interfaces=idrac-redfish
To enable all optional features (BIOS, inspection, RAID, and vendor passthru)
using Redfish where it is supported and WSMAN where not, use the
following configuration:
To enable all optional features (BIOS, inspection, RAID, and vendor passthru),
use the following configuration:
.. code-block:: ini
@ -100,43 +84,30 @@ order:
================ ===================================================
Interface Supported Implementations
================ ===================================================
``bios`` ``idrac-wsman``, ``idrac-redfish``, ``no-bios``
``bios`` ``idrac-redfish``, ``no-bios``
``boot`` ``ipxe``, ``pxe``, ``idrac-redfish-virtual-media``
``console`` ``no-console``
``deploy`` ``direct``, ``ansible``, ``ramdisk``
``firmware`` ``redfish``, ``no-firmware``
``inspect`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``,
``inspect`` ``idrac-redfish``,
``inspector``, ``no-inspect``
``management`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
``management`` ``idrac-redfish``
``network`` ``flat``, ``neutron``, ``noop``
``power`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
``raid`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``, ``no-raid``
``power`` ``idrac-redfish``
``raid`` ``idrac-redfish``, ``no-raid``
``rescue`` ``no-rescue``, ``agent``
``storage`` ``noop``, ``cinder``, ``external``
``vendor`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``,
``vendor`` ``idrac-redfish``,
``no-vendor``
================ ===================================================
.. NOTE::
``idrac`` is the legacy name of the WSMAN interface. It has been
deprecated in favor of ``idrac-wsman`` and may be removed in a
future release.
Protocol-specific Properties
----------------------------
The WSMAN and Redfish protocols require different properties to be specified
The Redfish protocols require different properties to be specified
in the Ironic node's ``driver_info`` field to communicate with the bare
metal system's iDRAC.
The WSMAN protocol requires the following properties:
* ``drac_username``: The WSMAN user name to use when communicating
with the iDRAC. Usually ``root``.
* ``drac_password``: The password for the WSMAN user to use when
communicating with the iDRAC.
* ``drac_address``: The IP address of the iDRAC.
The Redfish protocol requires the following properties:
* ``redfish_username``: The Redfish user name to use when
@ -151,25 +122,9 @@ The Redfish protocol requires the following properties:
For other Redfish protocol parameters see :doc:`/admin/drivers/redfish`.
If using only interfaces which use WSMAN (``idrac-wsman``), then only
the WSMAN properties must be supplied. If using only interfaces which
use Redfish (``idrac-redfish``), then only the Redfish properties must be
supplied. If using a mix of interfaces, where some use WSMAN and others
use Redfish, both the WSMAN and Redfish properties must be supplied.
Enrolling
---------
The following command enrolls a bare metal node with the ``idrac``
hardware type using WSMAN for all interfaces:
.. code-block:: bash
baremetal node create --driver idrac \
--driver-info drac_username=user \
--driver-info drac_password=pa$$w0rd \
--driver-info drac_address=drac.host
The following command enrolls a bare metal node with the ``idrac``
hardware type using Redfish for all interfaces:
@ -187,29 +142,6 @@ hardware type using Redfish for all interfaces:
--raid-interface idrac-redfish \
--vendor-interface idrac-redfish
The following command enrolls a bare metal node with the ``idrac``
hardware type assuming a mix of Redfish and WSMAN interfaces are used:
.. code-block:: bash
baremetal node create --driver idrac \
--driver-info drac_username=user \
--driver-info drac_password=pa$$w0rd
--driver-info drac_address=drac.host \
--driver-info redfish_username=user \
--driver-info redfish_password=pa$$w0rd \
--driver-info redfish_address=drac.host \
--driver-info redfish_system_id=/redfish/v1/Systems/System.Embedded.1 \
--bios-interface idrac-redfish \
--inspect-interface idrac-redfish \
--management-interface idrac-redfish \
--power-interface idrac-redfish
.. NOTE::
If using WSMAN for the management interface, then WSMAN must be used
for the power interface. The same applies to Redfish. It is currently not
possible to use Redfish for one and WSMAN for the other.
BIOS Interface
==============
@ -252,7 +184,7 @@ Inspect Interface
The Dell iDRAC out-of-band inspection process catalogs all the same
attributes of the server as the IPMI driver. Unlike IPMI, it does this
without requiring the system to be rebooted, or even to be powered on.
Inspection is performed using the Dell WSMAN or Redfish protocol directly
Inspection is performed using the Redfish protocol directly
without affecting the operation of the system being inspected.
The inspection discovers the following properties:
@ -267,8 +199,6 @@ Extra capabilities:
* ``pci_gpu_devices``: number of GPU devices connected to the bare metal.
It also creates baremetal ports for each NIC port detected in the system.
The ``idrac-wsman`` inspect interface discovers which NIC ports are
configured to PXE boot and sets ``pxe_enabled`` to ``True`` on those ports.
The ``idrac-redfish`` inspect interface does not currently set ``pxe_enabled``
on the ports. The user should ensure that ``pxe_enabled`` is set correctly on
the ports following inspection with the ``idrac-redfish`` inspect interface.
@ -487,7 +417,7 @@ Compared to ``redfish`` RAID interface, using ``idrac-redfish`` adds:
* Converting non-RAID disks to RAID mode if there are any,
* Clearing foreign configuration, if any, after deleting virtual disks.
The following properties are supported by the iDRAC WSMAN and Redfish RAID
The following properties are supported by the Redfish RAID
interface implementation:
.. NOTE::
@ -633,223 +563,6 @@ Or using ``sushy`` with Redfish:
Vendor Interface
================
idrac-wsman
-----------
Dell iDRAC BIOS management is available through the Ironic WSMAN vendor
passthru interface.
======================== ============ ======================================
Method Name HTTP Method Description
======================== ============ ======================================
``abandon_bios_config`` ``DELETE`` Abandon a BIOS configuration job.
``commit_bios_config`` ``POST`` Commit a BIOS configuration job
submitted through ``set_bios_config``.
Required argument: ``reboot`` -
indicates whether a reboot job
should be automatically created
with the config job. Returns a
dictionary containing the ``job_id``
key with the ID of the newly created
config job, and the
``reboot_required`` key indicating
whether the node needs to be rebooted
to execute the config job.
``get_bios_config`` ``GET`` Returns a dictionary containing the
node's BIOS settings.
``list_unfinished_jobs`` ``GET`` Returns a dictionary containing
the key ``unfinished_jobs``; its value
is a list of dictionaries. Each
dictionary represents an unfinished
config job object.
``set_bios_config`` ``POST`` Change the BIOS configuration on
a node. Required argument: a
dictionary of {``AttributeName``:
``NewValue``}. Returns a dictionary
containing the ``is_commit_required``
key indicating whether
``commit_bios_config`` needs to be
called to apply the changes and the
``is_reboot_required`` value
indicating whether the server must
also be rebooted. Possible values are
``true`` and ``false``.
======================== ============ ======================================
Examples
^^^^^^^^
Get BIOS Config
~~~~~~~~~~~~~~~
.. code-block:: bash
baremetal node passthru call --http-method GET <node> get_bios_config
Snippet of output showing virtualization enabled:
.. code-block:: json
{"ProcVirtualization": {
"current_value": "Enabled",
"instance_id": "BIOS.Setup.1-1:ProcVirtualization",
"name": "ProcVirtualization",
"pending_value": null,
"possible_values": [
"Enabled",
"Disabled"],
"read_only": false }}
There are a number of items to note from the above snippet:
* ``name``: this is the name to use in a call to ``set_bios_config``.
* ``current_value``: the current state of the setting.
* ``pending_value``: if the value has been set, but not yet committed,
the new value is shown here. The change can either be committed or
abandoned.
* ``possible_values``: shows a list of valid values which can be used
in a call to ``set_bios_config``.
* ``read_only``: indicates if the value is capable of being changed.
Set BIOS Config
~~~~~~~~~~~~~~~
.. code-block:: bash
baremetal node passthru call <node> set_bios_config --arg "name=value"
Walkthrough of performing a BIOS configuration change:
The following section demonstrates how to change BIOS configuration settings,
detect that a commit and reboot are required, and act on them accordingly. The
two properties that are being changed are:
* Enable virtualization technology of the processor
* Globally enable SR-IOV
.. code-block:: bash
baremetal node passthru call <node> set_bios_config \
--arg "ProcVirtualization=Enabled" \
--arg "SriovGlobalEnable=Enabled"
This returns a dictionary indicating what actions are required next:
.. code-block:: json
{
"is_reboot_required": true,
"is_commit_required": true
}
Commit BIOS Changes
~~~~~~~~~~~~~~~~~~~
The next step is to commit the pending change to the BIOS. Note that in this
example, the ``reboot`` argument is set to ``true``. The response indicates
that a reboot is no longer required as it has been scheduled automatically
by the ``commit_bios_config`` call. If the reboot argument is not supplied,
the job is still created, however it remains in the ``scheduled`` state
until a reboot is performed. The reboot can be initiated through the
Ironic power API.
.. code-block:: bash
baremetal node passthru call <node> commit_bios_config \
--arg "reboot=true"
.. code-block:: json
{
"job_id": "JID_499377293428",
"reboot_required": false
}
The state of any executing job can be queried:
.. code-block:: bash
baremetal node passthru call --http-method GET <node> list_unfinished_jobs
.. code-block:: json
{"unfinished_jobs":
[{"status": "Scheduled",
"name": "ConfigBIOS:BIOS.Setup.1-1",
"until_time": "TIME_NA",
"start_time": "TIME_NOW",
"message": "Task successfully scheduled.",
"percent_complete": "0",
"id": "JID_499377293428"}]}
Abandon BIOS Changes
~~~~~~~~~~~~~~~~~~~~
Instead of committing, a pending change can be abandoned:
.. code-block:: bash
baremetal node passthru call --http-method DELETE <node> abandon_bios_config
The abandon command does not provide a response body.
Change Boot Mode
^^^^^^^^^^^^^^^^
The boot mode of the iDRAC can be changed to:
* BIOS - Also called legacy or traditional boot mode. The BIOS initializes the
systems processors, memory, bus controllers, and I/O devices. After
initialization is complete, the BIOS passes control to operating system (OS)
software. The OS loader uses basic services provided by the system BIOS to
locate and load OS modules into system memory. After booting the system, the
BIOS and embedded management controllers execute system management
algorithms, which monitor and optimize the condition of the underlying
hardware. BIOS configuration settings enable fine-tuning of the
performance, power management, and reliability features of the system.
* UEFI - The Unified Extensible Firmware Interface does not change the
traditional purposes of the system BIOS. To a large extent, a UEFI-compliant
BIOS performs the same initialization, boot, configuration, and management
tasks as a traditional BIOS. However, UEFI does change the interfaces and
data structures the BIOS uses to interact with I/O device firmware and
operating system software. The primary intent of UEFI is to eliminate
shortcomings in the traditional BIOS environment, enabling system firmware to
continue scaling with industry trends.
The UEFI boot mode offers:
* Improved partitioning scheme for boot media
* Support for media larger than 2 TB
* Redundant partition tables
* Flexible handoff from BIOS to OS
* Consolidated firmware user interface
* Enhanced resource allocation for boot device firmware
The boot mode can be changed via the WSMAN vendor passthru interface as
follows:
.. code-block:: bash
baremetal node passthru call <node> set_bios_config \
--arg "BootMode=Uefi"
baremetal node passthru call <node> commit_bios_config \
--arg "reboot=true"
.. code-block:: bash
baremetal node passthru call <node> set_bios_config \
--arg "BootMode=Bios"
baremetal node passthru call <node> commit_bios_config \
--arg "reboot=true"
idrac-redfish
-------------
@ -896,27 +609,6 @@ settings.
.. _Ironic_RAID: https://docs.openstack.org/ironic/latest/admin/raid.html
.. _iDRAC: https://www.dell.com/idracmanuals
WSMAN vendor passthru timeout
-----------------------------
When iDRAC is not ready and executing WSMAN vendor passthru commands, they take
more time as waiting for iDRAC to become ready again and then time out,
for example:
.. code-block:: bash
baremetal node passthru call --http-method GET \
aed58dca-1b25-409a-a32f-3a817d59e1e0 list_unfinished_jobs
Timed out waiting for a reply to message ID 547ce7995342418c99ef1ea4a0054572 (HTTP 500)
To avoid this need to increase timeout for messaging in ``/etc/ironic/ironic.conf``
and restart Ironic API service.
.. code-block:: ini
[DEFAULT]
rpc_response_timeout = 600
Timeout when powering off
-------------------------

View File

@ -9,7 +9,6 @@ pysnmp-lextudio>=5.0.0 # BSD
pyasn1>=0.5.1 # BSD
pyasn1-modules>=0.3.0 # BSD
python-scciclient>=0.16.0,<0.17.0
python-dracclient>=5.1.0,<9.0.0
# Ansible-deploy interface
ansible>=2.7

View File

@ -47,20 +47,19 @@ class IDRACHardware(generic.GenericHardware):
@property
def supported_management_interfaces(self):
"""List of supported management interfaces."""
return [management.DracWSManManagement, management.DracManagement,
management.DracRedfishManagement]
return [management.DracRedfishManagement]
@property
def supported_power_interfaces(self):
"""List of supported power interfaces."""
return [power.DracWSManPower, power.DracPower, power.DracRedfishPower]
return [power.DracRedfishPower]
# Optional hardware interfaces
@property
def supported_bios_interfaces(self):
"""List of supported bios interfaces."""
return [bios.DracWSManBIOS, bios.DracRedfishBIOS, noop.NoBIOS]
return [bios.DracRedfishBIOS, noop.NoBIOS]
@property
def supported_firmware_interfaces(self):
@ -72,20 +71,17 @@ class IDRACHardware(generic.GenericHardware):
# Inspector support should have a higher priority than NoInspect
# if it is enabled by an operator (implying that the service is
# installed).
return [drac_inspect.DracWSManInspect, drac_inspect.DracInspect,
drac_inspect.DracRedfishInspect] + super(
return [drac_inspect.DracRedfishInspect] + super(
IDRACHardware, self).supported_inspect_interfaces
@property
def supported_raid_interfaces(self):
"""List of supported raid interfaces."""
return [raid.DracWSManRAID, raid.DracRAID,
raid.DracRedfishRAID] + super(
return [raid.DracRedfishRAID] + super(
IDRACHardware, self).supported_raid_interfaces
@property
def supported_vendor_interfaces(self):
"""List of supported vendor interfaces."""
return [vendor_passthru.DracWSManVendorPassthru,
vendor_passthru.DracVendorPassthru,
vendor_passthru.DracRedfishVendorPassthru, noop.NoVendor]
return [vendor_passthru.DracRedfishVendorPassthru,
noop.NoVendor]

View File

@ -15,31 +15,7 @@
DRAC BIOS configuration specific methods
"""
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from oslo_utils import timeutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.conductor import periodics
from ironic.conductor import utils as manager_utils
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import job as drac_job
from ironic.drivers.modules.redfish import bios as redfish_bios
from ironic import objects
drac_client = importutils.try_import('dracclient.client')
drac_exceptions = importutils.try_import('dracclient.exceptions')
drac_uris = importutils.try_import('dracclient.resources.uris')
drac_utils = importutils.try_import('dracclient.utils')
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
class DracRedfishBIOS(redfish_bios.RedfishBIOS):
@ -50,600 +26,3 @@ class DracRedfishBIOS(redfish_bios.RedfishBIOS):
specific incompatibilities and introduction of vendor value added
should be implemented by this class.
"""
class DracWSManBIOS(base.BIOSInterface):
"""BIOSInterface Implementation for iDRAC."""
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
# argsinfo dict for BIOS clean/deploy steps
_args_info = {
"settings": {
"description": "List of BIOS settings to apply",
"required": True
}
}
def __init__(self):
super(DracWSManBIOS, self).__init__()
if drac_exceptions is None:
raise exception.DriverLoadError(
driver='idrac',
reason=_("Unable to import dracclient.exceptions library"))
@METRICS.timer('DracWSManBIOS.apply_configuration')
@base.clean_step(priority=0, argsinfo=_args_info, requires_ramdisk=False)
@base.deploy_step(priority=0, argsinfo=_args_info)
def apply_configuration(self, task, settings):
"""Apply the BIOS configuration to the node
:param task: a TaskManager instance containing the node to act on
:param settings: List of BIOS settings to apply
:raises: DRACOperationError upon an error from python-dracclient
:returns: states.CLEANWAIT (cleaning) or states.DEPLOYWAIT (deployment)
if configuration is in progress asynchronously or None if it
is completed.
"""
LOG.debug("Configuring node %(node_uuid)s with BIOS settings:"
" %(settings)s", {"node_uuid": task.node.uuid,
"settings": settings})
node = task.node
# convert ironic settings list to DRAC kwsettings
kwsettings = {s['name']: s['value'] for s in settings}
drac_job.validate_job_queue(node)
client = drac_common.get_drac_client(node)
try:
# Argument validation is done by the dracclient method
# set_bios_settings. No need to do it here.
set_result = client.set_bios_settings(kwsettings)
except drac_exceptions.BaseClientException as exc:
LOG.error("Failed to apply BIOS config on node %(node_uuid)s."
" Error %(error)s", {"node_uuid": task.node.uuid,
"error": exc})
raise exception.DracOperationError(error=exc)
# If no commit is required, we're done
if not set_result['is_commit_required']:
LOG.info("Completed BIOS configuration on node %(node_uuid)s"
" with BIOS settings: %(settings)s",
{
"node_uuid": task.node.uuid,
"settings": settings
})
return
# Otherwise, need to reboot the node as well to commit configuration
else:
LOG.debug("Rebooting node %(node_uuid)s to apply BIOS settings",
{"node_uuid": task.node.uuid})
reboot_needed = set_result['is_reboot_required']
try:
commit_result = client.commit_pending_bios_changes(
reboot=reboot_needed)
except drac_exceptions.BaseClientException as exc:
LOG.error("Failed to commit BIOS changes on node %(node_uuid)s"
". Error %(error)s", {"node_uuid": task.node.uuid,
"error": exc})
raise exception.DracOperationError(error=exc)
# Store JobID for the async job handler _check_node_bios_jobs
bios_config_job_ids = node.driver_internal_info.get(
'bios_config_job_ids', [])
bios_config_job_ids.append(commit_result)
node.set_driver_internal_info('bios_config_job_ids',
bios_config_job_ids)
# This method calls node.save(), bios_config_job_ids will then be
# saved.
# These flags are for the conductor to manage the asynchronous
# jobs that have been initiated by this method
deploy_utils.set_async_step_flags(
node,
reboot=reboot_needed,
skip_current_step=True,
polling=True)
# Return the clean/deploy state string
return deploy_utils.get_async_step_return_state(node)
@METRICS.timer('DracWSManBIOS._query_bios_config_job_status')
# TODO(noor): Consider patch of CONF to add an entry for BIOS query
# spacing since BIOS jobs could be comparatively shorter in time than
# RAID ones currently using the raid spacing to avoid errors
# spacing parameter for periodic method
@periodics.node_periodic(
purpose='checking async bios configuration jobs',
spacing=CONF.drac.query_raid_config_job_status_interval,
filters={'reserved': False, 'maintenance': False},
predicate_extra_fields=['driver_internal_info'],
predicate=lambda n: (
n.driver_internal_info.get('bios_config_job_ids')
or n.driver_internal_info.get('factory_reset_time_before_reboot')),
)
def _query_bios_config_job_status(self, task, manager, context):
"""Periodic task to check the progress of running BIOS config jobs.
:param manager: an instance of Ironic Conductor Manager with
the node list to act on
:param context: context of the request, needed when acquiring
a lock on a node. For access control.
"""
# check bios_config_job_id exist & checks job is completed
if task.node.driver_internal_info.get("bios_config_job_ids"):
self._check_node_bios_jobs(task)
if task.node.driver_internal_info.get(
"factory_reset_time_before_reboot"):
self._check_last_system_inventory_changed(task)
def _check_last_system_inventory_changed(self, task):
"""Check the progress of last system inventory time of a node.
This handles jobs for BIOS factory reset. Handle means,
it checks for job status to not only signify completed jobs but
also handle failures by invoking the 'fail' event, allowing the
conductor to put the node into clean/deploy FAIL state.
:param task: a TaskManager instance with the node to act on
"""
node = task.node
client = drac_common.get_drac_client(node)
# Get the last system inventory time from node before reboot
factory_reset_time_before_reboot = node.driver_internal_info.get(
'factory_reset_time_before_reboot')
# Get the factory reset start time
factory_reset_time = node.driver_internal_info.get(
'factory_reset_time')
LOG.debug("Factory resetting node %(node_uuid)s factory reset time "
" %(factory_reset_time)s", {"node_uuid": task.node.uuid,
"factory_reset_time":
factory_reset_time})
# local variable to track difference between current time and factory
# reset start time
time_difference = 0
# Get the last system inventory time after reboot
factory_reset_time_endof_reboot = (client.get_system()
.last_system_inventory_time)
LOG.debug("Factory resetting node %(node_uuid)s "
"last inventory reboot time after factory reset "
"%(factory_reset_time_endof_reboot)s",
{"node_uuid": task.node.uuid,
"factory_reset_time_endof_reboot":
factory_reset_time_endof_reboot})
if factory_reset_time_before_reboot != factory_reset_time_endof_reboot:
# from the database cleanup with factory reset time
self._delete_cached_reboot_time(node)
# Cache the new BIOS settings,
self.cache_bios_settings(task)
self._resume_current_operation(task)
else:
# Calculate difference between current time and factory reset
# start time if it is more than configured timeout then set
# the node to fail state
time = timeutils.utcnow(with_timezone=True
) - timeutils.parse_isotime(str(
factory_reset_time))
time_difference = time.total_seconds()
LOG.debug("Factory resetting node %(node_uuid)s "
"time difference %(time_difference)s ",
{"node_uuid": task.node.uuid, "time_difference":
time_difference})
if time_difference > CONF.drac.bios_factory_reset_timeout:
task.upgrade_lock()
self._delete_cached_reboot_time(node)
error_message = ("BIOS factory reset was not completed within "
"{} seconds, unable to cache updated bios "
"setting").format(
CONF.drac.bios_factory_reset_timeout)
self._set_failed(task, error_message)
else:
LOG.debug("Factory reset for a node %(node)s is not done "
"will check again later", {'node': task.node.uuid})
def _check_node_bios_jobs(self, task):
"""Check the progress of running BIOS config jobs of a node.
This handles jobs for BIOS set and reset. Handle means,
it checks for job status to not only signify completed jobs but
also handle failures by invoking the 'fail' event, allowing the
conductor to put the node into clean/deploy FAIL state.
:param task: a TaskManager instance with the node to act on
"""
node = task.node
bios_config_job_ids = node.driver_internal_info['bios_config_job_ids']
finished_job_ids = []
# local variable to track job failures
job_failed = False
for config_job_id in bios_config_job_ids:
config_job = drac_job.get_job(node, job_id=config_job_id)
if config_job is None or config_job.status == 'Completed':
finished_job_ids.append(config_job_id)
elif (config_job.status == 'Failed'
or config_job.status == 'Completed with Errors'):
finished_job_ids.append(config_job_id)
job_failed = True
# If no job has finished, return
if not finished_job_ids:
return
# The finished jobs will require a node reboot, need to update the
# node lock to exclusive, allowing a destructive reboot operation
task.upgrade_lock()
# Cleanup the database with finished jobs, they're no longer needed
self._delete_cached_config_job_ids(node, finished_job_ids)
if not job_failed:
# Cache the new BIOS settings, caching needs to happen here
# since the config steps are async. Decorator won't work.
self.cache_bios_settings(task)
# if no failure, continue with clean/deploy
self._resume_current_operation(task)
else:
# invoke 'fail' event to allow conductor to put the node in
# a clean/deploy fail state
error_message = ("Failed config job: {}. Message: '{}'.".format(
config_job.id, config_job.message))
self._set_failed(task, error_message)
def _delete_cached_config_job_ids(self, node, finished_job_ids=None):
"""Remove Job IDs from the driver_internal_info table in database.
:param node: an ironic node object
:param finished_job_ids: a list of finished Job ID strings to remove
"""
if finished_job_ids is None:
finished_job_ids = []
# take out the unfinished job ids from all the jobs
unfinished_job_ids = [
job_id for job_id
in node.driver_internal_info['bios_config_job_ids']
if job_id not in finished_job_ids]
# assign the unfinished job ids back to the total list
# this will clear the finished jobs from the list
node.set_driver_internal_info('bios_config_job_ids',
unfinished_job_ids)
node.save()
def _delete_cached_reboot_time(self, node):
"""Remove factory time from the driver_internal_info table in database.
:param node: an ironic node object
"""
# Remove the last reboot time and factory reset time
node.del_driver_internal_info('factory_reset_time_before_reboot')
node.del_driver_internal_info('factory_reset_time')
node.save()
def _set_failed(self, task, error_message):
"""Set the node in failed state by invoking 'fail' event.
:param task: a TaskManager instance with node to act on
:param error_message: Error message
"""
log_msg = ("BIOS configuration failed for node %(node)s. %(error)s " %
{'node': task.node.uuid,
'error': error_message})
if task.node.clean_step:
manager_utils.cleaning_error_handler(task, log_msg, error_message)
else:
manager_utils.deploying_error_handler(task, log_msg, error_message)
def _resume_current_operation(self, task):
"""Continue cleaning/deployment of the node.
For asynchronous operations, it is necessary to notify the
conductor manager to continue the cleaning/deployment operation
after a job has finished. This is done through an RPC call. The
notify_conductor_resume_* wrapper methods provide that.
:param task: a TaskManager instance with node to act on
"""
if task.node.clean_step:
manager_utils.notify_conductor_resume_clean(task)
else:
manager_utils.notify_conductor_resume_deploy(task)
@METRICS.timer('DracWSManBIOS.factory_reset')
@base.clean_step(priority=0, requires_ramdisk=False)
@base.deploy_step(priority=0)
def factory_reset(self, task):
"""Reset the BIOS settings of the node to the factory default.
This uses the Lifecycle Controller configuration to perform
BIOS configuration reset. Leveraging the python-dracclient
methods already available.
:param task: a TaskManager instance containing the node to act on
:raises: DracOperationError on an error from python-dracclient
:returns: states.CLEANWAIT (cleaning) or states.DEPLOYWAIT
(deployment) if reset is in progress asynchronously or None
if it is completed.
"""
node = task.node
drac_job.validate_job_queue(node)
client = drac_common.get_drac_client(node)
lc_bios_reset_attrib = {
"BIOS Reset To Defaults Requested": "True"
}
try:
set_result = client.set_lifecycle_settings(lc_bios_reset_attrib)
except drac_exceptions.BaseClientException as exc:
LOG.error('Failed to reset BIOS on the node %(node_uuid)s.'
' Reason: %(error)s.', {'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
if not set_result['is_commit_required']:
LOG.info("BIOS reset successful on the node "
"%(node_uuid)s", {"node_uuid": node.uuid})
return
else:
# Rebooting the Node is compulsory, LC call returns
# reboot_required=False/Optional, which is not desired
reboot_needed = True
try:
factory_reset_time_before_reboot =\
client.get_system().last_system_inventory_time
LOG.debug("Factory resetting node %(node_uuid)s "
"last inventory reboot time before factory reset "
"%(factory_reset_time_before_reboot)s",
{"node_uuid": task.node.uuid,
"factory_reset_time_before_reboot":
factory_reset_time_before_reboot})
commit_job_id = client.commit_pending_lifecycle_changes(
reboot=reboot_needed)
LOG.info("Commit job id of a node %(node_uuid)s."
"%(commit_job_id)s", {'node_uuid': node.uuid,
"commit_job_id": commit_job_id})
except drac_exceptions.BaseClientException as exc:
LOG.error('Failed to commit BIOS reset on node '
'%(node_uuid)s. Reason: %(error)s.', {
'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
# Store the last inventory time on reboot for async job handler
# _check_last_system_inventory_changed
node.set_driver_internal_info('factory_reset_time_before_reboot',
factory_reset_time_before_reboot)
# Store the current time to later check if factory reset times out
node.timestamp_driver_internal_info('factory_reset_time')
# rebooting the server to apply factory reset value
task.driver.power.reboot(task)
# This method calls node.save(), bios_config_job_id will be
# saved automatically
# These flags are for the conductor to manage the asynchronous
# jobs that have been initiated by this method
deploy_utils.set_async_step_flags(
node,
reboot=reboot_needed,
skip_current_step=True,
polling=True)
return deploy_utils.get_async_step_return_state(task.node)
def cache_bios_settings(self, task):
"""Store or update the current BIOS settings for the node.
Get the current BIOS settings and store them in the bios_settings
database table.
:param task: a TaskManager instance containing the node to act on.
:raises: DracOperationError on an error from python-dracclient
"""
node = task.node
node_id = node.id
node_uuid = node.uuid
client = drac_common.get_drac_client(node)
try:
kwsettings = client.list_bios_settings()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to get the BIOS settings for node '
'%(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
# convert dracclient BIOS settings into ironic settings list
settings = [{"name": name, "value": attrib.current_value}
for name, attrib in kwsettings.items()]
# Store them in the database table
LOG.debug('Caching BIOS settings for node %(node_uuid)s', {
'node_uuid': node_uuid})
create_list, update_list, delete_list, nochange_list = (
objects.BIOSSettingList.sync_node_setting(
task.context, node_id, settings))
if create_list:
objects.BIOSSettingList.create(
task.context, node_id, create_list)
if update_list:
objects.BIOSSettingList.save(
task.context, node_id, update_list)
if delete_list:
delete_names = [d['name'] for d in delete_list]
objects.BIOSSettingList.delete(
task.context, node_id, delete_names)
# BaseInterface methods implementation
def get_properties(self):
"""Return the properties of the BIOS Interface
:returns: dictionary of <property name>: <property description> entries
"""
return drac_common.COMMON_PROPERTIES
def validate(self, task):
"""Validates the driver-specific information used by the idrac BMC
:param task: a TaskManager instance containing the node to act on
:raises: InvalidParameterValue if some mandatory information
is missing on the node or on invalid inputs
"""
drac_common.parse_driver_info(task.node)
def get_config(node):
"""Get the BIOS configuration.
The BIOS settings look like::
{'EnumAttrib': {'name': 'EnumAttrib',
'current_value': 'Value',
'pending_value': 'New Value', # could also be None
'read_only': False,
'possible_values': ['Value', 'New Value', 'None']},
'StringAttrib': {'name': 'StringAttrib',
'current_value': 'Information',
'pending_value': None,
'read_only': False,
'min_length': 0,
'max_length': 255,
'pcre_regex': '^[0-9A-Za-z]{0,255}$'},
'IntegerAttrib': {'name': 'IntegerAttrib',
'current_value': 0,
'pending_value': None,
'read_only': True,
'lower_bound': 0,
'upper_bound': 65535}}
:param node: an ironic node object.
:raises: DracOperationError on an error from python-dracclient.
:returns: a dictionary containing BIOS settings
The above values are only examples, of course. BIOS attributes exposed via
this API will always be either an enumerated attribute, a string attribute,
or an integer attribute. All attributes have the following parameters:
:param name: is the name of the BIOS attribute.
:param current_value: is the current value of the attribute.
It will always be either an integer or a string.
:param pending_value: is the new value that we want the attribute to have.
None means that there is no pending value.
:param read_only: indicates whether this attribute can be changed.
Trying to change a read-only value will result in
an error. The read-only flag can change depending
on other attributes.
A future version of this call may expose the
dependencies that indicate when that may happen.
Enumerable attributes also have the following parameters:
:param possible_values: is an array of values it is permissible to set
the attribute to.
String attributes also have the following parameters:
:param min_length: is the minimum length of the string.
:param max_length: is the maximum length of the string.
:param pcre_regex: is a PCRE compatible regular expression that the string
must match. It may be None if the string is read only
or if the string does not have to match any particular
regular expression.
Integer attributes also have the following parameters:
:param lower_bound: is the minimum value the attribute can have.
:param upper_bound: is the maximum value the attribute can have.
"""
client = drac_common.get_drac_client(node)
try:
return client.list_bios_settings()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to get the BIOS settings for node '
'%(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
def set_config(task, **kwargs):
"""Sets the pending_value parameter for each of the values passed in.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: a dictionary of {'AttributeName': 'NewValue'}
:raises: DracOperationError on an error from python-dracclient.
:returns: A dictionary containing the 'is_commit_required' key with a
boolean value indicating whether commit_config() needs to be
called to make the changes, and the 'is_reboot_required' key
which has a value of 'true' or 'false'. This key is used to
indicate to the commit_config() call if a reboot should be
performed.
"""
node = task.node
drac_job.validate_job_queue(node)
client = drac_common.get_drac_client(node)
if 'http_method' in kwargs:
del kwargs['http_method']
try:
return client.set_bios_settings(kwargs)
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to set the BIOS settings for node '
'%(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
def commit_config(task, reboot=False):
"""Commits pending changes added by set_config
:param task: a TaskManager instance containing the node to act on.
:param reboot: indicates whether a reboot job should be automatically
created with the config job.
:raises: DracOperationError on an error from python-dracclient.
:returns: the job_id key with the id of the newly created config job.
"""
node = task.node
drac_job.validate_job_queue(node)
client = drac_common.get_drac_client(node)
try:
return client.commit_pending_bios_changes(reboot)
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to commit the pending BIOS changes '
'for node %(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
def abandon_config(task):
"""Abandons uncommitted changes added by set_config
:param task: a TaskManager instance containing the node to act on.
:raises: DracOperationError on an error from python-dracclient.
"""
node = task.node
client = drac_common.get_drac_client(node)
try:
client.abandon_pending_bios_changes()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to delete the pending BIOS '
'settings for node %(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)

View File

@ -1,117 +0,0 @@
#
# 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.
"""
Common functionalities shared between different DRAC modules.
"""
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 import utils
drac_client = importutils.try_import('dracclient.client')
drac_constants = importutils.try_import('dracclient.constants')
LOG = logging.getLogger(__name__)
REQUIRED_PROPERTIES = {
'drac_address': _('IP address or hostname of the DRAC card. Required.'),
'drac_username': _('username used for authentication. Required.'),
'drac_password': _('password used for authentication. Required.')
}
OPTIONAL_PROPERTIES = {
'drac_port': _('port used for WS-Man endpoint; default is 443. Optional.'),
'drac_path': _('path used for WS-Man endpoint; default is "/wsman". '
'Optional.'),
'drac_protocol': _('protocol used for WS-Man endpoint; one of http, https;'
' default is "https". Optional.'),
}
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
def parse_driver_info(node):
"""Parse a node's driver_info values.
Parses the driver_info of the node, reads default values
and returns a dict containing the combination of both.
:param node: an ironic node object.
:returns: a dict containing information from driver_info
and default values.
:raises: InvalidParameterValue if some mandatory information
is missing on the node or on invalid inputs.
"""
driver_info = node.driver_info
parsed_driver_info = {}
error_msgs = []
for param in REQUIRED_PROPERTIES:
try:
parsed_driver_info[param] = str(driver_info[param])
except KeyError:
error_msgs.append(_("'%s' not supplied to DracDriver.") % param)
except UnicodeEncodeError:
error_msgs.append(_("'%s' contains non-ASCII symbol.") % param)
parsed_driver_info['drac_port'] = driver_info.get('drac_port', 443)
try:
parsed_driver_info['drac_path'] = str(driver_info.get('drac_path',
'/wsman'))
except UnicodeEncodeError:
error_msgs.append(_("'drac_path' contains non-ASCII symbol."))
try:
parsed_driver_info['drac_protocol'] = str(
driver_info.get('drac_protocol', 'https'))
if parsed_driver_info['drac_protocol'] not in ['http', 'https']:
error_msgs.append(_("'drac_protocol' must be either 'http' or "
"'https'."))
except UnicodeEncodeError:
error_msgs.append(_("'drac_protocol' contains non-ASCII symbol."))
if error_msgs:
msg = (_('The following errors were encountered while parsing '
'driver_info:\n%s') % '\n'.join(error_msgs))
raise exception.InvalidParameterValue(msg)
port = parsed_driver_info['drac_port']
parsed_driver_info['drac_port'] = utils.validate_network_port(
port, 'drac_port')
return parsed_driver_info
def get_drac_client(node):
"""Returns a DRACClient object from python-dracclient library.
:param node: an ironic node object.
:returns: a DRACClient object.
:raises: InvalidParameterValue if mandatory information is missing on the
node or on invalid input.
"""
driver_info = parse_driver_info(node)
client = drac_client.DRACClient(driver_info['drac_address'],
driver_info['drac_username'],
driver_info['drac_password'],
driver_info['drac_port'],
driver_info['drac_path'],
driver_info['drac_protocol'])
return client

View File

@ -15,29 +15,12 @@
DRAC inspection interface
"""
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from oslo_utils import units
from ironic.common import boot_modes
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states
from ironic.common import utils
from ironic.drivers import base
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import utils as drac_utils
from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.redfish import inspect as redfish_inspect
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic import objects
drac_exceptions = importutils.try_import('dracclient.exceptions')
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
_PXE_DEV_ENABLED_INTERFACES = [('PxeDev1EnDis', 'PxeDev1Interface'),
('PxeDev2EnDis', 'PxeDev2Interface'),
@ -63,8 +46,8 @@ class DracRedfishInspect(redfish_inspect.RedfishInspect):
"""
# Ensure we create a port for every NIC port found for consistency
# with our WSMAN inspect behavior and to work around a bug in some
# versions of the firmware where the port state is not being
# with our previous WSMAN inspect behavior and to work around a bug
# in some versions of the firmware where the port state is not being
# reported correctly.
ethernet_interfaces_mac = list(self._get_mac_address(task).values())
@ -124,218 +107,3 @@ class DracRedfishInspect(redfish_inspect.RedfishInspect):
pxe_port_macs = [mac for mac in pxe_port_macs_list]
return pxe_port_macs
class DracWSManInspect(base.InspectInterface):
_GPU_SUPPORTED_LIST = {"TU104GL [Tesla T4]",
"GV100GL [Tesla V100 PCIe 16GB]"}
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return drac_common.COMMON_PROPERTIES
@METRICS.timer('DracInspect.validate')
def validate(self, task):
"""Validate the driver-specific info supplied.
This method validates whether the 'driver_info' property of the
supplied node contains the required information for this driver to
manage the node.
:param task: a TaskManager instance containing the node to act on.
:raises: InvalidParameterValue if required driver_info attribute
is missing or invalid on the node.
"""
return drac_common.parse_driver_info(task.node)
@METRICS.timer('DracInspect.inspect_hardware')
def inspect_hardware(self, task):
"""Inspect hardware.
Inspect hardware to obtain the essential & additional hardware
properties.
:param task: a TaskManager instance containing the node to act on.
:raises: HardwareInspectionFailure, if unable to get essential
hardware properties.
:returns: states.MANAGEABLE
"""
node = task.node
client = drac_common.get_drac_client(node)
properties = {}
try:
properties['memory_mb'] = sum(
[memory.size_mb for memory in client.list_memory()])
cpus = client.list_cpus()
if cpus:
properties['cpu_arch'] = 'x86_64' if cpus[0].arch64 else 'x86'
bios_settings = client.list_bios_settings()
video_controllers = client.list_video_controllers()
current_capabilities = node.properties.get('capabilities', '')
new_capabilities = {
'boot_mode': bios_settings["BootMode"].current_value.lower(),
'pci_gpu_devices': self._calculate_gpus(video_controllers)}
capabilities = utils.get_updated_capabilities(current_capabilities,
new_capabilities)
properties['capabilities'] = capabilities
virtual_disks = client.list_virtual_disks()
root_disk = self._guess_root_disk(virtual_disks)
if root_disk:
properties['local_gb'] = int(root_disk.size_mb / units.Ki)
else:
physical_disks = client.list_physical_disks()
root_disk = self._guess_root_disk(physical_disks)
if root_disk:
properties['local_gb'] = int(
root_disk.size_mb / units.Ki)
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to introspect node '
'%(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.HardwareInspectionFailure(error=exc)
valid_keys = self.ESSENTIAL_PROPERTIES
missing_keys = valid_keys - set(properties)
if missing_keys:
error = (_('Failed to discover the following properties: '
'%(missing_keys)s') %
{'missing_keys': ', '.join(missing_keys)})
raise exception.HardwareInspectionFailure(error=error)
node.properties = dict(node.properties, **properties)
node.save()
try:
nics = client.list_nics()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to introspect node '
'%(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.HardwareInspectionFailure(error=exc)
pxe_dev_nics = self._get_pxe_dev_nics(client, nics, node)
if pxe_dev_nics is None:
LOG.warning('No PXE enabled NIC was found for node '
'%(node_uuid)s.', {'node_uuid': node.uuid})
for nic in nics:
try:
port = objects.Port(task.context, address=nic.mac,
node_id=node.id,
pxe_enabled=(nic.id in pxe_dev_nics))
port.create()
LOG.info('Port created with MAC address %(mac)s '
'for node %(node_uuid)s during inspection',
{'mac': nic.mac, 'node_uuid': node.uuid})
except exception.MACAlreadyExists:
LOG.warning('Failed to create a port with MAC address '
'%(mac)s when inspecting the node '
'%(node_uuid)s because the address is already '
'registered',
{'mac': nic.mac, 'node_uuid': node.uuid})
LOG.info('Node %s successfully inspected.', node.uuid)
return states.MANAGEABLE
def _guess_root_disk(self, disks, min_size_required_mb=4 * units.Ki):
"""Find a root disk.
:param disks: list of disks.
:param min_size_required_mb: minimum required size of the root disk in
megabytes.
:returns: root disk.
"""
disks.sort(key=lambda disk: disk.size_mb)
for disk in disks:
if disk.size_mb >= min_size_required_mb:
return disk
def _calculate_gpus(self, video_controllers):
"""Find actual GPU count.
This method reports number of NVIDIA Tesla T4 GPU devices present
on the server.
:param video_controllers: list of video controllers.
:returns: returns total gpu count.
"""
gpu_cnt = 0
for video_controller in video_controllers:
for gpu in self._GPU_SUPPORTED_LIST:
if video_controller.description == gpu:
gpu_cnt += 1
return gpu_cnt
def _get_pxe_dev_nics(self, client, nics, node):
"""Get a list of pxe device interfaces.
:param client: Dracclient to list the bios settings and nics
:param nics: list of nics
:returns: Returns list of pxe device interfaces.
"""
pxe_dev_nics = []
pxe_params = ["PxeDev1EnDis", "PxeDev2EnDis",
"PxeDev3EnDis", "PxeDev4EnDis"]
pxe_nics = ["PxeDev1Interface", "PxeDev2Interface",
"PxeDev3Interface", "PxeDev4Interface"]
try:
bios_settings = client.list_bios_settings()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to list bios settings '
'for %(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.HardwareInspectionFailure(error=exc)
if bios_settings["BootMode"].current_value == "Uefi":
for param, nic in zip(pxe_params, pxe_nics):
if param in bios_settings and bios_settings[
param].current_value == "Enabled":
pxe_dev_nics.append(
bios_settings[nic].current_value)
elif bios_settings["BootMode"].current_value == "Bios":
for nic in nics:
try:
nic_cap = client.list_nic_settings(nic_id=nic.id)
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to list nic settings '
'for %(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.HardwareInspectionFailure(error=exc)
if ("LegacyBootProto" in nic_cap and nic_cap[
'LegacyBootProto'].current_value == "PXE"):
pxe_dev_nics.append(nic.id)
return pxe_dev_nics
class DracInspect(DracWSManInspect):
"""Class alias of class DracWSManInspect.
This class provides ongoing support of the deprecated 'idrac'
inspect interface implementation entrypoint.
All bug fixes and new features should be implemented in its base
class, DracWSManInspect. That makes them available to both the
deprecated 'idrac' and new 'idrac-wsman' entrypoints. Such changes
should not be made to this class.
"""
def __init__(self):
super(DracInspect, self).__init__()
LOG.warning("Inspect interface 'idrac' is deprecated and may be "
"removed in a future release. Use 'idrac-wsman' instead.")

View File

@ -1,116 +0,0 @@
#
# 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.
"""
DRAC Lifecycle job specific methods
"""
from oslo_log import log as logging
from oslo_utils import importutils
import tenacity
from ironic.common import exception
from ironic.common.i18n import _
from ironic.conf import CONF
from ironic.drivers.modules.drac import common as drac_common
drac_exceptions = importutils.try_import('dracclient.exceptions')
LOG = logging.getLogger(__name__)
WAIT_CLOCK = 5
def validate_job_queue(node, name_prefix=None):
"""Validates the job queue on the node.
It raises an exception if an unfinished configuration job exists.
:param node: an ironic node object.
:param name_prefix: A name prefix for jobs to validate.
:raises: DracOperationError on an error from python-dracclient.
"""
unfinished_jobs = list_unfinished_jobs(node)
if name_prefix is not None:
# Filter out jobs that don't match the name prefix.
unfinished_jobs = [job for job in unfinished_jobs
if job.name.startswith(name_prefix)]
if not unfinished_jobs:
return
msg = _('Unfinished config jobs found: %(jobs)r. Make sure they are '
'completed before retrying.') % {'jobs': unfinished_jobs}
raise exception.DracOperationError(error=msg)
def get_job(node, job_id):
"""Get the details of a Lifecycle job of the node.
:param node: an ironic node object.
:param job_id: ID of the Lifecycle job.
:returns: a Job object from dracclient.
:raises: DracOperationError on an error from python-dracclient.
"""
client = drac_common.get_drac_client(node)
try:
return client.get_job(job_id)
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to get the job %(job_id)s '
'for node %(node_uuid)s. Reason: %(error)s.',
{'job_id': job_id,
'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
def list_unfinished_jobs(node):
"""List unfinished config jobs of the node.
:param node: an ironic node object.
:returns: a list of Job objects from dracclient.
:raises: DracOperationError on an error from python-dracclient.
"""
client = drac_common.get_drac_client(node)
try:
return client.list_jobs(only_unfinished=True)
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to get the list of unfinished jobs '
'for node %(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid,
'error': exc})
raise exception.DracOperationError(error=exc)
@tenacity.retry(
retry=tenacity.retry_if_exception_type(exception.DracOperationError),
stop=tenacity.stop_after_attempt(CONF.drac.config_job_max_retries),
wait=tenacity.wait_fixed(WAIT_CLOCK),
reraise=True)
def wait_for_job_completion(node,
retries=CONF.drac.config_job_max_retries):
"""Wait for job to complete
It will wait for the job to complete for 20 minutes and raises timeout
if job never complete within given interval of time.
:param node: an ironic node object.
:param retries: no of retries to make conductor wait.
:raises: DracOperationError on exception raised from python-dracclient
or a timeout while waiting for job completion.
"""
if not list_unfinished_jobs(node):
return
err_msg = _(
'There are unfinished jobs in the job '
'queue on node %(node_uuid)s.') % {'node_uuid': node.uuid}
LOG.warning(err_msg)
raise exception.DracOperationError(error=err_msg)

View File

@ -21,13 +21,11 @@ DRAC management interface
"""
import json
import time
from ironic_lib import metrics_utils
import jsonschema
from jsonschema import exceptions as json_schema_exc
from oslo_log import log as logging
from oslo_utils import importutils
import sushy
from ironic.common import boot_devices
@ -36,20 +34,15 @@ from ironic.common.i18n import _
from ironic.common import molds
from ironic.common import states
from ironic.conductor import periodics
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.conf import CONF
from ironic.drivers import base
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import job as drac_job
from ironic.drivers.modules.drac import utils as drac_utils
from ironic.drivers.modules.redfish import management as redfish_management
from ironic.drivers.modules.redfish import utils as redfish_utils
drac_exceptions = importutils.try_import('dracclient.exceptions')
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
@ -113,73 +106,6 @@ _CONF_MOLD_SCHEMA = {
}
def _get_boot_device(node, drac_boot_devices=None):
client = drac_common.get_drac_client(node)
try:
boot_modes = client.list_boot_modes()
next_boot_modes = [mode.id for mode in boot_modes if mode.is_next]
if _NON_PERSISTENT_BOOT_MODE in next_boot_modes:
next_boot_mode = _NON_PERSISTENT_BOOT_MODE
else:
next_boot_mode = next_boot_modes[0]
if drac_boot_devices is None:
drac_boot_devices = client.list_boot_devices()
# It is possible for there to be no boot device.
boot_device = None
if next_boot_mode in drac_boot_devices:
drac_boot_device = drac_boot_devices[next_boot_mode][0]
for key, value in _BOOT_DEVICES_MAP.items():
for id_component in value:
if id_component in drac_boot_device.id:
boot_device = key
break
if boot_device:
break
return {'boot_device': boot_device,
'persistent': next_boot_mode != _NON_PERSISTENT_BOOT_MODE}
except (drac_exceptions.BaseClientException, IndexError) as exc:
LOG.error('DRAC driver failed to get next boot mode for '
'node %(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.DracOperationError(error=exc)
def _get_next_persistent_boot_mode(node):
client = drac_common.get_drac_client(node)
try:
boot_modes = client.list_boot_modes()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to get next persistent boot mode for '
'node %(node_uuid)s. Reason: %(error)s',
{'node_uuid': node.uuid, 'error': exc})
raise exception.DracOperationError(error=exc)
next_persistent_boot_mode = None
for mode in boot_modes:
if mode.is_next and mode.id != _NON_PERSISTENT_BOOT_MODE:
next_persistent_boot_mode = mode.id
break
if not next_persistent_boot_mode:
message = _('List of boot modes, %(list_boot_modes)s, does not '
'contain a persistent mode') % {
'list_boot_modes': boot_modes}
LOG.error('DRAC driver failed to get next persistent boot mode for '
'node %(node_uuid)s. Reason: %(message)s',
{'node_uuid': node.uuid, 'message': message})
raise exception.DracOperationError(error=message)
return next_persistent_boot_mode
def _is_boot_order_flexibly_programmable(persistent, bios_settings):
return persistent and 'SetBootOrderFqdd1' in bios_settings
@ -218,129 +144,6 @@ def _validate_conf_mold(data):
_("Invalid configuration mold: %(error)s") % {'error': e})
def set_boot_device(node, device, persistent=False):
"""Set the boot device for a node.
Set the boot device to use on next boot of the node.
:param node: an ironic node object.
:param device: the boot device, one of
:mod:`ironic.common.boot_devices`.
:param persistent: Boolean value. True if the boot device will
persist to all future boots, False if not.
Default: False.
:raises: DracOperationError on an error from python-dracclient.
"""
client = drac_common.get_drac_client(node)
# If pending BIOS job or pending non-BIOS job found in job queue,
# we need to clear that jobs before executing clear_job_queue or
# known_good_state clean step of management interface.
# Otherwise, pending BIOS config job can cause creating new config jobs
# to fail and pending non-BIOS job can execute on reboot the node.
validate_job_queue = True
if node.driver_internal_info.get("clean_steps"):
if node.driver_internal_info.get("clean_steps")[0].get(
'step') in _CLEAR_JOBS_CLEAN_STEPS:
unfinished_jobs = drac_job.list_unfinished_jobs(node)
if unfinished_jobs:
validate_job_queue = False
client.delete_jobs(job_ids=[job.id for job in unfinished_jobs])
if validate_job_queue:
drac_job.validate_job_queue(node, name_prefix="Configure: BIOS")
try:
drac_boot_devices = client.list_boot_devices()
current_boot_device = _get_boot_device(node, drac_boot_devices)
# If we are already booting from the right device, do nothing.
if current_boot_device == {'boot_device': device,
'persistent': persistent}:
LOG.debug('DRAC already set to boot from %s', device)
return
persistent_boot_mode = _get_next_persistent_boot_mode(node)
drac_boot_device = None
for drac_device in drac_boot_devices[persistent_boot_mode]:
for id_component in _BOOT_DEVICES_MAP[device]:
if id_component in drac_device.id:
drac_boot_device = drac_device.id
break
if drac_boot_device:
break
if drac_boot_device:
if persistent:
boot_list = persistent_boot_mode
else:
boot_list = _NON_PERSISTENT_BOOT_MODE
client.change_boot_device_order(boot_list, drac_boot_device)
else:
# No DRAC boot device of the type requested by the argument
# 'device' is present. This is normal for UEFI boot mode,
# following deployment's writing of the operating system to
# disk. It can also occur when a server has not been
# powered on after a new boot device has been installed.
#
# If the boot order is flexibly programmable, use that to
# attempt to detect and boot from a device of the requested
# type during the next boot. That avoids the need for an
# extra reboot. Otherwise, this function cannot satisfy the
# request, because it was called with an invalid device.
bios_settings = client.list_bios_settings(by_name=True)
if _is_boot_order_flexibly_programmable(persistent, bios_settings):
drac_boot_mode = bios_settings['BootMode'].current_value
if drac_boot_mode not in _DRAC_BOOT_MODES:
message = _("DRAC reported unknown boot mode "
"'%(drac_boot_mode)s'") % {
'drac_boot_mode': drac_boot_mode}
LOG.error('DRAC driver failed to change boot device order '
'for node %(node_uuid)s. Reason: %(message)s.',
{'node_uuid': node.uuid, 'message': message})
raise exception.DracOperationError(error=message)
flexibly_program_settings = _flexibly_program_boot_order(
device, drac_boot_mode)
client.set_bios_settings(flexibly_program_settings)
else:
raise exception.InvalidParameterValue(
_("set_boot_device called with invalid device "
"'%(device)s' for node %(node_id)s.") %
{'device': device, 'node_id': node.uuid})
job_id = client.commit_pending_bios_changes()
job_entry = client.get_job(job_id)
timeout = CONF.drac.boot_device_job_status_timeout
end_time = time.time() + timeout
LOG.debug('Waiting for BIOS configuration job %(job_id)s '
'to be scheduled for node %(node)s',
{'job_id': job_id,
'node': node.uuid})
while job_entry.status != "Scheduled":
if time.time() >= end_time:
raise exception.DracOperationError(
error=_(
'Timed out waiting BIOS configuration for job '
'%(job)s to reach Scheduled state. Job is still '
'in %(status)s state.') %
{'job': job_id, 'status': job_entry.status})
time.sleep(3)
job_entry = client.get_job(job_id)
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to change boot device order for '
'node %(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.DracOperationError(error=exc)
class DracRedfishManagement(redfish_management.RedfishManagement):
"""iDRAC Redfish interface for management-related actions."""
@ -633,10 +436,7 @@ class DracRedfishManagement(redfish_management.RedfishManagement):
LOG.warning('iDRAC on node %(node)s does not support '
'clearing Lifecycle Controller job queue '
'using the idrac-redfish driver. '
'If using iDRAC9, consider upgrading firmware. '
'If using iDRAC8, consider switching to '
'idrac-wsman for management interface if '
'possible.',
'If using iDRAC9, consider upgrading firmware.',
{'node': task.node.uuid})
if task.node.provision_state != states.VERIFYING:
raise
@ -661,10 +461,7 @@ class DracRedfishManagement(redfish_management.RedfishManagement):
if "Oem/Dell/DelliDRACCardService is missing" in str(exc):
LOG.warning('iDRAC on node %(node)s does not support '
'iDRAC reset using the idrac-redfish driver. '
'If using iDRAC9, consider upgrading firmware. '
'If using iDRAC8, consider switching to '
'idrac-wsman for management interface if '
'possible.',
'If using iDRAC9, consider upgrading firmware. ',
{'node': task.node.uuid})
if task.node.provision_state != states.VERIFYING:
raise
@ -686,181 +483,3 @@ class DracRedfishManagement(redfish_management.RedfishManagement):
self.clear_job_queue(task)
LOG.info('Reset iDRAC to known good state for node %(node)s',
{'node': task.node.uuid})
class DracWSManManagement(base.ManagementInterface):
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
def get_properties(self):
"""Return the properties of the interface."""
return drac_common.COMMON_PROPERTIES
@METRICS.timer('DracManagement.validate')
def validate(self, task):
"""Validate the driver-specific info supplied.
This method validates whether the 'driver_info' property of the
supplied node contains the required information for this driver to
manage the node.
:param task: a TaskManager instance containing the node to act on.
:raises: InvalidParameterValue if required driver_info attribute
is missing or invalid on the node.
"""
return drac_common.parse_driver_info(task.node)
@METRICS.timer('DracManagement.get_supported_boot_devices')
def get_supported_boot_devices(self, task):
"""Get a list of the supported boot devices.
:param task: a TaskManager instance containing the node to act on.
:returns: A list with the supported boot devices defined
in :mod:`ironic.common.boot_devices`.
"""
return list(_BOOT_DEVICES_MAP)
@METRICS.timer('DracManagement.get_boot_device')
def get_boot_device(self, task):
"""Get the current boot device for a node.
Returns the current boot device of the node.
:param task: a TaskManager instance containing the node to act on.
:raises: DracOperationError on an error from python-dracclient.
: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.
"""
node = task.node
boot_device = node.driver_internal_info.get('drac_boot_device')
if boot_device is not None:
return boot_device
return _get_boot_device(node)
@METRICS.timer('DracManagement.set_boot_device')
@task_manager.require_exclusive_lock
def set_boot_device(self, task, device, persistent=False):
"""Set the boot device for a node.
Set the boot device to use on next reboot of the node.
:param task: a TaskManager instance containing the node to act on.
:param device: the boot device, one of
:mod:`ironic.common.boot_devices`.
:param persistent: Boolean value. True if the boot device will
persist to all future boots, False if not.
Default: False.
:raises: InvalidParameterValue if an invalid boot device is specified.
"""
node = task.node
if device not in _BOOT_DEVICES_MAP:
raise exception.InvalidParameterValue(
_("set_boot_device called with invalid device '%(device)s' "
"for node %(node_id)s.") % {'device': device,
'node_id': node.uuid})
# NOTE(ifarkas): DRAC interface doesn't allow changing the boot device
# multiple times in a row without a reboot. This is
# because a change need to be committed via a
# configuration job, and further configuration jobs
# cannot be created until the previous one is processed
# at the next boot. As a workaround, saving it to
# driver_internal_info and committing the change during
# power state change.
node.set_driver_internal_info('drac_boot_device',
{'boot_device': device,
'persistent': persistent})
node.save()
@METRICS.timer('DracManagement.get_sensors_data')
def get_sensors_data(self, task):
"""Get sensors data.
:param task: a TaskManager instance.
:raises: FailedToGetSensorData when getting the sensor data fails.
:raises: FailedToParseSensorData when parsing sensor data fails.
:returns: returns a consistent format dict of sensor data grouped by
sensor type, which can be processed by Ceilometer.
"""
raise NotImplementedError()
@METRICS.timer('DracManagement.reset_idrac')
@base.verify_step(priority=0)
@base.clean_step(priority=0, requires_ramdisk=False)
def reset_idrac(self, task):
"""Reset the iDRAC.
:param task: a TaskManager instance containing the node to act on.
:returns: None if it is completed.
:raises: DracOperationError on an error from python-dracclient.
"""
node = task.node
client = drac_common.get_drac_client(node)
client.reset_idrac(force=True, wait=True)
@METRICS.timer('DracManagement.known_good_state')
@base.verify_step(priority=0)
@base.clean_step(priority=0, requires_ramdisk=False)
def known_good_state(self, task):
"""Reset the iDRAC, Clear the job queue.
:param task: a TaskManager instance containing the node to act on.
:returns: None if it is completed.
:raises: DracOperationError on an error from python-dracclient.
"""
node = task.node
client = drac_common.get_drac_client(node)
client.reset_idrac(force=True, wait=True)
client.delete_jobs(job_ids=[_CLEAR_JOB_IDS])
@METRICS.timer('DracManagement.clear_job_queue')
@base.verify_step(priority=0)
@base.clean_step(priority=0, requires_ramdisk=False)
def clear_job_queue(self, task):
"""Clear the job queue.
:param task: a TaskManager instance containing the node to act on.
:returns: None if it is completed.
:raises: DracOperationError on an error from python-dracclient.
"""
try:
node = task.node
client = drac_common.get_drac_client(node)
client.delete_jobs(job_ids=[_CLEAR_JOB_IDS])
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to clear the job queue for node '
'%(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.DracOperationError(error=exc)
class DracManagement(DracWSManManagement):
"""Class alias of class DracWSManManagement.
This class provides ongoing support of the deprecated 'idrac'
management interface implementation entrypoint.
All bug fixes and new features should be implemented in its base
class, DracWSManManagement. That makes them available to both the
deprecated 'idrac' and new 'idrac-wsman' entrypoints. Such changes
should not be made to this class.
"""
def __init__(self):
super(DracManagement, self).__init__()
LOG.warning("Management interface 'idrac' is deprecated and may be "
"removed in a future release. Use 'idrac-wsman' instead.")

View File

@ -15,168 +15,9 @@
DRAC power interface
"""
import time
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.conductor import utils as cond_utils
from ironic.drivers import base
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import management as drac_management
from ironic.drivers.modules.redfish import power as redfish_power
drac_constants = importutils.try_import('dracclient.constants')
drac_exceptions = importutils.try_import('dracclient.exceptions')
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
if drac_constants:
POWER_STATES = {
drac_constants.POWER_ON: states.POWER_ON,
drac_constants.POWER_OFF: states.POWER_OFF,
drac_constants.REBOOT: states.REBOOT
}
REVERSE_POWER_STATES = dict((v, k) for (k, v) in POWER_STATES.items())
POWER_STATE_TRIES = 15
POWER_STATE_SLEEP = 2
POWER_STATE_CHANGE_FAIL = 'The command failed to set RequestedState'
def _get_power_state(node):
"""Returns the current power state of the node.
:param node: an ironic node object.
:returns: the power state, one of :mod:`ironic.common.states`.
:raises: InvalidParameterValue if required DRAC credentials are missing.
:raises: DracOperationError on an error from python-dracclient
"""
client = drac_common.get_drac_client(node)
try:
drac_power_state = client.get_power_state()
except drac_exceptions.BaseClientException as exc:
LOG.error('DRAC driver failed to get power state for node '
'%(node_uuid)s. Reason: %(error)s.',
{'node_uuid': node.uuid, 'error': exc})
raise exception.DracOperationError(error=exc)
return POWER_STATES[drac_power_state]
def _commit_boot_list_change(node):
boot_device = node.driver_internal_info.get('drac_boot_device')
if boot_device is None:
return
drac_management.set_boot_device(node, boot_device['boot_device'],
boot_device['persistent'])
node.set_driver_internal_info('drac_boot_device', None)
node.save()
def _set_power_state(task, power_state, timeout=None):
"""Turns the server power on/off or do a reboot.
:param task: a TaskManager instance containing the node to act on.
:param power_state: a power state from :mod:`ironic.common.states`.
:param timeout: Time to wait for the node to reach the requested state.
When requested state is reboot, not used as not waiting then.
:raises: InvalidParameterValue if required DRAC credentials are missing.
:raises: DracOperationError on an error from python-dracclient
"""
node = task.node
# NOTE(ifarkas): DRAC interface doesn't allow changing the boot device
# multiple times in a row without a reboot. This is
# because a change need to be committed via a
# configuration job, and further configuration jobs
# cannot be created until the previous one is processed
# at the next boot. As a workaround, it is saved to
# driver_internal_info during set_boot_device and committing
# it here.
_commit_boot_list_change(node)
client = drac_common.get_drac_client(node)
tries = POWER_STATE_TRIES
# Cases have been seen where the iDRAC returns a SYS021 error even when
# the server is in the right power state and a valid power state change
# is attempted. Retry in this case.
while tries > 0:
# The iDRAC will return a SYS021 error if the server is powered off
# and a reboot is requested. In this situation, convert the requested
# reboot into a power on to avoid this error. To minimize the chance
# of a race condition, it is critical to do this check immediately
# before sending the power state change command. This keeps the
# window during which the server could change power states without us
# knowing about it as small as possible.
calc_power_state = power_state
if power_state == states.REBOOT:
current_power_state = _get_power_state(node)
# If the server is not on, then power it on instead of rebooting
if current_power_state != states.POWER_ON:
calc_power_state = states.POWER_ON
target_power_state = REVERSE_POWER_STATES[calc_power_state]
try:
client.set_power_state(target_power_state)
if calc_power_state == states.REBOOT:
# TODO(rloo): Support timeouts!
if timeout is not None:
LOG.warning("The 'idrac-wsman' Power Interface does not "
"support 'timeout' parameter when setting "
"power state to reboot. Ignoring "
"timeout=%(timeout)s",
{'timeout': timeout})
else:
# Skipped for reboot as can't match reboot with on/off.
# Reboot so far has been part of workflow that is not followed
# by another power state change that could break the flow.
cond_utils.node_wait_for_power_state(
task, calc_power_state, timeout)
break
except drac_exceptions.BaseClientException as exc:
if (power_state == states.REBOOT
and POWER_STATE_CHANGE_FAIL in str(exc)
and tries > 0):
LOG.warning('DRAC driver failed to set power state for node '
'%(node_uuid)s to %(calc_power_state)s. '
'Reason: %(error)s. Retrying...',
{'node_uuid': node.uuid,
'calc_power_state': calc_power_state,
'error': exc})
tries -= 1
time.sleep(POWER_STATE_SLEEP)
else:
LOG.error('DRAC driver failed to set power state for node '
'%(node_uuid)s to %(calc_power_state)s. '
'Reason: %(error)s.',
{'node_uuid': node.uuid,
'calc_power_state': calc_power_state,
'error': exc})
raise exception.DracOperationError(error=exc)
if tries <= 0:
error_msg = (_('DRAC driver timed out while trying to set the power '
'state for node %(node_uuid)s to '
'%(calc_power_state)s.') %
{'node_uuid': node.uuid,
'calc_power_state': calc_power_state})
LOG.error(error_msg)
raise exception.DracOperationError(error_msg)
class DracRedfishPower(redfish_power.RedfishPower):
"""iDRAC Redfish interface for power-related actions.
@ -187,87 +28,3 @@ class DracRedfishPower(redfish_power.RedfishPower):
should be implemented by this class.
"""
pass
class DracWSManPower(base.PowerInterface):
"""Interface for power-related actions."""
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
def get_properties(self):
"""Return the properties of the interface."""
return drac_common.COMMON_PROPERTIES
@METRICS.timer('DracPower.validate')
def validate(self, task):
"""Validate the driver-specific Node power info.
This method validates whether the 'driver_info' property of the
supplied node contains the required information for this driver to
manage the power state of the node.
:param task: a TaskManager instance containing the node to act on.
:raises: InvalidParameterValue if required driver_info attribute
is missing or invalid on the node.
"""
return drac_common.parse_driver_info(task.node)
@METRICS.timer('DracPower.get_power_state')
def get_power_state(self, task):
"""Return the power state of the node.
:param task: a TaskManager instance containing the node to act on.
:returns: the power state, one of :mod:`ironic.common.states`.
:raises: InvalidParameterValue if required DRAC credentials are
missing.
:raises: DracOperationError on an error from python-dracclient.
"""
return _get_power_state(task.node)
@METRICS.timer('DracPower.set_power_state')
@task_manager.require_exclusive_lock
def set_power_state(self, task, power_state, timeout=None):
"""Set the power state of the node.
:param task: a TaskManager instance containing the node to act on.
:param power_state: a power state from :mod:`ironic.common.states`.
:param timeout: Time to wait for the node to reach the requested state.
When requested state is reboot, not used as not waiting then.
:raises: InvalidParameterValue if required DRAC credentials are
missing.
:raises: DracOperationError on an error from python-dracclient.
"""
_set_power_state(task, power_state, timeout)
@METRICS.timer('DracPower.reboot')
@task_manager.require_exclusive_lock
def reboot(self, task, timeout=None):
"""Perform a reboot of the task's node.
:param task: a TaskManager instance containing the node to act on.
:param timeout: timeout (in seconds). Unsupported by this interface.
:raises: InvalidParameterValue if required DRAC credentials are
missing.
:raises: DracOperationError on an error from python-dracclient.
"""
_set_power_state(task, states.REBOOT, timeout)
class DracPower(DracWSManPower):
"""Class alias of class DracWSManPower.
This class provides ongoing support of the deprecated 'idrac' power
interface implementation entrypoint.
All bug fixes and new features should be implemented in its base
class, DracWSManPower. That makes them available to both the
deprecated 'idrac' and new 'idrac-wsman' entrypoints. Such changes
should not be made to this class.
"""
def __init__(self):
super(DracPower, self).__init__()
LOG.warning("Power interface 'idrac' is deprecated and may be removed "
"in a future release. Use 'idrac-wsman' instead.")

File diff suppressed because it is too large Load Diff

View File

@ -15,187 +15,8 @@
DRAC vendor-passthru interface
"""
from ironic_lib import metrics_utils
from oslo_log import log as logging
from ironic.common.i18n import _
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules.drac import bios as drac_bios
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import job as drac_job
from ironic.drivers.modules.redfish import vendor as redfish_vendor
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
class DracWSManVendorPassthru(base.VendorInterface):
"""Interface for DRAC specific methods."""
# NOTE(TheJulia): Deprecating November 2023 in favor of Redfish
# and due to a lack of active driver maintenance.
supported = False
def get_properties(self):
"""Return the properties of the interface."""
return drac_common.COMMON_PROPERTIES
@METRICS.timer('DracVendorPassthru.validate')
def validate(self, task, **kwargs):
"""Validate the driver-specific info supplied.
This method validates whether the 'driver_info' property of the
supplied node contains the required information for this driver to
manage the power state of the node.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: not used.
:raises: InvalidParameterValue if required driver_info attribute
is missing or invalid on the node.
"""
return drac_common.parse_driver_info(task.node)
@METRICS.timer('DracVendorPassthru.get_bios_config')
@base.passthru(['GET'], async_call=False,
description=_("Returns a dictionary containing the BIOS "
"settings from a node."))
def get_bios_config(self, task, **kwargs):
"""Get the BIOS configuration.
This method is used to retrieve the BIOS settings from a node.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: not used.
:raises: DracOperationError on an error from python-dracclient.
:returns: a dictionary containing BIOS settings.
"""
bios_attrs = {}
for name, bios_attr in drac_bios.get_config(task.node).items():
bios_attrs[name] = bios_attr.__dict__
return bios_attrs
@METRICS.timer('DracVendorPassthru.set_bios_config')
@base.passthru(['POST'], async_call=False,
description=_("Change the BIOS configuration on a node. "
"Required argument : a dictionary of "
"{'AttributeName': 'NewValue'}. Returns "
"a dictionary containing the "
"'is_commit_required' key with a Boolean "
"value indicating whether "
"commit_bios_config() needs to be called "
"to make the changes, and the "
"'is_reboot_required' key with a value of "
"'true' or 'false'. This key is used to "
"indicate to the commit_bios_config() call "
"if a reboot should be performed."))
@task_manager.require_exclusive_lock
def set_bios_config(self, task, **kwargs):
"""Change BIOS settings.
This method is used to change the BIOS settings on a node.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: a dictionary of {'AttributeName': 'NewValue'}
:raises: DracOperationError on an error from python-dracclient.
:returns: A dictionary containing the ``is_commit_required`` key with a
Boolean value indicating whether commit_bios_config() needs
to be called to make the changes, and the
``is_reboot_required`` key with a value of 'true' or 'false'.
This key is used to indicate to the commit_bios_config() call
if a reboot should be performed.
"""
return drac_bios.set_config(task, **kwargs)
@METRICS.timer('DracVendorPassthru.commit_bios_config')
@base.passthru(['POST'], async_call=False,
description=_("Commit a BIOS configuration job submitted "
"through set_bios_config(). Required "
"argument: 'reboot' - indicates whether a "
"reboot job should be automatically created "
"with the config job. Returns a dictionary "
"containing the 'job_id' key with the ID of "
"the newly created config job, and the "
"'reboot_required' key indicating whether "
"the node needs to be rebooted to start the "
"config job."))
@task_manager.require_exclusive_lock
def commit_bios_config(self, task, reboot=False, **kwargs):
"""Commit a BIOS configuration job.
This method is used to commit a BIOS configuration job.
submitted through set_bios_config().
:param task: a TaskManager instance containing the node to act on.
:param reboot: indicates whether a reboot job should be automatically
created with the config job.
:param kwargs: not used.
:raises: DracOperationError on an error from python-dracclient.
:returns: A dictionary containing the ``job_id`` key with the id of the
newly created config job, and the ``reboot_required`` key
indicating whether the node needs to be rebooted to start the
config job.
"""
job_id = drac_bios.commit_config(task, reboot=reboot)
return {'job_id': job_id, 'reboot_required': not reboot}
@METRICS.timer('DracVendorPassthru.abandon_bios_config')
@base.passthru(['DELETE'], async_call=False,
description=_("Abandon a BIOS configuration job previously "
"submitted through set_bios_config()."))
@task_manager.require_exclusive_lock
def abandon_bios_config(self, task, **kwargs):
"""Abandon a BIOS configuration job.
This method is used to abandon a BIOS configuration previously
submitted through set_bios_config().
:param task: a TaskManager instance containing the node to act on.
:param kwargs: not used.
:raises: DracOperationError on an error from python-dracclient.
"""
drac_bios.abandon_config(task)
@base.passthru(['GET'], async_call=False,
description=_('Returns a dictionary containing the key '
'"unfinished_jobs"; its value is a list of '
'dictionaries. Each dictionary represents '
'an unfinished config Job object.'))
def list_unfinished_jobs(self, task, **kwargs):
"""List unfinished config jobs of the node.
:param task: a TaskManager instance containing the node to act on.
:param kwargs: not used.
:returns: a dictionary containing the ``unfinished_jobs`` key; this key
points to a list of dicts, with each dict representing a Job
object.
:raises: DracOperationError on an error from python-dracclient.
"""
jobs = drac_job.list_unfinished_jobs(task.node)
# FIXME(mgould) Do this without calling private methods.
return {'unfinished_jobs': [job._asdict() for job in jobs]}
class DracVendorPassthru(DracWSManVendorPassthru):
"""Class alias of class DracWSManVendorPassthru.
This class provides ongoing support of the deprecated 'idrac' vendor
passthru interface implementation entrypoint.
All bug fixes and new features should be implemented in its base
class, DracWSManVendorPassthru. That makes them available to both
the deprecated 'idrac' and new 'idrac-wsman' entrypoints. Such
changes should not be made to this class.
"""
def __init__(self):
super(DracVendorPassthru, self).__init__()
LOG.warning("Vendor passthru interface 'idrac' is deprecated and may "
"be removed in a future release. Use 'idrac-wsman' "
"instead.")
class DracRedfishVendorPassthru(redfish_vendor.RedfishVendorPassthru):
"""iDRAC Redfish interface for vendor_passthru.

View File

@ -89,12 +89,6 @@ def get_test_ilo_info():
def get_test_drac_info():
return {
"drac_address": "1.2.3.4",
"drac_port": 443,
"drac_path": "/wsman",
"drac_protocol": "https",
"drac_username": "admin",
"drac_password": "fake",
"redfish_address": "1.2.3.4",
"redfish_system_id": "/redfish/v1/Systems/System.Embedded.1",
"redfish_username": "admin",

View File

@ -1,647 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015-2021 Dell Inc. or its subsidiaries.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Test class for DRAC BIOS configuration specific methods
"""
from unittest import mock
from dracclient import exceptions as drac_exceptions
from oslo_utils import importutils
from oslo_utils import timeutils
from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.drac import bios as drac_bios
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import job as drac_job
from ironic import objects
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
drac_constants = importutils.try_import('dracclient.constants')
INFO_DICT = test_utils.INFO_DICT
class DracWSManBIOSConfigurationTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracWSManBIOSConfigurationTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
self.bios = drac_bios.DracWSManBIOS()
patch_get_drac_client = mock.patch.object(
drac_common, 'get_drac_client', spec_set=True, autospec=True)
mock_get_drac_client = patch_get_drac_client.start()
self.mock_client = mock_get_drac_client.return_value
self.addCleanup(patch_get_drac_client.stop)
proc_virt_attr = {
'current_value': 'Enabled',
'pending_value': None,
'read_only': False,
'possible_values': ['Enabled', 'Disabled']}
mock_proc_virt_attr = mock.NonCallableMock(spec=[], **proc_virt_attr)
mock_proc_virt_attr.name = 'ProcVirtualization'
self.bios_attrs = {'ProcVirtualization': mock_proc_virt_attr}
self.mock_client.set_lifecycle_settings.return_value = {
"is_commit_required": True
}
self.mock_client.commit_pending_lifecycle_changes.return_value = \
"JID_1234"
self.mock_client.set_bios_settings.return_value = {
"is_commit_required": True,
"is_reboot_required": True
}
self.mock_client.commit_pending_bios_changes.return_value = \
"JID_5678"
self.mock_client.get_power_state.return_value = drac_constants.POWER_ON
@mock.patch.object(drac_common, 'parse_driver_info',
autospec=True)
def test_validate(self, mock_parse_driver_info):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.bios.validate(task)
mock_parse_driver_info.assert_called_once_with(task.node)
def test_get_properties(self):
with task_manager.acquire(self.context, self.node.uuid) as task:
test_properties = task.driver.bios.get_properties()
for each_property in drac_common.COMMON_PROPERTIES:
self.assertIn(each_property, test_properties)
@mock.patch.object(objects, 'BIOSSettingList', autospec=True)
def test_cache_bios_settings_noop(self, mock_BIOSSettingList):
create_list = []
update_list = []
delete_list = []
nochange_list = [{'name': 'ProcVirtualization', 'value': 'Enabled'}]
mock_BIOSSettingList.sync_node_setting.return_value = (
create_list, update_list, delete_list, nochange_list)
self.mock_client.list_bios_settings.return_value = self.bios_attrs
with task_manager.acquire(self.context, self.node.uuid) as task:
kwsettings = self.mock_client.list_bios_settings()
settings = [{"name": name,
"value": attrib.__dict__['current_value']}
for name, attrib in kwsettings.items()]
self.mock_client.list_bios_settings.reset_mock()
task.driver.bios.cache_bios_settings(task)
self.mock_client.list_bios_settings.assert_called_once_with()
mock_BIOSSettingList.sync_node_setting.assert_called_once_with(
task.context, task.node.id, settings)
mock_BIOSSettingList.create.assert_not_called()
mock_BIOSSettingList.save.assert_not_called()
mock_BIOSSettingList.delete.assert_not_called()
def test_cache_bios_settings_fail(self):
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.list_bios_settings.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.DracOperationError,
task.driver.bios.cache_bios_settings, task)
@mock.patch.object(deploy_utils, 'get_async_step_return_state',
autospec=True)
@mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True)
@mock.patch.object(drac_bios.DracWSManBIOS, 'cache_bios_settings',
spec_set=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def _test_step(self, mock_validate_job_queue, mock_cache_bios_settings,
mock_set_async_step_flags,
mock_get_async_step_return_state):
if self.node.clean_step:
step_data = self.node.clean_step
expected_state = states.CLEANWAIT
mock_get_async_step_return_state.return_value = states.CLEANWAIT
else:
step_data = self.node.deploy_step
expected_state = states.DEPLOYWAIT
mock_get_async_step_return_state.return_value = states.DEPLOYWAIT
data = step_data['argsinfo'].get('settings', None)
step = step_data['step']
if step == 'apply_configuration':
attributes = {s['name']: s['value'] for s in data}
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
info = task.node.driver_internal_info
if step == 'factory_reset':
mock_system = None
factory_reset_time_before_reboot = None
mock_system = mock.Mock()
factory_reset_time_before_reboot = "20200910233024"
mock_system.last_system_inventory_time = "20200910233024"
self.mock_client.get_system.return_value = mock_system
ret_state = task.driver.bios.factory_reset(task)
attrib = {"BIOS Reset To Defaults Requested": "True"}
self.mock_client.set_lifecycle_settings.\
assert_called_once_with(attrib)
self.mock_client.commit_pending_lifecycle_changes.\
assert_called_once_with(reboot=True)
self.mock_client.get_system.assert_called_once()
self.assertEqual(factory_reset_time_before_reboot,
info['factory_reset_time_before_reboot'])
if step == 'apply_configuration':
ret_state = task.driver.bios.apply_configuration(task, data)
self.mock_client.set_bios_settings.assert_called_once_with(
attributes)
self.mock_client.commit_pending_bios_changes.\
assert_called_once_with(reboot=True)
job_id = self.mock_client.commit_pending_bios_changes()
self.assertIn(job_id, info['bios_config_job_ids'])
mock_validate_job_queue.assert_called_once_with(task.node)
mock_set_async_step_flags.assert_called_once_with(
task.node, reboot=True, skip_current_step=True, polling=True)
mock_get_async_step_return_state.assert_called_once_with(
task.node)
self.assertEqual(expected_state, ret_state)
def test_factory_reset_clean(self):
self.node.clean_step = {'priority': 100, 'interface': 'bios',
'step': 'factory_reset', 'argsinfo': {}}
self.node.save()
self._test_step()
def test_factory_reset_deploy(self):
self.node.deploy_step = {'priority': 100, 'interface': 'bios',
'step': 'factory_reset', 'argsinfo': {}}
self.node.save()
self._test_step()
def test_apply_configuration_clean(self):
settings = [{'name': 'ProcVirtualization', 'value': 'Enabled'}]
self.node.clean_step = {'priority': 100, 'interface': 'bios',
'step': 'apply_configuration',
'argsinfo': {'settings': settings}}
self.node.save()
self._test_step()
def test_apply_configuration_deploy(self):
settings = [{'name': 'ProcVirtualization', 'value': 'Enabled'}]
self.node.deploy_step = {'priority': 100, 'interface': 'bios',
'step': 'apply_configuration',
'argsinfo': {'settings': settings}}
self.node.save()
self._test_step()
def test_apply_conf_set_fail(self):
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.set_bios_settings.side_affect = exc
settings = [{'name': 'ProcVirtualization', 'value': 'Enabled'}]
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.DracOperationError,
task.driver.bios.apply_configuration, task,
settings)
def test_apply_conf_commit_fail(self):
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.commit_pending_bios_changes.side_affect = exc
settings = [{'name': 'ProcVirtualization', 'value': 'Enabled'}]
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.DracOperationError,
task.driver.bios.apply_configuration, task,
settings)
def test_factory_reset_set_fail(self):
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.set_lifecycle_settings.side_affect = exc
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.DracOperationError,
task.driver.bios.factory_reset, task)
def test_factory_reset_commit_fail(self):
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.commit_pending_lifecycle_changes.side_affect = exc
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.DracOperationError,
task.driver.bios.factory_reset, task)
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
autospec=True)
@mock.patch.object(drac_job, 'get_job', spec_set=True,
autospec=True)
def test__check_node_bios_jobs(self, mock_get_job,
mock_notify_conductor_resume_clean):
mock_job = mock.Mock()
mock_job.status = 'Completed'
mock_get_job.return_value = mock_job
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['bios_config_job_ids'] = ['123', '789']
task.node.driver_internal_info = driver_internal_info
task.node.clean_step = {'priority': 100, 'interface': 'bios',
'step': 'factory_reset', 'argsinfo': {}}
task.node.save()
mock_cache = mock.Mock()
task.driver.bios.cache_bios_settings = mock_cache
task.driver.bios._check_node_bios_jobs(task)
self.assertEqual([], task.node.driver_internal_info.get(
'bios_config_job_ids'))
mock_cache.assert_called_once_with(task)
mock_notify_conductor_resume_clean.assert_called_once_with(task)
@mock.patch.object(drac_job, 'get_job', spec_set=True,
autospec=True)
def test__check_node_bios_jobs_still_running(self, mock_get_job):
mock_job = mock.Mock()
mock_job.status = 'Running'
mock_get_job.return_value = mock_job
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['bios_config_job_ids'] = ['123']
task.node.driver_internal_info = driver_internal_info
task.node.save()
mock_resume = mock.Mock()
task.driver.bios._resume_current_operation = mock_resume
mock_cache = mock.Mock()
task.driver.bios.cache_bios_settings = mock_cache
task.driver.bios._check_node_bios_jobs(task)
self.assertEqual(['123'],
task.node.driver_internal_info.get(
'bios_config_job_ids'))
mock_cache.assert_not_called()
mock_resume.assert_not_called()
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
@mock.patch.object(drac_job, 'get_job', spec_set=True,
autospec=True)
def test__check_node_bios_jobs_failed(self, mock_get_job,
mock_cleaning_error_handler):
mock_job = mock.Mock()
mock_job.status = 'Failed'
mock_job.id = '123'
mock_job.message = 'Invalid'
mock_get_job.return_value = mock_job
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['bios_config_job_ids'] = ['123']
task.node.driver_internal_info = driver_internal_info
task.node.clean_step = {'priority': 100, 'interface': 'bios',
'step': 'factory_reset', 'argsinfo': {}}
task.node.save()
task.driver.bios._check_node_bios_jobs(task)
self.assertEqual([],
task.node.driver_internal_info.get(
'bios_config_job_ids'))
mock_cleaning_error_handler.assert_called_once_with(
task, mock.ANY, "Failed config job: 123. Message: 'Invalid'.")
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
@mock.patch.object(drac_job, 'get_job', spec_set=True,
autospec=True)
def test__check_node_bios_jobs_completed_with_errors(
self, mock_get_job, mock_cleaning_error_handler):
mock_job = mock.Mock()
mock_job.status = 'Completed with Errors'
mock_job.id = '123'
mock_job.message = 'PR31: Completed with Errors'
mock_get_job.return_value = mock_job
with task_manager.acquire(self.context, self.node.uuid) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['bios_config_job_ids'] = ['123']
task.node.driver_internal_info = driver_internal_info
task.node.clean_step = {'priority': 100, 'interface': 'bios',
'step': 'factory_reset', 'argsinfo': {}}
task.node.save()
task.driver.bios._check_node_bios_jobs(task)
self.assertEqual([],
task.node.driver_internal_info.get(
'bios_config_job_ids'))
mock_cleaning_error_handler.assert_called_once_with(
task, mock.ANY, "Failed config job: 123. Message: "
"'PR31: Completed with Errors'.")
def test__check_last_system_inventory_changed_different_inventory_time(
self):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info["factory_reset_time_before_reboot"] = \
"20200910233024"
current_time = str(timeutils.utcnow(True))
driver_internal_info["factory_reset_time"] = current_time
task.node.driver_internal_info = driver_internal_info
task.node.save()
mock_system = mock.Mock()
mock_system.last_system_inventory_time =\
"20200910233523"
self.mock_client.get_system.return_value = mock_system
mock_resume = mock.Mock()
task.driver.bios._resume_current_operation = mock_resume
mock_cache = mock.Mock()
task.driver.bios.cache_bios_settings = mock_cache
task.driver.bios._check_last_system_inventory_changed(task)
self.assertIsNone(task.node.driver_internal_info.get(
'factory_reset_time_before_reboot'))
self.assertIsNone(
task.node.driver_internal_info.get('factory_reset_time'))
mock_cache.assert_called_once_with(task)
mock_resume.assert_called_once_with(task)
def test__check_last_system_inventory_changed_same_inventory_time(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['factory_reset_time_before_reboot'] = \
"20200910233024"
current_time = str(timeutils.utcnow(True))
driver_internal_info['factory_reset_time'] = current_time
task.node.driver_internal_info = driver_internal_info
task.node.save()
mock_system = mock.Mock()
mock_system.last_system_inventory_time =\
"20200910233024"
self.mock_client.get_system.return_value = mock_system
task.driver.bios._check_last_system_inventory_changed(task)
self.assertIsNotNone(
task.node.driver_internal_info.get('factory_reset_time'))
self.assertEqual(current_time,
task.node.driver_internal_info.get(
'factory_reset_time'))
self.assertEqual("20200910233024",
task.node.driver_internal_info.get(
'factory_reset_time_before_reboot'))
def test__check_last_system_inventory_changed_same_inventory_time_timeout(
self):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
driver_internal_info = task.node.driver_internal_info
driver_internal_info['factory_reset_time_before_reboot'] = \
"20200910233024"
driver_internal_info['factory_reset_time'] = \
'2020-09-25 15:02:57.903318+00:00'
task.node.driver_internal_info = driver_internal_info
task.node.save()
mock_system = mock.Mock()
mock_system.last_system_inventory_time =\
"20200910233024"
self.mock_client.get_system.return_value = mock_system
mock_failed = mock.Mock()
task.driver.bios._set_failed = mock_failed
task.driver.bios._check_last_system_inventory_changed(task)
self.assertIsNone(task.node.driver_internal_info.get(
'factory_reset_time_before_reboot'))
self.assertIsNone(
task.node.driver_internal_info.get('factory_reset_time'))
fail = ("BIOS factory reset was not completed within 600 "
"seconds, unable to cache updated bios setting")
mock_failed.assert_called_once_with(task, fail)
@mock.patch.object(task_manager, 'acquire', autospec=True)
def test__query_bios_config_job_status(self, mock_acquire):
driver_internal_info = {'bios_config_job_ids': ['42'],
'factory_reset_time_before_reboot':
"20200910233024"}
self.node.driver_internal_info = driver_internal_info
self.node.save()
mock_manager = mock.Mock()
node_list = [(self.node.uuid, 'idrac', '',
driver_internal_info)]
mock_manager.iter_nodes.return_value = node_list
# mock task_manager.acquire
task = mock.Mock(node=self.node, driver=mock.Mock(bios=self.bios))
mock_acquire.return_value = mock.MagicMock(
__enter__=mock.MagicMock(return_value=task))
self.bios._check_node_bios_jobs = mock.Mock()
self.bios._check_last_system_inventory_changed = mock.Mock()
self.bios._query_bios_config_job_status(mock_manager,
self.context)
self.bios._check_node_bios_jobs.assert_called_once_with(task)
self.bios._check_last_system_inventory_changed.assert_called_once_with(
task)
@mock.patch.object(task_manager, 'acquire', autospec=True)
def test__query_bios_config_job_status_no_config_jobs(self,
mock_acquire):
# mock manager
mock_manager = mock.Mock()
node_list = [(self.node.uuid, 'idrac', '', {})]
mock_manager.iter_nodes.return_value = node_list
# mock task_manager.acquire
task = mock.Mock(node=self.node, driver=mock.Mock(bios=self.bios))
mock_acquire.return_value = mock.MagicMock(
__enter__=mock.MagicMock(return_value=task))
self.bios._check_node_bios_jobs = mock.Mock()
self.bios._check_last_system_inventory_changed = mock.Mock()
self.bios._query_bios_config_job_status(mock_manager,
None)
self.bios._check_node_bios_jobs.assert_not_called()
self.bios._check_last_system_inventory_changed.assert_not_called()
@mock.patch.object(task_manager, 'acquire', autospec=True)
def test__query_bios_config_job_status_no_driver(self,
mock_acquire):
driver_internal_info = {'bios_config_job_ids': ['42'],
'factory_reset_time_before_reboot':
"20200910233024"}
self.node.driver_internal_info = driver_internal_info
self.node.save()
mock_manager = mock.Mock()
node_list = [(self.node.uuid, '', '', driver_internal_info)]
mock_manager.iter_nodes.return_value = node_list
# mock task_manager.acquire
task = mock.Mock(node=self.node, driver=mock.Mock(bios=""))
mock_acquire.return_value = mock.MagicMock(
__enter__=mock.MagicMock(return_value=task))
self.bios._check_node_bios_jobs = mock.Mock()
self.bios._check_last_system_inventory_changed = mock.Mock()
self.bios._query_bios_config_job_status(mock_manager,
None)
self.bios._check_node_bios_jobs.assert_not_called()
self.bios._check_last_system_inventory_changed.assert_not_called()
class DracBIOSConfigurationTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracBIOSConfigurationTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
patch_get_drac_client = mock.patch.object(
drac_common, 'get_drac_client', spec_set=True, autospec=True)
mock_get_drac_client = patch_get_drac_client.start()
self.mock_client = mock.Mock()
mock_get_drac_client.return_value = self.mock_client
self.addCleanup(patch_get_drac_client.stop)
proc_virt_attr = {
'current_value': 'Enabled',
'pending_value': None,
'read_only': False,
'possible_values': ['Enabled', 'Disabled']}
mock_proc_virt_attr = mock.NonCallableMock(spec=[], **proc_virt_attr)
mock_proc_virt_attr.name = 'ProcVirtualization'
self.bios_attrs = {'ProcVirtualization': mock_proc_virt_attr}
def test_get_config(self):
self.mock_client.list_bios_settings.return_value = self.bios_attrs
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
bios_config = task.driver.vendor.get_bios_config(task)
self.mock_client.list_bios_settings.assert_called_once_with()
self.assertIn('ProcVirtualization', bios_config)
def test_get_config_fail(self):
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.list_bios_settings.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.DracOperationError,
task.driver.vendor.get_bios_config, task)
self.mock_client.list_bios_settings.assert_called_once_with()
def test_set_config(self):
self.mock_client.list_jobs.return_value = []
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.vendor.set_bios_config(task,
ProcVirtualization='Enabled')
self.mock_client.list_jobs.assert_called_once_with(
only_unfinished=True)
self.mock_client.set_bios_settings.assert_called_once_with(
{'ProcVirtualization': 'Enabled'})
def test_set_config_fail(self):
self.mock_client.list_jobs.return_value = []
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.set_bios_settings.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.DracOperationError,
task.driver.vendor.set_bios_config, task,
ProcVirtualization='Enabled')
self.mock_client.set_bios_settings.assert_called_once_with(
{'ProcVirtualization': 'Enabled'})
def test_commit_config(self):
self.mock_client.list_jobs.return_value = []
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.vendor.commit_bios_config(task)
self.mock_client.list_jobs.assert_called_once_with(
only_unfinished=True)
self.mock_client.commit_pending_bios_changes.assert_called_once_with(
False)
def test_commit_config_with_reboot(self):
self.mock_client.list_jobs.return_value = []
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.vendor.commit_bios_config(task, reboot=True)
self.mock_client.list_jobs.assert_called_once_with(
only_unfinished=True)
self.mock_client.commit_pending_bios_changes.assert_called_once_with(
True)
def test_commit_config_fail(self):
self.mock_client.list_jobs.return_value = []
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.commit_pending_bios_changes.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.DracOperationError,
task.driver.vendor.commit_bios_config, task)
self.mock_client.list_jobs.assert_called_once_with(
only_unfinished=True)
self.mock_client.commit_pending_bios_changes.assert_called_once_with(
False)
def test_abandon_config(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.vendor.abandon_bios_config(task)
self.mock_client.abandon_pending_bios_changes.assert_called_once_with()
def test_abandon_config_fail(self):
exc = drac_exceptions.BaseClientException('boom')
self.mock_client.abandon_pending_bios_changes.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.DracOperationError,
task.driver.vendor.abandon_bios_config, task)
self.mock_client.abandon_pending_bios_changes.assert_called_once_with()

View File

@ -1,125 +0,0 @@
#
# 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 DRAC modules.
"""
from unittest import mock
import dracclient.client
from ironic.common import exception
from ironic.drivers.modules.drac import common as drac_common
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
INFO_DICT = db_utils.get_test_drac_info()
class DracCommonMethodsTestCase(test_utils.BaseDracTest):
def test_parse_driver_info(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
info = drac_common.parse_driver_info(node)
self.assertEqual(INFO_DICT['drac_address'], info['drac_address'])
self.assertEqual(INFO_DICT['drac_port'], info['drac_port'])
self.assertEqual(INFO_DICT['drac_path'], info['drac_path'])
self.assertEqual(INFO_DICT['drac_protocol'], info['drac_protocol'])
self.assertEqual(INFO_DICT['drac_username'], info['drac_username'])
self.assertEqual(INFO_DICT['drac_password'], info['drac_password'])
def test_parse_driver_info_missing_host(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
del node.driver_info['drac_address']
self.assertRaises(exception.InvalidParameterValue,
drac_common.parse_driver_info, node)
def test_parse_driver_info_missing_port(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
del node.driver_info['drac_port']
info = drac_common.parse_driver_info(node)
self.assertEqual(443, info['drac_port'])
def test_parse_driver_info_invalid_port(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
node.driver_info['drac_port'] = 'foo'
self.assertRaises(exception.InvalidParameterValue,
drac_common.parse_driver_info, node)
def test_parse_driver_info_missing_path(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
del node.driver_info['drac_path']
info = drac_common.parse_driver_info(node)
self.assertEqual('/wsman', info['drac_path'])
def test_parse_driver_info_missing_protocol(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
del node.driver_info['drac_protocol']
info = drac_common.parse_driver_info(node)
self.assertEqual('https', info['drac_protocol'])
def test_parse_driver_info_invalid_protocol(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
node.driver_info['drac_protocol'] = 'foo'
self.assertRaises(exception.InvalidParameterValue,
drac_common.parse_driver_info, node)
def test_parse_driver_info_missing_username(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
del node.driver_info['drac_username']
self.assertRaises(exception.InvalidParameterValue,
drac_common.parse_driver_info, node)
def test_parse_driver_info_missing_password(self):
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
del node.driver_info['drac_password']
self.assertRaises(exception.InvalidParameterValue,
drac_common.parse_driver_info, node)
def test_get_drac_client(self):
if not mock._is_instance_mock(dracclient.client):
mock.patch.object(dracclient.client, 'DRACClient',
autospec=True).start()
mock_dracclient = dracclient.client.DRACClient
expected_call = mock.call('1.2.3.4', 'admin', 'fake', 443, '/wsman',
'https')
node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
drac_common.get_drac_client(node)
self.assertEqual(mock_dracclient.mock_calls, [expected_call])

View File

@ -17,512 +17,21 @@ Test class for DRAC inspection interface
from unittest import mock
from dracclient import exceptions as drac_exceptions
from oslo_utils import units
import sushy
from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import inspect as drac_inspect
from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.redfish import inspect as redfish_inspect
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic import objects
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
INFO_DICT = test_utils.INFO_DICT
class DracInspectionTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracInspectionTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
memory = [{'id': 'DIMM.Socket.A1',
'size_mb': 16384,
'speed': 2133,
'manufacturer': 'Samsung',
'model': 'DDR4 DIMM',
'state': 'ok'},
{'id': 'DIMM.Socket.B1',
'size_mb': 16384,
'speed': 2133,
'manufacturer': 'Samsung',
'model': 'DDR4 DIMM',
'state': 'ok'}]
cpus = [{'id': 'CPU.Socket.1',
'cores': 6,
'speed': 2400,
'model': 'Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz',
'state': 'ok',
'ht_enabled': True,
'turbo_enabled': True,
'vt_enabled': True,
'arch64': True},
{'id': 'CPU.Socket.2',
'cores': 6,
'speed': 2400,
'model': 'Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz',
'state': 'ok',
'ht_enabled': False,
'turbo_enabled': True,
'vt_enabled': True,
'arch64': True}]
virtual_disks = [
{'id': 'Disk.Virtual.0:RAID.Integrated.1-1',
'name': 'disk 0',
'description': 'Virtual Disk 0 on Integrated RAID Controller 1',
'controller': 'RAID.Integrated.1-1',
'raid_level': '1',
'size_mb': 1143552,
'state': 'ok',
'raid_state': 'online',
'span_depth': 1,
'span_length': 2,
'pending_operations': None}]
physical_disks = [
{'id': 'Disk.Bay.1:Enclosure.Internal.0-1:RAID.Integrated.1-1',
'description': ('Disk 1 in Backplane 1 of '
'Integrated RAID Controller 1'),
'controller': 'RAID.Integrated.1-1',
'manufacturer': 'SEAGATE',
'model': 'ST600MM0006',
'media_type': 'hdd',
'interface_type': 'sas',
'size_mb': 571776,
'free_size_mb': 571776,
'serial_number': 'S0M3EY2Z',
'firmware_version': 'LS0A',
'state': 'ok',
'raid_state': 'ready'},
{'id': 'Disk.Bay.2:Enclosure.Internal.0-1:RAID.Integrated.1-1',
'description': ('Disk 1 in Backplane 1 of '
'Integrated RAID Controller 1'),
'controller': 'RAID.Integrated.1-1',
'manufacturer': 'SEAGATE',
'model': 'ST600MM0006',
'media_type': 'hdd',
'interface_type': 'sas',
'size_mb': 285888,
'free_size_mb': 285888,
'serial_number': 'S0M3EY2Z',
'firmware_version': 'LS0A',
'state': 'ok',
'raid_state': 'ready'}]
nics = [
{'id': 'NIC.Embedded.1-1-1',
'mac': 'B0:83:FE:C6:6F:A1',
'model': 'Broadcom Gigabit Ethernet BCM5720 - B0:83:FE:C6:6F:A1',
'speed': '1000 Mbps',
'duplex': 'full duplex',
'media_type': 'Base T'},
{'id': 'NIC.Embedded.2-1-1',
'mac': 'B0:83:FE:C6:6F:A2',
'model': 'Broadcom Gigabit Ethernet BCM5720 - B0:83:FE:C6:6F:A2',
'speed': '1000 Mbps',
'duplex': 'full duplex',
'media_type': 'Base T'}]
bios_boot_settings = {'BootMode': {'current_value': 'Bios'}}
uefi_boot_settings = {'BootMode': {'current_value': 'Uefi'},
'PxeDev1EnDis': {'current_value': 'Enabled'},
'PxeDev2EnDis': {'current_value': 'Disabled'},
'PxeDev3EnDis': {'current_value': 'Disabled'},
'PxeDev4EnDis': {'current_value': 'Disabled'},
'PxeDev1Interface': {
'current_value': 'NIC.Embedded.1-1-1'},
'PxeDev2Interface': None,
'PxeDev3Interface': None,
'PxeDev4Interface': None}
nic_settings = {'LegacyBootProto': {'current_value': 'PXE'},
'FQDD': 'NIC.Embedded.1-1-1'}
video_controllers = [
{'id': 'Video.Embedded.1-1',
'description': 'Integrated Matrox G200eW3 Graphics Controller',
'function_number': 0,
'manufacturer': 'Matrox Electronics Systems Ltd.',
'pci_device_id': '0536',
'pci_vendor_id': '102B',
'pci_subdevice_id': '0737',
'pci_subvendor_id': '1028'},
{'id': 'Video.Slot.7-1',
'description': 'TU104GL [Tesla T4]',
'function_number': 0,
'manufacturer': 'NVIDIA Corporation',
'pci_device_id': '1EB8',
'pci_vendor_id': '10DE',
'pci_subdevice_id': '12A2',
'pci_subvendor_id': '10DE'}]
self.memory = [test_utils.dict_to_namedtuple(values=m) for m in memory]
self.cpus = [test_utils.dict_to_namedtuple(values=c) for c in cpus]
self.virtual_disks = [test_utils.dict_to_namedtuple(values=vd)
for vd in virtual_disks]
self.physical_disks = [test_utils.dict_to_namedtuple(values=pd)
for pd in physical_disks]
self.nics = [test_utils.dict_to_namedtuple(values=n) for n in nics]
self.bios_boot_settings = test_utils.dict_of_object(bios_boot_settings)
self.uefi_boot_settings = test_utils.dict_of_object(uefi_boot_settings)
self.nic_settings = test_utils.dict_of_object(nic_settings)
self.video_controllers = [test_utils.dict_to_namedtuple(values=vc)
for vc in video_controllers]
def test_get_properties(self):
expected = drac_common.COMMON_PROPERTIES
driver = drac_inspect.DracInspect()
self.assertEqual(expected, driver.get_properties())
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware(self, mock_port_create, mock_get_drac_client):
expected_node_properties = {
'memory_mb': 32768,
'local_gb': 1116,
'cpu_arch': 'x86_64',
'capabilities': 'boot_mode:uefi,pci_gpu_devices:1'}
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = self.cpus
mock_client.list_virtual_disks.return_value = self.virtual_disks
mock_client.list_nics.return_value = self.nics
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
mock_client.list_video_controllers.return_value = \
self.video_controllers
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
return_value = task.driver.inspect.inspect_hardware(task)
self.node.refresh()
self.assertEqual(expected_node_properties, self.node.properties)
self.assertEqual(states.MANAGEABLE, return_value)
self.assertEqual(2, mock_port_create.call_count)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware_fail(self, mock_port_create,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = self.cpus
mock_client.list_virtual_disks.side_effect = (
drac_exceptions.BaseClientException('boom'))
mock_client.list_bios_settings.return_value = self.bios_boot_settings
mock_client.list_video_controllers.return_value = \
self.video_controllers
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware_no_virtual_disk(self, mock_port_create,
mock_get_drac_client):
expected_node_properties = {
'memory_mb': 32768,
'local_gb': 279,
'cpu_arch': 'x86_64',
'capabilities': 'boot_mode:uefi,pci_gpu_devices:1'}
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = self.cpus
mock_client.list_virtual_disks.return_value = []
mock_client.list_physical_disks.return_value = self.physical_disks
mock_client.list_nics.return_value = self.nics
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
mock_client.list_video_controllers.return_value = \
self.video_controllers
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
return_value = task.driver.inspect.inspect_hardware(task)
self.node.refresh()
self.assertEqual(expected_node_properties, self.node.properties)
self.assertEqual(states.MANAGEABLE, return_value)
self.assertEqual(2, mock_port_create.call_count)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware_no_cpu(
self, mock_port_create, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = []
mock_client.list_virtual_disks.return_value = []
mock_client.list_physical_disks.return_value = self.physical_disks
mock_client.list_nics.return_value = self.nics
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
mock_client.list_video_controllers.return_value = \
self.video_controllers
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware_no_supported_gpu(self, mock_port_create,
mock_get_drac_client):
controllers = [
{'id': 'Video.Embedded.1-1',
'description': 'Integrated Matrox G200eW3 Graphics Controller',
'function_number': 0,
'manufacturer': 'Matrox Electronics Systems Ltd.',
'pci_device_id': '0536',
'pci_vendor_id': '102B',
'pci_subdevice_id': '0737',
'pci_subvendor_id': '1028'},
{'id': 'Video.Slot.7-1',
'description': 'GV100 [TITAN V]',
'function_number': 0,
'manufacturer': 'NVIDIA Corporation',
'pci_device_id': '1D81',
'pci_vendor_id': '10DE',
'pci_subdevice_id': '1214',
'pci_subvendor_id': '10DE'}]
expected_node_properties = {
'memory_mb': 32768,
'local_gb': 279,
'cpu_arch': 'x86_64',
'capabilities': 'boot_mode:uefi,pci_gpu_devices:0'}
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = self.cpus
mock_client.list_virtual_disks.return_value = []
mock_client.list_physical_disks.return_value = self.physical_disks
mock_client.list_nics.return_value = self.nics
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
video_controllers = [test_utils.dict_to_namedtuple(values=vc)
for vc in controllers]
mock_client.list_video_controllers.return_value = video_controllers
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
return_value = task.driver.inspect.inspect_hardware(task)
self.node.refresh()
self.assertEqual(expected_node_properties, self.node.properties)
self.assertEqual(states.MANAGEABLE, return_value)
self.assertEqual(2, mock_port_create.call_count)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware_multiple_supported_gpu(self, mock_port_create,
mock_get_drac_client):
controllers = [
{'id': 'Video.Slot.7-1',
'description': 'TU104GL [Tesla T4]',
'function_number': 0,
'manufacturer': 'NVIDIA Corporation',
'pci_device_id': '1EB8',
'pci_vendor_id': '10DE',
'pci_subdevice_id': '12A2',
'pci_subvendor_id': '10DE'},
{'id': 'Video.Slot.8-1',
'description': 'GV100GL [Tesla V100 PCIe 16GB]',
'function_number': 0,
'manufacturer': 'NVIDIA Corporation',
'pci_device_id': '1DB4',
'pci_vendor_id': '10DE',
'pci_subdevice_id': '1214',
'pci_subvendor_id': '10DE'}]
expected_node_properties = {
'memory_mb': 32768,
'local_gb': 279,
'cpu_arch': 'x86_64',
'capabilities': 'boot_mode:uefi,pci_gpu_devices:2'}
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = self.cpus
mock_client.list_virtual_disks.return_value = []
mock_client.list_physical_disks.return_value = self.physical_disks
mock_client.list_nics.return_value = self.nics
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
video_controllers = [test_utils.dict_to_namedtuple(values=vc)
for vc in controllers]
mock_client.list_video_controllers.return_value = video_controllers
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
return_value = task.driver.inspect.inspect_hardware(task)
self.node.refresh()
self.assertEqual(expected_node_properties, self.node.properties)
self.assertEqual(states.MANAGEABLE, return_value)
self.assertEqual(2, mock_port_create.call_count)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware_no_gpu(self, mock_port_create,
mock_get_drac_client):
expected_node_properties = {
'memory_mb': 32768,
'local_gb': 279,
'cpu_arch': 'x86_64',
'capabilities': 'boot_mode:uefi,pci_gpu_devices:0'}
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = self.cpus
mock_client.list_virtual_disks.return_value = []
mock_client.list_physical_disks.return_value = self.physical_disks
mock_client.list_nics.return_value = self.nics
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
mock_client.list_video_controllers.return_value = []
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
return_value = task.driver.inspect.inspect_hardware(task)
self.node.refresh()
self.assertEqual(expected_node_properties, self.node.properties)
self.assertEqual(states.MANAGEABLE, return_value)
self.assertEqual(2, mock_port_create.call_count)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(objects.Port, 'create', spec_set=True, autospec=True)
def test_inspect_hardware_with_existing_ports(self, mock_port_create,
mock_get_drac_client):
expected_node_properties = {
'memory_mb': 32768,
'local_gb': 1116,
'cpu_arch': 'x86_64',
'capabilities': 'boot_mode:uefi,pci_gpu_devices:1'}
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_memory.return_value = self.memory
mock_client.list_cpus.return_value = self.cpus
mock_client.list_virtual_disks.return_value = self.virtual_disks
mock_client.list_nics.return_value = self.nics
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
mock_client.list_video_controllers.return_value = \
self.video_controllers
mock_port_create.side_effect = exception.MACAlreadyExists("boom")
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
return_value = task.driver.inspect.inspect_hardware(task)
self.node.refresh()
self.assertEqual(expected_node_properties, self.node.properties)
self.assertEqual(states.MANAGEABLE, return_value)
self.assertEqual(2, mock_port_create.call_count)
def test__guess_root_disk(self):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
root_disk = task.driver.inspect._guess_root_disk(
self.physical_disks)
self.assertEqual(285888, root_disk.size_mb)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__get_pxe_dev_nics_with_UEFI_boot_mode(self, mock_get_drac_client):
expected_pxe_nic = self.uefi_boot_settings[
'PxeDev1Interface'].current_value
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_bios_settings.return_value = self.uefi_boot_settings
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
pxe_dev_nics = task.driver.inspect._get_pxe_dev_nics(
mock_client, self.nics, self.node)
self.assertEqual(expected_pxe_nic, pxe_dev_nics[0])
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__get_pxe_dev_nics_with_BIOS_boot_mode(self, mock_get_drac_client):
expected_pxe_nic = self.nic_settings['FQDD']
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_bios_settings.return_value = self.bios_boot_settings
mock_client.list_nic_settings.return_value = self.nic_settings
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
pxe_dev_nics = task.driver.inspect._get_pxe_dev_nics(
mock_client, self.nics, self.node)
self.assertEqual(expected_pxe_nic, pxe_dev_nics[0])
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__get_pxe_dev_nics_list_boot_setting_failure(self,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_bios_settings.side_effect = (
drac_exceptions.BaseClientException('foo'))
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect._get_pxe_dev_nics,
mock_client,
self.nics,
self.node)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__get_pxe_dev_nics_list_nic_setting_failure(self,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_bios_settings.return_value = self.bios_boot_settings
mock_client.list_nic_settings.side_effect = (
drac_exceptions.BaseClientException('bar'))
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect._get_pxe_dev_nics,
mock_client,
self.nics,
self.node)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__get_pxe_dev_nics_with_empty_list(self, mock_get_drac_client):
expected_pxe_nic = []
nic_setting = []
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_bios_settings.return_value = self.bios_boot_settings
mock_client.list_nic_settings.return_value = nic_setting
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
pxe_dev_nics = task.driver.inspect._get_pxe_dev_nics(
mock_client, self.nics, self.node)
self.assertEqual(expected_pxe_nic, pxe_dev_nics)
class DracRedfishInspectionTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracRedfishInspectionTestCase, self).setUp()

View File

@ -1,175 +0,0 @@
#
# 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 DRAC job specific methods
"""
from unittest import mock
from dracclient import exceptions as drac_exceptions
from ironic.common import exception
from ironic.conductor import task_manager
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import job as drac_job
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
INFO_DICT = test_utils.INFO_DICT
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
class DracJobTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracJobTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
self.job_dict = {
'id': 'JID_001436912645',
'name': 'ConfigBIOS:BIOS.Setup.1-1',
'start_time': '00000101000000',
'until_time': 'TIME_NA',
'message': 'Job in progress',
'status': 'Running',
'percent_complete': 34}
self.job = test_utils.make_job(self.job_dict)
def test_get_job(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.return_value = self.job
job = drac_job.get_job(self.node, 'foo')
mock_client.get_job.assert_called_once_with('foo')
self.assertEqual(self.job, job)
def test_get_job_fail(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
exc = exception.DracOperationError('boom')
mock_client.get_job.side_effect = exc
self.assertRaises(exception.DracOperationError,
drac_job.get_job, self.node, 'foo')
def test_list_unfinished_jobs(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_jobs.return_value = [self.job]
jobs = drac_job.list_unfinished_jobs(self.node)
mock_client.list_jobs.assert_called_once_with(only_unfinished=True)
self.assertEqual([self.job], jobs)
def test_list_unfinished_jobs_fail(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
exc = exception.DracOperationError('boom')
mock_client.list_jobs.side_effect = exc
self.assertRaises(exception.DracOperationError,
drac_job.list_unfinished_jobs, self.node)
def test_validate_job_queue(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_jobs.return_value = []
drac_job.validate_job_queue(self.node)
mock_client.list_jobs.assert_called_once_with(only_unfinished=True)
def test_validate_job_queue_fail(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
exc = drac_exceptions.BaseClientException('boom')
mock_client.list_jobs.side_effect = exc
self.assertRaises(exception.DracOperationError,
drac_job.validate_job_queue, self.node)
def test_validate_job_queue_invalid(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_jobs.return_value = [self.job]
self.assertRaises(exception.DracOperationError,
drac_job.validate_job_queue, self.node)
def test_validate_job_queue_name_prefix(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_jobs.return_value = [self.job]
drac_job.validate_job_queue(self.node, name_prefix='Fake')
mock_client.list_jobs.assert_called_once_with(only_unfinished=True)
def test_validate_job_queue_name_prefix_invalid(self,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_jobs.return_value = [self.job]
self.assertRaises(exception.DracOperationError,
drac_job.validate_job_queue, self.node,
name_prefix='ConfigBIOS')
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
class DracVendorPassthruJobTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracVendorPassthruJobTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
self.job_dict = {
'id': 'JID_001436912645',
'name': 'ConfigBIOS:BIOS.Setup.1-1',
'start_time': '00000101000000',
'until_time': 'TIME_NA',
'message': 'Job in progress',
'status': 'Running',
'percent_complete': 34}
self.job = test_utils.make_job(self.job_dict)
def test_list_unfinished_jobs(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_jobs.return_value = [self.job]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
resp = task.driver.vendor.list_unfinished_jobs(task)
mock_client.list_jobs.assert_called_once_with(only_unfinished=True)
self.assertEqual([self.job_dict], resp['unfinished_jobs'])
def test_list_unfinished_jobs_fail(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
exc = exception.DracOperationError('boom')
mock_client.list_jobs.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.DracOperationError,
task.driver.vendor.list_unfinished_jobs, task)

View File

@ -23,7 +23,6 @@ Test class for DRAC management interface
import json
from unittest import mock
from oslo_utils import importutils
import sushy
import ironic.common.boot_devices
@ -34,808 +33,16 @@ from ironic.conductor import periodics
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import job as drac_job
from ironic.drivers.modules.drac import management as drac_mgmt
from ironic.drivers.modules.drac import utils as drac_utils
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
dracclient_exceptions = importutils.try_import('dracclient.exceptions')
INFO_DICT = test_utils.INFO_DICT
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
class DracManagementInternalMethodsTestCase(test_utils.BaseDracTest):
def boot_modes(self, *next_modes):
modes = [
{'id': 'IPL', 'name': 'BootSeq',
'is_current': True, 'is_next': False},
{'id': 'OneTime', 'name': 'OneTimeBootMode',
'is_current': False, 'is_next': False}]
for mode in modes:
if mode['id'] in next_modes:
mode['is_next'] = True
return [test_utils.dict_to_namedtuple(values=mode) for mode in modes]
def setUp(self):
super(DracManagementInternalMethodsTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
boot_device_ipl_pxe = {
'id': 'BIOS.Setup.1-1#BootSeq#NIC.Embedded.1-1-1',
'boot_mode': 'IPL',
'current_assigned_sequence': 0,
'pending_assigned_sequence': 0,
'bios_boot_string': 'Embedded NIC 1 Port 1 Partition 1'}
boot_device_ipl_disk = {
'id': 'BIOS.Setup.1-1#BootSeq#HardDisk.List.1-1',
'boot_mode': 'IPL',
'current_assigned_sequence': 1,
'pending_assigned_sequence': 1,
'bios_boot_string': 'Hard drive C: BootSeq'}
ipl_boot_device_namedtuples = [
test_utils.dict_to_namedtuple(values=boot_device_ipl_pxe),
test_utils.dict_to_namedtuple(values=boot_device_ipl_disk)]
ipl_boot_devices = {'IPL': ipl_boot_device_namedtuples,
'OneTime': ipl_boot_device_namedtuples}
boot_device_uefi_pxe = {
'id': 'UEFI:BIOS.Setup.1-1#UefiBootSeq#NIC.PxeDevice.1-1',
'boot_mode': 'UEFI',
'current_assigned_sequence': 0,
'pending_assigned_sequence': 0,
'bios_boot_string':
'PXE Device 1: Integrated NIC 1 Port 1 Partition 1'}
uefi_boot_device_namedtuples = [
test_utils.dict_to_namedtuple(values=boot_device_uefi_pxe)]
uefi_boot_devices = {'UEFI': uefi_boot_device_namedtuples,
'OneTime': uefi_boot_device_namedtuples}
self.boot_devices = {'IPL': ipl_boot_devices,
'UEFI': uefi_boot_devices}
def test__get_boot_device(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = drac_mgmt._get_boot_device(self.node)
expected_boot_device = {'boot_device': 'pxe', 'persistent': True}
self.assertEqual(expected_boot_device, boot_device)
mock_client.list_boot_modes.assert_called_once_with()
mock_client.list_boot_devices.assert_called_once_with()
def test__get_boot_device_not_persistent(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
# if a non-persistent boot mode is marked as "next", it over-rides any
# persistent boot modes
mock_client.list_boot_modes.return_value = self.boot_modes('IPL',
'OneTime')
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = drac_mgmt._get_boot_device(self.node)
expected_boot_device = {'boot_device': 'pxe', 'persistent': False}
self.assertEqual(expected_boot_device, boot_device)
mock_client.list_boot_modes.assert_called_once_with()
mock_client.list_boot_devices.assert_called_once_with()
def test__get_boot_device_with_no_boot_device(self,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
mock_client.list_boot_devices.return_value = {}
boot_device = drac_mgmt._get_boot_device(self.node)
expected_boot_device = {'boot_device': None, 'persistent': True}
self.assertEqual(expected_boot_device, boot_device)
mock_client.list_boot_modes.assert_called_once_with()
mock_client.list_boot_devices.assert_called_once_with()
def test__get_boot_device_with_empty_boot_mode_list(self,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = []
self.assertRaises(exception.DracOperationError,
drac_mgmt._get_boot_device, self.node)
def test__get_next_persistent_boot_mode(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL')
boot_mode = drac_mgmt._get_next_persistent_boot_mode(self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
expected_boot_mode = 'IPL'
self.assertEqual(expected_boot_mode, boot_mode)
def test__get_next_persistent_boot_mode_with_non_persistent_boot_mode(
self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = self.boot_modes('IPL',
'OneTime')
boot_mode = drac_mgmt._get_next_persistent_boot_mode(self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
expected_boot_mode = 'IPL'
self.assertEqual(expected_boot_mode, boot_mode)
def test__get_next_persistent_boot_mode_list_boot_modes_fail(
self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
exc = dracclient_exceptions.BaseClientException('boom')
mock_client.list_boot_modes.side_effect = exc
self.assertRaises(exception.DracOperationError,
drac_mgmt._get_next_persistent_boot_mode, self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
def test__get_next_persistent_boot_mode_with_empty_boot_mode_list(
self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_modes.return_value = []
self.assertRaises(exception.DracOperationError,
drac_mgmt._get_next_persistent_boot_mode, self.node)
mock_get_drac_client.assert_called_once_with(self.node)
mock_client.list_boot_modes.assert_called_once_with()
def test__is_boot_order_flexibly_programmable(self, mock_get_drac_client):
self.assertTrue(drac_mgmt._is_boot_order_flexibly_programmable(
persistent=True, bios_settings={'SetBootOrderFqdd1': ()}))
def test__is_boot_order_flexibly_programmable_not_persistent(
self, mock_get_drac_client):
self.assertFalse(drac_mgmt._is_boot_order_flexibly_programmable(
persistent=False, bios_settings={'SetBootOrderFqdd1': ()}))
def test__is_boot_order_flexibly_programmable_with_no_bios_setting(
self, mock_get_drac_client):
self.assertFalse(drac_mgmt._is_boot_order_flexibly_programmable(
persistent=True, bios_settings={}))
def test__flexibly_program_boot_order_for_disk_and_bios(
self, mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.DISK, drac_boot_mode='Bios')
expected_settings = {'SetBootOrderFqdd1': 'HardDisk.List.1-1'}
self.assertEqual(expected_settings, settings)
def test__flexibly_program_boot_order_for_disk_and_uefi(
self, mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.DISK, drac_boot_mode='Uefi')
expected_settings = {
'SetBootOrderFqdd1': '*.*.*',
'SetBootOrderFqdd2': 'NIC.*.*',
'SetBootOrderFqdd3': 'Optical.*.*',
'SetBootOrderFqdd4': 'Floppy.*.*',
}
self.assertEqual(expected_settings, settings)
def test__flexibly_program_boot_order_for_pxe(self, mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.PXE, drac_boot_mode='Uefi')
expected_settings = {'SetBootOrderFqdd1': 'NIC.*.*'}
self.assertEqual(expected_settings, settings)
def test__flexibly_program_boot_order_for_cdrom(self,
mock_get_drac_client):
settings = drac_mgmt._flexibly_program_boot_order(
ironic.common.boot_devices.CDROM, drac_boot_mode='Uefi')
expected_settings = {'SetBootOrderFqdd1': 'Optical.*.*'}
self.assertEqual(expected_settings, settings)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device(self, mock_validate_job_queue,
mock_list_unfinished_jobs,
mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
mock_list_unfinished_jobs.return_value = []
mock_job = mock.Mock()
mock_job.status = "Scheduled"
mock_client.get_job.return_value = mock_job
boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
self.node.driver_internal_info['clean_steps'] = []
boot_device = drac_mgmt.set_boot_device(
self.node, ironic.common.boot_devices.PXE, persistent=False)
self.assertEqual(0, mock_list_unfinished_jobs.call_count)
self.assertEqual(0, mock_client.delete_jobs.call_count)
mock_validate_job_queue.assert_called_once_with(
self.node, name_prefix="Configure: BIOS")
mock_client.change_boot_device_order.assert_called_once_with(
'OneTime', 'BIOS.Setup.1-1#BootSeq#NIC.Embedded.1-1-1')
self.assertEqual(0, mock_client.set_bios_settings.call_count)
mock_client.commit_pending_bios_changes.assert_called_once_with()
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
def test_set_boot_device_called_with_no_change(
self, mock_list_unfinished_jobs, mock__get_boot_device,
mock__get_next_persistent_boot_mode, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
mock_list_unfinished_jobs.return_value = []
boot_device = drac_mgmt.set_boot_device(
self.node, ironic.common.boot_devices.PXE, persistent=True)
mock_list_unfinished_jobs.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)
@mock.patch.object(drac_mgmt, '_flexibly_program_boot_order',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_is_boot_order_flexibly_programmable',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
def test_set_boot_device_called_with_no_drac_boot_device(
self, mock_list_unfinished_jobs,
mock__get_boot_device, mock__get_next_persistent_boot_mode,
mock__is_boot_order_flexibly_programmable,
mock__flexibly_program_boot_order,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['UEFI']
mock_list_unfinished_jobs.return_value = []
mock_job = mock.Mock()
mock_job.status = "Scheduled"
mock_client.get_job.return_value = mock_job
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': False}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'UEFI'
settings = [
{
'name': 'BootMode',
'instance_id': 'BIOS.Setup.1-1:BootMode',
'current_value': 'Uefi',
'pending_value': None,
'read_only': False,
'possible_values': ['Bios', 'Uefi']
},
]
bios_settings = {
s['name']: test_utils.dict_to_namedtuple(
values=s) for s in settings}
mock_client.list_bios_settings.return_value = bios_settings
mock__is_boot_order_flexibly_programmable.return_value = True
flexibly_program_settings = {
'SetBootOrderFqdd1': '*.*.*',
'SetBootOrderFqdd2': 'NIC.*.*',
'SetBootOrderFqdd3': 'Optical.*.*',
'SetBootOrderFqdd4': 'Floppy.*.*',
}
mock__flexibly_program_boot_order.return_value = \
flexibly_program_settings
drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK,
persistent=True)
mock_list_unfinished_jobs.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
mock_client.set_bios_settings.assert_called_once_with(
flexibly_program_settings)
mock_client.commit_pending_bios_changes.assert_called_once_with()
@mock.patch.object(drac_mgmt, '_is_boot_order_flexibly_programmable',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
def test_set_boot_device_called_with_not_flexibly_programmable(
self, mock_list_unfinished_jobs,
mock__get_boot_device, mock__get_next_persistent_boot_mode,
mock__is_boot_order_flexibly_programmable,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_list_unfinished_jobs.return_value = []
mock_client.list_boot_devices.return_value = self.boot_devices['UEFI']
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': False}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'UEFI'
mock__is_boot_order_flexibly_programmable.return_value = False
self.assertRaises(exception.InvalidParameterValue,
drac_mgmt.set_boot_device, self.node,
ironic.common.boot_devices.CDROM, persistent=False)
mock_list_unfinished_jobs.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)
@mock.patch.object(drac_mgmt, '_is_boot_order_flexibly_programmable',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
def test_set_boot_device_called_with_unknown_boot_mode(
self, mock_list_unfinished_jobs, mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock__is_boot_order_flexibly_programmable,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['UEFI']
boot_device = {'boot_device': ironic.common.boot_devices.PXE,
'persistent': False}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'UEFI'
settings = [
{
'name': 'BootMode',
'instance_id': 'BIOS.Setup.1-1:BootMode',
'current_value': 'Bad',
'pending_value': None,
'read_only': False,
'possible_values': ['Bios', 'Uefi', 'Bad']
},
]
bios_settings = {
s['name']: test_utils.dict_to_namedtuple(
values=s) for s in settings}
mock_client.list_bios_settings.return_value = bios_settings
mock__is_boot_order_flexibly_programmable.return_value = True
mock_list_unfinished_jobs.return_value = []
self.assertRaises(exception.DracOperationError,
drac_mgmt.set_boot_device, self.node,
ironic.common.boot_devices.DISK, persistent=True)
mock_list_unfinished_jobs.assert_called_once_with(self.node)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)
@mock.patch('time.time', autospec=True)
@mock.patch('time.sleep', autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
def test_set_boot_device_job_not_scheduled(
self,
mock_list_unfinished_jobs,
mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock_sleep,
mock_time,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_list_unfinished_jobs.return_value = []
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
mock_job = mock.Mock()
mock_job.status = "New"
mock_client.get_job.return_value = mock_job
mock_time.side_effect = [10, 50]
boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
self.assertRaises(exception.DracOperationError,
drac_mgmt.set_boot_device, self.node,
ironic.common.boot_devices.PXE,
persistent=True)
mock_list_unfinished_jobs.assert_called_once_with(self.node)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
def test_set_boot_device_with_list_unfinished_jobs_fail(
self, mock_list_unfinished_jobs, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_list_unfinished_jobs.side_effect = exception.DracOperationError(
'boom')
self.assertRaises(exception.DracOperationError,
drac_mgmt.set_boot_device, self.node,
ironic.common.boot_devices.PXE, persistent=True)
self.assertEqual(0, mock_client.change_boot_device_order.call_count)
self.assertEqual(0, mock_client.set_bios_settings.call_count)
self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
def test_set_boot_device_with_list_unfinished_jobs_without_clean_step(
self, mock__get_next_persistent_boot_mode, mock__get_boot_device,
mock_list_unfinished_jobs, mock_validate_job_queue,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
bios_job_dict = {
'id': 'JID_602553293345',
'name': 'ConfigBIOS:BIOS.Setup.1-1',
'start_time': 'TIME_NOW',
'until_time': 'TIME_NA',
'message': 'Task successfully scheduled.',
'status': 'Scheduled',
'percent_complete': 0}
bios_job = test_utils.make_job(bios_job_dict)
mock_list_unfinished_jobs.return_value = [bios_job]
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
self.node.driver_internal_info['clean_steps'] = []
drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK,
persistent=True)
self.assertEqual(0, mock_list_unfinished_jobs.call_count)
self.assertEqual(0, mock_client.delete_jobs.call_count)
mock_validate_job_queue.assert_called_once_with(
self.node, name_prefix="Configure: BIOS")
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
def test_set_boot_device_with_multiple_unfinished_jobs_without_clean_step(
self, mock__get_next_persistent_boot_mode, mock__get_boot_device,
mock_list_unfinished_jobs, mock_validate_job_queue,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
job_dict = {
'id': 'JID_602553293345',
'name': 'Config:RAID:RAID.Integrated.1-1',
'start_time': 'TIME_NOW',
'until_time': 'TIME_NA',
'message': 'Task successfully scheduled.',
'status': 'Scheduled',
'percent_complete': 0}
job = test_utils.make_job(job_dict)
bios_job_dict = {
'id': 'JID_602553293346',
'name': 'ConfigBIOS:BIOS.Setup.1-1',
'start_time': 'TIME_NOW',
'until_time': 'TIME_NA',
'message': 'Task successfully scheduled.',
'status': 'Scheduled',
'percent_complete': 0}
bios_job = test_utils.make_job(bios_job_dict)
mock_list_unfinished_jobs.return_value = [job, bios_job]
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
self.node.driver_internal_info['clean_steps'] = []
drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK,
persistent=True)
self.assertEqual(0, mock_list_unfinished_jobs.call_count)
self.assertEqual(0, mock_client.delete_jobs.call_count)
mock_validate_job_queue.assert_called_once_with(
self.node, name_prefix="Configure: BIOS")
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
def test_set_boot_device_with_list_unfinished_jobs_with_clean_step(
self, mock_validate_job_queue,
mock_list_unfinished_jobs,
mock__get_boot_device,
mock__get_next_persistent_boot_mode,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
mock_job = mock.Mock()
mock_job.status = "Scheduled"
mock_client.get_job.return_value = mock_job
bios_job_dict = {
'id': 'JID_602553293345',
'name': 'ConfigBIOS:BIOS.Setup.1-1',
'start_time': 'TIME_NOW',
'until_time': 'TIME_NA',
'message': 'Task successfully scheduled.',
'status': 'Scheduled',
'percent_complete': 0}
bios_job = test_utils.make_job(bios_job_dict)
mock_list_unfinished_jobs.return_value = [bios_job]
self.node.driver_internal_info['clean_steps'] = [{
u'interface': u'management', u'step': u'clear_job_queue'}]
boot_device = drac_mgmt.set_boot_device(
self.node, ironic.common.boot_devices.PXE, persistent=False)
mock_list_unfinished_jobs.assert_called_once_with(self.node)
mock_client.delete_jobs.assert_called_once_with(
job_ids=['JID_602553293345'])
self.assertEqual(0, mock_validate_job_queue.call_count)
@mock.patch.object(drac_job, 'validate_job_queue', spec_set=True,
autospec=True)
@mock.patch.object(drac_job, 'list_unfinished_jobs', spec_set=True,
autospec=True)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
@mock.patch.object(drac_mgmt, '_get_next_persistent_boot_mode',
spec_set=True, autospec=True)
def test_set_boot_device_with_multiple_unfinished_jobs_with_clean_step(
self, mock__get_next_persistent_boot_mode, mock__get_boot_device,
mock_list_unfinished_jobs, mock_validate_job_queue,
mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
job_dict = {
'id': 'JID_602553293345',
'name': 'Config:RAID:RAID.Integrated.1-1',
'start_time': 'TIME_NOW',
'until_time': 'TIME_NA',
'message': 'Task successfully scheduled.',
'status': 'Scheduled',
'percent_complete': 0}
job = test_utils.make_job(job_dict)
bios_job_dict = {
'id': 'JID_602553293346',
'name': 'ConfigBIOS:BIOS.Setup.1-1',
'start_time': 'TIME_NOW',
'until_time': 'TIME_NA',
'message': 'Task successfully scheduled.',
'status': 'Scheduled',
'percent_complete': 0}
bios_job = test_utils.make_job(bios_job_dict)
mock_list_unfinished_jobs.return_value = [job, bios_job]
mock_client.list_boot_devices.return_value = self.boot_devices['IPL']
boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = boot_device
mock__get_next_persistent_boot_mode.return_value = 'IPL'
self.node.driver_internal_info['clean_steps'] = [{
u'interface': u'management', u'step': u'clear_job_queue'}]
drac_mgmt.set_boot_device(self.node, ironic.common.boot_devices.DISK,
persistent=True)
mock_list_unfinished_jobs.assert_called_once_with(self.node)
mock_client.delete_jobs.assert_called_once_with(
job_ids=['JID_602553293345', 'JID_602553293346'])
self.assertEqual(0, mock_validate_job_queue.call_count)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
class DracManagementTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracManagementTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
def test_get_properties(self, mock_get_drac_client):
expected = drac_common.COMMON_PROPERTIES
driver = drac_mgmt.DracManagement()
self.assertEqual(expected, driver.get_properties())
def test_get_supported_boot_devices(self, mock_get_drac_client):
expected_boot_devices = [ironic.common.boot_devices.PXE,
ironic.common.boot_devices.DISK,
ironic.common.boot_devices.CDROM]
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
boot_devices = (
task.driver.management.get_supported_boot_devices(task))
self.assertEqual(sorted(expected_boot_devices), sorted(boot_devices))
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
def test_get_boot_device(self, mock__get_boot_device,
mock_get_drac_client):
expected_boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
mock__get_boot_device.return_value = expected_boot_device
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
boot_device = task.driver.management.get_boot_device(task)
self.assertEqual(expected_boot_device, boot_device)
mock__get_boot_device.assert_called_once_with(task.node)
@mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True,
autospec=True)
def test_get_boot_device_from_driver_internal_info(self,
mock__get_boot_device,
mock_get_drac_client):
expected_boot_device = {'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.driver_internal_info['drac_boot_device'] = (
expected_boot_device)
boot_device = task.driver.management.get_boot_device(task)
self.assertEqual(expected_boot_device, boot_device)
self.assertEqual(0, mock__get_boot_device.call_count)
def test_set_boot_device(self, mock_get_drac_client):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.management.set_boot_device(
task, ironic.common.boot_devices.DISK, persistent=True)
expected_boot_device = {
'boot_device': ironic.common.boot_devices.DISK,
'persistent': True}
self.node.refresh()
self.assertEqual(
self.node.driver_internal_info['drac_boot_device'],
expected_boot_device)
def test_set_boot_device_fail(self, mock_get_drac_client):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.InvalidParameterValue,
task.driver.management.set_boot_device, task,
'foo')
def test_get_sensors_data(self, mock_get_drac_client):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(NotImplementedError,
task.driver.management.get_sensors_data, task)
def test_reset_idrac(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
return_value = task.driver.management.reset_idrac(task)
mock_client.reset_idrac.assert_called_once_with(
force=True, wait=True)
self.assertIsNone(return_value)
def test_known_good_state(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
return_value = task.driver.management.known_good_state(task)
mock_client.reset_idrac.assert_called_once_with(
force=True, wait=True)
mock_client.delete_jobs.assert_called_once_with(
job_ids=['JID_CLEARALL'])
self.assertIsNone(return_value)
def test_clear_job_queue(self, mock_get_drac_client):
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
return_value = task.driver.management.clear_job_queue(task)
mock_client.delete_jobs.assert_called_once_with(
job_ids=['JID_CLEARALL'])
self.assertIsNone(return_value)
class DracRedfishManagementTestCase(test_utils.BaseDracTest):
def setUp(self):
@ -1453,10 +660,7 @@ class DracRedfishManagementTestCase(test_utils.BaseDracTest):
'iDRAC on node %(node)s does not support '
'clearing Lifecycle Controller job queue '
'using the idrac-redfish driver. '
'If using iDRAC9, consider upgrading firmware. '
'If using iDRAC8, consider switching to '
'idrac-wsman for management interface if '
'possible.',
'If using iDRAC9, consider upgrading firmware.',
{'node': task.node.uuid})
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
@ -1511,10 +715,7 @@ class DracRedfishManagementTestCase(test_utils.BaseDracTest):
mock_log.warning.assert_called_once_with(
'iDRAC on node %(node)s does not support '
'iDRAC reset using the idrac-redfish driver. '
'If using iDRAC9, consider upgrading firmware. '
'If using iDRAC8, consider switching to '
'idrac-wsman for management interface if '
'possible.',
'If using iDRAC9, consider upgrading firmware. ',
{'node': task.node.uuid})
@mock.patch.object(redfish_utils, 'wait_until_get_system_ready',

View File

@ -1,458 +0,0 @@
#
# 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 DRAC periodic tasks
"""
from unittest import mock
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import raid as drac_raid
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
INFO_DICT = test_utils.INFO_DICT
class DracPeriodicTaskTestCase(db_base.DbTestCase):
def setUp(self):
super(DracPeriodicTaskTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
self.raid = drac_raid.DracRAID()
self.raid_wsman = drac_raid.DracWSManRAID()
self.job = {
'id': 'JID_001436912645',
'name': 'ConfigBIOS:BIOS.Setup.1-1',
'start_time': '00000101000000',
'until_time': 'TIME_NA',
'message': 'Job in progress',
'status': 'Running',
'percent_complete': 34}
self.virtual_disk = {
'id': 'Disk.Virtual.0:RAID.Integrated.1-1',
'name': 'disk 0',
'description': 'Virtual Disk 0 on Integrated RAID Controller 1',
'controller': 'RAID.Integrated.1-1',
'raid_level': '1',
'size_mb': 571776,
'status': 'ok',
'raid_status': 'online',
'span_depth': 1,
'span_length': 2,
'pending_operations': None
}
def test__query_raid_config_job_status_drac(self):
self._test__query_raid_config_job_status(self.raid)
def test__query_raid_config_job_status_drac_wsman(self):
self._test__query_raid_config_job_status(self.raid_wsman)
@mock.patch.object(task_manager, 'acquire', autospec=True)
def _test__query_raid_config_job_status(self, raid, mock_acquire):
# mock node.driver_internal_info
driver_internal_info = {'raid_config_job_ids': ['42']}
self.node.driver_internal_info = driver_internal_info
self.node.save()
# mock manager
mock_manager = mock.Mock()
node_list = [(self.node.uuid, 'idrac', '',
{'raid_config_job_ids': ['42']})]
mock_manager.iter_nodes.return_value = node_list
# mock task_manager.acquire
task = mock.Mock(node=self.node, driver=mock.Mock(raid=raid))
mock_acquire.return_value = mock.MagicMock(
__enter__=mock.MagicMock(return_value=task))
# mock _check_node_raid_jobs
raid._check_node_raid_jobs = mock.Mock()
raid._query_raid_config_job_status(mock_manager,
self.context)
raid._check_node_raid_jobs.assert_called_once_with(task)
def test__query_raid_config_job_status_no_config_jobs_drac(self):
self._test__query_raid_config_job_status_no_config_jobs(self.raid)
def test__query_raid_config_job_status_no_config_jobs_drac_wsman(self):
self._test__query_raid_config_job_status_no_config_jobs(
self.raid_wsman)
@mock.patch.object(task_manager, 'acquire', autospec=True)
def _test__query_raid_config_job_status_no_config_jobs(self, raid,
mock_acquire):
# mock manager
mock_manager = mock.Mock()
node_list = [(self.node.uuid, 'idrac', '', {})]
mock_manager.iter_nodes.return_value = node_list
# mock task_manager.acquire
task = mock.Mock(node=self.node, driver=mock.Mock(raid=raid))
mock_acquire.return_value = mock.MagicMock(
__enter__=mock.MagicMock(return_value=task))
# mock _check_node_raid_jobs
raid._check_node_raid_jobs = mock.Mock()
raid._query_raid_config_job_status(mock_manager, None)
self.assertEqual(0, raid._check_node_raid_jobs.call_count)
def test__query_raid_config_job_status_no_nodes(self):
# mock manager
mock_manager = mock.Mock()
node_list = []
mock_manager.iter_nodes.return_value = node_list
# mock _check_node_raid_jobs
self.raid._check_node_raid_jobs = mock.Mock()
self.raid._query_raid_config_job_status(mock_manager, None)
self.assertEqual(0, self.raid._check_node_raid_jobs.call_count)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__check_node_raid_jobs_without_update(self, mock_get_drac_client):
# mock node.driver_internal_info
driver_internal_info = {'raid_config_job_ids': ['42']}
self.node.driver_internal_info = driver_internal_info
self.node.save()
# mock task
task = mock.Mock(node=self.node)
# mock dracclient.get_job
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.return_value = test_utils.dict_to_namedtuple(
values=self.job)
self.raid._check_node_raid_jobs(task)
mock_client.get_job.assert_called_once_with('42')
self.assertEqual(0, mock_client.list_virtual_disks.call_count)
self.node.refresh()
self.assertEqual(['42'],
self.node.driver_internal_info['raid_config_job_ids'])
self.assertEqual({}, self.node.raid_config)
self.assertIs(False, self.node.maintenance)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(drac_raid.DracRAID, 'get_logical_disks',
spec_set=True, autospec=True)
def _test__check_node_raid_jobs_with_completed_job(
self, mock_notify_conductor_resume,
mock_get_logical_disks, mock_get_drac_client):
expected_logical_disk = {'size_gb': 558,
'raid_level': '1',
'name': 'disk 0'}
# mock node.driver_internal_info
driver_internal_info = {'raid_config_job_ids': ['42']}
self.node.driver_internal_info = driver_internal_info
self.node.save()
# mock task
task = mock.Mock(node=self.node, context=self.context)
# mock dracclient.get_job
self.job['status'] = 'Completed'
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.return_value = test_utils.dict_to_namedtuple(
values=self.job)
# mock driver.raid.get_logical_disks
mock_get_logical_disks.return_value = {
'logical_disks': [expected_logical_disk]
}
self.raid._check_node_raid_jobs(task)
mock_client.get_job.assert_called_once_with('42')
self.node.refresh()
self.assertEqual([],
self.node.driver_internal_info['raid_config_job_ids'])
self.assertEqual([expected_logical_disk],
self.node.raid_config['logical_disks'])
mock_notify_conductor_resume.assert_called_once_with(task)
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
autospec=True)
def test__check_node_raid_jobs_with_completed_job_in_clean(
self, mock_notify_conductor_resume):
self.node.clean_step = {'foo': 'bar'}
self.node.save()
self._test__check_node_raid_jobs_with_completed_job(
mock_notify_conductor_resume)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
def test__check_node_raid_jobs_with_completed_job_in_deploy(
self, mock_notify_conductor_resume):
self._test__check_node_raid_jobs_with_completed_job(
mock_notify_conductor_resume)
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__check_node_raid_jobs_with_failed_job(
self, mock_get_drac_client, mock_cleaning_error_handler):
# mock node.driver_internal_info and node.clean_step
driver_internal_info = {'raid_config_job_ids': ['42']}
self.node.driver_internal_info = driver_internal_info
self.node.clean_step = {'foo': 'bar'}
self.node.save()
# mock task
task = mock.Mock(node=self.node, context=self.context)
# mock dracclient.get_job
self.job['status'] = 'Failed'
self.job['message'] = 'boom'
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.return_value = test_utils.dict_to_namedtuple(
values=self.job)
# mock dracclient.list_virtual_disks
mock_client.list_virtual_disks.return_value = [
test_utils.dict_to_namedtuple(values=self.virtual_disk)]
self.raid._check_node_raid_jobs(task)
mock_client.get_job.assert_called_once_with('42')
self.assertEqual(0, mock_client.list_virtual_disks.call_count)
self.node.refresh()
self.assertEqual([],
self.node.driver_internal_info['raid_config_job_ids'])
self.assertEqual({}, self.node.raid_config)
mock_cleaning_error_handler.assert_called_once_with(task, mock.ANY)
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
def test__check_node_raid_jobs_with_completed_with_errors_job(
self, mock_get_drac_client, mock_cleaning_error_handler):
# mock node.driver_internal_info and node.clean_step
driver_internal_info = {'raid_config_job_ids': ['42']}
self.node.driver_internal_info = driver_internal_info
self.node.clean_step = {'foo': 'bar'}
self.node.save()
# mock task
task = mock.Mock(node=self.node, context=self.context)
# mock dracclient.get_job
self.job['status'] = 'Completed with Errors'
self.job['message'] = 'PR31: Completed with Errors'
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.return_value = test_utils.dict_to_namedtuple(
values=self.job)
# mock dracclient.list_virtual_disks
mock_client.list_virtual_disks.return_value = [
test_utils.dict_to_namedtuple(values=self.virtual_disk)]
self.raid._check_node_raid_jobs(task)
mock_client.get_job.assert_called_once_with('42')
self.assertEqual(0, mock_client.list_virtual_disks.call_count)
self.node.refresh()
self.assertEqual([],
self.node.driver_internal_info['raid_config_job_ids'])
self.assertEqual({}, self.node.raid_config)
mock_cleaning_error_handler.assert_called_once_with(task, mock.ANY)
@mock.patch.object(manager_utils, 'deploying_error_handler', autospec=True)
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(drac_raid.DracRAID, 'get_logical_disks',
spec_set=True, autospec=True)
def _test__check_node_raid_jobs_with_completed_job_already_failed(
self, mock_notify_conductor_resume,
mock_get_logical_disks, mock_get_drac_client,
mock_cleaning_error_handler, mock_deploying_error_handler):
expected_logical_disk = {'size_gb': 558,
'raid_level': '1',
'name': 'disk 0'}
# mock node.driver_internal_info
driver_internal_info = {'raid_config_job_ids': ['42'],
'raid_config_job_failure': True}
self.node.driver_internal_info = driver_internal_info
self.node.save()
# mock task
task = mock.Mock(node=self.node, context=self.context)
# mock dracclient.get_job
self.job['status'] = 'Completed'
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.return_value = test_utils.dict_to_namedtuple(
values=self.job)
# mock driver.raid.get_logical_disks
mock_get_logical_disks.return_value = {
'logical_disks': [expected_logical_disk]
}
self.raid._check_node_raid_jobs(task)
mock_client.get_job.assert_called_once_with('42')
self.node.refresh()
self.assertEqual([],
self.node.driver_internal_info['raid_config_job_ids'])
self.assertNotIn('raid_config_job_failure',
self.node.driver_internal_info)
self.assertNotIn('logical_disks', self.node.raid_config)
if self.node.clean_step:
mock_cleaning_error_handler.assert_called_once_with(task, mock.ANY)
else:
mock_deploying_error_handler.assert_called_once_with(task,
mock.ANY,
mock.ANY)
self.assertFalse(mock_notify_conductor_resume.called)
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
autospec=True)
def test__check_node_raid_jobs_with_completed_job_already_failed_in_clean(
self, mock_notify_conductor_resume):
self.node.clean_step = {'foo': 'bar'}
self.node.save()
self._test__check_node_raid_jobs_with_completed_job_already_failed(
mock_notify_conductor_resume)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
def test__check_node_raid_jobs_with_completed_job_already_failed_in_deploy(
self, mock_notify_conductor_resume):
self._test__check_node_raid_jobs_with_completed_job_already_failed(
mock_notify_conductor_resume)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(drac_raid.DracRAID, 'get_logical_disks',
spec_set=True, autospec=True)
def _test__check_node_raid_jobs_with_multiple_jobs_completed(
self, mock_notify_conductor_resume,
mock_get_logical_disks, mock_get_drac_client):
expected_logical_disk = {'size_gb': 558,
'raid_level': '1',
'name': 'disk 0'}
# mock node.driver_internal_info
driver_internal_info = {'raid_config_job_ids': ['42', '36']}
self.node.driver_internal_info = driver_internal_info
self.node.save()
# mock task
task = mock.Mock(node=self.node, context=self.context)
# mock dracclient.get_job
self.job['status'] = 'Completed'
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.return_value = test_utils.dict_to_namedtuple(
values=self.job)
# mock driver.raid.get_logical_disks
mock_get_logical_disks.return_value = {
'logical_disks': [expected_logical_disk]
}
self.raid._check_node_raid_jobs(task)
mock_client.get_job.assert_has_calls([mock.call('42'),
mock.call('36')])
self.node.refresh()
self.assertEqual([],
self.node.driver_internal_info['raid_config_job_ids'])
self.assertNotIn('raid_config_job_failure',
self.node.driver_internal_info)
self.assertEqual([expected_logical_disk],
self.node.raid_config['logical_disks'])
mock_notify_conductor_resume.assert_called_once_with(task)
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
autospec=True)
def test__check_node_raid_jobs_with_multiple_jobs_completed_in_clean(
self, mock_notify_conductor_resume):
self.node.clean_step = {'foo': 'bar'}
self.node.save()
self._test__check_node_raid_jobs_with_multiple_jobs_completed(
mock_notify_conductor_resume)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
def test__check_node_raid_jobs_with_multiple_jobs_completed_in_deploy(
self, mock_notify_conductor_resume):
self._test__check_node_raid_jobs_with_multiple_jobs_completed(
mock_notify_conductor_resume)
@mock.patch.object(manager_utils, 'deploying_error_handler', autospec=True)
@mock.patch.object(manager_utils, 'cleaning_error_handler', autospec=True)
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
@mock.patch.object(drac_raid.DracRAID, 'get_logical_disks',
spec_set=True, autospec=True)
def _test__check_node_raid_jobs_with_multiple_jobs_failed(
self, mock_notify_conductor_resume,
mock_get_logical_disks, mock_get_drac_client,
mock_cleaning_error_handler, mock_deploying_error_handler):
expected_logical_disk = {'size_gb': 558,
'raid_level': '1',
'name': 'disk 0'}
# mock node.driver_internal_info
driver_internal_info = {'raid_config_job_ids': ['42', '36']}
self.node.driver_internal_info = driver_internal_info
self.node.save()
# mock task
task = mock.Mock(node=self.node, context=self.context)
# mock dracclient.get_job
self.job['status'] = 'Completed'
failed_job = self.job.copy()
failed_job['status'] = 'Failed'
failed_job['message'] = 'boom'
mock_client = mock.Mock()
mock_get_drac_client.return_value = mock_client
mock_client.get_job.side_effect = [
test_utils.dict_to_namedtuple(values=failed_job),
test_utils.dict_to_namedtuple(values=self.job)]
# mock driver.raid.get_logical_disks
mock_get_logical_disks.return_value = {
'logical_disks': [expected_logical_disk]
}
self.raid._check_node_raid_jobs(task)
mock_client.get_job.assert_has_calls([mock.call('42'),
mock.call('36')])
self.node.refresh()
self.assertEqual([],
self.node.driver_internal_info['raid_config_job_ids'])
self.assertNotIn('raid_config_job_failure',
self.node.driver_internal_info)
self.assertNotIn('logical_disks', self.node.raid_config)
if self.node.clean_step:
mock_cleaning_error_handler.assert_called_once_with(task, mock.ANY)
else:
mock_deploying_error_handler.assert_called_once_with(task,
mock.ANY,
mock.ANY)
self.assertFalse(mock_notify_conductor_resume.called)
@mock.patch.object(manager_utils, 'notify_conductor_resume_clean',
autospec=True)
def test__check_node_raid_jobs_with_multiple_jobs_failed_in_clean(
self, mock_notify_conductor_resume):
self.node.clean_step = {'foo': 'bar'}
self.node.save()
self._test__check_node_raid_jobs_with_multiple_jobs_failed(
mock_notify_conductor_resume)
@mock.patch.object(manager_utils, 'notify_conductor_resume_deploy',
autospec=True)
def test__check_node_raid_jobs_with_multiple_jobs_failed_in_deploy(
self, mock_notify_conductor_resume):
self._test__check_node_raid_jobs_with_multiple_jobs_failed(
mock_notify_conductor_resume)

View File

@ -1,212 +0,0 @@
#
# 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 DRAC power interface
"""
from unittest import mock
from dracclient import constants as drac_constants
from dracclient import exceptions as drac_exceptions
from oslo_service import loopingcall
from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers.modules.drac import common as drac_common
from ironic.drivers.modules.drac import power as drac_power
from ironic.tests.unit.drivers.modules.drac import utils as test_utils
from ironic.tests.unit.objects import utils as obj_utils
INFO_DICT = test_utils.INFO_DICT
@mock.patch.object(drac_common, 'get_drac_client', spec_set=True,
autospec=True)
class DracPowerTestCase(test_utils.BaseDracTest):
def setUp(self):
super(DracPowerTestCase, self).setUp()
self.node = obj_utils.create_test_node(self.context,
driver='idrac',
driver_info=INFO_DICT)
def test_get_properties(self, mock_get_drac_client):
expected = drac_common.COMMON_PROPERTIES
driver = drac_power.DracPower()
self.assertEqual(expected, driver.get_properties())
def test_get_power_state(self, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.return_value = drac_constants.POWER_ON
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
power_state = task.driver.power.get_power_state(task)
self.assertEqual(states.POWER_ON, power_state)
mock_client.get_power_state.assert_called_once_with()
def test_get_power_state_fail(self, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
exc = drac_exceptions.BaseClientException('boom')
mock_client.get_power_state.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.DracOperationError,
task.driver.power.get_power_state, task)
mock_client.get_power_state.assert_called_once_with()
@mock.patch.object(loopingcall.BackOffLoopingCall, '_sleep', autospec=True)
@mock.patch.object(drac_power.LOG, 'warning', autospec=True)
def test_set_power_state(self, mock_log, mock_sleep, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.side_effect = [drac_constants.POWER_ON,
drac_constants.POWER_OFF]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.set_power_state(task, states.POWER_OFF)
drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_OFF]
mock_client.set_power_state.assert_called_once_with(drac_power_state)
self.assertFalse(mock_log.called)
def test_set_power_state_fail(self, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
exc = drac_exceptions.BaseClientException('boom')
mock_client.set_power_state.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.DracOperationError,
task.driver.power.set_power_state, task,
states.POWER_OFF)
drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_OFF]
mock_client.set_power_state.assert_called_once_with(drac_power_state)
@mock.patch.object(loopingcall.BackOffLoopingCall, '_sleep', autospec=True)
@mock.patch.object(drac_power.LOG, 'warning', autospec=True)
def test_set_power_state_timeout(self, mock_log, mock_sleep,
mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.side_effect = [drac_constants.POWER_ON,
drac_constants.POWER_OFF]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.set_power_state(task, states.POWER_OFF,
timeout=11)
drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_OFF]
mock_client.set_power_state.assert_called_once_with(drac_power_state)
self.assertFalse(mock_log.called)
@mock.patch.object(drac_power.LOG, 'warning', autospec=True)
def test_reboot_while_powered_on(self, mock_log, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.return_value = drac_constants.POWER_ON
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.reboot(task)
drac_power_state = drac_power.REVERSE_POWER_STATES[states.REBOOT]
mock_client.set_power_state.assert_called_once_with(drac_power_state)
self.assertFalse(mock_log.called)
@mock.patch.object(drac_power.LOG, 'warning', autospec=True)
def test_reboot_while_powered_on_timeout(self, mock_log,
mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.return_value = drac_constants.POWER_ON
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.reboot(task, timeout=42)
drac_power_state = drac_power.REVERSE_POWER_STATES[states.REBOOT]
mock_client.set_power_state.assert_called_once_with(drac_power_state)
self.assertTrue(mock_log.called)
def test_reboot_while_powered_off(self, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.side_effect = [drac_constants.POWER_OFF,
drac_constants.POWER_ON]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.reboot(task)
drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_ON]
mock_client.set_power_state.assert_called_once_with(drac_power_state)
@mock.patch('time.sleep', autospec=True)
def test_reboot_retries_success(self, mock_sleep, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.side_effect = [drac_constants.POWER_OFF,
drac_constants.POWER_OFF,
drac_constants.POWER_ON]
exc = drac_exceptions.DRACOperationFailed(
drac_messages=['The command failed to set RequestedState'])
mock_client.set_power_state.side_effect = [exc, None]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.reboot(task)
drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_ON]
self.assertEqual(2, mock_client.set_power_state.call_count)
mock_client.set_power_state.assert_has_calls(
[mock.call(drac_power_state),
mock.call(drac_power_state)])
@mock.patch('time.sleep', autospec=True)
def test_reboot_retries_fail(self, mock_sleep, mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.return_value = drac_constants.POWER_OFF
exc = drac_exceptions.DRACOperationFailed(
drac_messages=['The command failed to set RequestedState'])
mock_client.set_power_state.side_effect = exc
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
self.assertRaises(exception.DracOperationError,
task.driver.power.reboot, task)
self.assertEqual(drac_power.POWER_STATE_TRIES,
mock_client.set_power_state.call_count)
@mock.patch('time.sleep', autospec=True)
def test_reboot_retries_power_change_success(self, mock_sleep,
mock_get_drac_client):
mock_client = mock_get_drac_client.return_value
mock_client.get_power_state.side_effect = [drac_constants.POWER_OFF,
drac_constants.POWER_ON]
exc = drac_exceptions.DRACOperationFailed(
drac_messages=['The command failed to set RequestedState'])
mock_client.set_power_state.side_effect = [exc, None]
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.power.reboot(task)
self.assertEqual(2, mock_client.set_power_state.call_count)
drac_power_state1 = drac_power.REVERSE_POWER_STATES[states.POWER_ON]
drac_power_state2 = drac_power.REVERSE_POWER_STATES[states.REBOOT]
mock_client.set_power_state.assert_has_calls(
[mock.call(drac_power_state1),
mock.call(drac_power_state2)])

File diff suppressed because it is too large Load Diff

View File

@ -13,17 +13,12 @@
import collections
from oslo_utils import importutils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
INFO_DICT = db_utils.get_test_drac_info()
dracclient_job = importutils.try_import('dracclient.resources.job')
dracclient_raid = importutils.try_import('dracclient.resources.raid')
class BaseDracTest(db_base.DbTestCase):
def setUp(self):
@ -31,14 +26,15 @@ class BaseDracTest(db_base.DbTestCase):
self.config(enabled_hardware_types=['idrac', 'fake-hardware'],
enabled_boot_interfaces=[
'idrac-redfish-virtual-media', 'fake'],
enabled_power_interfaces=['idrac-wsman', 'fake'],
enabled_management_interfaces=['idrac-wsman', 'fake'],
enabled_power_interfaces=['idrac-redfish', 'fake'],
enabled_management_interfaces=['idrac-redfish', 'fake'],
enabled_inspect_interfaces=[
'idrac-wsman', 'fake', 'no-inspect'],
'idrac-redfish', 'fake', 'no-inspect'],
enabled_vendor_interfaces=[
'idrac-wsman', 'fake', 'no-vendor'],
enabled_raid_interfaces=['idrac-wsman', 'fake', 'no-raid'],
enabled_bios_interfaces=['idrac-wsman', 'no-bios'])
'idrac-redfish', 'fake', 'no-vendor'],
enabled_raid_interfaces=['idrac-redfish', 'fake',
'no-raid'],
enabled_bios_interfaces=['idrac-redfish', 'no-bios'])
class DictToObj(object):
@ -74,30 +70,6 @@ def dict_of_object(data):
return data
def make_job(job_dict):
tuple_class = dracclient_job.Job if dracclient_job else None
return dict_to_namedtuple(values=job_dict,
tuple_class=tuple_class)
def make_raid_controller(raid_controller_dict):
tuple_class = dracclient_raid.RAIDController if dracclient_raid else None
return dict_to_namedtuple(values=raid_controller_dict,
tuple_class=tuple_class)
def make_virtual_disk(virtual_disk_dict):
tuple_class = dracclient_raid.VirtualDisk if dracclient_raid else None
return dict_to_namedtuple(values=virtual_disk_dict,
tuple_class=tuple_class)
def make_physical_disk(physical_disk_dict):
tuple_class = dracclient_raid.PhysicalDisk if dracclient_raid else None
return dict_to_namedtuple(values=physical_disk_dict,
tuple_class=tuple_class)
def create_raid_setting(raid_settings_dict):
"""Returns the raid configuration tuple object"""
return dict_to_namedtuple(values=raid_settings_dict)

View File

@ -34,21 +34,17 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
self.config(enabled_hardware_types=['idrac'],
enabled_boot_interfaces=[
'idrac-redfish-virtual-media', 'ipxe', 'pxe'],
enabled_management_interfaces=[
'idrac', 'idrac-redfish', 'idrac-wsman'],
enabled_power_interfaces=[
'idrac', 'idrac-redfish', 'idrac-wsman'],
enabled_management_interfaces=['idrac-redfish'],
enabled_power_interfaces=['idrac-redfish'],
enabled_inspect_interfaces=[
'idrac', 'idrac-redfish', 'idrac-wsman', 'inspector',
'idrac-redfish', 'inspector',
'no-inspect'],
enabled_network_interfaces=['flat', 'neutron', 'noop'],
enabled_raid_interfaces=[
'idrac', 'idrac-wsman', 'idrac-redfish', 'no-raid',
'idrac-redfish', 'no-raid',
'agent'],
enabled_vendor_interfaces=[
'idrac', 'idrac-wsman', 'no-vendor'],
enabled_bios_interfaces=[
'idrac-wsman', 'idrac-redfish', 'no-bios'])
enabled_vendor_interfaces=['idrac-redfish', 'no-vendor'],
enabled_bios_interfaces=['idrac-redfish', 'no-bios'])
def _validate_interfaces(self, driver, **kwargs):
self.assertIsInstance(
@ -59,14 +55,14 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
kwargs.get('deploy', agent.AgentDeploy))
self.assertIsInstance(
driver.management,
kwargs.get('management', drac.management.DracWSManManagement))
kwargs.get('management', drac.management.DracRedfishManagement))
self.assertIsInstance(
driver.power,
kwargs.get('power', drac.power.DracWSManPower))
kwargs.get('power', drac.power.DracRedfishPower))
self.assertIsInstance(
driver.bios,
kwargs.get('bios', drac.bios.DracWSManBIOS))
kwargs.get('bios', drac.bios.DracRedfishBIOS))
self.assertIsInstance(
driver.console,
@ -74,7 +70,7 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(
driver.inspect,
kwargs.get('inspect', drac.inspect.DracWSManInspect))
kwargs.get('inspect', drac.inspect.DracRedfishInspect))
self.assertIsInstance(
driver.network,
@ -82,7 +78,7 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(
driver.raid,
kwargs.get('raid', drac.raid.DracWSManRAID))
kwargs.get('raid', drac.raid.DracRedfishRAID))
self.assertIsInstance(
driver.storage,
@ -90,7 +86,8 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(
driver.vendor,
kwargs.get('vendor', drac.vendor_passthru.DracWSManVendorPassthru))
kwargs.get('vendor',
drac.vendor_passthru.DracRedfishVendorPassthru))
def test_default_interfaces(self):
node = obj_utils.create_test_node(self.context, driver='idrac')
@ -130,22 +127,6 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
self._validate_interfaces(task.driver,
vendor=noop.NoVendor)
def test_override_with_idrac(self):
node = obj_utils.create_test_node(self.context, driver='idrac',
management_interface='idrac',
power_interface='idrac',
inspect_interface='idrac',
raid_interface='idrac',
vendor_interface='idrac')
with task_manager.acquire(self.context, node.id) as task:
self._validate_interfaces(
task.driver,
management=drac.management.DracManagement,
power=drac.power.DracPower,
inspect=drac.inspect.DracInspect,
raid=drac.raid.DracRAID,
vendor=drac.vendor_passthru.DracVendorPassthru)
def test_override_with_redfish_management_and_power(self):
node = obj_utils.create_test_node(self.context, driver='idrac',
management_interface='idrac-redfish',

View File

@ -17,36 +17,6 @@
"""This module provides mock 'specs' for third party modules that can be used
when needing to mock those third party modules"""
# python-dracclient
DRACCLIENT_SPEC = (
'client',
'constants',
'exceptions',
)
DRACCLIENT_CLIENT_MOD_SPEC = (
'DRACClient',
)
DRACCLIENT_CONSTANTS_MOD_SPEC = (
'POWER_OFF',
'POWER_ON',
'REBOOT',
'RebootRequired',
'RaidStatus'
)
DRACCLIENT_CONSTANTS_REBOOT_REQUIRED_MOD_SPEC = (
'true',
'optional',
'false'
)
DRACCLIENT_CONSTANTS_RAID_STATUS_MOD_SPEC = (
'jbod',
'raid'
)
# sushy_oem_idrac
SUSHY_OEM_IDRAC_MOD_SPEC = (
'PHYSICAL_DISK_STATE_MODE_RAID',

View File

@ -26,7 +26,6 @@ Current list of mocked libraries:
- proliantutils
- pysnmp
- scciclient
- python-dracclient
- sushy_oem_idrac
"""
@ -79,50 +78,6 @@ if not redfish:
if 'ironic.drivers.redfish' in sys.modules:
importlib.reload(sys.modules['ironic.drivers.modules.redfish'])
# attempt to load the external 'python-dracclient' library, which is required
# by the optional drivers.modules.drac module
dracclient = importutils.try_import('dracclient')
if not dracclient:
dracclient = mock.MagicMock(spec_set=mock_specs.DRACCLIENT_SPEC)
dracclient.client = mock.MagicMock(
spec_set=mock_specs.DRACCLIENT_CLIENT_MOD_SPEC)
dracclient.constants = mock.MagicMock(
spec_set=mock_specs.DRACCLIENT_CONSTANTS_MOD_SPEC,
POWER_OFF=mock.sentinel.POWER_OFF,
POWER_ON=mock.sentinel.POWER_ON,
REBOOT=mock.sentinel.REBOOT)
dracclient.constants.RebootRequired = mock.MagicMock(
spec_set=mock_specs.DRACCLIENT_CONSTANTS_REBOOT_REQUIRED_MOD_SPEC,
true=mock.sentinel.true,
optional=mock.sentinel.optional,
false=mock.sentinel.false)
dracclient.constants.RaidStatus = mock.MagicMock(
spec_set=mock_specs.DRACCLIENT_CONSTANTS_RAID_STATUS_MOD_SPEC,
jbod=mock.sentinel.jbod,
raid=mock.sentinel.raid)
sys.modules['dracclient'] = dracclient
sys.modules['dracclient.client'] = dracclient.client
sys.modules['dracclient.constants'] = dracclient.constants
sys.modules['dracclient.exceptions'] = dracclient.exceptions
dracclient.exceptions.BaseClientException = type('BaseClientException',
(Exception,), {})
dracclient.exceptions.DRACRequestFailed = type(
'DRACRequestFailed', (dracclient.exceptions.BaseClientException,), {})
class DRACOperationFailed(dracclient.exceptions.DRACRequestFailed):
def __init__(self, **kwargs):
super(DRACOperationFailed, self).__init__(
'DRAC operation failed. Messages: %(drac_messages)s' % kwargs)
dracclient.exceptions.DRACOperationFailed = DRACOperationFailed
# Now that the external library has been mocked, if anything had already
# loaded any of the drivers, reload them.
if 'ironic.drivers.modules.drac' in sys.modules:
importlib.reload(sys.modules['ironic.drivers.modules.drac'])
sushy_oem_idrac = importutils.try_import('sushy_oem_idrac')
if not sushy_oem_idrac:
raidmode = mock.sentinel.PHYSICAL_DISK_STATE_MODE_RAID

View File

@ -0,0 +1,5 @@
---
upgrade:
- |
The deprecated ``idrac-wsman`` and related ``idrac`` interface
aliases have been removed from the ``idrac`` hardware type.

View File

@ -63,7 +63,6 @@ ironic.dhcp =
ironic.hardware.interfaces.bios =
fake = ironic.drivers.modules.fake:FakeBIOS
idrac-redfish = ironic.drivers.modules.drac.bios:DracRedfishBIOS
idrac-wsman = ironic.drivers.modules.drac.bios:DracWSManBIOS
ilo = ironic.drivers.modules.ilo.bios:IloBIOS
irmc = ironic.drivers.modules.irmc.bios:IRMCBIOS
no-bios = ironic.drivers.modules.noop:NoBIOS
@ -108,9 +107,7 @@ ironic.hardware.interfaces.firmware =
ironic.hardware.interfaces.inspect =
agent = ironic.drivers.modules.inspector:AgentInspect
fake = ironic.drivers.modules.fake:FakeInspect
idrac = ironic.drivers.modules.drac.inspect:DracInspect
idrac-redfish = ironic.drivers.modules.drac.inspect:DracRedfishInspect
idrac-wsman = ironic.drivers.modules.drac.inspect:DracWSManInspect
ilo = ironic.drivers.modules.ilo.inspect:IloInspect
inspector = ironic.drivers.modules.inspector:Inspector
irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect
@ -119,9 +116,7 @@ ironic.hardware.interfaces.inspect =
ironic.hardware.interfaces.management =
fake = ironic.drivers.modules.fake:FakeManagement
idrac = ironic.drivers.modules.drac.management:DracManagement
idrac-redfish = ironic.drivers.modules.drac.management:DracRedfishManagement
idrac-wsman = ironic.drivers.modules.drac.management:DracWSManManagement
ilo = ironic.drivers.modules.ilo.management:IloManagement
ilo5 = ironic.drivers.modules.ilo.management:Ilo5Management
intel-ipmitool = ironic.drivers.modules.intel_ipmi.management:IntelIPMIManagement
@ -138,9 +133,7 @@ ironic.hardware.interfaces.network =
ironic.hardware.interfaces.power =
agent = ironic.drivers.modules.agent_power:AgentPower
fake = ironic.drivers.modules.fake:FakePower
idrac = ironic.drivers.modules.drac.power:DracPower
idrac-redfish = ironic.drivers.modules.drac.power:DracRedfishPower
idrac-wsman = ironic.drivers.modules.drac.power:DracWSManPower
ilo = ironic.drivers.modules.ilo.power:IloPower
ipmitool = ironic.drivers.modules.ipmitool:IPMIPower
irmc = ironic.drivers.modules.irmc.power:IRMCPower
@ -150,9 +143,7 @@ ironic.hardware.interfaces.power =
ironic.hardware.interfaces.raid =
agent = ironic.drivers.modules.agent:AgentRAID
fake = ironic.drivers.modules.fake:FakeRAID
idrac = ironic.drivers.modules.drac.raid:DracRAID
idrac-redfish = ironic.drivers.modules.drac.raid:DracRedfishRAID
idrac-wsman = ironic.drivers.modules.drac.raid:DracWSManRAID
ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID
irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
no-raid = ironic.drivers.modules.noop:NoRAID
@ -171,8 +162,6 @@ ironic.hardware.interfaces.storage =
ironic.hardware.interfaces.vendor =
fake = ironic.drivers.modules.fake:FakeVendorB
idrac = ironic.drivers.modules.drac.vendor_passthru:DracVendorPassthru
idrac-wsman = ironic.drivers.modules.drac.vendor_passthru:DracWSManVendorPassthru
idrac-redfish = ironic.drivers.modules.drac.vendor_passthru:DracRedfishVendorPassthru
ilo = ironic.drivers.modules.ilo.vendor:VendorPassthru
irmc = ironic.drivers.modules.irmc.vendor:IRMCVendorPassthru