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
|
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
|
The integrated Dell Remote Access Controller (iDRAC_) is an out-of-band
|
||||||
management platform on Dell EMC servers, and is supported directly by
|
management platform on Dell EMC servers, and is supported directly by
|
||||||
the ``idrac`` hardware type. This driver uses the Dell Web Services for
|
the ``idrac`` hardware type. This driver utilizes the Distributed
|
||||||
Management (WSMAN) protocol and the standard Distributed Management Task
|
Management Task Force (DMTF) Redfish protocol to perform all of it's
|
||||||
Force (DMTF) Redfish protocol to perform all of its functions.
|
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``
|
iDRAC_ hardware is also supported by the generic ``ipmi`` and ``redfish``
|
||||||
hardware types, though with smaller feature sets.
|
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
|
* `Management Interface`_: Boot device and firmware management
|
||||||
* Power Interface: Power management
|
* Power Interface: Power management
|
||||||
* `RAID Interface`_: RAID controller and disk management
|
* `RAID Interface`_: RAID controller and disk management
|
||||||
* `Vendor Interface`_: BIOS management (WSMAN) and eject virtual media
|
* `Vendor Interface`_: eject virtual media (Redfish)
|
||||||
(Redfish)
|
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The ``idrac`` hardware type requires the ``python-dracclient`` 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-wsman`` interface implementation, for example::
|
|
||||||
|
|
||||||
sudo pip install 'python-dracclient>=3.1.0'
|
|
||||||
|
|
||||||
Additionally, the ``idrac`` hardware type requires the ``sushy`` library
|
|
||||||
to be installed on the ironic conductor node(s) if an Ironic node is
|
to be installed on the ironic conductor node(s) if an Ironic node is
|
||||||
configured to use an ``idrac-redfish`` interface implementation, for example::
|
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
|
Enabling
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The iDRAC driver supports WSMAN for the bios, inspect, management, power,
|
The iDRAC driver supports Redfish for the bios, inspect, management, power,
|
||||||
raid, and vendor interfaces. In addition, it supports Redfish for
|
and raid interfaces.
|
||||||
the bios, inspect, management, power, and raid interfaces. The iDRAC driver
|
|
||||||
allows you to mix and match WSMAN and Redfish interfaces.
|
|
||||||
|
|
||||||
The ``idrac-wsman`` implementation must be enabled to use WSMAN for
|
The ``idrac-redfish`` implementation must be enabled
|
||||||
an interface. The ``idrac-redfish`` implementation must be enabled
|
|
||||||
to use Redfish for an interface.
|
to use Redfish for an interface.
|
||||||
|
|
||||||
To enable the ``idrac`` hardware type with the minimum interfaces,
|
To enable the ``idrac`` hardware type, add the following to your
|
||||||
all using WSMAN, add the following to your ``/etc/ironic/ironic.conf``:
|
``/etc/ironic/ironic.conf``:
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
enabled_hardware_types=idrac
|
enabled_hardware_types=idrac
|
||||||
enabled_management_interfaces=idrac-wsman
|
enabled_management_interfaces=idrac-redfish
|
||||||
enabled_power_interfaces=idrac-wsman
|
enabled_power_interfaces=idrac-redfish
|
||||||
|
|
||||||
To enable all optional features (BIOS, inspection, RAID, and vendor passthru)
|
To enable all optional features (BIOS, inspection, RAID, and vendor passthru),
|
||||||
using Redfish where it is supported and WSMAN where not, use the
|
use the following configuration:
|
||||||
following configuration:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
@ -100,43 +84,30 @@ order:
|
|||||||
================ ===================================================
|
================ ===================================================
|
||||||
Interface Supported Implementations
|
Interface Supported Implementations
|
||||||
================ ===================================================
|
================ ===================================================
|
||||||
``bios`` ``idrac-wsman``, ``idrac-redfish``, ``no-bios``
|
``bios`` ``idrac-redfish``, ``no-bios``
|
||||||
``boot`` ``ipxe``, ``pxe``, ``idrac-redfish-virtual-media``
|
``boot`` ``ipxe``, ``pxe``, ``idrac-redfish-virtual-media``
|
||||||
``console`` ``no-console``
|
``console`` ``no-console``
|
||||||
``deploy`` ``direct``, ``ansible``, ``ramdisk``
|
``deploy`` ``direct``, ``ansible``, ``ramdisk``
|
||||||
``firmware`` ``redfish``, ``no-firmware``
|
``firmware`` ``redfish``, ``no-firmware``
|
||||||
``inspect`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``,
|
``inspect`` ``idrac-redfish``,
|
||||||
``inspector``, ``no-inspect``
|
``inspector``, ``no-inspect``
|
||||||
``management`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
|
``management`` ``idrac-redfish``
|
||||||
``network`` ``flat``, ``neutron``, ``noop``
|
``network`` ``flat``, ``neutron``, ``noop``
|
||||||
``power`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``
|
``power`` ``idrac-redfish``
|
||||||
``raid`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``, ``no-raid``
|
``raid`` ``idrac-redfish``, ``no-raid``
|
||||||
``rescue`` ``no-rescue``, ``agent``
|
``rescue`` ``no-rescue``, ``agent``
|
||||||
``storage`` ``noop``, ``cinder``, ``external``
|
``storage`` ``noop``, ``cinder``, ``external``
|
||||||
``vendor`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``,
|
``vendor`` ``idrac-redfish``,
|
||||||
``no-vendor``
|
``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
|
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
|
in the Ironic node's ``driver_info`` field to communicate with the bare
|
||||||
metal system's iDRAC.
|
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:
|
The Redfish protocol requires the following properties:
|
||||||
|
|
||||||
* ``redfish_username``: The Redfish user name to use when
|
* ``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`.
|
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
|
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``
|
The following command enrolls a bare metal node with the ``idrac``
|
||||||
hardware type using Redfish for all interfaces:
|
hardware type using Redfish for all interfaces:
|
||||||
|
|
||||||
@ -187,29 +142,6 @@ hardware type using Redfish for all interfaces:
|
|||||||
--raid-interface idrac-redfish \
|
--raid-interface idrac-redfish \
|
||||||
--vendor-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
|
BIOS Interface
|
||||||
==============
|
==============
|
||||||
|
|
||||||
@ -252,7 +184,7 @@ Inspect Interface
|
|||||||
The Dell iDRAC out-of-band inspection process catalogs all the same
|
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
|
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.
|
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.
|
without affecting the operation of the system being inspected.
|
||||||
|
|
||||||
The inspection discovers the following properties:
|
The inspection discovers the following properties:
|
||||||
@ -267,8 +199,6 @@ Extra capabilities:
|
|||||||
* ``pci_gpu_devices``: number of GPU devices connected to the bare metal.
|
* ``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.
|
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``
|
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
|
on the ports. The user should ensure that ``pxe_enabled`` is set correctly on
|
||||||
the ports following inspection with the ``idrac-redfish`` inspect interface.
|
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,
|
* Converting non-RAID disks to RAID mode if there are any,
|
||||||
* Clearing foreign configuration, if any, after deleting virtual disks.
|
* 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:
|
interface implementation:
|
||||||
|
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
@ -633,223 +563,6 @@ Or using ``sushy`` with Redfish:
|
|||||||
Vendor Interface
|
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
|
idrac-redfish
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@ -896,27 +609,6 @@ settings.
|
|||||||
.. _Ironic_RAID: https://docs.openstack.org/ironic/latest/admin/raid.html
|
.. _Ironic_RAID: https://docs.openstack.org/ironic/latest/admin/raid.html
|
||||||
.. _iDRAC: https://www.dell.com/idracmanuals
|
.. _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
|
Timeout when powering off
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ pysnmp-lextudio>=5.0.0 # BSD
|
|||||||
pyasn1>=0.5.1 # BSD
|
pyasn1>=0.5.1 # BSD
|
||||||
pyasn1-modules>=0.3.0 # BSD
|
pyasn1-modules>=0.3.0 # BSD
|
||||||
python-scciclient>=0.16.0,<0.17.0
|
python-scciclient>=0.16.0,<0.17.0
|
||||||
python-dracclient>=5.1.0,<9.0.0
|
|
||||||
|
|
||||||
# Ansible-deploy interface
|
# Ansible-deploy interface
|
||||||
ansible>=2.7
|
ansible>=2.7
|
||||||
|
@ -47,20 +47,19 @@ class IDRACHardware(generic.GenericHardware):
|
|||||||
@property
|
@property
|
||||||
def supported_management_interfaces(self):
|
def supported_management_interfaces(self):
|
||||||
"""List of supported management interfaces."""
|
"""List of supported management interfaces."""
|
||||||
return [management.DracWSManManagement, management.DracManagement,
|
return [management.DracRedfishManagement]
|
||||||
management.DracRedfishManagement]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_power_interfaces(self):
|
def supported_power_interfaces(self):
|
||||||
"""List of supported power interfaces."""
|
"""List of supported power interfaces."""
|
||||||
return [power.DracWSManPower, power.DracPower, power.DracRedfishPower]
|
return [power.DracRedfishPower]
|
||||||
|
|
||||||
# Optional hardware interfaces
|
# Optional hardware interfaces
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_bios_interfaces(self):
|
def supported_bios_interfaces(self):
|
||||||
"""List of supported bios interfaces."""
|
"""List of supported bios interfaces."""
|
||||||
return [bios.DracWSManBIOS, bios.DracRedfishBIOS, noop.NoBIOS]
|
return [bios.DracRedfishBIOS, noop.NoBIOS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_firmware_interfaces(self):
|
def supported_firmware_interfaces(self):
|
||||||
@ -72,20 +71,17 @@ class IDRACHardware(generic.GenericHardware):
|
|||||||
# Inspector support should have a higher priority than NoInspect
|
# Inspector support should have a higher priority than NoInspect
|
||||||
# if it is enabled by an operator (implying that the service is
|
# if it is enabled by an operator (implying that the service is
|
||||||
# installed).
|
# installed).
|
||||||
return [drac_inspect.DracWSManInspect, drac_inspect.DracInspect,
|
return [drac_inspect.DracRedfishInspect] + super(
|
||||||
drac_inspect.DracRedfishInspect] + super(
|
|
||||||
IDRACHardware, self).supported_inspect_interfaces
|
IDRACHardware, self).supported_inspect_interfaces
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_raid_interfaces(self):
|
def supported_raid_interfaces(self):
|
||||||
"""List of supported raid interfaces."""
|
"""List of supported raid interfaces."""
|
||||||
return [raid.DracWSManRAID, raid.DracRAID,
|
return [raid.DracRedfishRAID] + super(
|
||||||
raid.DracRedfishRAID] + super(
|
|
||||||
IDRACHardware, self).supported_raid_interfaces
|
IDRACHardware, self).supported_raid_interfaces
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_vendor_interfaces(self):
|
def supported_vendor_interfaces(self):
|
||||||
"""List of supported vendor interfaces."""
|
"""List of supported vendor interfaces."""
|
||||||
return [vendor_passthru.DracWSManVendorPassthru,
|
return [vendor_passthru.DracRedfishVendorPassthru,
|
||||||
vendor_passthru.DracVendorPassthru,
|
noop.NoVendor]
|
||||||
vendor_passthru.DracRedfishVendorPassthru, noop.NoVendor]
|
|
||||||
|
@ -15,31 +15,7 @@
|
|||||||
DRAC BIOS configuration specific methods
|
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.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):
|
class DracRedfishBIOS(redfish_bios.RedfishBIOS):
|
||||||
@ -50,600 +26,3 @@ class DracRedfishBIOS(redfish_bios.RedfishBIOS):
|
|||||||
specific incompatibilities and introduction of vendor value added
|
specific incompatibilities and introduction of vendor value added
|
||||||
should be implemented by this class.
|
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
|
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 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.drac import utils as drac_utils
|
||||||
from ironic.drivers.modules import inspect_utils
|
from ironic.drivers.modules import inspect_utils
|
||||||
from ironic.drivers.modules.redfish import inspect as redfish_inspect
|
from ironic.drivers.modules.redfish import inspect as redfish_inspect
|
||||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
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'),
|
_PXE_DEV_ENABLED_INTERFACES = [('PxeDev1EnDis', 'PxeDev1Interface'),
|
||||||
('PxeDev2EnDis', 'PxeDev2Interface'),
|
('PxeDev2EnDis', 'PxeDev2Interface'),
|
||||||
@ -63,8 +46,8 @@ class DracRedfishInspect(redfish_inspect.RedfishInspect):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# Ensure we create a port for every NIC port found for consistency
|
# 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
|
# with our previous WSMAN inspect behavior and to work around a bug
|
||||||
# versions of the firmware where the port state is not being
|
# in some versions of the firmware where the port state is not being
|
||||||
# reported correctly.
|
# reported correctly.
|
||||||
|
|
||||||
ethernet_interfaces_mac = list(self._get_mac_address(task).values())
|
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]
|
pxe_port_macs = [mac for mac in pxe_port_macs_list]
|
||||||
|
|
||||||
return pxe_port_macs
|
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 json
|
||||||
import time
|
|
||||||
|
|
||||||
from ironic_lib import metrics_utils
|
from ironic_lib import metrics_utils
|
||||||
import jsonschema
|
import jsonschema
|
||||||
from jsonschema import exceptions as json_schema_exc
|
from jsonschema import exceptions as json_schema_exc
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
|
||||||
import sushy
|
import sushy
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
@ -36,20 +34,15 @@ from ironic.common.i18n import _
|
|||||||
from ironic.common import molds
|
from ironic.common import molds
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
from ironic.conductor import periodics
|
from ironic.conductor import periodics
|
||||||
from ironic.conductor import task_manager
|
|
||||||
from ironic.conductor import utils as manager_utils
|
from ironic.conductor import utils as manager_utils
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
from ironic.drivers.modules import deploy_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 utils as drac_utils
|
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 management as redfish_management
|
||||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
from ironic.drivers.modules.redfish import utils as redfish_utils
|
||||||
|
|
||||||
|
|
||||||
drac_exceptions = importutils.try_import('dracclient.exceptions')
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
METRICS = metrics_utils.get_metrics_logger(__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):
|
def _is_boot_order_flexibly_programmable(persistent, bios_settings):
|
||||||
return persistent and 'SetBootOrderFqdd1' in 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})
|
_("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):
|
class DracRedfishManagement(redfish_management.RedfishManagement):
|
||||||
"""iDRAC Redfish interface for management-related actions."""
|
"""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 '
|
LOG.warning('iDRAC on node %(node)s does not support '
|
||||||
'clearing Lifecycle Controller job queue '
|
'clearing Lifecycle Controller job queue '
|
||||||
'using the idrac-redfish driver. '
|
'using the idrac-redfish driver. '
|
||||||
'If using iDRAC9, consider upgrading firmware. '
|
'If using iDRAC9, consider upgrading firmware.',
|
||||||
'If using iDRAC8, consider switching to '
|
|
||||||
'idrac-wsman for management interface if '
|
|
||||||
'possible.',
|
|
||||||
{'node': task.node.uuid})
|
{'node': task.node.uuid})
|
||||||
if task.node.provision_state != states.VERIFYING:
|
if task.node.provision_state != states.VERIFYING:
|
||||||
raise
|
raise
|
||||||
@ -661,10 +461,7 @@ class DracRedfishManagement(redfish_management.RedfishManagement):
|
|||||||
if "Oem/Dell/DelliDRACCardService is missing" in str(exc):
|
if "Oem/Dell/DelliDRACCardService is missing" in str(exc):
|
||||||
LOG.warning('iDRAC on node %(node)s does not support '
|
LOG.warning('iDRAC on node %(node)s does not support '
|
||||||
'iDRAC reset using the idrac-redfish driver. '
|
'iDRAC reset using the idrac-redfish driver. '
|
||||||
'If using iDRAC9, consider upgrading firmware. '
|
'If using iDRAC9, consider upgrading firmware. ',
|
||||||
'If using iDRAC8, consider switching to '
|
|
||||||
'idrac-wsman for management interface if '
|
|
||||||
'possible.',
|
|
||||||
{'node': task.node.uuid})
|
{'node': task.node.uuid})
|
||||||
if task.node.provision_state != states.VERIFYING:
|
if task.node.provision_state != states.VERIFYING:
|
||||||
raise
|
raise
|
||||||
@ -686,181 +483,3 @@ class DracRedfishManagement(redfish_management.RedfishManagement):
|
|||||||
self.clear_job_queue(task)
|
self.clear_job_queue(task)
|
||||||
LOG.info('Reset iDRAC to known good state for node %(node)s',
|
LOG.info('Reset iDRAC to known good state for node %(node)s',
|
||||||
{'node': task.node.uuid})
|
{'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
|
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
|
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):
|
class DracRedfishPower(redfish_power.RedfishPower):
|
||||||
"""iDRAC Redfish interface for power-related actions.
|
"""iDRAC Redfish interface for power-related actions.
|
||||||
@ -187,87 +28,3 @@ class DracRedfishPower(redfish_power.RedfishPower):
|
|||||||
should be implemented by this class.
|
should be implemented by this class.
|
||||||
"""
|
"""
|
||||||
pass
|
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
|
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
|
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):
|
class DracRedfishVendorPassthru(redfish_vendor.RedfishVendorPassthru):
|
||||||
"""iDRAC Redfish interface for vendor_passthru.
|
"""iDRAC Redfish interface for vendor_passthru.
|
||||||
|
@ -89,12 +89,6 @@ def get_test_ilo_info():
|
|||||||
|
|
||||||
def get_test_drac_info():
|
def get_test_drac_info():
|
||||||
return {
|
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_address": "1.2.3.4",
|
||||||
"redfish_system_id": "/redfish/v1/Systems/System.Embedded.1",
|
"redfish_system_id": "/redfish/v1/Systems/System.Embedded.1",
|
||||||
"redfish_username": "admin",
|
"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 unittest import mock
|
||||||
|
|
||||||
from dracclient import exceptions as drac_exceptions
|
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import sushy
|
import sushy
|
||||||
|
|
||||||
from ironic.common import exception
|
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
from ironic.conductor import task_manager
|
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.drac import inspect as drac_inspect
|
||||||
from ironic.drivers.modules import inspect_utils
|
from ironic.drivers.modules import inspect_utils
|
||||||
from ironic.drivers.modules.redfish import inspect as redfish_inspect
|
from ironic.drivers.modules.redfish import inspect as redfish_inspect
|
||||||
from ironic.drivers.modules.redfish import utils as redfish_utils
|
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.drivers.modules.drac import utils as test_utils
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
INFO_DICT = test_utils.INFO_DICT
|
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):
|
class DracRedfishInspectionTestCase(test_utils.BaseDracTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(DracRedfishInspectionTestCase, self).setUp()
|
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
|
import json
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from oslo_utils import importutils
|
|
||||||
import sushy
|
import sushy
|
||||||
|
|
||||||
import ironic.common.boot_devices
|
import ironic.common.boot_devices
|
||||||
@ -34,808 +33,16 @@ from ironic.conductor import periodics
|
|||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.conductor import utils as manager_utils
|
from ironic.conductor import utils as manager_utils
|
||||||
from ironic.drivers.modules import deploy_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 management as drac_mgmt
|
||||||
from ironic.drivers.modules.drac import utils as drac_utils
|
from ironic.drivers.modules.drac import utils as drac_utils
|
||||||
from ironic.drivers.modules.redfish import utils as redfish_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.drivers.modules.drac import utils as test_utils
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
dracclient_exceptions = importutils.try_import('dracclient.exceptions')
|
|
||||||
|
|
||||||
INFO_DICT = test_utils.INFO_DICT
|
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):
|
class DracRedfishManagementTestCase(test_utils.BaseDracTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -1453,10 +660,7 @@ class DracRedfishManagementTestCase(test_utils.BaseDracTest):
|
|||||||
'iDRAC on node %(node)s does not support '
|
'iDRAC on node %(node)s does not support '
|
||||||
'clearing Lifecycle Controller job queue '
|
'clearing Lifecycle Controller job queue '
|
||||||
'using the idrac-redfish driver. '
|
'using the idrac-redfish driver. '
|
||||||
'If using iDRAC9, consider upgrading firmware. '
|
'If using iDRAC9, consider upgrading firmware.',
|
||||||
'If using iDRAC8, consider switching to '
|
|
||||||
'idrac-wsman for management interface if '
|
|
||||||
'possible.',
|
|
||||||
{'node': task.node.uuid})
|
{'node': task.node.uuid})
|
||||||
|
|
||||||
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
@mock.patch.object(drac_mgmt, 'LOG', autospec=True)
|
||||||
@ -1511,10 +715,7 @@ class DracRedfishManagementTestCase(test_utils.BaseDracTest):
|
|||||||
mock_log.warning.assert_called_once_with(
|
mock_log.warning.assert_called_once_with(
|
||||||
'iDRAC on node %(node)s does not support '
|
'iDRAC on node %(node)s does not support '
|
||||||
'iDRAC reset using the idrac-redfish driver. '
|
'iDRAC reset using the idrac-redfish driver. '
|
||||||
'If using iDRAC9, consider upgrading firmware. '
|
'If using iDRAC9, consider upgrading firmware. ',
|
||||||
'If using iDRAC8, consider switching to '
|
|
||||||
'idrac-wsman for management interface if '
|
|
||||||
'possible.',
|
|
||||||
{'node': task.node.uuid})
|
{'node': task.node.uuid})
|
||||||
|
|
||||||
@mock.patch.object(redfish_utils, 'wait_until_get_system_ready',
|
@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
|
import collections
|
||||||
|
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
|
|
||||||
|
|
||||||
INFO_DICT = db_utils.get_test_drac_info()
|
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):
|
class BaseDracTest(db_base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -31,14 +26,15 @@ class BaseDracTest(db_base.DbTestCase):
|
|||||||
self.config(enabled_hardware_types=['idrac', 'fake-hardware'],
|
self.config(enabled_hardware_types=['idrac', 'fake-hardware'],
|
||||||
enabled_boot_interfaces=[
|
enabled_boot_interfaces=[
|
||||||
'idrac-redfish-virtual-media', 'fake'],
|
'idrac-redfish-virtual-media', 'fake'],
|
||||||
enabled_power_interfaces=['idrac-wsman', 'fake'],
|
enabled_power_interfaces=['idrac-redfish', 'fake'],
|
||||||
enabled_management_interfaces=['idrac-wsman', 'fake'],
|
enabled_management_interfaces=['idrac-redfish', 'fake'],
|
||||||
enabled_inspect_interfaces=[
|
enabled_inspect_interfaces=[
|
||||||
'idrac-wsman', 'fake', 'no-inspect'],
|
'idrac-redfish', 'fake', 'no-inspect'],
|
||||||
enabled_vendor_interfaces=[
|
enabled_vendor_interfaces=[
|
||||||
'idrac-wsman', 'fake', 'no-vendor'],
|
'idrac-redfish', 'fake', 'no-vendor'],
|
||||||
enabled_raid_interfaces=['idrac-wsman', 'fake', 'no-raid'],
|
enabled_raid_interfaces=['idrac-redfish', 'fake',
|
||||||
enabled_bios_interfaces=['idrac-wsman', 'no-bios'])
|
'no-raid'],
|
||||||
|
enabled_bios_interfaces=['idrac-redfish', 'no-bios'])
|
||||||
|
|
||||||
|
|
||||||
class DictToObj(object):
|
class DictToObj(object):
|
||||||
@ -74,30 +70,6 @@ def dict_of_object(data):
|
|||||||
return 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):
|
def create_raid_setting(raid_settings_dict):
|
||||||
"""Returns the raid configuration tuple object"""
|
"""Returns the raid configuration tuple object"""
|
||||||
return dict_to_namedtuple(values=raid_settings_dict)
|
return dict_to_namedtuple(values=raid_settings_dict)
|
||||||
|
@ -34,21 +34,17 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
|||||||
self.config(enabled_hardware_types=['idrac'],
|
self.config(enabled_hardware_types=['idrac'],
|
||||||
enabled_boot_interfaces=[
|
enabled_boot_interfaces=[
|
||||||
'idrac-redfish-virtual-media', 'ipxe', 'pxe'],
|
'idrac-redfish-virtual-media', 'ipxe', 'pxe'],
|
||||||
enabled_management_interfaces=[
|
enabled_management_interfaces=['idrac-redfish'],
|
||||||
'idrac', 'idrac-redfish', 'idrac-wsman'],
|
enabled_power_interfaces=['idrac-redfish'],
|
||||||
enabled_power_interfaces=[
|
|
||||||
'idrac', 'idrac-redfish', 'idrac-wsman'],
|
|
||||||
enabled_inspect_interfaces=[
|
enabled_inspect_interfaces=[
|
||||||
'idrac', 'idrac-redfish', 'idrac-wsman', 'inspector',
|
'idrac-redfish', 'inspector',
|
||||||
'no-inspect'],
|
'no-inspect'],
|
||||||
enabled_network_interfaces=['flat', 'neutron', 'noop'],
|
enabled_network_interfaces=['flat', 'neutron', 'noop'],
|
||||||
enabled_raid_interfaces=[
|
enabled_raid_interfaces=[
|
||||||
'idrac', 'idrac-wsman', 'idrac-redfish', 'no-raid',
|
'idrac-redfish', 'no-raid',
|
||||||
'agent'],
|
'agent'],
|
||||||
enabled_vendor_interfaces=[
|
enabled_vendor_interfaces=['idrac-redfish', 'no-vendor'],
|
||||||
'idrac', 'idrac-wsman', 'no-vendor'],
|
enabled_bios_interfaces=['idrac-redfish', 'no-bios'])
|
||||||
enabled_bios_interfaces=[
|
|
||||||
'idrac-wsman', 'idrac-redfish', 'no-bios'])
|
|
||||||
|
|
||||||
def _validate_interfaces(self, driver, **kwargs):
|
def _validate_interfaces(self, driver, **kwargs):
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
@ -59,14 +55,14 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
|||||||
kwargs.get('deploy', agent.AgentDeploy))
|
kwargs.get('deploy', agent.AgentDeploy))
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.management,
|
driver.management,
|
||||||
kwargs.get('management', drac.management.DracWSManManagement))
|
kwargs.get('management', drac.management.DracRedfishManagement))
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.power,
|
driver.power,
|
||||||
kwargs.get('power', drac.power.DracWSManPower))
|
kwargs.get('power', drac.power.DracRedfishPower))
|
||||||
|
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.bios,
|
driver.bios,
|
||||||
kwargs.get('bios', drac.bios.DracWSManBIOS))
|
kwargs.get('bios', drac.bios.DracRedfishBIOS))
|
||||||
|
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.console,
|
driver.console,
|
||||||
@ -74,7 +70,7 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.inspect,
|
driver.inspect,
|
||||||
kwargs.get('inspect', drac.inspect.DracWSManInspect))
|
kwargs.get('inspect', drac.inspect.DracRedfishInspect))
|
||||||
|
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.network,
|
driver.network,
|
||||||
@ -82,7 +78,7 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.raid,
|
driver.raid,
|
||||||
kwargs.get('raid', drac.raid.DracWSManRAID))
|
kwargs.get('raid', drac.raid.DracRedfishRAID))
|
||||||
|
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.storage,
|
driver.storage,
|
||||||
@ -90,7 +86,8 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
|||||||
|
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
driver.vendor,
|
driver.vendor,
|
||||||
kwargs.get('vendor', drac.vendor_passthru.DracWSManVendorPassthru))
|
kwargs.get('vendor',
|
||||||
|
drac.vendor_passthru.DracRedfishVendorPassthru))
|
||||||
|
|
||||||
def test_default_interfaces(self):
|
def test_default_interfaces(self):
|
||||||
node = obj_utils.create_test_node(self.context, driver='idrac')
|
node = obj_utils.create_test_node(self.context, driver='idrac')
|
||||||
@ -130,22 +127,6 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
|
|||||||
self._validate_interfaces(task.driver,
|
self._validate_interfaces(task.driver,
|
||||||
vendor=noop.NoVendor)
|
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):
|
def test_override_with_redfish_management_and_power(self):
|
||||||
node = obj_utils.create_test_node(self.context, driver='idrac',
|
node = obj_utils.create_test_node(self.context, driver='idrac',
|
||||||
management_interface='idrac-redfish',
|
management_interface='idrac-redfish',
|
||||||
|
@ -17,36 +17,6 @@
|
|||||||
"""This module provides mock 'specs' for third party modules that can be used
|
"""This module provides mock 'specs' for third party modules that can be used
|
||||||
when needing to mock those third party modules"""
|
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
|
||||||
SUSHY_OEM_IDRAC_MOD_SPEC = (
|
SUSHY_OEM_IDRAC_MOD_SPEC = (
|
||||||
'PHYSICAL_DISK_STATE_MODE_RAID',
|
'PHYSICAL_DISK_STATE_MODE_RAID',
|
||||||
|
@ -26,7 +26,6 @@ Current list of mocked libraries:
|
|||||||
- proliantutils
|
- proliantutils
|
||||||
- pysnmp
|
- pysnmp
|
||||||
- scciclient
|
- scciclient
|
||||||
- python-dracclient
|
|
||||||
- sushy_oem_idrac
|
- sushy_oem_idrac
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -79,50 +78,6 @@ if not redfish:
|
|||||||
if 'ironic.drivers.redfish' in sys.modules:
|
if 'ironic.drivers.redfish' in sys.modules:
|
||||||
importlib.reload(sys.modules['ironic.drivers.modules.redfish'])
|
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')
|
sushy_oem_idrac = importutils.try_import('sushy_oem_idrac')
|
||||||
if not sushy_oem_idrac:
|
if not sushy_oem_idrac:
|
||||||
raidmode = mock.sentinel.PHYSICAL_DISK_STATE_MODE_RAID
|
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 =
|
ironic.hardware.interfaces.bios =
|
||||||
fake = ironic.drivers.modules.fake:FakeBIOS
|
fake = ironic.drivers.modules.fake:FakeBIOS
|
||||||
idrac-redfish = ironic.drivers.modules.drac.bios:DracRedfishBIOS
|
idrac-redfish = ironic.drivers.modules.drac.bios:DracRedfishBIOS
|
||||||
idrac-wsman = ironic.drivers.modules.drac.bios:DracWSManBIOS
|
|
||||||
ilo = ironic.drivers.modules.ilo.bios:IloBIOS
|
ilo = ironic.drivers.modules.ilo.bios:IloBIOS
|
||||||
irmc = ironic.drivers.modules.irmc.bios:IRMCBIOS
|
irmc = ironic.drivers.modules.irmc.bios:IRMCBIOS
|
||||||
no-bios = ironic.drivers.modules.noop:NoBIOS
|
no-bios = ironic.drivers.modules.noop:NoBIOS
|
||||||
@ -108,9 +107,7 @@ ironic.hardware.interfaces.firmware =
|
|||||||
ironic.hardware.interfaces.inspect =
|
ironic.hardware.interfaces.inspect =
|
||||||
agent = ironic.drivers.modules.inspector:AgentInspect
|
agent = ironic.drivers.modules.inspector:AgentInspect
|
||||||
fake = ironic.drivers.modules.fake:FakeInspect
|
fake = ironic.drivers.modules.fake:FakeInspect
|
||||||
idrac = ironic.drivers.modules.drac.inspect:DracInspect
|
|
||||||
idrac-redfish = ironic.drivers.modules.drac.inspect:DracRedfishInspect
|
idrac-redfish = ironic.drivers.modules.drac.inspect:DracRedfishInspect
|
||||||
idrac-wsman = ironic.drivers.modules.drac.inspect:DracWSManInspect
|
|
||||||
ilo = ironic.drivers.modules.ilo.inspect:IloInspect
|
ilo = ironic.drivers.modules.ilo.inspect:IloInspect
|
||||||
inspector = ironic.drivers.modules.inspector:Inspector
|
inspector = ironic.drivers.modules.inspector:Inspector
|
||||||
irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect
|
irmc = ironic.drivers.modules.irmc.inspect:IRMCInspect
|
||||||
@ -119,9 +116,7 @@ ironic.hardware.interfaces.inspect =
|
|||||||
|
|
||||||
ironic.hardware.interfaces.management =
|
ironic.hardware.interfaces.management =
|
||||||
fake = ironic.drivers.modules.fake:FakeManagement
|
fake = ironic.drivers.modules.fake:FakeManagement
|
||||||
idrac = ironic.drivers.modules.drac.management:DracManagement
|
|
||||||
idrac-redfish = ironic.drivers.modules.drac.management:DracRedfishManagement
|
idrac-redfish = ironic.drivers.modules.drac.management:DracRedfishManagement
|
||||||
idrac-wsman = ironic.drivers.modules.drac.management:DracWSManManagement
|
|
||||||
ilo = ironic.drivers.modules.ilo.management:IloManagement
|
ilo = ironic.drivers.modules.ilo.management:IloManagement
|
||||||
ilo5 = ironic.drivers.modules.ilo.management:Ilo5Management
|
ilo5 = ironic.drivers.modules.ilo.management:Ilo5Management
|
||||||
intel-ipmitool = ironic.drivers.modules.intel_ipmi.management:IntelIPMIManagement
|
intel-ipmitool = ironic.drivers.modules.intel_ipmi.management:IntelIPMIManagement
|
||||||
@ -138,9 +133,7 @@ ironic.hardware.interfaces.network =
|
|||||||
ironic.hardware.interfaces.power =
|
ironic.hardware.interfaces.power =
|
||||||
agent = ironic.drivers.modules.agent_power:AgentPower
|
agent = ironic.drivers.modules.agent_power:AgentPower
|
||||||
fake = ironic.drivers.modules.fake:FakePower
|
fake = ironic.drivers.modules.fake:FakePower
|
||||||
idrac = ironic.drivers.modules.drac.power:DracPower
|
|
||||||
idrac-redfish = ironic.drivers.modules.drac.power:DracRedfishPower
|
idrac-redfish = ironic.drivers.modules.drac.power:DracRedfishPower
|
||||||
idrac-wsman = ironic.drivers.modules.drac.power:DracWSManPower
|
|
||||||
ilo = ironic.drivers.modules.ilo.power:IloPower
|
ilo = ironic.drivers.modules.ilo.power:IloPower
|
||||||
ipmitool = ironic.drivers.modules.ipmitool:IPMIPower
|
ipmitool = ironic.drivers.modules.ipmitool:IPMIPower
|
||||||
irmc = ironic.drivers.modules.irmc.power:IRMCPower
|
irmc = ironic.drivers.modules.irmc.power:IRMCPower
|
||||||
@ -150,9 +143,7 @@ ironic.hardware.interfaces.power =
|
|||||||
ironic.hardware.interfaces.raid =
|
ironic.hardware.interfaces.raid =
|
||||||
agent = ironic.drivers.modules.agent:AgentRAID
|
agent = ironic.drivers.modules.agent:AgentRAID
|
||||||
fake = ironic.drivers.modules.fake:FakeRAID
|
fake = ironic.drivers.modules.fake:FakeRAID
|
||||||
idrac = ironic.drivers.modules.drac.raid:DracRAID
|
|
||||||
idrac-redfish = ironic.drivers.modules.drac.raid:DracRedfishRAID
|
idrac-redfish = ironic.drivers.modules.drac.raid:DracRedfishRAID
|
||||||
idrac-wsman = ironic.drivers.modules.drac.raid:DracWSManRAID
|
|
||||||
ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID
|
ilo5 = ironic.drivers.modules.ilo.raid:Ilo5RAID
|
||||||
irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
|
irmc = ironic.drivers.modules.irmc.raid:IRMCRAID
|
||||||
no-raid = ironic.drivers.modules.noop:NoRAID
|
no-raid = ironic.drivers.modules.noop:NoRAID
|
||||||
@ -171,8 +162,6 @@ ironic.hardware.interfaces.storage =
|
|||||||
|
|
||||||
ironic.hardware.interfaces.vendor =
|
ironic.hardware.interfaces.vendor =
|
||||||
fake = ironic.drivers.modules.fake:FakeVendorB
|
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
|
idrac-redfish = ironic.drivers.modules.drac.vendor_passthru:DracRedfishVendorPassthru
|
||||||
ilo = ironic.drivers.modules.ilo.vendor:VendorPassthru
|
ilo = ironic.drivers.modules.ilo.vendor:VendorPassthru
|
||||||
irmc = ironic.drivers.modules.irmc.vendor:IRMCVendorPassthru
|
irmc = ironic.drivers.modules.irmc.vendor:IRMCVendorPassthru
|
||||||
|
Loading…
Reference in New Issue
Block a user