Remove deprecated idrac wsman driver interfaces
Change-Id: I70738db25fdf9902575ac92195c3a40f1d7a0976
This commit is contained in:
parent
f14794ca2e
commit
578f24bf18
@ -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
|
||||
system’s 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
|
||||
-------------------------
|
||||
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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
|
@ -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.")
|
||||
|
@ -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)
|
@ -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.")
|
||||
|
@ -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
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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()
|
@ -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])
|
@ -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()
|
||||
|
@ -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)
|
@ -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',
|
||||
|
@ -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)
|
@ -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
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The deprecated ``idrac-wsman`` and related ``idrac`` interface
|
||||
aliases have been removed from the ``idrac`` hardware type.
|
11
setup.cfg
11
setup.cfg
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user