From 908d4d35f7a0d473cc9581bdda2535d6920b7d89 Mon Sep 17 00:00:00 2001 From: Jay Faulkner Date: Wed, 25 Mar 2026 11:52:15 -0700 Subject: [PATCH 01/24] Remove the iRMC hardware type and all interfaces The iRMC hardware type has been deprecated since 2019 due to the third-party CI being offline and the vendor being unresponsive. Remove the hardware type and all associated interfaces (power, management, boot, inspect, raid, bios, vendor), configuration, tests, and documentation. Additionally, prior to this change, test_manager was implicitly relying on the irmc driver being imported to make redfish.management available -- this means these tests were invalidly failing. Those tests were fixed by importing redfish.management directly and using it directly (instead of relying on a side-effect). Assisted-by: claude-code Change-Id: I78dca9df7f70d5174badb89daa8ce4143abb02c3 Signed-off-by: Jay Faulkner --- devstack/lib/ironic | 20 +- doc/source/admin/boot-from-volume.rst | 6 +- doc/source/admin/console.rst | 2 +- doc/source/admin/drivers.rst | 1 - doc/source/admin/drivers/irmc.rst | 696 ------ doc/source/admin/inspection/index.rst | 2 +- doc/source/admin/interfaces/boot.rst | 2 +- doc/source/admin/report.txt | 17 - doc/source/admin/security.rst | 4 +- doc/source/admin/troubleshooting.rst | 13 - doc/source/install/configure-ipmi.rst | 2 +- doc/source/install/configure-pxe.rst | 2 +- doc/source/install/enabling-drivers.rst | 21 +- driver-requirements.txt | 1 - ironic/api/controllers/v1/ramdisk.py | 1 - ironic/common/exception.py | 8 - ironic/conf/__init__.py | 2 - ironic/conf/irmc.py | 197 -- ironic/conf/opts.py | 1 - ironic/drivers/irmc.py | 91 - ironic/drivers/modules/irmc/__init__.py | 0 ironic/drivers/modules/irmc/bios.py | 151 -- ironic/drivers/modules/irmc/boot.py | 1121 ---------- ironic/drivers/modules/irmc/common.py | 670 ------ ironic/drivers/modules/irmc/inspect.py | 348 --- ironic/drivers/modules/irmc/management.py | 630 ------ ironic/drivers/modules/irmc/power.py | 340 --- ironic/drivers/modules/irmc/raid.py | 499 ----- ironic/drivers/modules/irmc/vendor.py | 78 - .../unit/api/controllers/v1/test_ramdisk.py | 13 - ironic/tests/unit/conductor/test_manager.py | 24 +- ironic/tests/unit/db/utils.py | 10 - .../unit/drivers/modules/irmc/__init__.py | 0 .../modules/irmc/fake_sensors_data_ng.xml | 156 -- .../modules/irmc/fake_sensors_data_ok.xml | 156 -- .../unit/drivers/modules/irmc/test_bios.py | 163 -- .../unit/drivers/modules/irmc/test_boot.py | 1953 ----------------- .../unit/drivers/modules/irmc/test_common.py | 585 ----- .../unit/drivers/modules/irmc/test_inspect.py | 719 ------ .../drivers/modules/irmc/test_management.py | 815 ------- .../modules/irmc/test_periodic_task.py | 333 --- .../unit/drivers/modules/irmc/test_power.py | 473 ---- .../unit/drivers/modules/irmc/test_raid.py | 856 -------- ironic/tests/unit/drivers/test_irmc.py | 197 -- ironic/tests/unit/drivers/test_utils.py | 9 - .../drivers/third_party_driver_mock_specs.py | 50 - .../unit/drivers/third_party_driver_mocks.py | 34 - pyproject.toml | 9 - .../remove-irmc-driver-a1b2c3d4e5f6a7b8.yaml | 6 + 49 files changed, 39 insertions(+), 11448 deletions(-) delete mode 100644 doc/source/admin/drivers/irmc.rst delete mode 100644 ironic/conf/irmc.py delete mode 100644 ironic/drivers/irmc.py delete mode 100644 ironic/drivers/modules/irmc/__init__.py delete mode 100644 ironic/drivers/modules/irmc/bios.py delete mode 100644 ironic/drivers/modules/irmc/boot.py delete mode 100644 ironic/drivers/modules/irmc/common.py delete mode 100644 ironic/drivers/modules/irmc/inspect.py delete mode 100644 ironic/drivers/modules/irmc/management.py delete mode 100644 ironic/drivers/modules/irmc/power.py delete mode 100644 ironic/drivers/modules/irmc/raid.py delete mode 100644 ironic/drivers/modules/irmc/vendor.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/__init__.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml delete mode 100644 ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_bios.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_boot.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_common.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_inspect.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_management.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_periodic_task.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_power.py delete mode 100644 ironic/tests/unit/drivers/modules/irmc/test_raid.py delete mode 100644 ironic/tests/unit/drivers/test_irmc.py create mode 100644 releasenotes/notes/remove-irmc-driver-a1b2c3d4e5f6a7b8.yaml diff --git a/devstack/lib/ironic b/devstack/lib/ironic index 0d57931dc4..fad75220d6 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -155,9 +155,6 @@ IRONIC_NODE_SHARD_NAME=${IRONIC_NODE_SHARD_NAME:-ds_shard_1} # idrac: # # -# irmc: -# -# IRONIC_HWINFO_FILE=${IRONIC_HWINFO_FILE:-$IRONIC_DATA_DIR/hardware_info} # Set up defaults for functional / integration testing @@ -345,7 +342,7 @@ fi # are ``ipmi``, ``snmp`` and ``redfish``. # # Additional valid choices if IRONIC_IS_HARDWARE == true are: -# ``idrac`` and ``irmc``. +# ``idrac``. IRONIC_DEPLOY_DRIVER=${IRONIC_DEPLOY_DRIVER:-ipmi} # If present, these files are used as deploy ramdisk/kernel. @@ -1008,11 +1005,6 @@ function is_deployed_by_redfish { return 1 } -function is_deployed_by_irmc { - [[ "$IRONIC_DEPLOY_DRIVER" == irmc ]] && return 0 - return 1 -} - function is_drac_enabled { [[ -z "${IRONIC_ENABLED_HARDWARE_TYPES%%*idrac*}" ]] && return 0 return 1 @@ -1338,9 +1330,6 @@ function install_ironic { pip_install python-dracclient fi - if is_irmc_enabled; then - pip_install python-scciclient pysnmp - fi if is_ansible_deploy_enabled; then pip_install "$(grep '^ansible' $IRONIC_DIR/driver-requirements.txt | awk '{print $1}')" @@ -3479,13 +3468,6 @@ function enroll_nodes { --driver-info redfish_password=$bmc_passwd \ --driver-info redfish_username=$bmc_username \ --driver-info redfish_verify_ca=False" - elif is_deployed_by_irmc; then - node_options+=" --driver-info irmc_address=$bmc_address \ - --driver-info irmc_password=$bmc_passwd \ - --driver-info irmc_username=$bmc_username" - if [[ -n "$IRONIC_DEPLOY_ISO_ID" ]]; then - node_options+=" --driver-info deploy_iso=$IRONIC_DEPLOY_ISO_ID" - fi fi interface_info="${mac_address}" diff --git a/doc/source/admin/boot-from-volume.rst b/doc/source/admin/boot-from-volume.rst index 10a39ec25f..3a21c5678f 100644 --- a/doc/source/admin/boot-from-volume.rst +++ b/doc/source/admin/boot-from-volume.rst @@ -24,9 +24,9 @@ the node OR the iPXE boot templates such that the node CAN be booted. .. figure:: ./../images/boot-from-volume.svg :width: 100% -In this example, the boot interface does the heavy lifting. For drivers the -``irmc`` and ``ilo`` hardware types with hardware type-specific boot -interfaces, they are able to signal via an out-of-band mechanism to the +In this example, the boot interface does the heavy lifting. For the +``ilo`` hardware type with its hardware type-specific boot +interface, it is able to signal via an out-of-band mechanism to the baremetal node's BMC that the integrated iSCSI initiators are to connect to the supplied volume target information. diff --git a/doc/source/admin/console.rst b/doc/source/admin/console.rst index 3d50342411..653a393af0 100644 --- a/doc/source/admin/console.rst +++ b/doc/source/admin/console.rst @@ -54,7 +54,7 @@ Node serial console ------------------- Serial consoles for nodes are implemented using `socat`_. It is supported by -the ``ipmi``, ``irmc``, and ``redfish`` hardware types. +the ``ipmi`` and ``redfish`` hardware types. .. NOTE:: The use of the ``ipmitool-socat`` console interface on any hardware type diff --git a/doc/source/admin/drivers.rst b/doc/source/admin/drivers.rst index 43cb8f846a..7f12c273b2 100644 --- a/doc/source/admin/drivers.rst +++ b/doc/source/admin/drivers.rst @@ -28,7 +28,6 @@ Hardware Types drivers/ilo drivers/intel-ipmi drivers/ipmitool - drivers/irmc drivers/redfish drivers/snmp drivers/fake diff --git a/doc/source/admin/drivers/irmc.rst b/doc/source/admin/drivers/irmc.rst deleted file mode 100644 index 1e3e71e8fe..0000000000 --- a/doc/source/admin/drivers/irmc.rst +++ /dev/null @@ -1,696 +0,0 @@ -.. _irmc: - -=========== -iRMC driver -=========== - -.. warning:: - **The iRMC driver is deprecated and will be removed in a future release.** - - The Third Party CI for the iRMC driver stopped responding in 2019, and - attempts to contact the vendor have been unsuccessful. As a result, the - driver cannot be maintained and is being deprecated for removal. - - Users of the ``irmc`` hardware type should begin planning migration to - alternative hardware types, ideally redfish. The use of ``ipmi`` as a - replacement to ``irmc`` is discouraged due to it being an ageing - management protocol which should be used with caution. - -Overview -======== - -The iRMC driver enables control FUJITSU PRIMERGY via ServerView -Common Command Interface (SCCI). Support for FUJITSU PRIMERGY servers consists -of the ``irmc`` hardware type and a few hardware interfaces specific for that -hardware type. - -Prerequisites -============= - -* Install `python-scciclient `_ - and `pysnmp `_ packages:: - - $ pip install "python-scciclient>=0.7.2" pysnmp - -Hardware Type -============= - -The ``irmc`` hardware type is available for FUJITSU PRIMERGY servers. For -information on how to enable the ``irmc`` hardware type, see -:ref:`enable-hardware-types`. - -Hardware interfaces -^^^^^^^^^^^^^^^^^^^ - -The ``irmc`` hardware type overrides the selection of the following -hardware interfaces: - -* bios - Supports ``irmc`` and ``no-bios``. - The default is ``irmc``. - -* boot - Supports ``irmc-virtual-media``, ``irmc-pxe``, and ``pxe``. - The default is ``irmc-virtual-media``. The ``irmc-virtual-media`` boot - interface enables the virtual media based deploy with IPA (Ironic Python - Agent). - - .. warning:: - We deprecated the ``pxe`` boot interface when used with ``irmc`` - hardware type. Support for this interface will be removed in the - future. Instead, use ``irmc-pxe``. - -* console - Supports ``ipmitool-socat``, and ``no-console``. - The default is ``ipmitool-socat``. - -* inspect - Supports ``irmc``, ``agent``, and ``no-inspect``. - The default is ``irmc``. - -* management - Supports only ``irmc``. - -* power - Supports ``irmc``, which enables power control via ServerView Common - Command Interface (SCCI), by default. Also supports ``ipmitool``. - -* raid - Supports ``irmc``, ``no-raid`` and ``agent``. - The default is ``no-raid``. - -For other hardware interfaces, ``irmc`` hardware type supports the -Bare Metal reference interfaces. For more details about the hardware -interfaces and how to enable the desired ones, see -:ref:`enable-hardware-interfaces`. - -Here is a complete configuration example with most of the supported hardware -interfaces enabled for ``irmc`` hardware type. - -.. code-block:: ini - - [DEFAULT] - enabled_hardware_types = irmc - enabled_bios_interfaces = irmc - enabled_boot_interfaces = irmc-virtual-media,irmc-pxe - enabled_console_interfaces = ipmitool-socat,no-console - enabled_deploy_interfaces = direct - enabled_inspect_interfaces = irmc,agent,no-inspect - enabled_management_interfaces = irmc - enabled_network_interfaces = flat,neutron - enabled_power_interfaces = irmc - enabled_raid_interfaces = no-raid,irmc - enabled_storage_interfaces = noop,cinder - enabled_vendor_interfaces = no-vendor,ipmitool - -Here is a command example to enroll a node with ``irmc`` hardware type. - -.. code-block:: console - - baremetal node create \ - --bios-interface irmc \ - --boot-interface irmc-pxe \ - --deploy-interface direct \ - --inspect-interface irmc \ - --raid-interface irmc - -Node configuration -^^^^^^^^^^^^^^^^^^ - -Configuration via ``driver_info`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Each node is configured for ``irmc`` hardware type by setting the following - ironic node object's properties: - - - ``driver_info/irmc_address`` property to be ``IP address`` or - ``hostname`` of the iRMC. - - ``driver_info/irmc_username`` property to be ``username`` for - the iRMC with administrator privileges. - - ``driver_info/irmc_password`` property to be ``password`` for - irmc_username. - - .. note:: - Fujitsu server equipped with iRMC S6 2.00 or later version of firmware - disables IPMI over LAN by default. However user may be able to enable IPMI - via BMC settings. - To handle this change, ``irmc`` hardware type first tries IPMI and, - if IPMI operation fails, ``irmc`` hardware type uses Redfish API of Fujitsu - server to provide Ironic functionalities. - So if user deploys Fujitsu server with iRMC S6 2.00 or later, user needs - to set Redfish related parameters in ``driver_info``. - - - ``driver_info/redifsh_address`` property to be ``IP address`` or - ``hostname`` of the iRMC. You can prefix it with protocol (e.g. - ``https://``). If you don't provide protocol, Ironic assumes HTTPS - (i.e. add ``https://`` prefix). - iRMC with S6 2.00 or later only support HTTPS connection to Redfish API. - - ``driver_info/redfish_username`` to be user name of iRMC with administrative - privileges - - ``driver_info/redfish_password`` to be password of ``redfish_username`` - - ``driver_info/redfish_verify_ca`` accepts values those accepted in - ``driver_info/irmc_verify_ca`` - - ``driver_info/redfish_auth_type`` to be one of ``basic``, ``session`` or - ``auto`` - -* If ``port`` in ``[irmc]`` section of ``/etc/ironic/ironic.conf`` or - ``driver_info/irmc_port`` is set to 443, ``driver_info/irmc_verify_ca`` - will take effect: - - ``driver_info/irmc_verify_ca`` property takes one of 4 value (default value - is ``True``): - - - ``True``: When set to ``True``, which certification file iRMC driver uses - is determined by ``requests`` Python module. - - Value of ``driver_info/irmc_verify_ca`` is passed to ``verify`` argument - of functions defined in ``requests`` Python module. So which certification - will be used is depend on behavior of ``requests`` module. - (maybe certification provided by ``certifi`` Python module) - - - ``False``: When set to ``False``, iRMC driver won't verify server - certification with certification file during HTTPS connection with iRMC. - Just stop to verify server certification, but does HTTPS. - - .. warning:: - When set to ``False``, user must notice that it can result in - vulnerable situation. Stopping verification of server certification - during HTTPS connection means it cannot prevent Man-in-the-middle - attack. When set to ``False``, Ironic user must take enough care - around infrastructure environment in terms of security. - (e.g. make sure network between Ironic conductor and iRMC is secure) - - - string representing filesystem path to directory which contains - certification file: In this case, iRMC driver uses certification file - stored at specified directory. Ironic conductor must be able to access - that directory. For iRMC to recognize certification file, Ironic user - must run ``openssl rehash ``. - - - string representing filesystem path to certification file: In this case, - iRMC driver uses certification file specified. Ironic conductor must have - access to that file. - - -* The following properties are also required if ``irmc-virtual-media`` boot - interface is used: - - - ``driver_info/deploy_iso`` property to be either deploy iso - file name, Glance UUID, or Image Service URL. - - ``instance info/boot_iso`` property to be either boot iso - file name, Glance UUID, or Image Service URL. This is used - with the ``ramdisk`` deploy interface. - - .. note:: - The ``deploy_iso`` and ``boot_iso`` properties used to be called - ``irmc_deploy_iso`` and ``irmc_boot_iso`` accordingly before the Xena - release. - -* The following properties are also required if ``irmc`` inspect interface is - enabled and SNMPv3 inspection is desired. - - - ``driver_info/irmc_snmp_user`` property to be the SNMPv3 username. SNMPv3 - functionality should be enabled for this user on iRMC server side. - - ``driver_info/irmc_snmp_auth_password`` property to be the auth protocol - pass phrase. The length of pass phrase should be at least 8 characters. - - ``driver_info/irmc_snmp_priv_password`` property to be the privacy protocol - pass phrase. The length of pass phrase should be at least 8 characters. - - -Configuration via ``properties`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Each node is configured for ``irmc`` hardware type by setting the following - ironic node object's properties: - - - ``properties/capabilities`` property to be ``boot_mode:uefi`` if - UEFI boot is required, or ``boot_mode:bios`` if Legacy BIOS is required. - If this is not set, ``default_boot_mode`` at ``[default]`` section in - ``ironic.conf`` will be used. - - ``properties/capabilities`` property to be ``secure_boot:true`` if - UEFI Secure Boot is required. Please refer to `UEFI Secure Boot Support`_ - for more information. - - -Configuration via ``ironic.conf`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* All of the nodes are configured by setting the following configuration - options in the ``[irmc]`` section of ``/etc/ironic/ironic.conf``: - - - ``port``: Port to be used for iRMC operations; either 80 - or 443. The default value is 443. Optional. - - .. note:: - Since iRMC S6 2.00, iRMC firmware doesn't support HTTP connection to - REST API. If you deploy server with iRMS S6 2.00 and later, please - set ``port`` to 443. - - ``irmc`` hardware type provides ``verify_step`` named - ``verify_http_https_connection_and_fw_version`` to check HTTP(S) - connection to iRMC REST API. If HTTP(S) connection is successfully - established, then it fetches and caches iRMC firmware version. - If HTTP(S) connection to iRMC REST API failed, Ironic node's state - moves to ``enroll`` with suggestion put in log message. - Default priority of this verify step is 10. - - If operator updates iRMC firmware version of node, operator should - run ``cache_irmc_firmware_version`` node vendor passthru method - to update iRMC firmware version stored in - ``driver_internal_info/irmc_fw_version``. - - - ``auth_method``: Authentication method for iRMC operations; - either ``basic`` or ``digest``. The default value is ``basic``. Optional. - - ``client_timeout``: Timeout (in seconds) for iRMC - operations. The default value is 60. Optional. - - ``sensor_method``: Sensor data retrieval method; either - ``ipmitool`` or ``scci``. The default value is ``ipmitool``. Optional. - -* The following options are required if ``irmc-virtual-media`` boot - interface is enabled: - - - ``remote_image_share_root``: Ironic conductor node's ``NFS`` or - ``CIFS`` root path. The default value is ``/remote_image_share_root``. - - ``remote_image_server``: IP of remote image server. - - ``remote_image_share_type``: Share type of virtual media, either - ``NFS`` or ``CIFS``. The default is ``CIFS``. - - ``remote_image_share_name``: share name of ``remote_image_server``. - The default value is ``share``. - - ``remote_image_user_name``: User name of ``remote_image_server``. - - ``remote_image_user_password``: Password of ``remote_image_user_name``. - - ``remote_image_user_domain``: Domain name of ``remote_image_user_name``. - -* The following options are required if ``irmc`` inspect interface is enabled: - - - ``snmp_version``: SNMP protocol version; either ``v1``, ``v2c`` or - ``v3``. The default value is ``v2c``. Optional. - - ``snmp_port``: SNMP port. The default value is ``161``. Optional. - - ``snmp_community``: SNMP community required for versions ``v1`` - and ``v2c``. The default value is ``public``. Optional. - - ``snmp_security``: SNMP security name required for version ``v3``. - Optional. - - ``snmp_auth_proto``: The SNMPv3 auth protocol. If using iRMC S4 or S5, the - valid value of this option is only ``sha``. If using iRMC S6, the valid - values are ``sha256``, ``sha384`` and ``sha512``. The default value is - ``sha``. Optional. - - ``snmp_priv_proto``: The SNMPv3 privacy protocol. The valid value and - the default value are both ``aes``. We will add more supported valid values - in the future. Optional. - - .. warning:: - We deprecated the ``snmp_security`` option when use SNMPv3 inspection. - Support for this option will be removed in the future. Instead, set - ``driver_info/irmc_snmp_user`` parameter for each node if SNMPv3 - inspection is needed. - - -Override ``ironic.conf`` configuration via ``driver_info`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Each node can be further configured by setting the following ironic - node object's properties which override the parameter values in - ``[irmc]`` section of ``/etc/ironic/ironic.conf``: - - - ``driver_info/irmc_port`` property overrides ``port``. - - ``driver_info/irmc_auth_method`` property overrides ``auth_method``. - - ``driver_info/irmc_client_timeout`` property overrides ``client_timeout``. - - ``driver_info/irmc_sensor_method`` property overrides ``sensor_method``. - - ``driver_info/irmc_snmp_version`` property overrides ``snmp_version``. - - ``driver_info/irmc_snmp_port`` property overrides ``snmp_port``. - - ``driver_info/irmc_snmp_community`` property overrides ``snmp_community``. - - ``driver_info/irmc_snmp_security`` property overrides ``snmp_security``. - - ``driver_info/irmc_snmp_auth_proto`` property overrides - ``snmp_auth_proto``. - - ``driver_info/irmc_snmp_priv_proto`` property overrides - ``snmp_priv_proto``. - - -Optional functionalities for the ``irmc`` hardware type -======================================================= - -UEFI Secure Boot Support -^^^^^^^^^^^^^^^^^^^^^^^^ -The hardware type ``irmc`` supports secure boot deploy, see :ref:`secure-boot` -for details. - -.. warning:: - Secure boot feature is not supported with ``pxe`` boot interface. - -.. _irmc_node_cleaning: - -Node Cleaning Support -^^^^^^^^^^^^^^^^^^^^^ -The ``irmc`` hardware type supports node cleaning. -For more information on node cleaning, see :ref:`cleaning`. - -Supported **Automated** Cleaning Operations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The automated cleaning operations supported are: - -* ``restore_irmc_bios_config``: - Restores BIOS settings on a baremetal node from backup data. If this - clean step is enabled, the BIOS settings of a baremetal node will be - backed up automatically before the deployment. By default, this clean - step is disabled with priority ``0``. Set its priority to a positive - integer to enable it. The recommended value is ``10``. - - .. warning:: - ``pxe`` boot interface, when used with ``irmc`` hardware type, does - not support this clean step. If uses ``irmc`` hardware type, it is - required to select ``irmc-pxe`` or ``irmc-virtual-media`` as the - boot interface in order to make this clean step work. - - -Configuration options for the automated cleaning steps are listed under -``[irmc]`` section in ironic.conf :: - - clean_priority_restore_irmc_bios_config = 0 - -For more information on node automated cleaning, see :ref:`automated_cleaning` - -Boot from Remote Volume -^^^^^^^^^^^^^^^^^^^^^^^ -The ``irmc`` hardware type supports the generic PXE-based remote volume -booting when using the following boot interfaces: - -* ``irmc-pxe`` -* ``pxe`` - -In addition, the ``irmc`` hardware type supports remote volume booting without -PXE. This is available when using the ``irmc-virtual-media`` boot interface. -This feature configures a node to boot from a remote volume by using the API -of iRMC. It supports iSCSI and FibreChannel. - -Configuration -~~~~~~~~~~~~~ - -In addition to the configuration for generic drivers to -:ref:`remote volume boot `, -the iRMC driver requires the following configuration: - -* It is necessary to set physical port IDs to network ports and volume - connectors. All cards including those not used for volume boot should be - registered. - - The format of a physical port ID is: ``-`` where: - - - ````: could be ``LAN``, ``FC`` or ``CNA`` - - ````: 0 indicates onboard slot. Use 1 to 9 for add-on slots. - - ````: A port number starting from 1. - - These IDs are specified in a node's ``driver_info[irmc_pci_physical_ids]``. - This value is a dictionary. The key is the UUID of a resource (Port or Volume - Connector) and its value is the physical port ID. For example:: - - { - "1ecd14ee-c191-4007-8413-16bb5d5a73a2":"LAN0-1", - "87f6c778-e60e-4df2-bdad-2605d53e6fc0":"CNA1-1" - } - - It can be set with the following command:: - - baremetal node set $NODE_UUID \ - --driver-info irmc_pci_physical_ids={} \ - --driver-info irmc_pci_physical_ids/$PORT_UUID=LAN0-1 \ - --driver-info irmc_pci_physical_ids/$VOLUME_CONNECTOR_UUID=CNA1-1 - -* For iSCSI boot, volume connectors with both types ``iqn`` and ``ip`` are - required. The configuration with DHCP is not supported yet. - -* For iSCSI, the size of the storage network is needed. This value should be - specified in a node's ``driver_info[irmc_storage_network_size]``. It must be - a positive integer < 32. - For example, if the storage network is 10.2.0.0/22, use the following - command:: - - baremetal node set $NODE_UUID --driver-info irmc_storage_network_size=22 - -Supported hardware -~~~~~~~~~~~~~~~~~~ - -The driver supports the PCI controllers, Fibrechannel Cards, Converged Network -Adapters supported by -`Fujitsu ServerView Virtual-IO Manager `_. - -Hardware Inspection Support -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``irmc`` hardware type provides the iRMC-specific hardware inspection -with ``irmc`` inspect interface. - -.. note:: - SNMP requires being enabled in ServerView® iRMC S4 Web Server(Network - Settings\SNMP section). - -Configuration -~~~~~~~~~~~~~ - -The Hardware Inspection Support in the iRMC driver requires the following -configuration: - -* It is necessary to set ironic configuration with ``gpu_ids`` and - ``fpga_ids`` options in ``[irmc]`` section. - - ``gpu_ids`` and ``fpga_ids`` are lists of ``/`` where: - - - ````: 4 hexadecimal digits starts with '0x'. - - ````: 4 hexadecimal digits starts with '0x'. - - Here are sample values for ``gpu_ids`` and ``fpga_ids``:: - - gpu_ids = 0x1000/0x0079,0x2100/0x0080 - fpga_ids = 0x1000/0x005b,0x1100/0x0180 - -* The python-scciclient package requires pyghmi version >= 1.0.22 and pysnmp - version >= 4.2.3. They are used by the conductor service on the conductor. - The latest version of pyghmi can be downloaded from `here - `__ - and pysnmp can be downloaded from `here - `__. - -Supported properties -~~~~~~~~~~~~~~~~~~~~ - -The inspection process will discover the following properties: - -* ``memory_mb``: memory size - -* ``cpu_arch``: cpu architecture - -* ``local_gb``: disk size - -Inspection can also discover the following extra capabilities for iRMC -driver: - -* ``irmc_firmware_version``: iRMC firmware version - -* ``rom_firmware_version``: ROM firmware version - -* ``server_model``: server model - -* ``pci_gpu_devices``: number of gpu devices connected to the bare metal. - -Inspection can also set/unset node's traits with the following cpu type for -iRMC driver: - -* ``CUSTOM_CPU_FPGA``: The bare metal contains fpga cpu type. - -.. note:: - - * The disk size is returned only when eLCM License for FUJITSU PRIMERGY - servers is activated. If the license is not activated, then Hardware - Inspection will fail to get this value. - * Before inspecting, if the server is power-off, it will be turned on - automatically. System will wait for a few second before start - inspecting. After inspection, power status will be restored to the - previous state. - -The operator can specify these capabilities in compute service flavor, for -example:: - - openstack flavor set baremetal-flavor-name --property capabilities:irmc_firmware_version="iRMC S4-8.64F" - - openstack flavor set baremetal-flavor-name --property capabilities:server_model="TX2540M1F5" - - openstack flavor set baremetal-flavor-name --property capabilities:pci_gpu_devices="1" - -See :ref:`capabilities-discovery` for more details and examples. - -The operator can add a trait in compute service flavor, for example:: - - baremetal node add trait $NODE_UUID CUSTOM_CPU_FPGA - -A valid trait must be no longer than 255 characters. Standard traits are -defined in the os_traits library. A custom trait must start with the prefix -``CUSTOM_`` and use the following characters: A-Z, 0-9 and _. - -RAID configuration Support -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``irmc`` hardware type provides the iRMC RAID configuration with ``irmc`` -raid interface. - -.. note:: - - * RAID implementation for ``irmc`` hardware type is based on eLCM license - and SDCard. Otherwise, SP(Service Platform) in lifecycle management - must be available. - * RAID implementation only supported for RAIDAdapter 0 in Fujitsu Servers. - -Configuration -~~~~~~~~~~~~~ - -The RAID configuration Support in the iRMC drivers requires the following -configuration: - -* It is necessary to set ironic configuration into Node with - JSON file option:: - - $ baremetal node set \ - --target-raid-config - - Here is some sample values for JSON file:: - - { - "logical_disks": [ - { - "size_gb": 1000, - "raid_level": "1" - ] - } - - or:: - - { - "logical_disks": [ - { - "size_gb": 1000, - "raid_level": "1", - "controller": "FTS RAID Ctrl SAS 6G 1GB (D3116C) (0)", - "physical_disks": [ - "0", - "1" - ] - } - ] - } - -.. note:: - - RAID 1+0 and 5+0 in iRMC driver does not support property ``physical_disks`` - in ``target_raid_config`` during create raid configuration yet. See - following example:: - - { - "logical_disks": - [ - { - "size_gb": "MAX", - "raid_level": "1+0" - } - ] - } - -See :ref:`raid` for more details and examples. - -Supported properties -~~~~~~~~~~~~~~~~~~~~ - -The RAID configuration using iRMC driver supports following parameters in -JSON file: - -* ``size_gb``: is mandatory properties in Ironic. -* ``raid_level``: is mandatory properties in Ironic. Currently, iRMC Server - supports following RAID levels: 0, 1, 5, 6, 1+0 and 5+0. -* ``controller``: is name of the controller as read by the RAID interface. -* ``physical_disks``: are specific values for each raid array in - LogicalDrive which operator want to set them along with ``raid_level``. - -The RAID configuration is supported as a manual cleaning step. - -.. note:: - - * iRMC server will power-on after create/delete raid configuration is - applied, FGI (Foreground Initialize) will process raid configuration in - iRMC server, thus the operation will completed upon power-on and power-off - when created RAID on iRMC server. - -See :ref:`raid` for more details and examples. - -BIOS configuration Support -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``irmc`` hardware type provides the iRMC BIOS configuration with ``irmc`` -bios interface. - -.. warning:: - ``irmc`` bios interface does not support ``factory_reset``. - - Starting from version ``0.10.0`` of ``python-scciclient``, - the BIOS setting obtained may not be the latest. If you want to get the latest BIOS setting, - you need to delete the existing BIOS profile in iRMC. For example:: - - curl -u user:pass -H "Content-type: application/json" -X DELETE -i http://192.168.0.1/rest/v1/Oem/eLCM/ProfileManagement/BiosConfig - -Configuration -~~~~~~~~~~~~~ - -The BIOS configuration in the iRMC driver supports the following settings: - -- ``boot_option_filter``: Specifies from which drives can be booted. This - supports following options: ``UefiAndLegacy``, ``LegacyOnly``, ``UefiOnly``. -- ``check_controllers_health_status_enabled``: The UEFI FW checks the - controller health status. This supports following options: ``true``, ``false``. -- ``cpu_active_processor_cores``: The number of active processor cores 1...n. - Option 0 indicates that all available processor cores are active. -- ``cpu_adjacent_cache_line_prefetch_enabled``: The processor loads the requested - cache line and the adjacent cache line. This supports following options: - ``true``, ``false``. -- ``cpu_vt_enabled``: Supports the virtualization of platform hardware and - several software environments, based on Virtual Machine Extensions to - support the use of several software environments using virtual computers. - This supports following options: ``true``, ``false``. -- ``flash_write_enabled``: The system BIOS can be written. Flash BIOS update - is possible. This supports following options: ``true``, ``false``. -- ``hyper_threading_enabled``: Hyper-threading technology allows a single - physical processor core to appear as several logical processors. This - supports following options: ``true``, ``false``. -- ``keep_void_boot_options_enabled``: Boot Options will not be removed from - "Boot Option Priority" list. This supports following options: ``true``, - ``false``. -- ``launch_csm_enabled``: Specifies whether the Compatibility Support Module - (CSM) is executed. This supports following options: ``true``, ``false``. -- ``os_energy_performance_override_enabled``: Prevents the OS from overruling - any energy efficiency policy setting of the setup. This supports following - options: ``true``, ``false``. -- ``pci_aspm_support``: Active State Power Management (ASPM) is used to - power-manage the PCI Express links, thus consuming less power. This - supports following options: ``Disabled``, ``Auto``, ``L0Limited``, - ``L1only``, ``L0Force``. -- ``pci_above_4g_decoding_enabled``: Specifies if memory resources above the - 4GB address boundary can be assigned to PCI devices. This supports - following options: ``true``, ``false``. -- ``power_on_source``: Specifies whether the switch on sources for the system - are managed by the BIOS or the ACPI operating system. This supports - following options: ``BiosControlled``, ``AcpiControlled``. -- ``single_root_io_virtualization_support_enabled``: Single Root IO - Virtualization Support is active. This supports following - options: ``true``, ``false``. - -The BIOS configuration is supported as a manual cleaning step. See :ref:`bios` -for more details and examples. - -Supported platforms -=================== -This driver supports FUJITSU PRIMERGY RX M4 servers and above. - -When ``irmc`` power interface is used, Soft Reboot (Graceful Reset) and Soft -Power Off (Graceful Power Off) are only available if -`ServerView agents `_ -are installed. See `iRMC S4 Manual `_ -for more details. - -RAID configuration feature supports FUJITSU PRIMERGY servers with -RAID-Ctrl-SAS-6G-1GB(D3116C) controller and above. -For detail supported controller with OOB-RAID configuration, please see -`the whitepaper for iRMC RAID configuration `_. diff --git a/doc/source/admin/inspection/index.rst b/doc/source/admin/inspection/index.rst index 1b1e66912a..b5f37e83cc 100644 --- a/doc/source/admin/inspection/index.rst +++ b/doc/source/admin/inspection/index.rst @@ -13,7 +13,7 @@ discovered ethernet MACs. There are two kinds of inspection supported by Bare Metal service: #. Out-of-band inspection is currently implemented by several hardware types, - including ``redfish``, ``ilo``, ``idrac`` and ``irmc``. + including ``redfish``, ``ilo`` and ``idrac``. #. In-band inspection, also known as Agent inspection utilizing Ironic Python Agent to collect information. diff --git a/doc/source/admin/interfaces/boot.rst b/doc/source/admin/interfaces/boot.rst index e57943392b..1d6544b2bf 100644 --- a/doc/source/admin/interfaces/boot.rst +++ b/doc/source/admin/interfaces/boot.rst @@ -33,7 +33,7 @@ process. architectures that do not have BIOS support at all. The ``ipxe`` boot interface is used by default for many hardware types, -including ``ipmi``. Some hardware types, notably ``ilo`` and ``irmc`` have +including ``ipmi``. Some hardware types, notably ``ilo``, have their specific implementations of the PXE boot interface. Additional configuration is required for this boot interface - see diff --git a/doc/source/admin/report.txt b/doc/source/admin/report.txt index 05ca307df0..d3ebb6ff8a 100644 --- a/doc/source/admin/report.txt +++ b/doc/source/admin/report.txt @@ -401,23 +401,6 @@ ipmi: min_command_interval = 5 retry_timeout = 60 -irmc: - auth_method = basic - client_timeout = 60 - port = 443 - remote_image_server = None - remote_image_share_name = share - remote_image_share_root = /remote_image_share_root - remote_image_share_type = CIFS - remote_image_user_domain = - remote_image_user_name = None - remote_image_user_password = *** - sensor_method = ipmitool - snmp_community = public - snmp_port = 161 - snmp_security = None - snmp_version = v2c - ironic_lib: fatal_exception_format_errors = False root_helper = sudo ironic-rootwrap /etc/ironic/rootwrap.conf diff --git a/doc/source/admin/security.rst b/doc/source/admin/security.rst index 3a698f32ef..f87c99ad19 100644 --- a/doc/source/admin/security.rst +++ b/doc/source/admin/security.rst @@ -226,8 +226,8 @@ Driver support for Deployment with Secure Boot ---------------------------------------------- Some hardware types support turning `UEFI secure boot`_ dynamically when -deploying an instance. Currently these are :doc:`/admin/drivers/ilo`, -:doc:`/admin/drivers/irmc` and :doc:`/admin/drivers/redfish`. +deploying an instance. Currently these are :doc:`/admin/drivers/ilo` +and :doc:`/admin/drivers/redfish`. Other drivers, such as :doc:`/admin/drivers/ipmitool`, may be able to be manually configured on the host, but as there is not standardization of Secure Boot diff --git a/doc/source/admin/troubleshooting.rst b/doc/source/admin/troubleshooting.rst index 539c3abcb6..278af34ae7 100644 --- a/doc/source/admin/troubleshooting.rst +++ b/doc/source/admin/troubleshooting.rst @@ -1545,19 +1545,6 @@ For example, with iLO drivers... $ openstack baremetal node set --driver-info ilo_username= --driver-info ilo_password= $ openstack baremetal node maintenance unset -For example, with iRMC drivers... - -.. code-block:: console - - $ openstack baremetal node set --driver-info irmc_username= --driver-info irmc_password= - $ openstack baremetal node set --driver-info redfish_username= --driver-info redfish_password= - $ openstack baremetal node set --driver-info irmc_snmp_user= --driver-info irmc_snmp_auth_password= --driver-info irmc_snmp_priv_password= - $ openstack baremetal node maintenance unset - -.. note:: - iRMC drivers utilize a mix of protocols and can have explicit credentials - set for each protocol utilized. - .. note:: Ironic generally does not manage or rotate the remote BMC credentials due to the risk of user lockout if the account is not for the specific use of diff --git a/doc/source/install/configure-ipmi.rst b/doc/source/install/configure-ipmi.rst index f89409faf9..e5fdcd3dd2 100644 --- a/doc/source/install/configure-ipmi.rst +++ b/doc/source/install/configure-ipmi.rst @@ -77,7 +77,7 @@ Collecting sensor data ~~~~~~~~~~~~~~~~~~~~~~ Bare Metal service supports sending IPMI sensor data to Telemetry with -certain hardware types, such as ``ipmi``, ``ilo`` and ``irmc``. By default, +certain hardware types, such as ``ipmi`` and ``ilo``. By default, support for sending IPMI sensor data to Telemetry is disabled. If you want to enable it, you should make the following two changes in ``ironic.conf``: diff --git a/doc/source/install/configure-pxe.rst b/doc/source/install/configure-pxe.rst index e7324d87c6..6d060c24ad 100644 --- a/doc/source/install/configure-pxe.rst +++ b/doc/source/install/configure-pxe.rst @@ -214,7 +214,7 @@ the PXE UEFI environment. boot device is set to network/pxe. .. note:: - Some drivers, e.g. ``ilo``, ``irmc`` and ``redfish``, support automatic + Some drivers, e.g. ``ilo`` and ``redfish``, support automatic setting of the boot mode during deployment. This step is not required for them. Please check :doc:`../admin/drivers` for information on whether your driver requires manual UEFI configuration. diff --git a/doc/source/install/enabling-drivers.rst b/doc/source/install/enabling-drivers.rst index 95775d489e..e751da7bc4 100644 --- a/doc/source/install/enabling-drivers.rst +++ b/doc/source/install/enabling-drivers.rst @@ -8,8 +8,7 @@ The Bare Metal service delegates actual hardware management to **drivers**. *Drivers*, also called *hardware types*, consist of *hardware interfaces*: sets of functionality dealing with some aspect of bare metal provisioning in a vendor-specific way. There are generic **hardware types** (eg. -``redfish`` and ``ipmi``), and vendor-specific ones (eg. ``ilo`` and -``irmc``). +``redfish`` and ``ipmi``), and vendor-specific one (eg. ``ilo``). .. note:: The terminologies *driver*, *dynamic driver*, and *hardware type* @@ -98,8 +97,8 @@ inspect .. code-block:: ini [DEFAULT] - enabled_hardware_types = ipmi,ilo,irmc - enabled_inspect_interfaces = ilo,irmc,agent + enabled_hardware_types = ipmi,ilo + enabled_inspect_interfaces = ilo,agent See :doc:`/admin/inspection` for more details. management @@ -111,8 +110,8 @@ management .. code-block:: ini [DEFAULT] - enabled_hardware_types = ipmi,redfish,ilo,irmc - enabled_management_interfaces = ipmitool,redfish,ilo,irmc + enabled_hardware_types = ipmi,redfish,ilo + enabled_management_interfaces = ipmitool,redfish,ilo Using ``ipmitool`` requires :doc:`configure-ipmi`. See :doc:`/admin/drivers` for the required configuration of each driver. @@ -127,8 +126,8 @@ power .. code-block:: ini [DEFAULT] - enabled_hardware_types = ipmi,redfish,ilo,irmc - enabled_power_interfaces = ipmitool,redfish,ilo,irmc + enabled_hardware_types = ipmi,redfish,ilo + enabled_power_interfaces = ipmitool,redfish,ilo Using ``ipmitool`` requires :doc:`configure-ipmi`. See :doc:`/admin/drivers` for the required configuration of each driver. @@ -140,7 +139,7 @@ raid .. code-block:: ini [DEFAULT] - enabled_hardware_types = ipmi,redfish,ilo,irmc + enabled_hardware_types = ipmi,redfish,ilo enabled_raid_interfaces = agent,no-raid storage manages the interaction with a remote storage subsystem, such as the @@ -158,7 +157,7 @@ storage .. code-block:: ini [DEFAULT] - enabled_hardware_types = ipmi,irmc + enabled_hardware_types = ipmi enabled_storage_interfaces = cinder,noop vendor @@ -168,7 +167,7 @@ vendor .. code-block:: ini [DEFAULT] - enabled_hardware_types = ipmi,redfish,ilo,irmc + enabled_hardware_types = ipmi,redfish,ilo enabled_vendor_interfaces = ipmitool,no-vendor Here is a complete configuration example, enabling two generic protocols, diff --git a/driver-requirements.txt b/driver-requirements.txt index c805a5138a..6ae71b7666 100644 --- a/driver-requirements.txt +++ b/driver-requirements.txt @@ -8,7 +8,6 @@ proliantutils>=2.16.2,<2.17.0 pysnmp-lextudio>=5.0.0 # BSD pyasn1>=0.5.1 # BSD pyasn1-modules>=0.3.0 # BSD -python-scciclient>=0.16.0,<0.17.0 # Ansible-deploy interface ansible>=2.7 diff --git a/ironic/api/controllers/v1/ramdisk.py b/ironic/api/controllers/v1/ramdisk.py index ef455e942b..6a28733500 100644 --- a/ironic/api/controllers/v1/ramdisk.py +++ b/ironic/api/controllers/v1/ramdisk.py @@ -53,7 +53,6 @@ def config(token, node=None): skip_bmc_detect = mgmt_iface in ( 'redfish', 'idrac-redfish', 'ilo', 'ilo5', 'ilo6', - 'irmc' ) if skip_bmc_detect: LOG.debug('Skipping BMC detection for node %(node)s with ' diff --git a/ironic/common/exception.py b/ironic/common/exception.py index 49e8452c5a..77511b6db4 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -655,14 +655,6 @@ class FileSystemNotSupported(IronicException): "File system %(fs)s is not supported.") -class IRMCOperationError(DriverOperationError): - _msg_fmt = _('iRMC %(operation)s failed. Reason: %(error)s') - - -class IRMCSharedFileSystemNotMounted(DriverOperationError): - _msg_fmt = _("iRMC shared file system '%(share)s' is not mounted.") - - class HardwareInspectionFailure(IronicException): _msg_fmt = _("Failed to inspect hardware. Reason: %(error)s") diff --git a/ironic/conf/__init__.py b/ironic/conf/__init__.py index d52bbe3324..ddb6039caf 100644 --- a/ironic/conf/__init__.py +++ b/ironic/conf/__init__.py @@ -41,7 +41,6 @@ from ironic.conf import inspector from ironic.conf import inventory from ironic.conf import ipmi -from ironic.conf import irmc from ironic.conf import ironic_networking from ironic.conf import json_rpc from ironic.conf import mdns @@ -86,7 +85,6 @@ inspector.register_opts(CONF) inventory.register_opts(CONF) ipmi.register_opts(CONF) -irmc.register_opts(CONF) ironic_networking.register_opts(CONF) # Register default json_rpc group used for conductor json_rpc.register_opts(CONF) diff --git a/ironic/conf/irmc.py b/ironic/conf/irmc.py deleted file mode 100644 index 4ba066fe6d..0000000000 --- a/ironic/conf/irmc.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright 2016 Intel Corporation -# Copyright 2015 FUJITSU LIMITED -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_config import cfg - -from ironic.common.i18n import _ - -_DEPRECATION_REASON = _('The iRMC driver is unmaintained and is being ' - 'deprecated. It will be removed in a future release.') - -opts = [ - cfg.StrOpt('remote_image_share_root', - default='/remote_image_share_root', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Ironic conductor node\'s "NFS" or "CIFS" root path')), - cfg.StrOpt('remote_image_server', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('IP of remote image server')), - cfg.StrOpt('remote_image_share_type', - default='CIFS', - choices=[('CIFS', _('CIFS (Common Internet File System) ' - 'protocol')), - ('NFS', _('NFS (Network File System) protocol'))], - ignore_case=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Share type of virtual media')), - cfg.StrOpt('remote_image_share_name', - default='share', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('share name of remote_image_server')), - cfg.StrOpt('remote_image_user_name', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('User name of remote_image_server')), - cfg.StrOpt('remote_image_user_password', secret=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Password of remote_image_user_name')), - cfg.StrOpt('remote_image_user_domain', - default='', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Domain name of remote_image_user_name')), - cfg.PortOpt('port', - default=443, - choices=[(443, _('port 443')), - (80, _('port 80'))], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Port to be used for iRMC operations')), - cfg.StrOpt('auth_method', - default='basic', - choices=[('basic', _('Basic authentication')), - ('digest', _('Digest authentication'))], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Authentication method to be used for iRMC ' - 'operations')), - cfg.IntOpt('client_timeout', - default=60, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Timeout (in seconds) for iRMC operations')), - cfg.StrOpt('sensor_method', - default='ipmitool', - choices=[('ipmitool', _('IPMItool')), - ('scci', _('Fujitsu SCCI (ServerView Common Command ' - 'Interface)'))], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Sensor data retrieval method.')), - cfg.StrOpt('snmp_version', - default='v2c', - choices=[('v1', _('SNMPv1')), - ('v2c', _('SNMPv2c')), - ('v3', _('SNMPv3'))], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('SNMP protocol version')), - cfg.PortOpt('snmp_port', - default=161, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('SNMP port')), - cfg.StrOpt('snmp_community', - default='public', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('SNMP community. Required for versions "v1" and "v2c"')), - cfg.StrOpt('snmp_security', - help=_("SNMP security name. Required for version 'v3'."), - deprecated_for_removal=True, - deprecated_reason=_("Use irmc_snmp_user")), - cfg.IntOpt('snmp_polling_interval', - default=10, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help='SNMP polling interval in seconds'), - cfg.StrOpt('snmp_auth_proto', - default='sha', - choices=[('sha', _('Secure Hash Algorithm 1, supported in iRMC ' - 'S4 and S5.')), - ('sha256', ('Secure Hash Algorithm 2 with 256 bits ' - 'digest, only supported in iRMC S6.')), - ('sha384', ('Secure Hash Algorithm 2 with 384 bits ' - 'digest, only supported in iRMC S6.')), - ('sha512', ('Secure Hash Algorithm 2 with 512 bits ' - 'digest, only supported in iRMC S6.'))], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_("SNMPv3 message authentication protocol ID. " - "Required for version 'v3'. The valid options are " - "'sha', 'sha256', 'sha384' and 'sha512', while 'sha' is " - "the only supported protocol in iRMC S4 and S5, and " - "from iRMC S6, 'sha256', 'sha384' and 'sha512' are " - "supported, but 'sha' is not supported any more.")), - cfg.StrOpt('snmp_priv_proto', - default='aes', - choices=[('aes', _('Advanced Encryption Standard'))], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_("SNMPv3 message privacy (encryption) protocol ID. " - "Required for version 'v3'. 'aes' is supported.")), - cfg.IntOpt('clean_priority_restore_irmc_bios_config', - default=0, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Priority for restore_irmc_bios_config clean step.')), - cfg.ListOpt('gpu_ids', - default=[], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('List of vendor IDs and device IDs for GPU device to ' - 'inspect. List items are in format vendorID/deviceID ' - 'and separated by commas. GPU inspection will use this ' - 'value to count the number of GPU device in a node. If ' - 'this option is not defined, then leave out ' - 'pci_gpu_devices in capabilities property. ' - 'Sample gpu_ids value: 0x1000/0x0079,0x2100/0x0080')), - cfg.ListOpt('fpga_ids', - default=[], - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('List of vendor IDs and device IDs for CPU FPGA to ' - 'inspect. List items are in format vendorID/deviceID ' - 'and separated by commas. CPU inspection will use this ' - 'value to find existence of CPU FPGA in a node. If ' - 'this option is not defined, then leave out ' - 'CUSTOM_CPU_FPGA in node traits. ' - 'Sample fpga_ids value: 0x1000/0x0079,0x2100/0x0080')), - cfg.IntOpt('query_raid_config_fgi_status_interval', - min=1, - default=300, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Interval (in seconds) between periodic RAID status ' - 'checks to determine whether the asynchronous RAID ' - 'configuration was successfully finished or not. ' - 'Foreground Initialization (FGI) will start 5 minutes ' - 'after creating virtual drives.')), - cfg.StrOpt('kernel_append_params', - default='nofb vga=normal', - mutable=True, - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('Additional kernel parameters to pass down to the ' - 'instance kernel. These parameters can be consumed by ' - 'the kernel or by the applications by reading ' - '/proc/cmdline. Mind severe cmdline size limit! Can be ' - 'overridden by `instance_info/kernel_append_params` ' - 'property.')), - cfg.StrOpt('verify_ca', - deprecated_for_removal=True, - deprecated_reason=_DEPRECATION_REASON, - help=_('The default verify_ca path when irmc_verify_ca ' - 'in driver_info is missing or set to True.')), -] - - -def register_opts(conf): - conf.register_opts(opts, group='irmc') diff --git a/ironic/conf/opts.py b/ironic/conf/opts.py index 6b4ec619f5..e2adbc9e4c 100644 --- a/ironic/conf/opts.py +++ b/ironic/conf/opts.py @@ -42,7 +42,6 @@ ('inspector', ironic.conf.inspector.opts), ('inventory', ironic.conf.inventory.opts), ('ipmi', ironic.conf.ipmi.opts), - ('irmc', ironic.conf.irmc.opts), ('ironic_networking', ironic.conf.ironic_networking.list_opts()), # Expose the ironic networking-specific JSON-RPC group for sample configs ('ironic_networking_json_rpc', ironic.conf.json_rpc.list_opts()), diff --git a/ironic/drivers/irmc.py b/ironic/drivers/irmc.py deleted file mode 100644 index 45522be085..0000000000 --- a/ironic/drivers/irmc.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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. -""" -iRMC Driver for managing FUJITSU PRIMERGY BX S4 or RX S8 generation -of FUJITSU PRIMERGY servers, and above servers. -""" - -from ironic.drivers import generic -from ironic.drivers.modules import agent -from ironic.drivers.modules import ipmitool -from ironic.drivers.modules import ipxe -from ironic.drivers.modules.irmc import bios -from ironic.drivers.modules.irmc import boot -from ironic.drivers.modules.irmc import inspect -from ironic.drivers.modules.irmc import management -from ironic.drivers.modules.irmc import power -from ironic.drivers.modules.irmc import raid -from ironic.drivers.modules.irmc import vendor -from ironic.drivers.modules import noop -from ironic.drivers.modules import pxe - - -class IRMCHardware(generic.GenericHardware): - """iRMC hardware type. - - iRMC hardware type is targeted for FUJITSU PRIMERGY servers which - have iRMC S4 management system. - """ - - # NOTE(janders): The iRMC driver is unmaintained. The Third Party CI - # has been offline since 2019, and attempts to contact the vendor have - # been unsuccessful. As a result, the driver cannot be maintained and - # is being deprecated for removal in a future release. - # TODO(janders): Remove this driver in a future Ironic release. - supported = False - - @property - def supported_bios_interfaces(self): - """List of supported bios interfaces.""" - return [bios.IRMCBIOS, noop.NoBIOS] - - @property - def supported_boot_interfaces(self): - """List of supported boot interfaces.""" - # NOTE: Support for pxe boot is deprecated, and will be - # removed from the list in the future. - return [boot.IRMCVirtualMediaBoot, ipxe.iPXEBoot, - boot.IRMCPXEBoot, pxe.PXEBoot] - - @property - def supported_console_interfaces(self): - """List of supported console interfaces.""" - return [ - ipmitool.IPMISocatConsole - ] + super().supported_console_interfaces - - @property - def supported_inspect_interfaces(self): - """List of supported inspect interfaces.""" - return [inspect.IRMCInspect] + super().supported_inspect_interfaces - - @property - def supported_management_interfaces(self): - """List of supported management interfaces.""" - return [management.IRMCManagement] - - @property - def supported_power_interfaces(self): - """List of supported power interfaces.""" - return [power.IRMCPower, ipmitool.IPMIPower] - - @property - def supported_raid_interfaces(self): - """List of supported raid interfaces.""" - return [noop.NoRAID, raid.IRMCRAID, agent.AgentRAID] - - @property - def supported_vendor_interfaces(self): - """List of supported vendor interfaces.""" - return [noop.NoVendor, vendor.IRMCVendorPassthru] diff --git a/ironic/drivers/modules/irmc/__init__.py b/ironic/drivers/modules/irmc/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic/drivers/modules/irmc/bios.py b/ironic/drivers/modules/irmc/bios.py deleted file mode 100644 index 9f3fc6fe5d..0000000000 --- a/ironic/drivers/modules/irmc/bios.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright 2018 FUJITSU LIMITED -# -# 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. - -""" -iRMC BIOS configuration specific methods -""" -from oslo_log import log as logging -from oslo_utils import importutils - -from ironic.common import exception -from ironic.common import metrics_utils -from ironic.drivers import base -from ironic.drivers.modules.irmc import common as irmc_common -from ironic import objects - - -irmc = importutils.try_import('scciclient.irmc') - -LOG = logging.getLogger(__name__) - -METRICS = metrics_utils.get_metrics_logger(__name__) - - -class IRMCBIOS(base.BIOSInterface): - - supported = False - - def get_properties(self): - """Return the properties of the interface.""" - return irmc_common.COMMON_PROPERTIES - - @METRICS.timer('IRMCBIOS.validate') - def validate(self, task): - """Validate the driver-specific Node info. - - This method validates whether the 'driver_info' property of the - supplied node contains the required information for this driver to - manage the BIOS settings 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. - :raises: MissingParameterValue if a required parameter is missing - in the driver_info property. - """ - irmc_common.parse_driver_info(task.node) - - @METRICS.timer('IRMCBIOS.apply_configuration') - @base.clean_step(priority=0, abortable=False, argsinfo={ - 'settings': { - 'description': "Dictionary containing the BIOS configuration.", - 'required': True - } - }) - @base.cache_bios_settings - def apply_configuration(self, task, settings): - """Applies BIOS configuration on the given node. - - This method takes the BIOS settings from the settings param and - applies BIOS configuration on the given node. - After the BIOS configuration is done, self.cache_bios_settings() may - be called to sync the node's BIOS-related information with the BIOS - configuration applied on the node. - It will also validate the given settings before applying any - settings and manage failures when setting an invalid BIOS config. - In the case of needing password to update the BIOS config, it will be - taken from the driver_info properties. - - :param task: a TaskManager instance. - :param settings: Dictionary containing the BIOS configuration. It - may be an empty dictionary as well. - :raises: IRMCOperationError,if apply bios settings failed. - """ - - irmc_info = irmc_common.parse_driver_info(task.node) - - try: - LOG.info('Apply BIOS configuration for node %(node_uuid)s: ' - '%(settings)s', {'settings': settings, - 'node_uuid': task.node.uuid}) - irmc.elcm.set_bios_configuration(irmc_info, settings) - # NOTE(trungnv): Fix failed cleaning during rebooting node - # when combine OOB and IB steps in manual clean. - self._resume_cleaning(task) - except irmc.scci.SCCIError as e: - LOG.error('Failed to apply BIOS configuration on node ' - '%(node_uuid)s. Error: %(error)s', - {'node_uuid': task.node.uuid, 'error': e}) - raise exception.IRMCOperationError( - operation='Apply BIOS configuration', error=e) - - @METRICS.timer('IRMCBIOS.factory_reset') - @base.cache_bios_settings - def factory_reset(self, task): - """Reset BIOS configuration to factory default on the given node. - - :param task: a TaskManager instance. - :raises: UnsupportedDriverExtension, if the node's driver doesn't - support BIOS reset. - """ - - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='factory_reset') - - @METRICS.timer('IRMCBIOS.cache_bios_settings') - def cache_bios_settings(self, task): - """Store or update BIOS settings on the given node. - - This method stores BIOS properties to the bios settings db - - :param task: a TaskManager instance. - :raises: IRMCOperationError,if get bios settings failed. - :returns: None if it is complete. - """ - - irmc_info = irmc_common.parse_driver_info(task.node) - node_id = task.node.id - try: - settings = irmc.elcm.get_bios_settings(irmc_info) - except irmc.scci.SCCIError as e: - LOG.error('Failed to retrieve the current BIOS settings for node ' - '%(node)s. Error: %(error)s', {'node': task.node.uuid, - 'error': e}) - raise exception.IRMCOperationError(operation='Cache BIOS settings', - error=e) - create_list, update_list, delete_list, nochange_list = ( - objects.BIOSSettingList.sync_node_setting(task.context, node_id, - settings)) - if len(create_list) > 0: - objects.BIOSSettingList.create(task.context, node_id, create_list) - if len(update_list) > 0: - objects.BIOSSettingList.save(task.context, node_id, update_list) - if len(delete_list) > 0: - delete_names = [setting['name'] for setting in delete_list] - objects.BIOSSettingList.delete(task.context, node_id, - delete_names) - - def _resume_cleaning(self, task): - task.node.set_driver_internal_info('cleaning_reboot', True) - task.node.save() diff --git a/ironic/drivers/modules/irmc/boot.py b/ironic/drivers/modules/irmc/boot.py deleted file mode 100644 index a4b8b07f05..0000000000 --- a/ironic/drivers/modules/irmc/boot.py +++ /dev/null @@ -1,1121 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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. - -""" -iRMC Boot Driver -""" - -import os -import shutil -import tempfile -from urllib import parse as urlparse - -from oslo_log import log as logging -from oslo_utils import importutils -from oslo_utils import netutils - -from ironic.common import boot_devices -from ironic.common import exception -from ironic.common.glance_service import service_utils -from ironic.common.i18n import _ -from ironic.common import image_service -from ironic.common import images -from ironic.common import metrics_utils -from ironic.common import states -from ironic.common import utils -from ironic.conductor import utils as manager_utils -from ironic.conf import CONF -from ironic.drivers import base -from ironic.drivers.modules import boot_mode_utils -from ironic.drivers.modules import deploy_utils -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.irmc import management as irmc_management -from ironic.drivers.modules import pxe -from ironic.drivers import utils as driver_utils - - -scci = importutils.try_import('scciclient.irmc.scci') -viom = importutils.try_import('scciclient.irmc.viom.client') - -try: - if CONF.debug: - scci.DEBUG = True -except Exception: - pass - -LOG = logging.getLogger(__name__) - -METRICS = metrics_utils.get_metrics_logger(__name__) - -REQUIRED_PROPERTIES = { - 'deploy_iso': _("Deployment ISO image file name. Required."), -} - -RESCUE_PROPERTIES = { - 'rescue_iso': _("UUID (from Glance) of the rescue ISO. Only " - "required if rescue mode is being used and ironic " - "is managing booting the rescue ramdisk.") -} - -OPTIONAL_PROPERTIES = { - 'irmc_pci_physical_ids': - _("Physical IDs of PCI cards. A dictionary of pairs of resource UUID " - "and its physical ID like ':,...'. The resources " - "are Ports and Volume connectors. The Physical ID consists of card " - "type, slot No, and port No. The format is " - "{LAN|FC|CNA}-. This parameter is necessary for " - "booting a node from a remote volume. Optional."), - 'irmc_storage_network_size': - _("Size of the network for iSCSI storage network. This is the size of " - "the IPv4 subnet mask that the storage network is configured to " - "utilize, in a range between 1 and 31 inclusive. This is necessary " - "for booting a node from a remote iSCSI volume. Optional."), - 'kernel_append_params': driver_utils.KERNEL_APPEND_PARAMS_DESCRIPTION % - {'option_group': 'irmc'}, -} - -COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() -COMMON_PROPERTIES.update(driver_utils.OPTIONAL_PROPERTIES) -COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) - - -def _is_image_href_ordinary_file_name(image_href): - """Check if image_href is an ordinary file name. - - This method judges if image_href is an ordinary file name or not, - which is a file supposed to be stored in share file system. - The ordinary file name is neither glance image href - nor image service href. - - :returns: True if image_href is ordinary file name, False otherwise. - """ - return not (service_utils.is_glance_image(image_href) - or urlparse.urlparse(image_href).scheme.lower() in - image_service.protocol_mapping) - - -def _parse_config_option(): - """Parse config file options. - - This method checks config file options validity. - - :raises: InvalidParameterValue, if config option has invalid value. - """ - error_msgs = [] - if not os.path.isdir(CONF.irmc.remote_image_share_root): - error_msgs.append( - _("Value '%s' for remote_image_share_root isn't a directory " - "or doesn't exist.") % - CONF.irmc.remote_image_share_root) - if error_msgs: - msg = (_("The following errors were encountered while parsing " - "config file:%s") % error_msgs) - raise exception.InvalidParameterValue(msg) - - -def _parse_driver_info(node, mode='deploy'): - """Gets the driver specific Node deployment info. - - This method validates whether the 'driver_info' property of the - supplied node contains the required or optional information properly - for this driver to deploy images to the node. - - :param node: a target node of the deployment - :param mode: Label indicating a deploy or rescue operation being - carried out on the node. Supported values are - 'deploy' and 'rescue'. Defaults to 'deploy'. - :returns: the driver_info values of the node. - :raises: MissingParameterValue, if any of the required parameters are - missing. - :raises: InvalidParameterValue, if any of the parameters have invalid - value. - """ - deploy_info = {} - - image_iso = driver_utils.get_agent_iso(node, mode, - deprecated_prefix='irmc') - deploy_info[f'{mode}_iso'] = image_iso - - error_msg = (_("Error validating iRMC virtual media for %s. Some " - "parameters were missing in node's driver_info") % mode) - deploy_utils.check_for_missing_params(deploy_info, error_msg) - - if _is_image_href_ordinary_file_name(image_iso): - image_iso_file = os.path.join(CONF.irmc.remote_image_share_root, - image_iso) - if not os.path.isfile(image_iso_file): - msg = (_("%(mode)s ISO file, %(iso_file)s, " - "not found for node: %(node)s.") % - {'mode': mode.capitalize(), - 'iso_file': image_iso_file, - 'node': node.uuid}) - raise exception.InvalidParameterValue(msg) - - kernel_params = driver_utils.get_kernel_append_params( - node, default=CONF.irmc.kernel_append_params) - if kernel_params is None: - LOG.warning('Relying on [pxe]kernel_append_params in the iRMC ' - 'hardware type is deprecated, please set ' - '[irmc]kernel_append_params') - kernel_params = CONF.pxe.kernel_append_params - deploy_info['kernel_append_params'] = kernel_params - - return deploy_info - - -def _parse_instance_info(node): - """Gets the instance specific Node deployment info. - - This method validates whether the 'instance_info' property of the - supplied node contains the required or optional information properly - for this driver to deploy images to the node. - - :param node: a target node of the deployment - :returns: the instance_info values of the node. - :raises: InvalidParameterValue, if any of the parameters have invalid - value. - """ - deploy_info = {} - - boot_iso = driver_utils.get_field(node, 'boot_iso', - deprecated_prefix='irmc', - collection='instance_info') - if boot_iso: - deploy_info['boot_iso'] = boot_iso - - if _is_image_href_ordinary_file_name(boot_iso): - boot_iso = os.path.join(CONF.irmc.remote_image_share_root, - boot_iso) - - if not os.path.isfile(boot_iso): - msg = (_("Boot ISO file, %(boot_iso)s, " - "not found for node: %(node)s.") % - {'boot_iso': boot_iso, 'node': node.uuid}) - raise exception.InvalidParameterValue(msg) - - return deploy_info - - -def _parse_deploy_info(node): - """Gets the instance and driver specific Node deployment info. - - This method validates whether the 'instance_info' and 'driver_info' - property of the supplied node contains the required information for - this driver to deploy images to the node. - - :param node: a target node of the deployment - :returns: a dict with the instance_info and driver_info values. - :raises: MissingParameterValue, if any of the required parameters are - missing. - :raises: InvalidParameterValue, if any of the parameters have invalid - value. - """ - deploy_info = {} - deploy_info.update(deploy_utils.get_image_instance_info(node)) - deploy_info.update(_parse_driver_info(node)) - deploy_info.update(_parse_instance_info(node)) - - return deploy_info - - -def _setup_vmedia(task, mode, ramdisk_options): - """Attaches virtual media and sets it as boot device. - - This method attaches the deploy or rescue ISO as virtual media, prepares - the arguments for ramdisk in virtual media floppy. - - :param task: a TaskManager instance containing the node to act on. - :param mode: Label indicating a deploy or rescue operation being - carried out on the node. Supported values are - 'deploy' and 'rescue'. - :param ramdisk_options: the options to be passed to the ramdisk in virtual - media floppy. - :raises: ImageRefValidationFailed if no image service can handle specified - href. - :raises: ImageCreationFailed, if it failed while creating the floppy image. - :raises: IRMCOperationError, if some operation on iRMC failed. - :raises: InvalidParameterValue if the validation of the - PowerInterface or ManagementInterface fails. - """ - iso = driver_utils.get_agent_iso(task.node, mode, deprecated_prefix='irmc') - - if _is_image_href_ordinary_file_name(iso): - iso_file = iso - else: - iso_file = _get_iso_name(task.node, label=mode) - iso_fullpathname = os.path.join( - CONF.irmc.remote_image_share_root, iso_file) - images.fetch(task.context, iso, iso_fullpathname) - - _setup_vmedia_for_boot(task, iso_file, ramdisk_options) - manager_utils.node_set_boot_device(task, boot_devices.CDROM) - - -def _get_iso_name(node, label): - """Returns the ISO file name for a given node. - - :param node: the node for which ISO file name is to be provided. - :param label: a string used as a base name for the ISO file. - """ - return "%s-%s.iso" % (label, node.uuid) - - -def _prepare_boot_iso(task, root_uuid): - """Prepare a boot ISO to boot the node. - - :param task: a TaskManager instance containing the node to act on. - :param root_uuid: the uuid of the root partition. - :raises: MissingParameterValue, if any of the required parameters are - missing. - :raises: InvalidParameterValue, if any of the parameters have invalid - value. - :raises: ImageCreationFailed, if creating boot ISO - for BIOS boot_mode failed. - """ - deploy_info = _parse_deploy_info(task.node) - - # fetch boot iso - if deploy_info.get('boot_iso'): - boot_iso_href = deploy_info['boot_iso'] - if _is_image_href_ordinary_file_name(boot_iso_href): - task.node.set_driver_internal_info('boot_iso', boot_iso_href) - else: - boot_iso_filename = _get_iso_name(task.node, label='boot') - boot_iso_fullpathname = os.path.join( - CONF.irmc.remote_image_share_root, boot_iso_filename) - images.fetch(task.context, boot_iso_href, boot_iso_fullpathname) - - task.node.set_driver_internal_info('boot_iso', - boot_iso_filename) - - # create boot iso - else: - image_href = deploy_info['image_source'] - image_props = ['kernel_id', 'ramdisk_id'] - image_properties = images.get_image_properties( - task.context, image_href, image_props) - kernel_href = (task.node.instance_info.get('kernel') - or image_properties['kernel_id']) - ramdisk_href = (task.node.instance_info.get('ramdisk') - or image_properties['ramdisk_id']) - - deploy_iso_href = deploy_info['deploy_iso'] - boot_mode = boot_mode_utils.get_boot_mode(task.node) - kernel_params = deploy_info['kernel_append_params'] - - boot_iso_filename = _get_iso_name(task.node, label='boot') - boot_iso_fullpathname = os.path.join( - CONF.irmc.remote_image_share_root, boot_iso_filename) - - images.create_boot_iso(task.context, boot_iso_fullpathname, - kernel_href, ramdisk_href, - deploy_iso_href=deploy_iso_href, - root_uuid=root_uuid, - kernel_params=kernel_params, - boot_mode=boot_mode) - - task.node.set_driver_internal_info('boot_iso', - boot_iso_filename) - - # save driver_internal_info['boot_iso'] - task.node.save() - - -def _get_floppy_image_name(node): - """Returns the floppy image name for a given node. - - :param node: the node for which image name is to be provided. - """ - return "image-%s.img" % node.uuid - - -def _prepare_floppy_image(task, params): - """Prepares the floppy image for passing the parameters. - - This method prepares a temporary vfat filesystem image, which - contains the parameters to be passed to the ramdisk. - Then it uploads the file NFS or CIFS server. - - :param task: a TaskManager instance containing the node to act on. - :param params: a dictionary containing 'parameter name'->'value' mapping - to be passed to the deploy ramdisk via the floppy image. - :returns: floppy image filename - :raises: ImageCreationFailed, if it failed while creating the floppy image. - :raises: IRMCOperationError, if copying floppy image file failed. - """ - floppy_filename = _get_floppy_image_name(task.node) - floppy_fullpathname = os.path.join( - CONF.irmc.remote_image_share_root, floppy_filename) - - with tempfile.NamedTemporaryFile() as vfat_image_tmpfile_obj: - images.create_vfat_image(vfat_image_tmpfile_obj.name, - parameters=params) - try: - shutil.copyfile(vfat_image_tmpfile_obj.name, - floppy_fullpathname) - except IOError as e: - operation = _("Copying floppy image file") - raise exception.IRMCOperationError( - operation=operation, error=e) - - return floppy_filename - - -def attach_boot_iso_if_needed(task): - """Attaches boot ISO for a deployed node if it exists. - - This method checks the instance info of the bare metal node for a - boot ISO. If the instance info has a value of key 'boot_iso', - it indicates ramdisk deploy. Therefore it attaches the boot ISO on the bare - metal node and then sets the node to boot from virtual media cdrom. - - :param task: a TaskManager instance containing the node to act on. - :raises: IRMCOperationError if attaching virtual media failed. - :raises: InvalidParameterValue if the validation of the - ManagementInterface fails. - """ - d_info = task.node.driver_internal_info - node_state = task.node.provision_state - - # Internal field, no deprecation - boot_iso = d_info.get('boot_iso') or d_info.get('irmc_boot_iso') - if boot_iso and node_state == states.ACTIVE: - _setup_vmedia_for_boot(task, boot_iso) - manager_utils.node_set_boot_device(task, boot_devices.CDROM) - - -def _setup_vmedia_for_boot(task, bootable_iso_filename, parameters=None): - """Sets up the node to boot from the boot ISO image. - - This method attaches a boot_iso on the node and passes - the required parameters to it via a virtual floppy image. - - :param task: a TaskManager instance containing the node to act on. - :param bootable_iso_filename: a bootable ISO image to attach to. - The iso file should be present in NFS/CIFS server. - :param parameters: the parameters to pass in a virtual floppy image - in a dictionary. This is optional. - :raises: ImageCreationFailed, if it failed while creating a floppy image. - :raises: IRMCOperationError, if attaching a virtual media failed. - """ - LOG.info("Setting up node %s to boot from virtual media", - task.node.uuid) - - _detach_virtual_cd(task.node) - _detach_virtual_fd(task.node) - - if parameters: - floppy_image_filename = _prepare_floppy_image(task, parameters) - _attach_virtual_fd(task.node, floppy_image_filename) - - _attach_virtual_cd(task.node, bootable_iso_filename) - - -def _cleanup_vmedia_boot(task): - """Cleans a node after a virtual media boot. - - This method cleans up a node after a virtual media boot. - It deletes floppy and cdrom images if they exist in NFS/CIFS server. - It also ejects both the virtual media cdrom and the virtual media floppy. - - :param task: a TaskManager instance containing the node to act on. - :raises: IRMCOperationError if ejecting virtual media failed. - """ - LOG.debug("Cleaning up node %s after virtual media boot", task.node.uuid) - - node = task.node - _detach_virtual_cd(node) - _detach_virtual_fd(node) - - _remove_share_file(_get_floppy_image_name(node)) - _remove_share_file(_get_iso_name(node, label='deploy')) - _remove_share_file(_get_iso_name(node, label='rescue')) - - -def _remove_share_file(share_filename): - """Remove given file from the share file system. - - :param share_filename: a file name to be removed. - """ - share_fullpathname = os.path.join( - CONF.irmc.remote_image_share_root, share_filename) - utils.unlink_without_raise(share_fullpathname) - - -def _attach_virtual_cd(node, bootable_iso_filename): - """Attaches the given url as virtual media on the node. - - :param node: an ironic node object. - :param bootable_iso_filename: a bootable ISO image to attach to. - The iso file should be present in NFS/CIFS server. - :raises: IRMCOperationError if attaching virtual media failed. - """ - try: - irmc_client = irmc_common.get_irmc_client(node) - - cd_set_params = scci.get_virtual_cd_set_params_cmd( - CONF.irmc.remote_image_server, - CONF.irmc.remote_image_user_domain, - scci.get_share_type(CONF.irmc.remote_image_share_type), - CONF.irmc.remote_image_share_name, - bootable_iso_filename, - CONF.irmc.remote_image_user_name, - CONF.irmc.remote_image_user_password) - - irmc_client(cd_set_params, do_async=False) - irmc_client(scci.MOUNT_CD, do_async=False) - - except scci.SCCIClientError as irmc_exception: - LOG.exception("Error while inserting virtual cdrom " - "into node %(uuid)s. Error: %(error)s", - {'uuid': node.uuid, 'error': irmc_exception}) - operation = _("Inserting virtual cdrom") - raise exception.IRMCOperationError(operation=operation, - error=irmc_exception) - - LOG.info("Attached virtual cdrom successfully" - " for node %s", node.uuid) - - -def _detach_virtual_cd(node): - """Detaches virtual cdrom on the node. - - :param node: an ironic node object. - :raises: IRMCOperationError if eject virtual cdrom failed. - """ - try: - irmc_client = irmc_common.get_irmc_client(node) - - irmc_client(scci.UNMOUNT_CD) - - except scci.SCCIClientError as irmc_exception: - LOG.exception("Error while ejecting virtual cdrom " - "from node %(uuid)s. Error: %(error)s", - {'uuid': node.uuid, 'error': irmc_exception}) - operation = _("Ejecting virtual cdrom") - raise exception.IRMCOperationError(operation=operation, - error=irmc_exception) - - LOG.info("Detached virtual cdrom successfully" - " for node %s", node.uuid) - - -def _attach_virtual_fd(node, floppy_image_filename): - """Attaches virtual floppy on the node. - - :param node: an ironic node object. - :raises: IRMCOperationError if insert virtual floppy failed. - """ - try: - irmc_client = irmc_common.get_irmc_client(node) - - fd_set_params = scci.get_virtual_fd_set_params_cmd( - CONF.irmc.remote_image_server, - CONF.irmc.remote_image_user_domain, - scci.get_share_type(CONF.irmc.remote_image_share_type), - CONF.irmc.remote_image_share_name, - floppy_image_filename, - CONF.irmc.remote_image_user_name, - CONF.irmc.remote_image_user_password) - - irmc_client(fd_set_params, do_async=False) - irmc_client(scci.MOUNT_FD, do_async=False) - - except scci.SCCIClientError as irmc_exception: - LOG.exception("Error while inserting virtual floppy " - "into node %(uuid)s. Error: %(error)s", - {'uuid': node.uuid, 'error': irmc_exception}) - operation = _("Inserting virtual floppy") - raise exception.IRMCOperationError(operation=operation, - error=irmc_exception) - - LOG.info("Attached virtual floppy successfully" - " for node %s", node.uuid) - - -def _detach_virtual_fd(node): - """Detaches virtual media floppy on the node. - - :param node: an ironic node object. - :raises: IRMCOperationError if eject virtual media floppy failed. - """ - try: - irmc_client = irmc_common.get_irmc_client(node) - - irmc_client(scci.UNMOUNT_FD) - - except scci.SCCIClientError as irmc_exception: - LOG.exception("Error while ejecting virtual floppy " - "from node %(uuid)s. Error: %(error)s", - {'uuid': node.uuid, 'error': irmc_exception}) - operation = _("Ejecting virtual floppy") - raise exception.IRMCOperationError(operation=operation, - error=irmc_exception) - - LOG.info("Detached virtual floppy successfully" - " for node %s", node.uuid) - - -def check_share_fs_mounted(): - """Check if Share File System (NFS or CIFS) is mounted. - - :raises: InvalidParameterValue, if config option has invalid value. - :raises: IRMCSharedFileSystemNotMounted, if shared file system is - not mounted. - """ - _parse_config_option() - if not os.path.ismount(CONF.irmc.remote_image_share_root): - raise exception.IRMCSharedFileSystemNotMounted( - share=CONF.irmc.remote_image_share_root) - - -class IRMCVolumeBootMixIn(object): - """Mix-in class for volume boot configuration to iRMC - - iRMC has a feature to set up remote boot to a server. This feature can be - used by VIOM (Virtual I/O Manager) library of SCCI client. - """ - - def _validate_volume_boot(self, task): - """Validate information for volume boot with this interface. - - This interface requires physical information of connectors to - configure remote boot to iRMC. Physical information of LAN ports - is also required since VIOM feature manages all adapters. - - :param task: a TaskManager instance containing the node to act on. - :raises: InvalidParameterValue: If invalid value is set to resources. - :raises: MissingParameterValue: If some value is not set to resources. - """ - - if not deploy_utils.get_remote_boot_volume(task): - # No boot volume. Nothing to validate. - return - - irmc_common.parse_driver_info(task.node) - - for port in task.ports: - self._validate_lan_port(task.node, port) - - for vt in task.volume_targets: - if vt.volume_type == 'iscsi': - self._validate_iscsi_connectors(task) - elif vt.volume_type == 'fibre_channel': - self._validate_fc_connectors(task) - # Unknown volume type is filtered in storage interface validation. - - def _get_connector_physical_id(self, task, types): - """Get physical ID of volume connector. - - A physical ID of volume connector required by iRMC is registered in - "irmc_pci_physical_ids" of a Node's driver_info as a pair of resource - UUID and its physical ID. This method gets this ID from the parameter. - - :param task: a TaskManager instance containing the node to act on. - :param types: a list of types of volume connectors required for the - target volume. One of connectors must have a physical ID. - :raises InvalidParameterValue if a physical ID is invalid. - :returns: A physical ID of a volume connector, or None if not set. - """ - for vc in task.volume_connectors: - if vc.type not in types: - continue - pid = task.node.driver_info['irmc_pci_physical_ids'].get(vc.uuid) - if not pid: - continue - try: - viom.validate_physical_port_id(pid) - except scci.SCCIInvalidInputError as e: - raise exception.InvalidParameterValue( - _('Physical port information of volume connector ' - '%(connector)s is invalid: %(error)s') % - {'connector': vc.uuid, 'error': e}) - return pid - return None - - def _validate_iscsi_connectors(self, task): - """Validate if volume connectors are properly registered for iSCSI. - - For connecting a node to an iSCSI volume, volume connectors containing - an IQN and an IP address are necessary. One of connectors must have - a physical ID of the PCI card. Network size of a storage network is - also required by iRMC. which should be registered in the node's - driver_info. - - :param task: a TaskManager instance containing the node to act on. - :raises: InvalidParameterValue if a volume connector with a required - type is not registered. - :raises: InvalidParameterValue if a physical ID is not registered in - any volume connectors. - :raises: InvalidParameterValue if a physical ID is invalid. - """ - vc_dict = self._get_volume_connectors_by_type(task) - node = task.node - missing_types = [] - for vc_type in ('iqn', 'ip'): - vc = vc_dict.get(vc_type) - if not vc: - missing_types.append(vc_type) - - if missing_types: - raise exception.MissingParameterValue( - _('Failed to validate for node %(node)s because of missing ' - 'volume connector(s) with type(s) %(types)s') % - {'node': node.uuid, - 'types': ', '.join(missing_types)}) - - if not self._get_connector_physical_id(task, ['iqn', 'ip']): - raise exception.MissingParameterValue( - _('Failed to validate for node %(node)s because of missing ' - 'physical port information for iSCSI connector. This ' - 'information must be set in "pci_physical_ids" parameter of ' - 'node\'s driver_info as :.') % - {'node': node.uuid}) - self._get_network_size(node) - - def _validate_fc_connectors(self, task): - """Validate if volume connectors are properly registered for FC. - - For connecting a node to a FC volume, one of connectors representing - wwnn and wwpn must have a physical ID of the PCI card. - - :param task: a TaskManager instance containing the node to act on. - :raises: InvalidParameterValue if a physical ID is not registered in - any volume connectors. - :raises: InvalidParameterValue if a physical ID is invalid. - """ - node = task.node - if not self._get_connector_physical_id(task, ['wwnn', 'wwpn']): - raise exception.MissingParameterValue( - _('Failed to validate for node %(node)s because of missing ' - 'physical port information for FC connector. This ' - 'information must be set in "pci_physical_ids" parameter of ' - 'node\'s driver_info as :.') % - {'node': node.uuid}) - - def _validate_lan_port(self, node, port): - """Validate ports for VIOM configuration. - - Physical information of LAN ports must be registered to VIOM - configuration to activate them under VIOM management. The information - has to be set to "irmc_pci_physical_id" parameter in a nodes - driver_info. - - :param node: an ironic node object - :param port: a port to be validated - :raises: MissingParameterValue if a physical ID of the port is not set. - :raises: InvalidParameterValue if a physical ID is invalid. - """ - physical_id = node.driver_info['irmc_pci_physical_ids'].get(port.uuid) - if not physical_id: - raise exception.MissingParameterValue( - _('Failed to validate for node %(node)s because of ' - 'missing physical port information of port %(port)s. ' - 'This information should be contained in ' - '"pci_physical_ids" parameter of node\'s driver_info.') % - {'node': node.uuid, - 'port': port.uuid}) - try: - viom.validate_physical_port_id(physical_id) - except scci.SCCIInvalidInputError as e: - raise exception.InvalidParameterValue( - _('Failed to validate for node %(node)s because ' - 'the physical port ID for port %(port)s in node\'s' - ' driver_info is invalid: %(reason)s') % - {'node': node.uuid, - 'port': port.uuid, - 'reason': e}) - - def _get_network_size(self, node): - """Get network size of a storage network. - - The network size of iSCSI network is required by iRMC for connecting - a node to an iSCSI volume. This network size is set to node's - driver_info as "irmc_storage_network_size" parameter in the form of - positive integer. - - :param node: an ironic node object. - :raises: MissingParameterValue if the network size parameter is not - set. - :raises: InvalidParameterValue the network size is invalid. - """ - network_size = node.driver_info.get('irmc_storage_network_size') - if network_size is None: - raise exception.MissingParameterValue( - _('Failed to validate for node %(node)s because of ' - 'missing "irmc_storage_network_size" parameter in the ' - 'node\'s driver_info. This should be a positive integer ' - 'smaller than 32.') % - {'node': node.uuid}) - try: - network_size = int(network_size) - except (ValueError, TypeError): - raise exception.InvalidParameterValue( - _('Failed to validate for node %(node)s because ' - '"irmc_storage_network_size" parameter in the node\'s ' - 'driver_info is invalid. This should be a ' - 'positive integer smaller than 32.') % - {'node': node.uuid}) - - if network_size not in range(1, 32): - raise exception.InvalidParameterValue( - _('Failed to validate for node %(node)s because ' - '"irmc_storage_network_size" parameter in the node\'s ' - 'driver_info is invalid. This should be a ' - 'positive integer smaller than 32.') % - {'node': node.uuid}) - - return network_size - - def _get_volume_connectors_by_type(self, task): - """Create a dictionary of volume connectors by types. - - :param task: a TaskManager. - :returns: a volume connector dictionary whose key is a connector type. - """ - connectors = {} - for vc in task.volume_connectors: - if vc.type in ('ip', 'iqn', 'wwnn', 'wwpn'): - connectors[vc.type] = vc - else: - LOG.warning('Node %(node)s has a volume_connector (%(uuid)s) ' - 'defined with an unsupported type: %(type)s.', - {'node': task.node.uuid, - 'uuid': vc.uuid, - 'type': vc.type}) - return connectors - - def _register_lan_ports(self, viom_conf, task): - """Register ports to VIOM configuration. - - LAN ports information must be registered for VIOM configuration to - activate them under VIOM management. - - :param viom_conf: a configurator for iRMC - :param task: a TaskManager instance containing the node to act on. - """ - for port in task.ports: - viom_conf.set_lan_port( - task.node.driver_info['irmc_pci_physical_ids'].get(port.uuid)) - - def _configure_boot_from_volume(self, task): - """Set information for booting from a remote volume to iRMC. - - :param task: a TaskManager instance containing the node to act on. - :raises: IRMCOperationError if iRMC operation failed - """ - - irmc_info = irmc_common.parse_driver_info(task.node) - viom_conf = viom.VIOMConfiguration(irmc_info, - identification=task.node.uuid) - - self._register_lan_ports(viom_conf, task) - - for vt in task.volume_targets: - if vt.volume_type == 'iscsi': - self._set_iscsi_target(task, viom_conf, vt) - elif vt.volume_type == 'fibre_channel': - self._set_fc_target(task, viom_conf, vt) - - try: - LOG.debug('Set VIOM configuration for node %(node)s: %(table)s', - {'node': task.node.uuid, - 'table': viom_conf.dump_json()}) - viom_conf.apply() - except scci.SCCIError as e: - LOG.error('iRMC failed to set VIOM configuration for node ' - '%(node)s: %(error)s', - {'node': task.node.uuid, - 'error': e}) - raise exception.IRMCOperationError( - operation='Configure VIOM', error=e) - - def _set_iscsi_target(self, task, viom_conf, target): - """Set information for iSCSI boot to VIOM configuration.""" - connectors = self._get_volume_connectors_by_type(task) - target_host, target_port = netutils.parse_host_port( - target.properties['target_portal']) - - if target.properties.get('auth_method') == 'CHAP': - chap_user = target.properties.get('auth_username') - chap_secret = target.properties.get('auth_password') - else: - chap_user = None - chap_secret = None - - viom_conf.set_iscsi_volume( - self._get_connector_physical_id(task, ['iqn', 'ip']), - connectors['iqn'].connector_id, - initiator_ip=connectors['ip'].connector_id, - initiator_netmask=self._get_network_size(task.node), - target_iqn=target.properties['target_iqn'], - target_ip=target_host, - target_port=target_port, - target_lun=target.properties.get('target_lun'), - # Boot priority starts from 1 in the library. - boot_prio=target.boot_index + 1, - chap_user=chap_user, - chap_secret=chap_secret) - - def _set_fc_target(self, task, viom_conf, target): - """Set information for FC boot to VIOM configuration.""" - wwn = target.properties['target_wwn'] - if isinstance(wwn, list): - wwn = wwn[0] - viom_conf.set_fc_volume( - self._get_connector_physical_id(task, ['wwnn', 'wwpn']), - wwn, - target.properties['target_lun'], - # Boot priority starts from 1 in the library. - boot_prio=target.boot_index + 1) - - def _cleanup_boot_from_volume(self, task, reboot=False): - """Clear remote boot configuration. - - :param task: a task from TaskManager. - :param reboot: True if reboot node soon - :raises: IRMCOperationError if iRMC operation failed - """ - irmc_info = irmc_common.parse_driver_info(task.node) - try: - viom_conf = viom.VIOMConfiguration(irmc_info, task.node.uuid) - viom_conf.terminate(reboot=reboot) - except scci.SCCIError as e: - LOG.error('iRMC failed to terminate VIOM configuration from ' - 'node %(node)s: %(error)s', {'node': task.node.uuid, - 'error': e}) - raise exception.IRMCOperationError(operation='Terminate VIOM', - error=e) - - -class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn): - """iRMC Virtual Media boot-related actions.""" - - supported = False - capabilities = ['iscsi_volume_boot', 'fibre_channel_volume_boot'] - - def __init__(self): - """Constructor of IRMCVirtualMediaBoot. - - :raises: IRMCSharedFileSystemNotMounted, if shared file system is - not mounted. - :raises: InvalidParameterValue, if config option has invalid value. - """ - check_share_fs_mounted() - super(IRMCVirtualMediaBoot, self).__init__() - - def get_properties(self): - # TODO(tiendc): COMMON_PROPERTIES should also include rescue - # related properties (RESCUE_PROPERTIES). We can add them in Rocky, - # when classic drivers get removed. - return COMMON_PROPERTIES - - @METRICS.timer('IRMCVirtualMediaBoot.validate') - def validate(self, task): - """Validate the deployment information for the task's node. - - :param task: a TaskManager instance containing the node to act on. - :raises: InvalidParameterValue, if config option has invalid value. - :raises: IRMCSharedFileSystemNotMounted, if shared file system is - not mounted. - :raises: InvalidParameterValue, if some information is invalid. - :raises: MissingParameterValue if 'kernel_id' and 'ramdisk_id' are - missing in the Glance image, or if 'kernel' and 'ramdisk' are - missing in the Non Glance image. - """ - check_share_fs_mounted() - - self._validate_volume_boot(task) - if not task.driver.storage.should_write_image(task): - LOG.debug('Node %(node)s skips image validation because of ' - 'booting from a remote volume.', - {'node': task.node.uuid}) - return - - d_info = _parse_deploy_info(task.node) - deploy_utils.validate_image_properties(task, d_info) - - @METRICS.timer('IRMCVirtualMediaBoot.prepare_ramdisk') - def prepare_ramdisk(self, task, ramdisk_params): - """Prepares the deploy or rescue ramdisk using virtual media. - - Prepares the options for the deploy or rescue ramdisk, sets the node - to boot from virtual media cdrom. - - :param task: a TaskManager instance containing the node to act on. - :param ramdisk_params: the options to be passed to the ramdisk. - :raises: ImageRefValidationFailed if no image service can handle - specified href. - :raises: ImageCreationFailed, if it failed while creating the floppy - image. - :raises: InvalidParameterValue if the validation of the - PowerInterface or ManagementInterface fails. - :raises: IRMCOperationError, if some operation on iRMC fails. - """ - - if not driver_utils.need_prepare_ramdisk(task.node): - return - - # NOTE(tiendc): Before deploying, we need to backup BIOS config - # as the data will be used later when cleaning. - if task.node.provision_state == states.DEPLOYING: - irmc_management.backup_bios_config(task) - - if not task.driver.storage.should_write_image(task): - LOG.debug('Node %(node)s skips ramdisk preparation because of ' - 'booting from a remote volume.', - {'node': task.node.uuid}) - return - - # NOTE(TheJulia): Since we're deploying, cleaning, or rescuing, - # with virtual media boot, we should generate a token! - manager_utils.add_secret_token(task.node, pregenerated=True) - ramdisk_params['ipa-agent-token'] = \ - task.node.driver_internal_info['agent_secret_token'] - task.node.save() - - deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task) - if deploy_nic_mac is not None: - ramdisk_params['BOOTIF'] = deploy_nic_mac - - if task.node.provision_state == states.RESCUING: - mode = 'rescue' - else: - mode = 'deploy' - - _setup_vmedia(task, mode, ramdisk_params) - - @METRICS.timer('IRMCVirtualMediaBoot.clean_up_ramdisk') - def clean_up_ramdisk(self, task): - """Cleans up the boot of ironic ramdisk. - - This method cleans up the environment that was setup for booting the - deploy or rescue ramdisk. - - :param task: a task from TaskManager. - :returns: None - :raises: IRMCOperationError if iRMC operation failed. - """ - _cleanup_vmedia_boot(task) - - @METRICS.timer('IRMCVirtualMediaBoot.prepare_instance') - def prepare_instance(self, task): - """Prepares the boot of instance. - - This method prepares the boot of the instance after reading - relevant information from the node's database. - - :param task: a task from TaskManager. - :returns: None - """ - if task.node.driver_internal_info.get('boot_from_volume'): - LOG.debug('Node %(node)s is configured for booting from a remote ' - 'volume.', - {'node': task.node.uuid}) - self._configure_boot_from_volume(task) - return - - _cleanup_vmedia_boot(task) - - node = task.node - iwdi = node.driver_internal_info.get('is_whole_disk_image') - if deploy_utils.get_boot_option(node) == "local" or iwdi: - manager_utils.node_set_boot_device(task, boot_devices.DISK, - persistent=True) - else: - root_uuid_or_disk_id = node.driver_internal_info[ - 'root_uuid_or_disk_id'] - self._configure_vmedia_boot(task, root_uuid_or_disk_id) - - # Enable secure boot, if being requested - boot_mode_utils.configure_secure_boot_if_needed(task) - - @METRICS.timer('IRMCVirtualMediaBoot.clean_up_instance') - def clean_up_instance(self, task): - """Cleans up the boot of instance. - - This method cleans up the environment that was setup for booting - the instance. - - :param task: a task from TaskManager. - :returns: None - :raises: IRMCOperationError if iRMC operation failed. - """ - if task.node.driver_internal_info.get('boot_from_volume'): - self._cleanup_boot_from_volume(task) - return - - # Disable secure boot, if enabled secure boot - boot_mode_utils.deconfigure_secure_boot_if_needed(task) - - _remove_share_file(_get_iso_name(task.node, label='boot')) - task.node.del_driver_internal_info('boot_iso') - task.node.del_driver_internal_info('irmc_boot_iso') - - task.node.save() - _cleanup_vmedia_boot(task) - - def _configure_vmedia_boot(self, task, root_uuid_or_disk_id): - """Configure vmedia boot for the node.""" - node = task.node - _prepare_boot_iso(task, root_uuid_or_disk_id) - _setup_vmedia_for_boot( - task, node.driver_internal_info['boot_iso']) - manager_utils.node_set_boot_device(task, boot_devices.CDROM, - persistent=True) - - @METRICS.timer('IRMCVirtualMediaBoot.validate_rescue') - def validate_rescue(self, task): - """Validate that the node has required properties for rescue. - - :param task: a TaskManager instance with the node being checked - :raises: MissingParameterValue if node is missing one or more required - parameters - :raises: InvalidParameterValue, if any of the parameters have invalid - value. - """ - _parse_driver_info(task.node, mode='rescue') - - -class IRMCPXEBoot(pxe.PXEBoot): - """iRMC PXE boot.""" - - supported = False - - @METRICS.timer('IRMCPXEBoot.prepare_ramdisk') - def prepare_ramdisk(self, task, ramdisk_params): - """Prepares the boot of Ironic ramdisk using PXE. - - This method prepares the boot of the deploy kernel/ramdisk after - reading relevant information from the node's driver_info and - instance_info. - - :param task: a task from TaskManager. - :param ramdisk_params: the parameters to be passed to the ramdisk. - pxe driver passes these parameters as kernel command-line - arguments. - :returns: None - :raises: MissingParameterValue, if some information is missing in - node's driver_info or instance_info. - :raises: InvalidParameterValue, if some information provided is - invalid. - :raises: IronicException, if some power or set boot device - operation failed on the node. - """ - # NOTE(tiendc): Before deploying, we need to backup BIOS config - # as the data will be used later when cleaning. - if task.node.provision_state == states.DEPLOYING: - irmc_management.backup_bios_config(task) - - super(IRMCPXEBoot, self).prepare_ramdisk(task, ramdisk_params) diff --git a/ironic/drivers/modules/irmc/common.py b/ironic/drivers/modules/irmc/common.py deleted file mode 100644 index 157beb98ed..0000000000 --- a/ironic/drivers/modules/irmc/common.py +++ /dev/null @@ -1,670 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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 iRMC modules. -""" -import json -import os -import re - -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import importutils -from oslo_utils import strutils - -from ironic.common import exception -from ironic.common.i18n import _ -from ironic.common import utils -from ironic.conf import CONF -from ironic.drivers.modules import snmp -from ironic.drivers import utils as driver_utils - -scci = importutils.try_import('scciclient.irmc.scci') -elcm = importutils.try_import('scciclient.irmc.elcm') - -LOG = logging.getLogger(__name__) - - -IRMC_OS_NAME_R = re.compile(r'iRMC\s+S\d+') -IRMC_OS_NAME_NUM_R = re.compile(r'\d+$') -IRMC_FW_VER_R = re.compile(r'\d(\.\d+)*\w*') -IRMC_FW_VER_NUM_R = re.compile(r'\d(\.\d+)*') - -IPMI_ENABLED_BY_DEFAULT_RANGES = { - # iRMC S4 enables IPMI over LAN by default - '4': None, - # iRMC S5 enables IPMI over LAN by default - '5': None, - # iRMC S6 disables IPMI over LAN by default from version 2.00 - '6': {'upper': '2.00'}} - -ELCM_STATUS_PATH = '/rest/v1/Oem/eLCM/eLCMStatus' - -# List of xxx_interface & implementation pair which uses SNMP internally -# and iRMC driver supports -INTERFACE_IMPL_LIST_WITH_SNMP = { - 'inspect_interface': {'irmc', }, - 'power_interface': {'irmc', }} - -REQUIRED_PROPERTIES = { - 'irmc_address': _("IP address or hostname of the iRMC. Required."), - 'irmc_username': _("Username for the iRMC with administrator privileges. " - "Required."), - 'irmc_password': _("Password for irmc_username. Required."), -} -OPTIONAL_PROPERTIES = { - 'irmc_port': _("Port to be used for iRMC operations; either 80 or 443. " - "The default value is 443. Optional."), - 'irmc_auth_method': _("Authentication method for iRMC operations; " - "either 'basic' or 'digest'. The default value is " - "'basic'. Optional."), - 'irmc_client_timeout': _("Timeout (in seconds) for iRMC operations. " - "The default value is 60. Optional."), - 'irmc_sensor_method': _("Sensor data retrieval method; either " - "'ipmitool' or 'scci'. The default value is " - "'ipmitool'. Optional."), -} -OPTIONAL_DRIVER_INFO_PROPERTIES = { - 'irmc_verify_ca': _('Either a Boolean value, a path to a CA_BUNDLE ' - 'file or directory with certificates of trusted ' - 'CAs. If set to True the driver will verify the ' - 'host certificates; if False the driver will ' - 'ignore verifying the SSL certificate. If it\'s ' - 'a path the driver will use the specified ' - 'certificate or one of the certificates in the ' - 'directory. Defaults to True. Optional'), -} - -SNMP_PROPERTIES = { - 'irmc_snmp_version': _("SNMP protocol version; either 'v1', 'v2c', or " - "'v3'. The default value is 'v2c'. Optional."), - 'irmc_snmp_port': _("SNMP port. The default is 161. Optional."), - 'irmc_snmp_community': _("SNMP community required for versions 'v1' and " - "'v2c'. The default value is 'public'. " - "Optional."), -} - -SNMP_V3_REQUIRED_PROPERTIES = { - 'irmc_snmp_user': _("SNMPv3 User-based Security Model (USM) username. " - "Required for version 'v3’. "), - 'irmc_snmp_auth_password': _("SNMPv3 message authentication key. Must be " - "8+ characters long. Required when message " - "authentication is used."), - 'irmc_snmp_priv_password': _("SNMPv3 message privacy key. Must be 8+ " - "characters long. Required when message " - "privacy is used."), -} - -SNMP_V3_OPTIONAL_PROPERTIES = { - 'irmc_snmp_auth_proto': _("SNMPv3 message authentication protocol ID. " - "Required for version 'v3'. " - "If using iRMC S4/S5, only 'sha' is supported." - "If using iRMC S6, the valid options are " - "'sha256', 'sha384', 'sha512'."), - 'irmc_snmp_priv_proto': _("SNMPv3 message privacy (encryption) protocol " - "ID. Required for version 'v3'. " - "'aes' is supported."), -} - -SNMP_V3_DEPRECATED_PROPERTIES = { - 'irmc_snmp_security': _("SNMP security name required for version 'v3'. " - "Optional. Deprecated."), -} - - -COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() -COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) -COMMON_PROPERTIES.update(OPTIONAL_DRIVER_INFO_PROPERTIES) -COMMON_PROPERTIES.update(SNMP_PROPERTIES) -COMMON_PROPERTIES.update(SNMP_V3_REQUIRED_PROPERTIES) -COMMON_PROPERTIES.update(SNMP_V3_OPTIONAL_PROPERTIES) -COMMON_PROPERTIES.update(SNMP_V3_DEPRECATED_PROPERTIES) - - -def parse_driver_info(node): - """Gets the specific Node driver info. - - This method validates whether the 'driver_info' property of the - supplied node contains the required information for this driver. - - :param node: An ironic node object. - :returns: A dict containing information from driver_info - and default values. - :raises: InvalidParameterValue if invalid value is contained - in the 'driver_info' property. - :raises: MissingParameterValue if some mandatory key is missing - in the 'driver_info' property. - """ - info = node.driver_info - missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)] - if missing_info: - raise exception.MissingParameterValue(_( - "Missing the following iRMC parameters in node's" - " driver_info: %s.") % missing_info) - - req = {key: value for key, value in info.items() - if key in REQUIRED_PROPERTIES} - # corresponding config names don't have 'irmc_' prefix - opt = {param: info.get(param, CONF.irmc.get(param[len('irmc_'):])) - for param in OPTIONAL_PROPERTIES} - opt_driver_info = {param: info.get(param) - for param in OPTIONAL_DRIVER_INFO_PROPERTIES} - d_info = dict(req, **opt, **opt_driver_info) - d_info['irmc_port'] = utils.validate_network_port( - d_info['irmc_port'], 'irmc_port') - - error_msgs = [] - if (d_info['irmc_auth_method'].lower() not in ('basic', 'digest')): - error_msgs.append( - _("Value '%s' is not supported for 'irmc_auth_method'.") % - d_info['irmc_auth_method']) - if d_info['irmc_port'] not in (80, 443): - error_msgs.append( - _("Value '%s' is not supported for 'irmc_port'.") % - d_info['irmc_port']) - if not isinstance(d_info['irmc_client_timeout'], int): - error_msgs.append( - _("Value '%s' is not an integer for 'irmc_client_timeout'") % - d_info['irmc_client_timeout']) - if d_info['irmc_sensor_method'].lower() not in ('ipmitool', 'scci'): - error_msgs.append( - _("Value '%s' is not supported for 'irmc_sensor_method'.") % - d_info['irmc_sensor_method']) - - verify_ca = driver_utils.get_verify_ca(node, d_info.get('irmc_verify_ca')) - if verify_ca is None: - d_info['irmc_verify_ca'] = verify_ca = CONF.webserver_verify_ca - - # Check if verify_ca is a Boolean or a file/directory in the file-system - if isinstance(verify_ca, str): - if ((os.path.isdir(verify_ca) and os.path.isabs(verify_ca)) - or (os.path.isfile(verify_ca) and os.path.isabs(verify_ca))): - # If it's fullpath and dir/file, we don't need to do anything - pass - else: - try: - d_info['irmc_verify_ca'] = strutils.bool_from_string( - verify_ca, strict=True) - except ValueError: - error_msgs.append( - _('Invalid value type set in driver_info/' - 'irmc_verify_ca on node %(node)s. ' - 'The value should be a Boolean or the path ' - 'to a file/directory, not "%(value)s"' - ) % {'value': verify_ca, 'node': node.uuid}) - elif isinstance(verify_ca, bool): - # If it's a boolean it's grand, we don't need to do anything - pass - else: - error_msgs.append( - _('Invalid value type set in driver_info/irmc_verify_ca ' - 'on node %(node)s. The value should be a Boolean or the path ' - 'to a file/directory, not "%(value)s"') % {'value': verify_ca, - 'node': node.uuid}) - - if error_msgs: - msg = (_("The following errors were encountered while parsing " - "driver_info:\n%s") % "\n".join(error_msgs)) - raise exception.InvalidParameterValue(msg) - - d_info.update(_parse_snmp_driver_info(node, info)) - - return d_info - - -def _parse_snmp_driver_info(node, info): - """Parses the SNMP related driver_info parameters. - - :param node: An Ironic node object. - :param info: driver_info dictionary. - :returns: A dictionary containing SNMP information. - :raises: MissingParameterValue if any of the mandatory - parameter values are not provided. - :raises: InvalidParameterValue if there is any invalid - value provided. - """ - snmp_info = {param: info.get(param, CONF.irmc.get(param[len('irmc_'):])) - for param in SNMP_PROPERTIES} - valid_versions = {"v1": snmp.SNMP_V1, - "v2c": snmp.SNMP_V2C, - "v3": snmp.SNMP_V3} - - for int_name, impl_list in INTERFACE_IMPL_LIST_WITH_SNMP.items(): - if getattr(node, int_name) in impl_list: - break - else: - return snmp_info - - if snmp_info['irmc_snmp_version'].lower() not in valid_versions: - raise exception.InvalidParameterValue(_( - "Value '%s' is not supported for 'irmc_snmp_version'.") % - snmp_info['irmc_snmp_version'] - ) - snmp_info["irmc_snmp_version"] = \ - valid_versions[snmp_info["irmc_snmp_version"].lower()] - - snmp_info['irmc_snmp_port'] = utils.validate_network_port( - snmp_info['irmc_snmp_port'], 'irmc_snmp_port') - - if snmp_info['irmc_snmp_version'] != snmp.SNMP_V3: - if (snmp_info['irmc_snmp_community'] - and not isinstance(snmp_info['irmc_snmp_community'], str)): - raise exception.InvalidParameterValue(_( - "Value '%s' is not a string for 'irmc_snmp_community'") % - snmp_info['irmc_snmp_community']) - if utils.is_fips_enabled(): - raise exception.InvalidParameterValue(_( - "'v3' has to be set for 'irmc_snmp_version' " - "when FIPS mode is enabled.")) - - else: - snmp_info.update(_parse_snmp_v3_info(node, info)) - - return snmp_info - - -def _parse_snmp_v3_info(node, info): - snmp_info = {} - missing_info = [] - valid_values = {'irmc_snmp_auth_proto': ['sha', 'sha256', 'sha384', - 'sha512'], - 'irmc_snmp_priv_proto': ['aes']} - valid_protocols = {'irmc_snmp_auth_proto': snmp.snmp_auth_protocols, - 'irmc_snmp_priv_proto': snmp.snmp_priv_protocols} - snmp_keys = {'irmc_snmp_auth_password', 'irmc_snmp_priv_password'} - - security = info.get('irmc_snmp_security', CONF.irmc.get('snmp_security')) - for param in SNMP_V3_REQUIRED_PROPERTIES: - try: - snmp_info[param] = info[param] - except KeyError: - if param == 'irmc_snmp_user': - if not security: - missing_info.append(param) - else: - LOG.warning(_("'irmc_snmp_security' parameter is " - "deprecated in favor of 'irmc_snmp_user' " - "parameter. Please set 'irmc_snmp_user' " - "and remove 'irmc_snmp_security' for node " - "%s."), node.uuid) - # In iRMC, the username must start with a letter, so only - # a string can be a valid username and a string from a - # number is invalid. - if not isinstance(security, str): - raise exception.InvalidParameterValue(_( - "Value '%s' is not a string for " - "'irmc_snmp_security.") % - info['irmc_snmp_security']) - else: - snmp_info['irmc_snmp_user'] = security - security = None - else: - missing_info.append(param) - - if missing_info: - raise exception.MissingParameterValue(_( - "The following required SNMP parameters " - "are missing: %s") % missing_info) - - if security: - LOG.warning(_("'irmc_snmp_security' parameter is ignored in favor of " - "'irmc_snmp_user' parameter. Please remove " - "'irmc_snmp_security' from node %s " - "configuration."), node.uuid) - if not isinstance(snmp_info['irmc_snmp_user'], str): - raise exception.InvalidParameterValue(_( - "Value '%s' is not a string for 'irmc_snmp_user'.") % - info['irmc_snmp_user']) - - for param in snmp_keys: - if not isinstance(snmp_info[param], str): - raise exception.InvalidParameterValue(_( - "Value %(value)s is not a string for %(param)s.") % - {'param': param, 'value': snmp_info[param]}) - if len(snmp_info[param]) < 8: - raise exception.InvalidParameterValue(_( - "%s is too short. (8+ chars required)") % param) - - for param in SNMP_V3_OPTIONAL_PROPERTIES: - value = None - try: - value = info[param] - if value not in valid_values[param]: - raise exception.InvalidParameterValue(_( - "Invalid value %(value)s given for driver info parameter " - "%(param)s, the valid values are %(valid_values)s.") % - {'param': param, - 'value': value, - 'valid_values': valid_values[param]}) - except KeyError: - value = CONF.irmc.get(param[len('irmc_'):]) - snmp_info[param] = valid_protocols[param].get(value) - if not snmp_info[param]: - raise exception.InvalidParameterValue(_( - "Unknown SNMPv3 protocol %(value)s given for " - "driver info parameter %(param)s") % {'param': param, - 'value': value}) - - return snmp_info - - -def get_irmc_client(node): - """Gets an iRMC SCCI client. - - Given an ironic node object, this method gives back a iRMC SCCI client - to do operations on the iRMC. - - :param node: An ironic node object. - :returns: scci_cmd partial function which takes a SCCI command param. - :raises: InvalidParameterValue on invalid inputs. - :raises: MissingParameterValue if some mandatory information - is missing on the node - :raises: IRMCOperationError if iRMC operation failed - """ - driver_info = parse_driver_info(node) - - scci_client = scci.get_client( - driver_info['irmc_address'], - driver_info['irmc_username'], - driver_info['irmc_password'], - port=driver_info['irmc_port'], - auth_method=driver_info['irmc_auth_method'], - verify=driver_info.get('irmc_verify_ca'), - client_timeout=driver_info['irmc_client_timeout']) - return scci_client - - -def update_ipmi_properties(task): - """Update ipmi properties to node driver_info. - - :param task: A task from TaskManager. - """ - node = task.node - info = node.driver_info - - # updating ipmi credentials - info['ipmi_address'] = info.get('irmc_address') - info['ipmi_username'] = info.get('irmc_username') - info['ipmi_password'] = info.get('irmc_password') - - # saving ipmi credentials to task object - task.node.driver_info = info - - -def get_irmc_report(node): - """Gets iRMC SCCI report. - - Given an ironic node object, this method gives back a iRMC SCCI report. - - :param node: An ironic node object. - :returns: A xml.etree.ElementTree object. - :raises: InvalidParameterValue on invalid inputs. - :raises: MissingParameterValue if some mandatory information - is missing on the node. - :raises: scci.SCCIInvalidInputError if required parameters are invalid. - :raises: scci.SCCIClientError if SCCI failed. - """ - driver_info = parse_driver_info(node) - - return scci.get_report( - driver_info['irmc_address'], - driver_info['irmc_username'], - driver_info['irmc_password'], - port=driver_info['irmc_port'], - auth_method=driver_info['irmc_auth_method'], - verify=driver_info.get('irmc_verify_ca'), - client_timeout=driver_info['irmc_client_timeout']) - - -def get_secure_boot_mode(node): - """Get the current secure boot mode. - - :param node: An ironic node object. - :raises: UnsupportedDriverExtension if secure boot is not present. - :raises: IRMCOperationError if the operation fails. - """ - driver_info = parse_driver_info(node) - - try: - return elcm.get_secure_boot_mode(driver_info) - except elcm.SecureBootConfigNotFound: - raise exception.UnsupportedDriverExtension( - driver=node.driver, extension='get_secure_boot_state') - except scci.SCCIError as irmc_exception: - LOG.error("Failed to get secure boot for node %s", node.uuid) - raise exception.IRMCOperationError( - operation=_("getting secure boot mode"), - error=irmc_exception) - - -def set_secure_boot_mode(node, enable): - """Enable or disable UEFI Secure Boot - - :param node: An ironic node object. - :param enable: Boolean value. True if the secure boot to be - enabled. - :raises: IRMCOperationError if the operation fails. - """ - driver_info = parse_driver_info(node) - - try: - elcm.set_secure_boot_mode(driver_info, enable) - LOG.info("Set secure boot to %(flag)s for node %(node)s", - {'flag': enable, 'node': node.uuid}) - except scci.SCCIError as irmc_exception: - LOG.error("Failed to set secure boot to %(flag)s for node %(node)s", - {'flag': enable, 'node': node.uuid}) - raise exception.IRMCOperationError( - operation=_("setting secure boot mode"), - error=irmc_exception) - - -def check_elcm_license(node): - """Connect to iRMC and return status of eLCM license - - This function connects to iRMC REST API and check whether eLCM - license is active. This function can be used to check connection to - iRMC REST API. - - :param node: An ironic node object - :returns: dictionary whose keys are 'active' and 'status_code'. - value of 'active' is boolean showing if eLCM license is active - and value of 'status_code' is int which is HTTP return code - from iRMC REST API access - :raises: InvalidParameterValue if invalid value is contained - in the 'driver_info' property. - :raises: MissingParameterValue if some mandatory key is missing - in the 'driver_info' property. - :raises: IRMCOperationError if the operation fails. - """ - try: - d_info = parse_driver_info(node) - # GET to /rest/v1/Oem/eLCM/eLCMStatus returns - # JSON data like this: - # - # { - # "eLCMStatus":{ - # "EnabledAndLicenced":"true", - # "SDCardMounted":"false" - # } - # } - # - # EnabledAndLicenced tells whether eLCM license is valid - # - r = elcm.elcm_request(d_info, 'GET', ELCM_STATUS_PATH) - - # If r.status_code is 200, it means success and r.text is JSON. - # If it is 500, it means there is problem at iRMC side - # and iRMC cannot return eLCM status. - # If it was 401, elcm_request raises SCCIClientError. - # Otherwise, r.text may not be JSON. - if r.status_code == 200: - license_active = strutils.bool_from_string( - jsonutils.loads(r.text)['eLCMStatus']['EnabledAndLicenced'], - strict=True) - else: - license_active = False - - return {'active': license_active, 'status_code': r.status_code} - except (scci.SCCIError, - json.JSONDecodeError, - TypeError, - KeyError, - ValueError) as irmc_exception: - LOG.error("Failed to check eLCM license status for node $(node)s", - {'node': node.uuid}) - raise exception.IRMCOperationError( - operation='checking eLCM license status', - error=irmc_exception) - - -def set_irmc_version(task): - """Fetch and save iRMC firmware version. - - This function should be called before calling any other functions which - need to check node's iRMC firmware version. - - Set `/` to driver_internal_info['irmc_fw_version'] - - :param node: An ironic node object - :raises: InvalidParameterValue if invalid value is contained - in the 'driver_info' property. - :raises: MissingParameterValue if some mandatory key is missing - in the 'driver_info' property. - :raises: IRMCOperationError if the operation fails. - :raises: NodeLocked if the target node is already locked. - """ - - node = task.node - try: - report = get_irmc_report(node) - irmc_os, fw_version = scci.get_irmc_version_str(report) - - fw_ver = node.driver_internal_info.get('irmc_fw_version') - if fw_ver != '/'.join([irmc_os, fw_version]): - task.upgrade_lock(purpose='saving firmware version') - node.set_driver_internal_info('irmc_fw_version', - f"{irmc_os}/{fw_version}") - node.save() - except scci.SCCIError as irmc_exception: - LOG.error("Failed to fetch iRMC FW version for node %s", - node.uuid) - raise exception.IRMCOperationError( - operation=_("fetching irmc fw version "), - error=irmc_exception) - - -def _version_lt(v1, v2): - v1_l = v1.split('.') - v2_l = v2.split('.') - if len(v1_l) <= len(v2_l): - v1_l.extend(['0'] * (len(v2_l) - len(v1_l))) - else: - v2_l.extend(['0'] * (len(v1_l) - len(v2_l))) - - for i in range(len(v1_l)): - if int(v1_l[i]) < int(v2_l[i]): - return True - elif int(v1_l[i]) > int(v2_l[i]): - return False - else: - return False - - -def _version_le(v1, v2): - v1_l = v1.split('.') - v2_l = v2.split('.') - if len(v1_l) <= len(v2_l): - v1_l.extend(['0'] * (len(v2_l) - len(v1_l))) - else: - v2_l.extend(['0'] * (len(v1_l) - len(v2_l))) - - for i in range(len(v1_l)): - if int(v1_l[i]) < int(v2_l[i]): - return True - elif int(v1_l[i]) > int(v2_l[i]): - return False - else: - return True - - -def within_version_ranges(node, version_ranges): - """Read saved iRMC FW version and check if it is within the passed ranges. - - :param node: An ironic node object - :param version_ranges: A Python dictionary containing version ranges in the - next format: : , where is a string representing - iRMC OS number (e.g. '4') and is a dictionaries indicating - the specific firmware version ranges under the iRMC OS number . - - The dictionary used in only has two keys: 'min' and 'upper', - and value of each key is a string representing iRMC firmware version - number or None. Both keys can be absent and their value can be None. - - It is acceptable to not set ranges for a (for example set - to None, {}, etc...), in this case, this function only - checks if the node's iRMC OS number matches the . - - Valid example: - {'3': None, # all version of iRMC S3 matches - '4': {}, # all version of iRMC S4 matches - # all version of iRMC S5 matches - '5': {'min': None, 'upper': None}, - # iRMC S6 whose version is >=1.20 matches - '6': {'min': '1.20', 'upper': None}, - # iRMC S7 whose version is - # 5.51<= (version) <8.23 matches - '7': {'min': '5.51', 'upper': '8.23'}} - - :returns: True if node's iRMC FW is in range, False if not or - fails to parse firmware version - """ - - try: - fw_version = node.driver_internal_info.get('irmc_fw_version', '') - irmc_os, irmc_ver = fw_version.split('/') - - if IRMC_OS_NAME_R.match(irmc_os) and IRMC_FW_VER_R.match(irmc_ver): - os_num = IRMC_OS_NAME_NUM_R.search(irmc_os).group(0) - fw_num = IRMC_FW_VER_NUM_R.search(irmc_ver).group(0) - - if os_num not in version_ranges: - return False - - v_range = version_ranges[os_num] - - # An OS number with no ranges set means no need to check - # specific version, all the version under this OS number is valid. - if not v_range: - return True - - # Specific range is set, check if the node's - # firmware version is within it. - min_ver = v_range.get('min') - upper_ver = v_range.get('upper') - flag = True - if min_ver: - flag = _version_le(min_ver, fw_num) - if flag and upper_ver: - flag = _version_lt(fw_num, upper_ver) - return flag - - except Exception: - # All exceptions are ignored - pass - - LOG.warning('Failed to parse iRMC firmware version on node %(uuid)s: ' - '%(fw_ver)s', {'uuid': node.uuid, 'fw_ver': fw_version}) - return False diff --git a/ironic/drivers/modules/irmc/inspect.py b/ironic/drivers/modules/irmc/inspect.py deleted file mode 100644 index f0226242ce..0000000000 --- a/ironic/drivers/modules/irmc/inspect.py +++ /dev/null @@ -1,348 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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. -""" -iRMC Inspect Interface -""" -import re - -from oslo_log import log as logging -from oslo_utils import importutils - -from ironic.common import boot_devices -from ironic.common import exception -from ironic.common.i18n import _ -from ironic.common import metrics_utils -from ironic.common import states -from ironic.common import utils -from ironic.conductor import utils as manager_utils -from ironic.conf import CONF -from ironic.drivers import base -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules import snmp -from ironic import objects - -irmc = importutils.try_import('scciclient.irmc') - -LOG = logging.getLogger(__name__) - -METRICS = metrics_utils.get_metrics_logger(__name__) - -""" -SC2.mib: sc2UnitNodeClass returns NIC type. - -sc2UnitNodeClass OBJECT-TYPE - SYNTAX INTEGER - { - unknown(1), - primary(2), - secondary(3), - management-blade(4), - secondary-remote(5), - secondary-remote-backup(6), - baseboard-controller(7) - } - ACCESS read-only - STATUS mandatory - DESCRIPTION "Management node class: - primary: local operating system interface - secondary: local management controller LAN interface - management-blade: management blade interface (in a blade server - chassis) - secondary-remote: remote management controller (in an RSB - concentrator environment) - secondary-remote-backup: backup remote management controller - baseboard-controller: local baseboard management controller (BMC)" - ::= { sc2ManagementNodes 8 } -""" - -NODE_CLASS_OID_VALUE = { - 'unknown': 1, - 'primary': 2, - 'secondary': 3, - 'management-blade': 4, - 'secondary-remote': 5, - 'secondary-remote-backup': 6, - 'baseboard-controller': 7 -} - -NODE_CLASS_OID = '1.3.6.1.4.1.231.2.10.2.2.10.3.1.1.8.1' - -""" -SC2.mib: sc2UnitNodeMacAddress returns NIC MAC address - -sc2UnitNodeMacAddress OBJECT-TYPE - SYNTAX PhysAddress - ACCESS read-only - STATUS mandatory - DESCRIPTION "Management node hardware (MAC) address" - ::= { sc2ManagementNodes 9 } -""" - -MAC_ADDRESS_OID = '1.3.6.1.4.1.231.2.10.2.2.10.3.1.1.9.1' -CAPABILITIES_PROPERTIES = {'irmc_firmware_version', - 'rom_firmware_version', 'server_model', - 'pci_gpu_devices', 'cpu_fpga'} - - -def _get_mac_addresses(node): - """Get mac addresses of the node. - - :param node: node object. - :raises: SNMPFailure if SNMP operation failed. - :returns: a list of mac addresses. - """ - d_info = irmc_common.parse_driver_info(node) - snmp_client = snmp.SNMPClient( - address=d_info['irmc_address'], - port=d_info['irmc_snmp_port'], - version=d_info['irmc_snmp_version'], - read_community=d_info['irmc_snmp_community'], - user=d_info.get('irmc_snmp_user'), - auth_proto=d_info.get('irmc_snmp_auth_proto'), - auth_key=d_info.get('irmc_snmp_auth_password'), - priv_proto=d_info.get('irmc_snmp_priv_proto'), - priv_key=d_info.get('irmc_snmp_priv_password')) - - node_classes = snmp_client.get_next(NODE_CLASS_OID) - mac_addresses = [':'.join(['%02x' % x for x in mac]) - for mac in snmp_client.get_next(MAC_ADDRESS_OID)] - - return [a for c, a in zip(node_classes, mac_addresses) - if c == NODE_CLASS_OID_VALUE['primary']] - - -def _get_capabilities_properties_without_ipmi(d_info, cap_props, - current_cap, props): - capabilities = {} - snmp_client = snmp.SNMPClient( - address=d_info['irmc_address'], - port=d_info['irmc_snmp_port'], - version=d_info['irmc_snmp_version'], - read_community=d_info['irmc_snmp_community'], - user=d_info.get('irmc_snmp_user'), - auth_proto=d_info.get('irmc_snmp_auth_proto'), - auth_key=d_info.get('irmc_snmp_auth_password'), - priv_proto=d_info.get('irmc_snmp_priv_proto'), - priv_key=d_info.get('irmc_snmp_priv_password')) - - if 'rom_firmware_version' in cap_props: - capabilities['rom_firmware_version'] = \ - irmc.snmp.get_bios_firmware_version(snmp_client) - - if 'irmc_firmware_version' in cap_props: - capabilities['irmc_firmware_version'] = \ - irmc.snmp.get_irmc_firmware_version(snmp_client) - - if 'server_model' in cap_props: - capabilities['server_model'] = irmc.snmp.get_server_model( - snmp_client) - - capabilities = utils.get_updated_capabilities(current_cap, capabilities) - if capabilities: - props['capabilities'] = capabilities - - return props - - -def _inspect_hardware(node, existing_traits=None, **kwargs): - """Inspect the node and get hardware information. - - :param node: node object. - :param existing_traits: existing traits list. - :param kwargs: the dictionary of additional parameters. - :raises: HardwareInspectionFailure, if unable to get essential - hardware properties. - :returns: a pair of dictionary and list, the dictionary contains - keys as in IRMCInspect.ESSENTIAL_PROPERTIES and its inspected - values, the list contains mac addresses. - """ - capabilities_props = set(CAPABILITIES_PROPERTIES) - new_traits = list(existing_traits) if existing_traits else [] - - # Remove all capabilities item which will be inspected in the existing - # capabilities of node - if 'capabilities' in node.properties: - existing_cap = node.properties['capabilities'].split(',') - for item in capabilities_props: - for prop in existing_cap: - if item == prop.split(':')[0]: - existing_cap.remove(prop) - node.properties['capabilities'] = ",".join(existing_cap) - - # get gpu_ids, fpga_ids in ironic configuration - gpu_ids = [gpu_id.lower() for gpu_id in CONF.irmc.gpu_ids] - fpga_ids = [fpga_id.lower() for fpga_id in CONF.irmc.fpga_ids] - - # if gpu_ids = [], pci_gpu_devices will not be inspected - if len(gpu_ids) == 0: - capabilities_props.remove('pci_gpu_devices') - - # if fpga_ids = [], cpu_fpga will not be inspected - if len(fpga_ids) == 0: - capabilities_props.remove('cpu_fpga') - - try: - report = irmc_common.get_irmc_report(node) - props = irmc.scci.get_essential_properties( - report, IRMCInspect.ESSENTIAL_PROPERTIES) - d_info = irmc_common.parse_driver_info(node) - if (getattr(node, 'power_interface') == 'ipmitool' - or node.driver_internal_info.get('irmc_ipmi_succeed')): - capabilities = irmc.scci.get_capabilities_properties( - d_info, - capabilities_props, - gpu_ids, - fpga_ids=fpga_ids, - **kwargs) - if capabilities: - if capabilities.get('pci_gpu_devices') == 0: - capabilities.pop('pci_gpu_devices') - - cpu_fpga = capabilities.pop('cpu_fpga', 0) - if cpu_fpga == 0 and 'CUSTOM_CPU_FPGA' in new_traits: - new_traits.remove('CUSTOM_CPU_FPGA') - elif cpu_fpga != 0 and 'CUSTOM_CPU_FPGA' not in new_traits: - new_traits.append('CUSTOM_CPU_FPGA') - - # Ironic no longer supports trusted boot - capabilities.pop('trusted_boot', None) - capabilities = utils.get_updated_capabilities( - node.properties.get('capabilities', ''), capabilities) - if capabilities: - props['capabilities'] = capabilities - - else: - props = _get_capabilities_properties_without_ipmi( - d_info, capabilities_props, - node.properties.get('capabilities', ''), props) - - macs = _get_mac_addresses(node) - except (irmc.scci.SCCIInvalidInputError, - irmc.scci.SCCIClientError, - exception.SNMPFailure) as e: - advice = "" - if ("SNMP operation" in str(e)): - advice = ("The SNMP related parameters' value may be different " - "with the server, please check if you have set them " - "correctly.") - error = (_("Inspection failed for node %(node_id)s " - "with the following error: %(error)s. %(advice)s") % - {'node_id': node.uuid, 'error': e, 'advice': advice}) - raise exception.HardwareInspectionFailure(error=error) - - return props, macs, new_traits - - -class IRMCInspect(base.InspectInterface): - """Interface for out of band inspection.""" - - supported = False - - def __init__(self): - """Validate the driver-specific inspection information. - - This action will validate gpu_ids and fpga_ids value along with - starting ironic-conductor service. - """ - for gpu_id in CONF.irmc.gpu_ids: - if not re.match('^0x[0-9a-f]{4}/0x[0-9a-f]{4}$', gpu_id.lower()): - raise exception.InvalidParameterValue(_( - "Invalid [irmc]/gpu_ids configuration option.")) - - for fpga_id in CONF.irmc.fpga_ids: - if not re.match('^0x[0-9a-f]{4}/0x[0-9a-f]{4}$', fpga_id.lower()): - raise exception.InvalidParameterValue(_( - "Invalid [irmc]/fpga_ids configuration option.")) - - super(IRMCInspect, self).__init__() - - def get_properties(self): - """Return the properties of the interface. - - :returns: dictionary of : entries. - """ - return irmc_common.COMMON_PROPERTIES - - @METRICS.timer('IRMCInspect.validate') - def validate(self, task): - """Validate the driver-specific inspection information. - - This method validates whether the 'driver_info' property of the - supplied node contains the required information for this driver. - - :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. - :raises: MissingParameterValue if a required parameter is missing. - """ - irmc_common.parse_driver_info(task.node) - - @METRICS.timer('IRMCInspect.inspect_hardware') - def inspect_hardware(self, task): - """Inspect hardware. - - Inspect hardware to obtain the essential hardware properties and - mac addresses. - - :param task: a task from TaskManager. - :raises: HardwareInspectionFailure, if hardware inspection failed. - :returns: states.MANAGEABLE, if hardware inspection succeeded. - """ - node = task.node - kwargs = {} - # Inspect additional capabilities task requires node with power on - # status - old_power_state = task.driver.power.get_power_state(task) - if old_power_state == states.POWER_OFF: - manager_utils.node_set_boot_device(task, boot_devices.BIOS, False) - manager_utils.node_power_action(task, states.POWER_ON) - - LOG.info("The Node %(node_uuid)s being powered on for inspection", - {'node_uuid': task.node.uuid}) - - kwargs['sleep_flag'] = True - traits_obj = objects.TraitList.get_by_node_id(task.context, node.id) - existing_traits = traits_obj.get_trait_names() - props, macs, new_traits = _inspect_hardware(node, - existing_traits, - **kwargs) - node.properties = dict(node.properties, **props) - if existing_traits != new_traits: - objects.TraitList.create(task.context, node.id, new_traits) - node.save() - - for mac in macs: - try: - new_port = objects.Port(task.context, - address=mac, node_id=node.id) - new_port.create() - LOG.info("Port created for MAC address %(address)s " - "for node %(node_uuid)s during inspection", - {'address': mac, 'node_uuid': node.uuid}) - except exception.MACAlreadyExists: - LOG.warning("Port already existed for MAC address " - "%(address)s for node %(node_uuid)s " - "during inspection", - {'address': mac, 'node_uuid': node.uuid}) - - LOG.info("Node %s inspected", node.uuid) - # restore old power state - if old_power_state == states.POWER_OFF: - manager_utils.node_power_action(task, states.POWER_OFF) - - LOG.info("The Node %(node_uuid)s being powered off after " - "inspection", - {'node_uuid': task.node.uuid}) - - return states.MANAGEABLE diff --git a/ironic/drivers/modules/irmc/management.py b/ironic/drivers/modules/irmc/management.py deleted file mode 100644 index 3de1571277..0000000000 --- a/ironic/drivers/modules/irmc/management.py +++ /dev/null @@ -1,630 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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. -""" -iRMC Management Driver -""" - -from oslo_log import log as logging -from oslo_utils import importutils - -from ironic.common import boot_devices -from ironic.common import exception -from ironic.common.i18n import _ -from ironic.common import metrics_utils -from ironic.common import states -from ironic.conductor import task_manager -from ironic.conductor import utils as manager_utils -from ironic import conf -from ironic.drivers import base -from ironic.drivers.modules import boot_mode_utils -from ironic.drivers.modules import ipmitool -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.redfish import management as redfish_management - -irmc = importutils.try_import('scciclient.irmc') - -LOG = logging.getLogger(__name__) -CONF = conf.CONF - -METRICS = metrics_utils.get_metrics_logger(__name__) - -# Boot Option Parameters #5 Data2 defined in -# Set/Get System Boot Options Command, IPMI spec v2.0. -_BOOTPARAM5_DATA2 = {boot_devices.PXE: '0x04', - boot_devices.DISK: '0x08', - # note (naohirot) - # boot_devices.CDROM is tentatively set to '0x20' rather - # than '0x14' as a work-around to force iRMC vmedia boot. - # 0x14 = Force boot from default CD/DVD - # 0x20 = Force boot from remotely connected CD/DVD - boot_devices.CDROM: '0x20', - boot_devices.BIOS: '0x18', - boot_devices.SAFE: '0x0c', - } - - -def _get_sensors_data(task): - """Get sensors data method. - - It gets sensor data from the task's node via SCCI, and convert the data - from XML to the dict format. - - :param task: A TaskManager instance. - :raises: FailedToGetSensorData when getting the sensor data fails. - :returns: Returns a consistent formatted dict of sensor data grouped - by sensor type, which can be processed by Ceilometer. - """ - - try: - report = irmc_common.get_irmc_report(task.node) - sensor = irmc.scci.get_sensor_data(report) - - except (exception.InvalidParameterValue, - exception.MissingParameterValue, - irmc.scci.SCCIInvalidInputError, - irmc.scci.SCCIClientError) as e: - LOG.error("SCCI get sensor data failed for node %(node_id)s " - "with the following error: %(error)s", - {'node_id': task.node.uuid, 'error': e}) - raise exception.FailedToGetSensorData( - node=task.node.uuid, error=e) - - sensors_data = {} - for sdr in sensor: - sensor_type_name = sdr.find('./Data/Decoded/Sensor/TypeName') - sensor_type_number = sdr.find('./Data/Decoded/Sensor/Type') - entity_name = sdr.find('./Data/Decoded/Entity/Name') - entity_id = sdr.find('./Data/Decoded/Entity/ID') - - if None in (sensor_type_name, sensor_type_number, - entity_name, entity_id): - continue - - sensor_type = ('%s (%s)' % - (sensor_type_name.text, sensor_type_number.text)) - sensor_id = ('%s (%s)' % - (entity_name.text, entity_id.text)) - reading_value = sdr.find( - './Data/Decoded/Sensor/Thresholds/*/Normalized') - reading_value_text = "None" if ( - reading_value is None) else str(reading_value.text) - reading_units = sdr.find('./Data/Decoded/Sensor/BaseUnitName') - reading_units_text = "None" if ( - reading_units is None) else str(reading_units.text) - sensor_reading = '%s %s' % (reading_value_text, reading_units_text) - - sensors_data.setdefault(sensor_type, {})[sensor_id] = { - 'Sensor Reading': sensor_reading, - 'Sensor ID': sensor_id, - 'Units': reading_units_text, - } - - return sensors_data - - -def backup_bios_config(task): - """Backup BIOS config from a node. - - :param task: a TaskManager instance containing the node to act on. - :raises: IRMCOperationError on failure. - """ - node_uuid = task.node.uuid - - # Skip this operation if the clean step 'restore' is disabled - if CONF.irmc.clean_priority_restore_irmc_bios_config == 0: - LOG.debug('Skipped the operation backup_BIOS_config for node %s ' - 'as the clean step restore_BIOS_config is disabled.', - node_uuid) - return - - irmc_info = irmc_common.parse_driver_info(task.node) - - try: - # Backup bios config - result = irmc.elcm.backup_bios_config(irmc_info) - except irmc.scci.SCCIError as e: - LOG.error('Failed to backup BIOS config for node %(node)s. ' - 'Error: %(error)s', {'node': node_uuid, 'error': e}) - raise exception.IRMCOperationError(operation='backup BIOS config', - error=e) - - # Save bios config into the driver_internal_info - task.node.set_driver_internal_info('irmc_bios_config', - result['bios_config']) - task.node.save() - - LOG.info('BIOS config is backed up successfully for node %s', - node_uuid) - - # NOTE(tiendc): When the backup operation done, server is automatically - # shutdown. However, this function is called right before the method - # task.driver.deploy() that will trigger a reboot. So, we don't need - # to power on the server at this point. - - -def _restore_bios_config(task): - """Restore BIOS config to a node. - - :param task: a TaskManager instance containing the node to act on. - :raises: IRMCOperationError if the operation fails. - """ - node_uuid = task.node.uuid - - # Get bios config stored in the node object - bios_config = task.node.driver_internal_info.get('irmc_bios_config') - if not bios_config: - LOG.info('Skipped operation "restore BIOS config" on node %s ' - 'as the backup data not found.', node_uuid) - return - - def _remove_bios_config(task, reboot_flag=False): - """Remove backup bios config from the node.""" - task.node.del_driver_internal_info('irmc_bios_config') - # NOTE(tiendc): If reboot flag is raised, then the BM will - # reboot and cause a bug if the next clean step is in-band. - # See https://storyboard.openstack.org/#!/story/2002731 - if reboot_flag: - task.node.set_driver_internal_info('cleaning_reboot', True) - task.node.save() - - irmc_info = irmc_common.parse_driver_info(task.node) - - try: - # Restore bios config - irmc.elcm.restore_bios_config(irmc_info, bios_config) - except irmc.scci.SCCIError as e: - # If the input bios config is not correct or corrupted, then - # we should remove it from the node object. - if isinstance(e, irmc.scci.SCCIInvalidInputError): - _remove_bios_config(task) - - LOG.error('Failed to restore BIOS config on node %(node)s. ' - 'Error: %(error)s', {'node': node_uuid, 'error': e}) - raise exception.IRMCOperationError(operation='restore BIOS config', - error=e) - - # Remove the backup data after restoring - _remove_bios_config(task, reboot_flag=True) - - LOG.info('BIOS config is restored successfully on node %s', - node_uuid) - - # Change power state to ON as server is automatically - # shutdown after the operation. - manager_utils.node_power_action(task, states.POWER_ON) - - -class IRMCManagement(ipmitool.IPMIManagement, - redfish_management.RedfishManagement): - - supported = False - - def get_properties(self): - """Return the properties of the interface. - - :returns: Dictionary of : entries. - """ - return irmc_common.COMMON_PROPERTIES - - @METRICS.timer('IRMCManagement.validate') - def validate(self, task): - """Validate the driver-specific management information. - - This method validates whether the 'driver_info' property of the - supplied node contains the required information for this driver. - - :param task: A TaskManager instance containing the node to act on. - :raises: InvalidParameterValue if required parameters are invalid. - :raises: MissingParameterValue if a required parameter is missing. - """ - if (getattr(task.node, 'power_interface') == 'ipmitool' - or task.node.driver_internal_info.get('irmc_ipmi_succeed')): - irmc_common.parse_driver_info(task.node) - irmc_common.update_ipmi_properties(task) - super(IRMCManagement, self).validate(task) - else: - irmc_common.parse_driver_info(task.node) - super(ipmitool.IPMIManagement, self).validate(task) - - def get_supported_boot_devices(self, task): - """Get list of supported boot devices - - Actual code is delegated to IPMIManagement or RedfishManagement - based on iRMC firmware version. - - :param task: A TaskManager instance - :returns: A list with the supported boot devices defined - in :mod:`ironic.common.boot_devices`. - - """ - if (getattr(task.node, 'power_interface') == 'ipmitool' - or task.node.driver_internal_info.get('irmc_ipmi_succeed')): - return super(IRMCManagement, self).get_supported_boot_devices(task) - else: - return super(ipmitool.IPMIManagement, - self).get_supported_boot_devices(task) - - @METRICS.timer('IRMCManagement.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 task from TaskManager. - :param device: The boot device, one of the supported devices - listed in :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. - :raises: MissingParameterValue if a required parameter is missing. - :raises: IPMIFailure on an error from ipmitool. - :raises: RedfishConnectionError on Redfish operation failure. - :raises: RedfishError on Redfish operation failure. - """ - if (getattr(task.node, 'power_interface') == 'ipmitool' - or task.node.driver_internal_info.get('irmc_ipmi_succeed')): - if device not in self.get_supported_boot_devices(task): - raise exception.InvalidParameterValue(_( - "Invalid boot device %s specified.") % device) - - uefi_mode = ( - boot_mode_utils.get_boot_mode(task.node) == 'uefi') - - # disable 60 secs timer - timeout_disable = "0x00 0x08 0x03 0x08" - ipmitool.send_raw(task, timeout_disable) - - # note(naohirot): - # Set System Boot Options : ipmi cmd '0x08', bootparam '0x05' - # - # $ ipmitool raw 0x00 0x08 0x05 data1 data2 0x00 0x00 0x00 - # - # data1 : '0xe0' persistent + uefi - # '0xc0' persistent + bios - # '0xa0' next only + uefi - # '0x80' next only + bios - # data2 : boot device defined in the dict _BOOTPARAM5_DATA2 - - bootparam5 = '0x00 0x08 0x05 %s %s 0x00 0x00 0x00' - if persistent: - data1 = '0xe0' if uefi_mode else '0xc0' - else: - data1 = '0xa0' if uefi_mode else '0x80' - data2 = _BOOTPARAM5_DATA2[device] - - cmd8 = bootparam5 % (data1, data2) - ipmitool.send_raw(task, cmd8) - else: - if device not in self.get_supported_boot_devices(task): - raise exception.InvalidParameterValue(_( - "Invalid boot device %s specified. " - "Current iRMC firmware condition doesn't support IPMI " - "but Redfish.") % device) - super(ipmitool.IPMIManagement, self).set_boot_device( - task, device, persistent) - - def get_boot_device(self, task): - """Get the current boot device for the task's node. - - Returns the current boot device of the node. - - :param task: a task from TaskManager. - :raises: InvalidParameterValue if an invalid boot device is - specified. - :raises: MissingParameterValue if a required parameter is missing. - :raises: IPMIFailure on an error from ipmitool. - :raises: RedfishConnectionError on Redfish operation failure. - :raises: RedfishError on Redfish operation failure. - :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. - """ - if (getattr(task.node, 'power_interface') == 'ipmitool' - or task.node.driver_internal_info.get('irmc_ipmi_succeed')): - return super(IRMCManagement, self).get_boot_device(task) - else: - return super( - ipmitool.IPMIManagement, self).get_boot_device(task) - - def get_supported_boot_modes(self, task): - """Get a list of the supported boot modes. - - IRMCManagement class doesn't support this method - - :param task: a task from TaskManager. - :raises: UnsupportedDriverExtension if requested operation is - not supported by the driver - """ - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='get_supported_boot_modes') - - def set_boot_mode(self, task, mode): - """Set the boot mode for a node. - - IRMCManagement class doesn't support this method - - :param task: a task from TaskManager. - :param mode: The boot mode, one of - :mod:`ironic.common.boot_modes`. - :raises: UnsupportedDriverExtension if requested operation is - not supported by the driver - """ - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='set_boot_mode') - - def get_boot_mode(self, task): - """Get the current boot mode for a node. - - IRMCManagement class doesn't support this method - - :param task: a task from TaskManager. - :raises: UnsupportedDriverExtension if requested operation is - not supported by the driver - """ - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='get_boot_mode') - - @METRICS.timer('IRMCManagement.get_sensors_data') - def get_sensors_data(self, task): - """Get sensors data method. - - It gets sensor data from the task's node via SCCI, and convert the data - from XML to the dict format. - - :param task: A TaskManager instance. - :raises: FailedToGetSensorData when getting the sensor data fails. - :raises: FailedToParseSensorData when parsing sensor data fails. - :raises: InvalidParameterValue if required parameters are invalid. - :raises: MissingParameterValue if a required parameter is missing. - :returns: Returns a consistent formatted dict of sensor data grouped - by sensor type, which can be processed by Ceilometer. - Example:: - - { - 'Sensor Type 1': { - 'Sensor ID 1': { - 'Sensor Reading': 'Value1 Units1', - 'Sensor ID': 'Sensor ID 1', - 'Units': 'Units1' - }, - 'Sensor ID 2': { - 'Sensor Reading': 'Value2 Units2', - 'Sensor ID': 'Sensor ID 2', - 'Units': 'Units2' - } - }, - 'Sensor Type 2': { - 'Sensor ID 3': { - 'Sensor Reading': 'Value3 Units3', - 'Sensor ID': 'Sensor ID 3', - 'Units': 'Units3' - }, - 'Sensor ID 4': { - 'Sensor Reading': 'Value4 Units4', - 'Sensor ID': 'Sensor ID 4', - 'Units': 'Units4' - } - } - } - - """ - # irmc_common.parse_driver_info() makes sure that - # d_info['irmc_sensor_method'] is either 'scci' or 'ipmitool'. - d_info = irmc_common.parse_driver_info(task.node) - sensor_method = d_info['irmc_sensor_method'] - if sensor_method == 'scci': - return _get_sensors_data(task) - elif sensor_method == 'ipmitool': - if (getattr(task.node, 'power_interface') == 'ipmitool' - or task.node.driver_internal_info.get('irmc_ipmi_succeed')): - return super(IRMCManagement, self).get_sensors_data(task) - else: - raise exception.InvalidParameterValue(_( - "Invalid sensor method %s specified. " - "IPMI operation doesn't work on current iRMC " - "condition.") % sensor_method) - - @METRICS.timer('IRMCManagement.inject_nmi') - @task_manager.require_exclusive_lock - def inject_nmi(self, task): - """Inject NMI, Non Maskable Interrupt. - - Inject NMI (Non Maskable Interrupt) for a node immediately. - - :param task: A TaskManager instance containing the node to act on. - :raises: IRMCOperationError on an error from SCCI - :returns: None - - """ - node = task.node - irmc_client = irmc_common.get_irmc_client(node) - try: - irmc_client(irmc.scci.POWER_RAISE_NMI) - except irmc.scci.SCCIClientError as err: - LOG.error('iRMC Inject NMI failed for node %(node)s: %(err)s.', - {'node': node.uuid, 'err': err}) - raise exception.IRMCOperationError( - operation=irmc.scci.POWER_RAISE_NMI, error=err) - - @METRICS.timer('IRMCManagement.restore_irmc_bios_config') - @base.clean_step( - priority=CONF.irmc.clean_priority_restore_irmc_bios_config) - def restore_irmc_bios_config(self, task): - """Restore BIOS config for a node. - - :param task: a task from TaskManager. - :raises: NodeCleaningFailure, on failure to execute step. - :returns: None. - """ - try: - _restore_bios_config(task) - except exception.IRMCOperationError as e: - raise exception.NodeCleaningFailure(node=task.node.uuid, - reason=e) - - def get_secure_boot_state(self, task): - """Get the current secure boot state for the node. - - NOTE: Not all drivers support this method. Older hardware - may not implement that. - - :param task: A task from TaskManager. - :raises: MissingParameterValue if a required parameter is missing - :raises: DriverOperationError or its derivative in case - of driver runtime error. - :raises: UnsupportedDriverExtension if secure boot is - not supported by the driver or the hardware - :returns: Boolean - """ - return irmc_common.get_secure_boot_mode(task.node) - - def set_secure_boot_state(self, task, state): - """Set the current secure boot state for the node. - - NOTE: Not all drivers support this method. Older hardware - may not implement that. - - :param task: A task from TaskManager. - :param state: A new state as a boolean. - :raises: MissingParameterValue if a required parameter is missing - :raises: DriverOperationError or its derivative in case - of driver runtime error. - :raises: UnsupportedDriverExtension if secure boot is - not supported by the driver or the hardware - """ - return irmc_common.set_secure_boot_mode(task.node, state) - - def get_supported_indicators(self, task, component=None): - """Get a map of the supported indicators (e.g. LEDs). - - IRMCManagement class doesn't support this method - - :param task: a task from TaskManager. - :param component: If not `None`, return indicator information - for just this component, otherwise return indicators for - all existing components. - :raises: UnsupportedDriverExtension if requested operation is - not supported by the driver - - """ - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='get_supported_indicators') - - def set_indicator_state(self, task, component, indicator, state): - """Set indicator on the hardware component to the desired state. - - IRMCManagement class doesn't support this method - - :param task: A task from TaskManager. - :param component: The hardware component, one of - :mod:`ironic.common.components`. - :param indicator: Indicator ID (as reported by - `get_supported_indicators`). - :state: Desired state of the indicator, one of - :mod:`ironic.common.indicator_states`. - :raises: UnsupportedDriverExtension if requested operation is - not supported by the driver - """ - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='set_indicator_state') - - def get_indicator_state(self, task, component, indicator): - """Get current state of the indicator of the hardware component. - - IRMCManagement class doesn't support this method - - :param task: A task from TaskManager. - :param component: The hardware component, one of - :mod:`ironic.common.components`. - :param indicator: Indicator ID (as reported by - `get_supported_indicators`). - :raises: UnsupportedDriverExtension if requested operation is - not supported by the driver - """ - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='get_indicator_state') - - def detect_vendor(self, task): - """Detects and returns the hardware vendor. - - :param task: A task from TaskManager. - :raises: InvalidParameterValue if a required parameter is missing - :raises: MissingParameterValue if a required parameter is missing - :raises: RedfishError on Redfish operation error. - :raises: PasswordFileFailedToCreate from creating or writing to the - temporary file during IPMI operation. - :raises: processutils.ProcessExecutionError from executing ipmi command - :returns: String representing the BMC reported Vendor or - Manufacturer, otherwise returns None. - """ - if (getattr(task.node, 'power_interface') == 'ipmitool' - or task.node.driver_internal_info.get('irmc_ipmi_succeed')): - return super(IRMCManagement, self).detect_vendor(task) - else: - return super(ipmitool.IPMIManagement, self).detect_vendor(task) - - def get_mac_addresses(self, task): - """Get MAC address information for the node. - - IRMCManagement class doesn't support this method - - :param task: A TaskManager instance containing the node to act on. - :raises: UnsupportedDriverExtension - """ - raise exception.UnsupportedDriverExtension( - driver=task.node.driver, extension='get_mac_addresses') - - @base.verify_step(priority=10) - def verify_http_https_connection_and_fw_version(self, task): - """Check http(s) connection to iRMC and save fw version - - :param task' A task from TaskManager - 'raises: IRMCOperationError - """ - error_msg_https = ('Access to REST API returns unexpected ' - 'status code. Check driver_info parameter ' - 'related to iRMC driver') - error_msg_http = ('Access to REST API returns unexpected ' - 'status code. Check driver_info parameter ' - 'or version of iRMC because iRMC does not ' - 'support HTTP connection to iRMC REST API ' - 'since iRMC S6 2.00.') - try: - # Check connection to iRMC - elcm_license = irmc_common.check_elcm_license(task.node) - - # On iRMC S6 2.00, access to REST API through HTTP returns 404 - if elcm_license.get('status_code') not in (200, 500): - port = task.node.driver_info.get( - 'irmc_port', CONF.irmc.get('port')) - if port == 80: - e_msg = error_msg_http - else: - e_msg = error_msg_https - raise exception.IRMCOperationError( - operation='establishing connection to REST API', - error=e_msg) - - irmc_common.set_irmc_version(task) - except (exception.InvalidParameterValue, - exception.MissingParameterValue) as irmc_exception: - raise exception.IRMCOperationError( - operation='configuration validation', - error=irmc_exception) diff --git a/ironic/drivers/modules/irmc/power.py b/ironic/drivers/modules/irmc/power.py deleted file mode 100644 index 57cd091d6b..0000000000 --- a/ironic/drivers/modules/irmc/power.py +++ /dev/null @@ -1,340 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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. - -""" -iRMC Power Driver using the Base Server Profile -""" -from oslo_log import log as logging -from oslo_service import loopingcall -from oslo_utils import importutils - -from ironic.common import exception -from ironic.common.i18n import _ -from ironic.common import metrics_utils -from ironic.common import states -from ironic.conductor import task_manager -from ironic.conf import CONF -from ironic.drivers import base -from ironic.drivers.modules import ipmitool -from ironic.drivers.modules.irmc import boot as irmc_boot -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.redfish import power as redfish_power -from ironic.drivers.modules import snmp - -scci = importutils.try_import('scciclient.irmc.scci') - -LOG = logging.getLogger(__name__) - -METRICS = metrics_utils.get_metrics_logger(__name__) - -""" -SC2.mib: sc2srvCurrentBootStatus returns status of the current boot -""" -BOOT_STATUS_OID = "1.3.6.1.4.1.231.2.10.2.2.10.4.1.1.4.1" -BOOT_STATUS_VALUE = { - 'error': 0, - 'unknown': 1, - 'off': 2, - 'no-boot-cpu': 3, - 'self-test': 4, - 'setup': 5, - 'os-boot': 6, - 'diagnostic-boot': 7, - 'os-running': 8, - 'diagnostic-running': 9, - 'os-shutdown': 10, - 'diagnostic-shutdown': 11, - 'reset': 12 -} -BOOT_STATUS = {v: k for k, v in BOOT_STATUS_VALUE.items()} - -if scci: - STATES_MAP = {states.POWER_OFF: scci.POWER_OFF, - states.POWER_ON: scci.POWER_ON, - states.REBOOT: scci.POWER_RESET, - states.SOFT_REBOOT: scci.POWER_SOFT_CYCLE, - states.SOFT_POWER_OFF: scci.POWER_SOFT_OFF} - - -def _is_expected_power_state(target_state, boot_status_value): - """Predicate if target power state and boot status values match. - - :param target_state: Target power state. - :param boot_status_value: SNMP BOOT_STATUS_VALUE. - :returns: True if expected power state, otherwise False. - """ - if (target_state == states.SOFT_POWER_OFF - and boot_status_value in (BOOT_STATUS_VALUE['unknown'], - BOOT_STATUS_VALUE['off'])): - return True - elif (target_state == states.SOFT_REBOOT - and boot_status_value == BOOT_STATUS_VALUE['os-running']): - return True - - return False - - -def _wait_power_state(task, target_state, timeout=None): - """Wait for having changed to the target power state. - - :param task: A TaskManager instance containing the node to act on. - :raises: IRMCOperationError if the target state acknowledge failed. - :raises: SNMPFailure if SNMP request failed. - """ - node = task.node - d_info = irmc_common.parse_driver_info(node) - snmp_client = snmp.SNMPClient( - address=d_info['irmc_address'], - port=d_info['irmc_snmp_port'], - version=d_info['irmc_snmp_version'], - read_community=d_info['irmc_snmp_community'], - user=d_info.get('irmc_snmp_user'), - auth_proto=d_info.get('irmc_snmp_auth_proto'), - auth_key=d_info.get('irmc_snmp_auth_password'), - priv_proto=d_info.get('irmc_snmp_priv_proto'), - priv_key=d_info.get('irmc_snmp_priv_password')) - - interval = CONF.irmc.snmp_polling_interval - retry_timeout_soft = timeout or CONF.conductor.soft_power_off_timeout - max_retry = int(retry_timeout_soft / interval) - - def _wait(mutable): - mutable['boot_status_value'] = snmp_client.get(BOOT_STATUS_OID) - LOG.debug("iRMC SNMP agent of %(node_id)s returned " - "boot status value %(bootstatus)s on attempt %(times)s.", - {'node_id': node.uuid, - 'bootstatus': BOOT_STATUS[mutable['boot_status_value']], - 'times': mutable['times']}) - - if _is_expected_power_state(target_state, - mutable['boot_status_value']): - mutable['state'] = target_state - raise loopingcall.LoopingCallDone() - - mutable['times'] += 1 - if mutable['times'] > max_retry: - mutable['state'] = states.ERROR - raise loopingcall.LoopingCallDone() - - store = {'state': None, 'times': 0, 'boot_status_value': None} - timer = loopingcall.FixedIntervalLoopingCall(_wait, store) - timer.start(interval=interval).wait() - - if store['state'] == target_state: - # iRMC acknowledged the target state - node.last_error = None - node.power_state = (states.POWER_OFF - if target_state == states.SOFT_POWER_OFF - else states.POWER_ON) - node.target_power_state = states.NOSTATE - node.save() - LOG.info('iRMC successfully set node %(node_id)s ' - 'power state to %(bootstatus)s.', - {'node_id': node.uuid, - 'bootstatus': BOOT_STATUS[store['boot_status_value']]}) - else: - # iRMC failed to acknowledge the target state - last_error = (_('iRMC returned unexpected boot status value %s') % - BOOT_STATUS[store['boot_status_value']]) - node.last_error = last_error - node.power_state = states.ERROR - node.target_power_state = states.NOSTATE - node.save() - LOG.error('iRMC failed to acknowledge the target state for node ' - '%(node_id)s. Error: %(last_error)s', - {'node_id': node.uuid, 'last_error': last_error}) - error = _('unexpected boot status value') - raise exception.IRMCOperationError(operation=target_state, - error=error) - - -def _set_power_state(task, target_state, timeout=None): - """Turn the server power on/off or do a reboot. - - :param task: a TaskManager instance containing the node to act on. - :param target_state: target state of the node. - :param timeout: timeout (in seconds) positive integer (> 0) for any - power state. ``None`` indicates default timeout. - :raises: InvalidParameterValue if an invalid power state was specified. - :raises: MissingParameterValue if some mandatory information - is missing on the node - :raises: IRMCOperationError on an error from SCCI or SNMP - """ - node = task.node - irmc_client = irmc_common.get_irmc_client(node) - - if target_state in (states.POWER_ON, states.REBOOT, states.SOFT_REBOOT): - irmc_boot.attach_boot_iso_if_needed(task) - - try: - irmc_client(STATES_MAP[target_state]) - - except KeyError: - msg = _("_set_power_state called with invalid power state " - "'%s'") % target_state - raise exception.InvalidParameterValue(msg) - - except scci.SCCIClientError as irmc_exception: - LOG.error("iRMC set_power_state failed to set state to %(tstate)s " - " for node %(node_id)s with error: %(error)s", - {'tstate': target_state, 'node_id': node.uuid, - 'error': irmc_exception}) - operation = _('iRMC set_power_state') - raise exception.IRMCOperationError(operation=operation, - error=irmc_exception) - - try: - if target_state in (states.SOFT_REBOOT, states.SOFT_POWER_OFF): - # note (naohirot): - # The following call covers both cases since SOFT_REBOOT matches - # 'unknown' and SOFT_POWER_OFF matches 'off' or 'unknown'. - _wait_power_state(task, states.SOFT_POWER_OFF, timeout=timeout) - if target_state == states.SOFT_REBOOT: - _wait_power_state(task, states.SOFT_REBOOT, timeout=timeout) - - except exception.SNMPFailure as snmp_exception: - advice = ("The SNMP related parameters' value may be different with " - "the server, please check if you have set them correctly.") - LOG.error("iRMC failed to acknowledge the target state " - "for node %(node_id)s. Error: %(error)s. %(advice)s", - {'node_id': node.uuid, 'error': snmp_exception, - 'advice': advice}) - raise exception.IRMCOperationError(operation=target_state, - error=snmp_exception) - - -class IRMCPower(redfish_power.RedfishPower, base.PowerInterface): - """Interface for power-related actions.""" - - supported = False - - def get_properties(self): - """Return the properties of the interface. - - :returns: dictionary of : entries. - """ - return irmc_common.COMMON_PROPERTIES - - @METRICS.timer('IRMCPower.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. - :raises: MissingParameterValue if a required parameter is missing. - """ - # validate method of power interface is called at very first point - # in verifying. - # We take try-fallback approach against iRMC S6 2.00 and later - # incompatibility in which iRMC firmware disables IPMI by default. - # get_power_state method first try IPMI and if fails try Redfish - # along with setting irmc_ipmi_succeed flag to indicate if IPMI works. - if (task.node.driver_internal_info.get('irmc_ipmi_succeed') - or (task.node.driver_internal_info.get('irmc_ipmi_succeed') - is None)): - irmc_common.parse_driver_info(task.node) - else: - irmc_common.parse_driver_info(task.node) - super(IRMCPower, self).validate(task) - - @METRICS.timer('IRMCPower.get_power_state') - def get_power_state(self, task): - """Return the power state of the task's node. - - :param task: a TaskManager instance containing the node to act on. - :returns: a power state. One of :mod:`ironic.common.states`. - :raises: InvalidParameterValue if required parameters are incorrect. - :raises: MissingParameterValue if required parameters are missing. - :raises: IRMCOperationError If IPMI or Redfish operation fails - """ - # If IPMI operation failed, iRMC may not enable/support IPMI, - # so fallback to Redfish. - # get_power_state is called at verifying and is called periodically - # so this method is good choice to determine IPMI enablement. - try: - irmc_common.update_ipmi_properties(task) - ipmi_power = ipmitool.IPMIPower() - pw_state = ipmi_power.get_power_state(task) - if (task.node.driver_internal_info.get('irmc_ipmi_succeed') - is not True): - task.upgrade_lock(purpose='update irmc_ipmi_succeed flag', - retry=True) - task.node.set_driver_internal_info('irmc_ipmi_succeed', True) - task.node.save() - task.downgrade_lock() - return pw_state - except exception.IPMIFailure: - if (task.node.driver_internal_info.get('irmc_ipmi_succeed') - is not False): - task.upgrade_lock(purpose='update irmc_ipmi_succeed flag', - retry=True) - task.node.set_driver_internal_info('irmc_ipmi_succeed', False) - task.node.save() - task.downgrade_lock() - try: - return super(IRMCPower, self).get_power_state(task) - except (exception.RedfishConnectionError, - exception.RedfishError): - raise exception.IRMCOperationError( - operation='IPMI try and Redfish fallback operation') - - @METRICS.timer('IRMCPower.set_power_state') - @task_manager.require_exclusive_lock - def set_power_state(self, task, power_state, timeout=None): - """Set the power state of the task's node. - - :param task: a TaskManager instance containing the node to act on. - :param power_state: Any power state from :mod:`ironic.common.states`. - :param timeout: timeout (in seconds) positive integer (> 0) for any - power state. ``None`` indicates default timeout. - :raises: InvalidParameterValue if an invalid power state was specified. - :raises: MissingParameterValue if some mandatory information - is missing on the node - :raises: IRMCOperationError if failed to set the power state. - """ - _set_power_state(task, power_state, timeout=timeout) - - @METRICS.timer('IRMCPower.reboot') - @task_manager.require_exclusive_lock - def reboot(self, task, timeout=None): - """Perform a hard reboot of the task's node. - - :param task: a TaskManager instance containing the node to act on. - :param timeout: timeout (in seconds) positive integer (> 0) for any - power state. ``None`` indicates default timeout. - :raises: InvalidParameterValue if an invalid power state was specified. - :raises: IRMCOperationError if failed to set the power state. - """ - current_pstate = self.get_power_state(task) - if current_pstate == states.POWER_ON: - _set_power_state(task, states.REBOOT, timeout=timeout) - elif current_pstate == states.POWER_OFF: - _set_power_state(task, states.POWER_ON, timeout=timeout) - - @METRICS.timer('IRMCPower.get_supported_power_states') - def get_supported_power_states(self, task): - """Get a list of the supported power states. - - :param task: A TaskManager instance containing the node to act on. - currently not used. - :returns: A list with the supported power states defined - in :mod:`ironic.common.states`. - """ - return [states.POWER_ON, states.POWER_OFF, states.REBOOT, - states.SOFT_REBOOT, states.SOFT_POWER_OFF] diff --git a/ironic/drivers/modules/irmc/raid.py b/ironic/drivers/modules/irmc/raid.py deleted file mode 100644 index c1cf0d4344..0000000000 --- a/ironic/drivers/modules/irmc/raid.py +++ /dev/null @@ -1,499 +0,0 @@ -# Copyright 2018 FUJITSU LIMITED -# -# 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. - -""" -Irmc RAID specific methods -""" -from oslo_log import log as logging -from oslo_utils import importutils - -from ironic.common import exception -from ironic.common import metrics_utils -from ironic.common import raid as raid_common -from ironic.common import states -from ironic.conductor import periodics -from ironic.conductor import utils as manager_utils -from ironic import conf -from ironic.drivers import base -from ironic.drivers.modules import deploy_utils -from ironic.drivers.modules.irmc import common as irmc_common - -client = importutils.try_import('scciclient.irmc') - -LOG = logging.getLogger(__name__) -CONF = conf.CONF - -METRICS = metrics_utils.get_metrics_logger(__name__) - -RAID_LEVELS = { - '0': { - 'min_disks': 1, - 'max_disks': 1000, - 'factor': 0, - }, - '1': { - 'min_disks': 2, - 'max_disks': 2, - 'factor': 1, - }, - '5': { - 'min_disks': 3, - 'max_disks': 1000, - 'factor': 1, - }, - '6': { - 'min_disks': 4, - 'max_disks': 1000, - 'factor': 2, - }, - '10': { - 'min_disks': 4, - 'max_disks': 1000, - 'factor': 2, - }, - '50': { - 'min_disks': 6, - 'max_disks': 1000, - 'factor': 2, - } -} - -RAID_COMPLETING = 'completing' -RAID_COMPLETED = 'completed' -RAID_FAILED = 'failed' - - -def _get_raid_adapter(node): - """Get the RAID adapter info on a RAID controller. - - :param node: an ironic node object. - :returns: RAID adapter dictionary, None otherwise. - :raises: IRMCOperationError on an error from python-scciclient. - """ - irmc_info = irmc_common.parse_driver_info(node) - LOG.info('iRMC driver is gathering RAID adapter info for node %s', - node.uuid) - try: - return client.elcm.get_raid_adapter(irmc_info) - except client.elcm.ELCMProfileNotFound: - reason = ('Cannot find any RAID profile in "%s"' % node.uuid) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - - -def _get_fgi_status(report, node_uuid): - """Get a dict FGI(Foreground initialization) status on a RAID controller. - - :param report: SCCI report information. - :returns: FGI status on success, None if SCCIInvalidInputError and - waiting status if SCCIRAIDNotReady. - """ - try: - return client.scci.get_raid_fgi_status(report) - except client.scci.SCCIInvalidInputError: - LOG.warning('ServerViewRAID not available in %(node)s', - {'node': node_uuid}) - except client.scci.SCCIRAIDNotReady: - return RAID_COMPLETING - - -def _get_physical_disk(node): - """Get physical disks info on a RAID controller. - - This method only support to create the RAID configuration - on the RAIDAdapter 0. - - :param node: an ironic node object. - :returns: dict of physical disks on RAID controller. - """ - - physical_disk_dict = {} - raid_adapter = _get_raid_adapter(node) - physical_disks = raid_adapter['Server']['HWConfigurationIrmc'][ - 'Adapters']['RAIDAdapter'][0]['PhysicalDisks'] - - if physical_disks: - for disks in physical_disks['PhysicalDisk']: - physical_disk_dict.update({disks['Slot']: disks['Type']}) - - return physical_disk_dict - - -def _create_raid_adapter(node): - """Create RAID adapter info on a RAID controller. - - :param node: an ironic node object. - :raises: IRMCOperationError on an error from python-scciclient. - """ - - irmc_info = irmc_common.parse_driver_info(node) - target_raid_config = node.target_raid_config - - try: - return client.elcm.create_raid_configuration(irmc_info, - target_raid_config) - except client.elcm.ELCMProfileNotFound as exc: - LOG.error('iRMC driver failed with profile not found for node ' - '%(node_uuid)s. Reason: %(error)s.', - {'node_uuid': node.uuid, 'error': exc}) - raise exception.IRMCOperationError(operation='RAID config', - error=exc) - except client.scci.SCCIClientError as exc: - LOG.error('iRMC driver failed to create raid adapter info for node ' - '%(node_uuid)s. Reason: %(error)s.', - {'node_uuid': node.uuid, 'error': exc}) - raise exception.IRMCOperationError(operation='RAID config', - error=exc) - - -def _delete_raid_adapter(node): - """Delete the RAID adapter info on a RAID controller. - - :param node: an ironic node object. - :raises: IRMCOperationError if SCCI failed from python-scciclient. - """ - - irmc_info = irmc_common.parse_driver_info(node) - - try: - client.elcm.delete_raid_configuration(irmc_info) - except client.scci.SCCIClientError as exc: - LOG.error('iRMC driver failed to delete RAID configuration ' - 'for node %(node_uuid)s. Reason: %(error)s.', - {'node_uuid': node.uuid, 'error': exc}) - raise exception.IRMCOperationError(operation='RAID config', - error=exc) - - -def _commit_raid_config(task): - """Perform to commit RAID config into node.""" - - node = task.node - node_uuid = task.node.uuid - raid_config = {'logical_disks': []} - - raid_adapter = _get_raid_adapter(node) - - raid_adapter_info = raid_adapter['Server']['HWConfigurationIrmc'][ - 'Adapters']['RAIDAdapter'][0] - controller = raid_adapter_info['@AdapterId'] - raid_config['logical_disks'].append({'controller': controller}) - - logical_drives = raid_adapter_info['LogicalDrives']['LogicalDrive'] - for logical_drive in logical_drives: - raid_config['logical_disks'].append({'irmc_raid_info': { - 'logical_drive_number': logical_drive['@Number'], 'raid_level': - logical_drive['RaidLevel'], 'name': logical_drive['Name'], - ' size': logical_drive['Size']}}) - for physical_drive in \ - raid_adapter_info['PhysicalDisks']['PhysicalDisk']: - raid_config['logical_disks'].append({'physical_drives': { - 'physical_drive': physical_drive}}) - node.raid_config = raid_config - - raid_common.update_raid_info(node, node.raid_config) - LOG.info('RAID config is created successfully on node %s', - node_uuid) - - deploy_utils.set_async_step_flags( - task.node, - reboot=True, - skip_current_step=True, - polling=True) - - return states.CLEANWAIT - - -def _validate_logical_drive_capacity(disk, valid_disk_slots): - physical_disks = valid_disk_slots['PhysicalDisk'] - size_gb = {} - all_volume_list = [] - physical_disk_list = [] - - for size in physical_disks: - size_gb.update({size['@Number']: size['Size']['#text']}) - all_volume_list.append(size['Size']['#text']) - - factor = RAID_LEVELS[disk['raid_level']]['factor'] - - if disk.get('physical_disks'): - selected_disks = \ - [physical_disk for physical_disk in disk['physical_disks']] - for volume in selected_disks: - physical_disk_list.append(size_gb[volume]) - if disk['raid_level'] == '10': - valid_capacity = \ - min(physical_disk_list) * (len(physical_disk_list) / 2) - else: - valid_capacity = \ - min(physical_disk_list) * (len(physical_disk_list) - factor) - else: - valid_capacity = \ - min(all_volume_list) * \ - ((RAID_LEVELS[disk['raid_level']]['min_disks']) - factor) - - if disk['size_gb'] > valid_capacity: - raise exception.InvalidParameterValue( - 'Insufficient disk capacity with %s GB' % disk['size_gb']) - - if disk['size_gb'] == valid_capacity: - disk['size_gb'] = 'MAX' - - -def _validate_physical_disks(node, logical_disks): - """Validate physical disks on a RAID configuration. - - :param node: an ironic node object. - :param logical_disks: RAID info to set RAID configuration - :raises: IRMCOperationError on an error. - """ - raid_adapter = _get_raid_adapter(node) - physical_disk_dict = _get_physical_disk(node) - if raid_adapter is None: - reason = ('Cannot find any raid profile in "%s"' % node.uuid) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - if physical_disk_dict is None: - reason = ('Cannot find any physical disks in "%s"' % node.uuid) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - valid_disks = raid_adapter['Server']['HWConfigurationIrmc'][ - 'Adapters']['RAIDAdapter'][0]['PhysicalDisks'] - if valid_disks is None: - reason = ('Cannot find any HDD over in the node "%s"' % node.uuid) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - valid_disk_slots = [slot['Slot'] for slot in valid_disks['PhysicalDisk']] - remain_valid_disk_slots = list(valid_disk_slots) - number_of_valid_disks = len(valid_disk_slots) - used_valid_disk_slots = [] - - for disk in logical_disks: - # Check raid_level value in the target_raid_config of node - if disk.get('raid_level') not in RAID_LEVELS: - reason = ('RAID level is not supported: "%s"' - % disk.get('raid_level')) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - - min_disk_value = RAID_LEVELS[disk['raid_level']]['min_disks'] - max_disk_value = RAID_LEVELS[disk['raid_level']]['max_disks'] - remain_valid_disks = number_of_valid_disks - min_disk_value - number_of_valid_disks = number_of_valid_disks - min_disk_value - - if remain_valid_disks < 0: - reason = ('Physical disks do not enough slots for raid "%s"' - % disk['raid_level']) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - - if 'physical_disks' in disk: - type_of_disks = [] - number_of_physical_disks = len(disk['physical_disks']) - # Check number of physical disks along with raid level - if number_of_physical_disks > max_disk_value: - reason = ("Too many disks requested for RAID level %(level)s, " - "maximum is %(max)s", - {'level': disk['raid_level'], 'max': max_disk_value}) - raise exception.InvalidParameterValue(err=reason) - if number_of_physical_disks < min_disk_value: - reason = ("Not enough disks requested for RAID level " - "%(level)s, minimum is %(min)s ", - {'level': disk['raid_level'], 'min': min_disk_value}) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - # Check physical disks in valid disk slots - for phys_disk in disk['physical_disks']: - if int(phys_disk) not in valid_disk_slots: - reason = ("Incorrect physical disk %(disk)s, correct are " - "%(valid)s", - {'disk': phys_disk, 'valid': valid_disk_slots}) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - type_of_disks.append(physical_disk_dict[int(phys_disk)]) - if physical_disk_dict[int(phys_disk)] != type_of_disks[0]: - reason = ('Cannot create RAID configuration with ' - 'different hard drives type %s' - % physical_disk_dict[int(phys_disk)]) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - # Check physical disk values with used disk slots - if int(phys_disk) in used_valid_disk_slots: - reason = ("Disk %s is already used in a RAID configuration" - % disk['raid_level']) - raise exception.IRMCOperationError(operation='RAID config', - error=reason) - - used_valid_disk_slots.append(int(phys_disk)) - remain_valid_disk_slots.remove(int(phys_disk)) - - if disk['size_gb'] != 'MAX': - # Validate size_gb value input - _validate_logical_drive_capacity(disk, valid_disks) - - -class IRMCRAID(base.RAIDInterface): - - supported = False - - def get_properties(self): - """Return the properties of the interface.""" - return irmc_common.COMMON_PROPERTIES - - @METRICS.timer('IRMCRAID.create_configuration') - @base.clean_step(priority=0, argsinfo={ - 'create_root_volume': { - 'description': ('This specifies whether to create the root volume.' - 'Defaults to `True`.' - ), - 'required': False - }, - 'create_nonroot_volumes': { - 'description': ('This specifies whether to create the non-root ' - 'volumes. ' - 'Defaults to `True`.' - ), - 'required': False - } - }) - def create_configuration(self, task, - create_root_volume=True, - create_nonroot_volumes=True): - """Create the RAID configuration. - - This method creates the RAID configuration on the given node. - - :param task: a TaskManager instance containing the node to act on. - :param create_root_volume: If True, a root volume is created - during RAID configuration. Otherwise, no root volume is - created. Default is True. - :param create_nonroot_volumes: If True, non-root volumes are - created. If False, no non-root volumes are created. Default - is True. - :returns: states.CLEANWAIT if RAID configuration is in progress - asynchronously. - :raises: MissingParameterValue, if node.target_raid_config is missing - or empty. - :raises: IRMCOperationError on an error from scciclient - """ - - node = task.node - - if not node.target_raid_config: - raise exception.MissingParameterValue( - 'Missing the target_raid_config in node %s' % node.uuid) - - target_raid_config = node.target_raid_config.copy() - - logical_disks = target_raid_config['logical_disks'] - for log_disk in logical_disks: - if log_disk.get('raid_level'): - log_disk['raid_level'] = str( - log_disk['raid_level']).replace('+', '') - - # Validate physical disks on Fujitsu BM Server - _validate_physical_disks(node, logical_disks) - - # Executing raid configuration on Fujitsu BM Server - _create_raid_adapter(node) - - return _commit_raid_config(task) - - @METRICS.timer('IRMCRAID.delete_configuration') - @base.clean_step(priority=0) - def delete_configuration(self, task): - """Delete the RAID configuration. - - :param task: a TaskManager instance containing the node to act on. - :returns: states.CLEANWAIT if deletion is in progress - asynchronously or None if it is complete. - """ - node = task.node - node_uuid = task.node.uuid - - # Default delete everything raid configuration in BM Server - _delete_raid_adapter(node) - node.raid_config = {} - node.save() - LOG.info('RAID config is deleted successfully on node %(node_id)s.' - 'RAID config will clear and return (cfg)s value', - {'node_id': node_uuid, 'cfg': node.raid_config}) - - @METRICS.timer('IRMCRAID._query_raid_config_fgi_status') - @periodics.node_periodic( - purpose='checking async RAID configuration tasks', - spacing=CONF.irmc.query_raid_config_fgi_status_interval, - filters={'reserved': False, 'provision_state': states.CLEANWAIT, - 'maintenance': False}, - predicate_extra_fields=['raid_config'], - predicate=lambda n: ( - n.raid_config and not n.raid_config.get('fgi_status') - ), - ) - def _query_raid_config_fgi_status(self, task, manager, context): - """Periodic tasks to check the progress of running RAID config.""" - node = task.node - node_uuid = task.node.uuid - if task.node.target_raid_config is None: - return - task.upgrade_lock() - if node.provision_state != states.CLEANWAIT: - return - # Avoid hitting clean_callback_timeout expiration - node.touch_provisioning() - - raid_config = node.raid_config - - try: - report = irmc_common.get_irmc_report(node) - except client.scci.SCCIInvalidInputError: - raid_config.update({'fgi_status': RAID_FAILED}) - raid_common.update_raid_info(node, raid_config) - self._set_clean_failed(task, RAID_FAILED) - return - except client.scci.SCCIClientError: - raid_config.update({'fgi_status': RAID_FAILED}) - raid_common.update_raid_info(node, raid_config) - self._set_clean_failed(task, RAID_FAILED) - return - - fgi_status_dict = _get_fgi_status(report, node_uuid) - # Note(trungnv): Allow to check until RAID mechanism to be - # completed with RAID information in report. - if fgi_status_dict == 'completing': - return - if not fgi_status_dict: - raid_config.update({'fgi_status': RAID_FAILED}) - raid_common.update_raid_info(node, raid_config) - self._set_clean_failed(task, fgi_status_dict) - return - if all(fgi_status == 'Idle' for fgi_status in - fgi_status_dict.values()): - raid_config.update({'fgi_status': RAID_COMPLETED}) - raid_common.update_raid_info(node, raid_config) - LOG.info('RAID configuration has completed on ' - 'node %(node)s with fgi_status is %(fgi)s', - {'node': node_uuid, 'fgi': RAID_COMPLETED}) - self._resume_cleaning(task) - - def _set_clean_failed(self, task, fgi_status_dict): - LOG.error('RAID configuration task failed for node %(node)s. ' - 'with FGI status is: %(fgi)s. ', - {'node': task.node.uuid, 'fgi': fgi_status_dict}) - fgi_message = 'ServerViewRAID not available in Baremetal Server' - task.node.last_error = fgi_message - task.process_event('fail') - - def _resume_cleaning(self, task): - manager_utils.notify_conductor_resume_clean(task) diff --git a/ironic/drivers/modules/irmc/vendor.py b/ironic/drivers/modules/irmc/vendor.py deleted file mode 100644 index e19024f79e..0000000000 --- a/ironic/drivers/modules/irmc/vendor.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2022 FUJITSU LIMITED -# -# 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. - -""" -Vendor interface of iRMC driver -""" - -from ironic.common import exception -from ironic.common.i18n import _ -from ironic.drivers import base -from ironic.drivers.modules.irmc import common as irmc_common - - -class IRMCVendorPassthru(base.VendorInterface): - - supported = False - - def get_properties(self): - """Return the properties of the interface. - - :returns: Dictionary of : entries. - """ - return irmc_common.COMMON_PROPERTIES - - def validate(self, task, method=None, **kwargs): - """Validate vendor-specific actions. - - This method validates whether the 'driver_info' property of the - supplied node contains the required information for this driver. - - :param task: An instance of TaskManager. - :param method: Name of vendor passthru method - :raises: InvalidParameterValue if invalid value is contained - in the 'driver_info' property. - :raises: MissingParameterValue if some mandatory key is missing - in the 'driver_info' property. - """ - irmc_common.parse_driver_info(task.node) - - @base.passthru(['POST'], - async_call=True, - description='Connect to iRMC and fetch iRMC firmware ' - 'version and, if firmware version has not been cached ' - 'in or actual firmware version is different from one in ' - 'driver_internal_info/irmc_fw_version, store firmware ' - 'version in driver_internal_info/irmc_fw_version.', - attach=False, - require_exclusive_lock=False) - def cache_irmc_firmware_version(self, task, **kwargs): - """Fetch and save iRMC firmware version. - - This method connects to iRMC and fetch iRMC firmware version. - If fetched firmware version is not cached in or is different from - one in driver_internal_info/irmc_fw_version, store fetched version - in driver_internal_info/irmc_fw_version. - - :param task: An instance of TaskManager. - :raises: IRMCOperationError if some error occurs - """ - try: - irmc_common.set_irmc_version(task) - except (exception.IRMCOperationError, - exception.InvalidParameterValue, - exception.MissingParameterValue, - exception.NodeLocked) as e: - raise exception.IRMCOperationError( - operation=_('caching firmware version'), error=e) diff --git a/ironic/tests/unit/api/controllers/v1/test_ramdisk.py b/ironic/tests/unit/api/controllers/v1/test_ramdisk.py index 8f7cb6e476..24f7f3f2c3 100644 --- a/ironic/tests/unit/api/controllers/v1/test_ramdisk.py +++ b/ironic/tests/unit/api/controllers/v1/test_ramdisk.py @@ -267,19 +267,6 @@ def test_bmc_detect_skip_for_idrac_redfish(self): self.assertEqual(self.node.uuid, data['node']['uuid']) self._check_config(data, skip_bmc_detect=True) - def test_bmc_detect_skip_for_irmc(self): - """Test BMC detection skip is enabled for iRMC.""" - self.node.management_interface = 'irmc' - self.node.save() - self._set_secret_mock(self.node, 'test-token') - - data = self.get_json( - '/lookup?node_uuid=%s' % self.node.uuid, - headers={api_base.Version.string: str(api_v1.max_version())}) - - self.assertEqual(self.node.uuid, data['node']['uuid']) - self._check_config(data, skip_bmc_detect=True) - def test_bmc_detect_not_skipped_for_ipmi(self): """Test BMC detection is NOT skipped for IPMI interface.""" self.node.management_interface = 'ipmitool' diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 31dffb8c63..818375d687 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -63,7 +63,7 @@ from ironic.drivers.modules import inspect_utils from ironic.drivers.modules.network import common as n_common from ironic.drivers.modules.network import flat as n_flat -from ironic.drivers.modules import redfish +from ironic.drivers.modules.redfish import management as redfish_mgmt from ironic import objects from ironic.objects import base as obj_base from ironic.objects import fields as obj_fields @@ -9535,7 +9535,7 @@ def setUp(self): provision_state=states.ACTIVE) @mock.patch.object(image_utils, 'ISOImageCache', autospec=True) - @mock.patch.object(redfish.management.RedfishManagement, 'validate', + @mock.patch.object(redfish_mgmt.RedfishManagement, 'validate', autospec=True) @mock.patch.object(manager, 'do_attach_virtual_media', autospec=True) @@ -9552,7 +9552,7 @@ def test_attach_virtual_media_local(self, mock_attach, mock_validate, self.node.refresh() self.assertIsNone(self.node.last_error) - @mock.patch.object(redfish.management.RedfishManagement, 'validate', + @mock.patch.object(redfish_mgmt.RedfishManagement, 'validate', autospec=True) @mock.patch.object(manager, 'do_attach_virtual_media', autospec=True) def test_attach_virtual_media_http(self, mock_attach, mock_validate): @@ -9567,7 +9567,7 @@ def test_attach_virtual_media_http(self, mock_attach, mock_validate): self.node.refresh() self.assertIsNone(self.node.last_error) - @mock.patch.object(redfish.management.RedfishManagement, + @mock.patch.object(redfish_mgmt.RedfishManagement, 'attach_virtual_media', autospec=True) @mock.patch.object(image_utils, 'cleanup_remote_image', autospec=True) @mock.patch.object(image_utils, 'prepare_remote_image', autospec=True) @@ -9585,7 +9585,7 @@ def test_do_attach_virtual_media(self, mock_prepare_image, task.driver.management, task, device_type=boot_devices.CDROM, image_url=mock_prepare_image.return_value) - @mock.patch.object(redfish.management.RedfishManagement, + @mock.patch.object(redfish_mgmt.RedfishManagement, 'attach_virtual_media', autospec=True) @mock.patch.object(image_utils, 'cleanup_remote_image', autospec=True) @mock.patch.object(image_utils, 'prepare_remote_image', autospec=True) @@ -9606,7 +9606,7 @@ def test_do_attach_virtual_media_fails_on_prepare(self, mock_prepare_image, self.assertIn("Could not attach device cdrom", self.node.last_error) self.assertIn("Invalid image href", self.node.last_error) - @mock.patch.object(redfish.management.RedfishManagement, + @mock.patch.object(redfish_mgmt.RedfishManagement, 'attach_virtual_media', autospec=True) @mock.patch.object(image_utils, 'cleanup_remote_image', autospec=True) @mock.patch.object(image_utils, 'prepare_remote_image', autospec=True) @@ -9628,7 +9628,7 @@ def test_do_attach_virtual_media_fails_on_attach(self, mock_prepare_image, self.assertIn("Could not attach device cdrom", self.node.last_error) self.assertIn("disabled or not implemented", self.node.last_error) - @mock.patch.object(redfish.management.RedfishManagement, 'validate', + @mock.patch.object(redfish_mgmt.RedfishManagement, 'validate', autospec=True) def test_attach_virtual_media_power_failure(self, mock_validate): CONF.set_override('use_swift', 'false', group='redfish') @@ -9641,7 +9641,7 @@ def test_attach_virtual_media_power_failure(self, mock_validate): boot_devices.CDROM, 'https://url') mock_validate.assert_called_once_with(mock.ANY, mock.ANY) - @mock.patch.object(redfish.management.RedfishManagement, 'validate', + @mock.patch.object(redfish_mgmt.RedfishManagement, 'validate', autospec=True) def test_detach_virtual_media_power_failure(self, mock_validate): CONF.set_override('use_swift', 'false', group='redfish') @@ -9654,9 +9654,9 @@ def test_detach_virtual_media_power_failure(self, mock_validate): boot_devices.CDROM) mock_validate.assert_called_once_with(mock.ANY, mock.ANY) - @mock.patch.object(redfish.management.RedfishManagement, + @mock.patch.object(redfish_mgmt.RedfishManagement, 'get_virtual_media', autospec=True) - @mock.patch.object(redfish.management.RedfishManagement, 'validate', + @mock.patch.object(redfish_mgmt.RedfishManagement, 'validate', autospec=True) def test_get_virtual_media(self, mock_validate, mock_get): mock_get.return_value = [{'media_types': ['CD'], 'inserted': False, @@ -9666,9 +9666,9 @@ def test_get_virtual_media(self, mock_validate, mock_get): mock_get.assert_called_once_with(mock.ANY, mock.ANY) self.assertEqual(mock_get.return_value, result) - @mock.patch.object(redfish.management.RedfishManagement, + @mock.patch.object(redfish_mgmt.RedfishManagement, 'get_virtual_media', autospec=True) - @mock.patch.object(redfish.management.RedfishManagement, 'validate', + @mock.patch.object(redfish_mgmt.RedfishManagement, 'validate', autospec=True) def test_get_virtual_media_node_locked(self, mock_validate, mock_get): """get_virtual_media uses a shared lock. diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index b07b9607b8..b0f5e354bb 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -98,16 +98,6 @@ def get_test_drac_info(): } -def get_test_irmc_info(): - return { - "irmc_address": "1.2.3.4", - "irmc_username": "admin0", - "irmc_password": "fake0", - "irmc_port": "80", - "irmc_auth_method": "digest", - } - - def get_test_agent_instance_info(): return { 'image_source': 'fake-image', diff --git a/ironic/tests/unit/drivers/modules/irmc/__init__.py b/ironic/tests/unit/drivers/modules/irmc/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml b/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml deleted file mode 100644 index c8788427ba..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ng.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - 20 - 0 - 0 - - 1 - - - 55 - 0 - Ambient - - - 1 - - 1 - 0 - degree C - unspecified - - - 168 - 42 - - - 4 - 1 - - - 148 - 37 - - - 24 - 6 - - - - - - - - - - - - 20 - 0 - 0 - - 2 - - - 7 - 0 - Systemboard 1 - - - - Temperature - 1 - 0 - degree C - unspecified - - - 80 - 80 - - - 75 - 75 - - - - - - - - - - - - 20 - 0 - 0 - - 35 - - - 29 - 0 - - - - 4 - Fan - 18 - 0 - RPM - unspecified - - - 10 - 600 - - - - - - - - - - - - 20 - 0 - 0 - - 36 - - - - 1 - FAN2 SYS - - - 4 - Fan - 18 - 0 - RPM - unspecified - - - 10 - 600 - - - - - - - - - diff --git a/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml b/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml deleted file mode 100644 index fb8edba68e..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/fake_sensors_data_ok.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - 20 - 0 - 0 - - 1 - - - 55 - 0 - Ambient - - - 1 - Temperature - 1 - 0 - degree C - unspecified - - - 168 - 42 - - - 4 - 1 - - - 148 - 37 - - - 24 - 6 - - - - - - - - - - - - 20 - 0 - 0 - - 2 - - - 7 - 0 - Systemboard 1 - - - 1 - Temperature - 1 - 0 - degree C - unspecified - - - 80 - 80 - - - 75 - 75 - - - - - - - - - - - - 20 - 0 - 0 - - 35 - - - 29 - 0 - FAN1 SYS - - - 4 - Fan - 18 - 0 - RPM - unspecified - - - 10 - 600 - - - - - - - - - - - - 20 - 0 - 0 - - 36 - - - 29 - 1 - FAN2 SYS - - - 4 - Fan - 18 - 0 - - unspecified - - - 10 - - - - - - - - - - diff --git a/ironic/tests/unit/drivers/modules/irmc/test_bios.py b/ironic/tests/unit/drivers/modules/irmc/test_bios.py deleted file mode 100644 index 4b091adf13..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_bios.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright 2018 FUJITSU LIMITED -# -# 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 IRMC BIOS configuration -""" - -from unittest import mock - -from ironic.common import exception -from ironic.conductor import task_manager -from ironic.drivers.modules.irmc import bios as irmc_bios -from ironic.drivers.modules.irmc import common as irmc_common -from ironic import objects -from ironic.tests.unit.drivers.modules.irmc import test_common - - -class IRMCBIOSTestCase(test_common.BaseIRMCTest): - - def setUp(self): - super(IRMCBIOSTestCase, self).setUp() - - @mock.patch.object(irmc_common, 'parse_driver_info', - autospec=True) - def test_validate(self, parse_driver_info_mock): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.bios.validate(task) - parse_driver_info_mock.assert_called_once_with(task.node) - - def test_apply_configuration(self): - if not mock._is_instance_mock(irmc_bios.irmc.elcm): - mock.patch.object(irmc_bios.irmc, 'elcm', autospec=True).start() - set_bios_configuration_mock = ( - irmc_bios.irmc.elcm.set_bios_configuration) - get_bios_settings_mock = irmc_bios.irmc.elcm.get_bios_settings - settings = [{ - "name": "launch_csm_enabled", - "value": True - }, { - "name": "hyper_threading_enabled", - "value": True - }, { - "name": "cpu_vt_enabled", - "value": True - }] - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_info = irmc_common.parse_driver_info(task.node) - task.node.save = mock.Mock() - get_bios_settings_mock.return_value = settings - task.driver.bios.apply_configuration(task, settings) - set_bios_configuration_mock.assert_called_once_with(irmc_info, - settings) - - def test_apply_configuration_failed(self): - if not mock._is_instance_mock(irmc_bios.irmc.elcm): - mock.patch.object(irmc_bios.irmc, 'elcm', autospec=True).start() - set_bios_configuration_mock = ( - irmc_bios.irmc.elcm.set_bios_configuration) - settings = [{ - "name": "launch_csm_enabled", - "value": True - }, { - "name": "hyper_threading_enabled", - "value": True - }, { - "name": "setting", - "value": True - }] - irmc_bios.irmc.scci.SCCIError = Exception - set_bios_configuration_mock.side_effect = Exception - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.IRMCOperationError, - task.driver.bios.apply_configuration, - task, settings) - - def test_factory_reset(self): - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.UnsupportedDriverExtension, - task.driver.bios.factory_reset, task) - - @mock.patch.object(objects.BIOSSettingList, 'sync_node_setting', - autospec=True) - @mock.patch.object(objects.BIOSSettingList, 'create', - autospec=True) - @mock.patch.object(objects.BIOSSettingList, 'save', - autospec=True) - @mock.patch.object(objects.BIOSSettingList, 'delete', - autospec=True) - def test_cache_bios_settings(self, delete_mock, save_mock, create_mock, - sync_node_setting_mock): - if not mock._is_instance_mock(irmc_bios.irmc.elcm): - mock.patch.object(irmc_bios.irmc, 'elcm', autospec=True).start() - get_bios_settings_mock = irmc_bios.irmc.elcm.get_bios_settings - settings = [{ - "name": "launch_csm_enabled", - "value": True - }, { - "name": "hyper_threading_enabled", - "value": True - }, { - "name": "cpu_vt_enabled", - "value": True - }] - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_info = irmc_common.parse_driver_info(task.node) - get_bios_settings_mock.return_value = settings - sync_node_setting_mock.return_value = \ - ( - [ - { - "name": "launch_csm_enabled", - "value": True - }], - [ - { - "name": "hyper_threading_enabled", - "value": True - }], - [ - { - "name": "cpu_vt_enabled", - "value": True - }], - [] - ) - task.driver.bios.cache_bios_settings(task) - get_bios_settings_mock.assert_called_with(irmc_info) - sync_node_setting_mock.assert_called_once_with(task.context, - task.node.id, - settings) - create_mock.assert_called_once_with( - task.context, task.node.id, - sync_node_setting_mock.return_value[0]) - save_mock.assert_called_once_with( - task.context, task.node.id, - sync_node_setting_mock.return_value[1]) - delete_names = \ - [setting['name'] for setting in - sync_node_setting_mock.return_value[2]] - delete_mock.assert_called_once_with(task.context, task.node.id, - delete_names) - - def test_cache_bios_settings_failed(self): - if not mock._is_instance_mock(irmc_bios.irmc.elcm): - mock.patch.object(irmc_bios.irmc, 'elcm', autospec=True).start() - get_bios_settings_mock = irmc_bios.irmc.elcm.get_bios_settings - irmc_bios.irmc.scci.SCCIError = Exception - get_bios_settings_mock.side_effect = Exception - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.IRMCOperationError, - task.driver.bios.cache_bios_settings, - task) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_boot.py b/ironic/tests/unit/drivers/modules/irmc/test_boot.py deleted file mode 100644 index 5f85450591..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_boot.py +++ /dev/null @@ -1,1953 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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 iRMC Boot Driver -""" - -import io -import os -import shutil -import tempfile -import unittest -from unittest import mock - -from oslo_config import cfg -from oslo_utils import uuidutils - -from ironic.common import boot_devices -from ironic.common import exception -from ironic.common.glance_service import service_utils -from ironic.common.i18n import _ -from ironic.common import images -from ironic.common import states -from ironic.common import utils -from ironic.conductor import task_manager -from ironic.conductor import utils as manager_utils -from ironic.drivers.modules import boot_mode_utils -from ironic.drivers.modules import deploy_utils -from ironic.drivers.modules.irmc import boot as irmc_boot -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.irmc import management as irmc_management -from ironic.drivers.modules import pxe -from ironic.drivers.modules import pxe_base -from ironic.drivers.modules import snmp -from ironic.tests import base -from ironic.tests.unit.db import utils as db_utils -from ironic.tests.unit.drivers.modules.irmc import test_common -from ironic.tests.unit.drivers.modules import test_pxe -from ironic.tests.unit.drivers import third_party_driver_mock_specs \ - as mock_specs -from ironic.tests.unit.objects import utils as obj_utils - -INFO_DICT = db_utils.get_test_irmc_info() -CONF = cfg.CONF -PARSED_IFNO = { - 'irmc_address': '1.2.3.4', - 'irmc_port': 80, - 'irmc_username': 'admin0', - 'irmc_password': 'fake0', - 'irmc_auth_method': 'digest', - 'irmc_client_timeout': 60, - 'irmc_snmp_community': 'public', - 'irmc_snmp_port': 161, - 'irmc_snmp_version': snmp.SNMP_V2C, - 'irmc_sensor_method': 'ipmitool', - 'irmc_verify_ca': True, -} - - -@mock.patch.object(irmc_boot, 'check_share_fs_mounted', spec_set=True, - autospec=True) -class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): - boot_interface = 'irmc-virtual-media' - - def setUp(self): - super(IRMCDeployPrivateMethodsTestCase, self).setUp() - - CONF.set_override('remote_image_share_root', - '/remote_image_share_root', 'irmc') - CONF.set_override('remote_image_server', '10.20.30.40', 'irmc') - CONF.set_override('remote_image_share_type', 'NFS', 'irmc') - CONF.set_override('remote_image_share_name', 'share', 'irmc') - CONF.set_override('remote_image_user_name', 'admin', 'irmc') - CONF.set_override('remote_image_user_password', 'admin0', 'irmc') - CONF.set_override('remote_image_user_domain', 'local', 'irmc') - - @mock.patch.object(os.path, 'isdir', spec_set=True, autospec=True) - def test__parse_config_option(self, isdir_mock, - check_share_fs_mounted_mock): - isdir_mock.return_value = True - - result = irmc_boot._parse_config_option() - - isdir_mock.assert_called_once_with('/remote_image_share_root') - self.assertIsNone(result) - - @mock.patch.object(os.path, 'isdir', spec_set=True, autospec=True) - def test__parse_config_option_non_existed_root( - self, isdir_mock, check_share_fs_mounted_mock): - CONF.set_override('remote_image_share_root', '/non_existed_root', - 'irmc') - isdir_mock.return_value = False - - self.assertRaises(exception.InvalidParameterValue, - irmc_boot._parse_config_option) - isdir_mock.assert_called_once_with('/non_existed_root') - - @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_driver_info_in_share(self, isfile_mock, - check_share_fs_mounted_mock): - """With required 'deploy_iso' in share.""" - isfile_mock.return_value = True - self.node.driver_info['deploy_iso'] = 'deploy.iso' - driver_info_expected = { - 'deploy_iso': 'deploy.iso', - 'kernel_append_params': CONF.pxe.kernel_append_params, - } - - driver_info_actual = irmc_boot._parse_driver_info(self.node, - mode='deploy') - - isfile_mock.assert_called_once_with( - '/remote_image_share_root/deploy.iso') - self.assertEqual(driver_info_expected, driver_info_actual) - - @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_driver_info_deprecated(self, isfile_mock, - check_share_fs_mounted_mock): - """With required 'irmc_deploy_iso' in share.""" - isfile_mock.return_value = True - self.node.driver_info['irmc_deploy_iso'] = 'deploy.iso' - driver_info_expected = { - 'deploy_iso': 'deploy.iso', - 'kernel_append_params': CONF.pxe.kernel_append_params, - } - - driver_info_actual = irmc_boot._parse_driver_info(self.node, - mode='deploy') - - isfile_mock.assert_called_once_with( - '/remote_image_share_root/deploy.iso') - self.assertEqual(driver_info_expected, driver_info_actual) - - @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_driver_info_kernel_params(self, isfile_mock, - check_share_fs_mounted_mock): - """With overridden kernel_append_params.""" - isfile_mock.return_value = True - self.node.driver_info['deploy_iso'] = 'deploy.iso' - self.node.instance_info['kernel_append_params'] = 'kernel params' - driver_info_expected = { - 'deploy_iso': 'deploy.iso', - 'kernel_append_params': 'kernel params', - } - - driver_info_actual = irmc_boot._parse_driver_info(self.node, - mode='deploy') - - isfile_mock.assert_called_once_with( - '/remote_image_share_root/deploy.iso') - self.assertEqual(driver_info_expected, driver_info_actual) - - @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_driver_info_kernel_params_in_conf( - self, isfile_mock, check_share_fs_mounted_mock): - """With overridden kernel_append_params.""" - self.config(kernel_append_params='kernel params', group='irmc') - isfile_mock.return_value = True - self.node.driver_info['deploy_iso'] = 'deploy.iso' - driver_info_expected = { - 'deploy_iso': 'deploy.iso', - 'kernel_append_params': 'kernel params', - } - - driver_info_actual = irmc_boot._parse_driver_info(self.node, - mode='deploy') - - isfile_mock.assert_called_once_with( - '/remote_image_share_root/deploy.iso') - self.assertEqual(driver_info_expected, driver_info_actual) - - @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_driver_info_kernel_params_in_driver_info( - self, isfile_mock, check_share_fs_mounted_mock): - """With overridden kernel_append_params.""" - isfile_mock.return_value = True - self.node.driver_info['deploy_iso'] = 'deploy.iso' - self.node.driver_info['kernel_append_params'] = 'kernel params' - driver_info_expected = { - 'deploy_iso': 'deploy.iso', - 'kernel_append_params': 'kernel params', - } - - driver_info_actual = irmc_boot._parse_driver_info(self.node, - mode='deploy') - - isfile_mock.assert_called_once_with( - '/remote_image_share_root/deploy.iso') - self.assertEqual(driver_info_expected, driver_info_actual) - - @mock.patch.object(irmc_boot, '_is_image_href_ordinary_file_name', - spec_set=True, autospec=True) - def test__parse_driver_info_not_in_share( - self, is_image_href_ordinary_file_name_mock, - check_share_fs_mounted_mock): - """With required 'rescue_iso' not in share.""" - self.node.driver_info['rescue_iso'] = ( - 'bc784057-a140-4130-add3-ef890457e6b3') - driver_info_expected = { - 'rescue_iso': 'bc784057-a140-4130-add3-ef890457e6b3', - 'kernel_append_params': CONF.pxe.kernel_append_params - } - is_image_href_ordinary_file_name_mock.return_value = False - - driver_info_actual = irmc_boot._parse_driver_info(self.node, - mode='rescue') - - self.assertEqual(driver_info_expected, driver_info_actual) - - @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_driver_info_with_iso_invalid(self, isfile_mock, - check_share_fs_mounted_mock): - """With required 'deploy_iso' non existed.""" - isfile_mock.return_value = False - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['deploy_iso'] = 'deploy.iso' - error_msg = (_("Deploy ISO file, %(deploy_iso)s, " - "not found for node: %(node)s.") % - {'deploy_iso': '/remote_image_share_root/deploy.iso', - 'node': task.node.uuid}) - - e = self.assertRaises(exception.InvalidParameterValue, - irmc_boot._parse_driver_info, - task.node, mode='deploy') - self.assertEqual(error_msg, str(e)) - - def test__parse_driver_info_with_iso_missing(self, - check_share_fs_mounted_mock): - """With required 'rescue_iso' empty.""" - self.node.driver_info['rescue_iso'] = None - - error_msg = ("Error validating iRMC virtual media for rescue. Some" - " parameters were missing in node's driver_info." - " Missing are: ['rescue_iso']") - e = self.assertRaises(exception.MissingParameterValue, - irmc_boot._parse_driver_info, - self.node, mode='rescue') - self.assertEqual(error_msg, str(e)) - - def test__parse_instance_info_with_boot_iso_file_name_ok( - self, check_share_fs_mounted_mock): - """With optional 'boot_iso' file name.""" - CONF.set_override('remote_image_share_root', '/etc', 'irmc') - self.node.instance_info['boot_iso'] = 'hosts' - instance_info_expected = {'boot_iso': 'hosts'} - instance_info_actual = irmc_boot._parse_instance_info(self.node) - - self.assertEqual(instance_info_expected, instance_info_actual) - - def test__parse_instance_info_with_boot_iso_deprecated( - self, check_share_fs_mounted_mock): - """With optional 'irmc_boot_iso' file name.""" - CONF.set_override('remote_image_share_root', '/etc', 'irmc') - self.node.instance_info['irmc_boot_iso'] = 'hosts' - instance_info_expected = {'boot_iso': 'hosts'} - instance_info_actual = irmc_boot._parse_instance_info(self.node) - - self.assertEqual(instance_info_expected, instance_info_actual) - - def test__parse_instance_info_without_boot_iso_ok( - self, check_share_fs_mounted_mock): - """With optional no 'boot_iso' file name.""" - CONF.set_override('remote_image_share_root', '/etc', 'irmc') - - self.node.instance_info['boot_iso'] = None - instance_info_expected = {} - instance_info_actual = irmc_boot._parse_instance_info(self.node) - - self.assertEqual(instance_info_expected, instance_info_actual) - - def test__parse_instance_info_with_boot_iso_uuid_ok( - self, check_share_fs_mounted_mock): - """With optional 'boot_iso' glance uuid.""" - self.node.instance_info[ - 'boot_iso'] = 'bc784057-a140-4130-add3-ef890457e6b3' - instance_info_expected = {'boot_iso': - 'bc784057-a140-4130-add3-ef890457e6b3'} - instance_info_actual = irmc_boot._parse_instance_info(self.node) - - self.assertEqual(instance_info_expected, instance_info_actual) - - def test__parse_instance_info_with_boot_iso_glance_ok( - self, check_share_fs_mounted_mock): - """With optional 'boot_iso' glance url.""" - self.node.instance_info['boot_iso'] = ( - 'glance://bc784057-a140-4130-add3-ef890457e6b3') - instance_info_expected = { - 'boot_iso': 'glance://bc784057-a140-4130-add3-ef890457e6b3', - } - instance_info_actual = irmc_boot._parse_instance_info(self.node) - - self.assertEqual(instance_info_expected, instance_info_actual) - - def test__parse_instance_info_with_boot_iso_http_ok( - self, check_share_fs_mounted_mock): - """With optional 'boot_iso' http url.""" - self.node.driver_info[ - 'deploy_iso'] = 'http://irmc_boot_iso' - driver_info_expected = { - 'deploy_iso': 'http://irmc_boot_iso', - 'kernel_append_params': CONF.pxe.kernel_append_params - } - driver_info_actual = irmc_boot._parse_driver_info(self.node) - - self.assertEqual(driver_info_expected, driver_info_actual) - - def test__parse_instance_info_with_boot_iso_https_ok( - self, check_share_fs_mounted_mock): - """With optional 'boot_iso' https url.""" - self.node.instance_info[ - 'boot_iso'] = 'https://irmc_boot_iso' - instance_info_expected = {'boot_iso': 'https://irmc_boot_iso'} - instance_info_actual = irmc_boot._parse_instance_info(self.node) - - self.assertEqual(instance_info_expected, instance_info_actual) - - def test__parse_instance_info_with_boot_iso_file_url_ok( - self, check_share_fs_mounted_mock): - """With optional 'boot_iso' file url.""" - self.node.instance_info[ - 'boot_iso'] = 'file://irmc_boot_iso' - instance_info_expected = {'boot_iso': 'file://irmc_boot_iso'} - instance_info_actual = irmc_boot._parse_instance_info(self.node) - - self.assertEqual(instance_info_expected, instance_info_actual) - - @mock.patch.object(os.path, 'isfile', spec_set=True, autospec=True) - def test__parse_instance_info_with_boot_iso_invalid( - self, isfile_mock, check_share_fs_mounted_mock): - CONF.set_override('remote_image_share_root', '/etc', 'irmc') - isfile_mock.return_value = False - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.instance_info['boot_iso'] = 'hosts~non~existed' - - error_msg = (_("Boot ISO file, %(boot_iso)s, " - "not found for node: %(node)s.") % - {'boot_iso': '/etc/hosts~non~existed', - 'node': task.node.uuid}) - - e = self.assertRaises(exception.InvalidParameterValue, - irmc_boot._parse_instance_info, - task.node) - self.assertEqual(error_msg, str(e)) - - @mock.patch.object(deploy_utils, 'get_image_instance_info', - spec_set=True, autospec=True) - @mock.patch('os.path.isfile', autospec=True) - def test_parse_deploy_info_ok(self, mock_isfile, - get_image_instance_info_mock, - check_share_fs_mounted_mock): - CONF.set_override('remote_image_share_root', '/etc', 'irmc') - get_image_instance_info_mock.return_value = {'a': 'b'} - driver_info_expected = { - 'a': 'b', - 'deploy_iso': 'hosts', - 'boot_iso': 'fstab', - 'kernel_append_params': CONF.pxe.kernel_append_params - } - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['deploy_iso'] = 'hosts' - task.node.instance_info['boot_iso'] = 'fstab' - driver_info_actual = irmc_boot._parse_deploy_info(task.node) - self.assertEqual(driver_info_expected, driver_info_actual) - boot_iso_path = os.path.join( - CONF.irmc.remote_image_share_root, - task.node.instance_info['boot_iso'] - ) - mock_isfile.assert_any_call(boot_iso_path) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - @mock.patch.object(images, 'fetch', spec_set=True, - autospec=True) - def test__setup_vmedia_with_file_deploy(self, - fetch_mock, - setup_vmedia_mock, - set_boot_device_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_info['deploy_iso'] = 'deploy_iso_filename' - ramdisk_opts = {'a': 'b'} - irmc_boot._setup_vmedia(task, mode='deploy', - ramdisk_options=ramdisk_opts) - - self.assertFalse(fetch_mock.called) - - setup_vmedia_mock.assert_called_once_with( - task, - 'deploy_iso_filename', - ramdisk_opts) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.CDROM) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - @mock.patch.object(images, 'fetch', spec_set=True, - autospec=True) - def test__setup_vmedia_with_file_rescue(self, - fetch_mock, - setup_vmedia_mock, - set_boot_device_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_info['rescue_iso'] = 'rescue_iso_filename' - ramdisk_opts = {'a': 'b'} - irmc_boot._setup_vmedia(task, mode='rescue', - ramdisk_options=ramdisk_opts) - - self.assertFalse(fetch_mock.called) - - setup_vmedia_mock.assert_called_once_with( - task, - 'rescue_iso_filename', - ramdisk_opts) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.CDROM) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - @mock.patch.object(images, 'fetch', spec_set=True, - autospec=True) - def test_setup_vmedia_with_image_service_deploy( - self, - fetch_mock, - setup_vmedia_mock, - set_boot_device_mock, - check_share_fs_mounted_mock): - CONF.set_override('remote_image_share_root', '/', 'irmc') - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_info['deploy_iso'] = 'glance://deploy_iso' - ramdisk_opts = {'a': 'b'} - irmc_boot._setup_vmedia(task, mode='deploy', - ramdisk_options=ramdisk_opts) - - fetch_mock.assert_called_once_with( - task.context, - 'glance://deploy_iso', - "/deploy-%s.iso" % self.node.uuid) - - setup_vmedia_mock.assert_called_once_with( - task, - "deploy-%s.iso" % self.node.uuid, - ramdisk_opts) - set_boot_device_mock.assert_called_once_with( - task, boot_devices.CDROM) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - @mock.patch.object(images, 'fetch', spec_set=True, - autospec=True) - def test_setup_vmedia_with_image_service_rescue( - self, - fetch_mock, - setup_vmedia_mock, - set_boot_device_mock, - check_share_fs_mounted_mock): - CONF.set_override('remote_image_share_root', '/', 'irmc') - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_info['rescue_iso'] = 'glance://rescue_iso' - ramdisk_opts = {'a': 'b'} - irmc_boot._setup_vmedia(task, mode='rescue', - ramdisk_options=ramdisk_opts) - - fetch_mock.assert_called_once_with( - task.context, - 'glance://rescue_iso', - "/rescue-%s.iso" % self.node.uuid) - - setup_vmedia_mock.assert_called_once_with( - task, - "rescue-%s.iso" % self.node.uuid, - ramdisk_opts) - set_boot_device_mock.assert_called_once_with( - task, boot_devices.CDROM) - - def test__get_iso_name(self, check_share_fs_mounted_mock): - actual = irmc_boot._get_iso_name(self.node, label='deploy') - expected = "deploy-%s.iso" % self.node.uuid - self.assertEqual(expected, actual) - - @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True) - @mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy', - spec_set=True, autospec=True) - @mock.patch.object(images, 'get_image_properties', spec_set=True, - autospec=True) - @mock.patch.object(images, 'fetch', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_parse_deploy_info', spec_set=True, - autospec=True) - def test__prepare_boot_iso_file(self, - deploy_info_mock, - fetch_mock, - image_props_mock, - boot_mode_mock, - create_boot_iso_mock, - check_share_fs_mounted_mock): - deploy_info_mock.return_value = {'boot_iso': 'irmc_boot.iso'} - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_boot._prepare_boot_iso(task, 'root-uuid') - - deploy_info_mock.assert_called_once_with(task.node) - self.assertFalse(fetch_mock.called) - self.assertFalse(image_props_mock.called) - self.assertFalse(boot_mode_mock.called) - self.assertFalse(create_boot_iso_mock.called) - task.node.refresh() - self.assertEqual('irmc_boot.iso', - task.node.driver_internal_info['boot_iso']) - - @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True) - @mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy', - spec_set=True, autospec=True) - @mock.patch.object(images, 'get_image_properties', spec_set=True, - autospec=True) - @mock.patch.object(images, 'fetch', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_parse_deploy_info', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_is_image_href_ordinary_file_name', - spec_set=True, autospec=True) - def test__prepare_boot_iso_fetch_ok(self, - is_image_href_ordinary_file_name_mock, - deploy_info_mock, - fetch_mock, - image_props_mock, - boot_mode_mock, - create_boot_iso_mock, - check_share_fs_mounted_mock): - CONF.set_override('remote_image_share_root', '/', 'irmc') - image = '733d1c44-a2ea-414b-aca7-69decf20d810' - is_image_href_ordinary_file_name_mock.return_value = False - deploy_info_mock.return_value = {'boot_iso': image} - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.instance_info['boot_iso'] = image - irmc_boot._prepare_boot_iso(task, 'root-uuid') - - deploy_info_mock.assert_called_once_with(task.node) - fetch_mock.assert_called_once_with( - task.context, - image, - "/boot-%s.iso" % self.node.uuid) - self.assertFalse(image_props_mock.called) - self.assertFalse(boot_mode_mock.called) - self.assertFalse(create_boot_iso_mock.called) - task.node.refresh() - self.assertEqual("boot-%s.iso" % self.node.uuid, - task.node.driver_internal_info['boot_iso']) - - @mock.patch.object(images, 'create_boot_iso', spec_set=True, autospec=True) - @mock.patch.object(boot_mode_utils, 'get_boot_mode_for_deploy', - spec_set=True, autospec=True) - @mock.patch.object(images, 'get_image_properties', spec_set=True, - autospec=True) - @mock.patch.object(images, 'fetch', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_parse_deploy_info', spec_set=True, - autospec=True) - def test__prepare_boot_iso_create_ok(self, - deploy_info_mock, - fetch_mock, - image_props_mock, - boot_mode_mock, - create_boot_iso_mock, - check_share_fs_mounted_mock): - deploy_info_mock.return_value = \ - {'image_source': 'image-uuid', - 'deploy_iso': '02f9d414-2ce0-4cf5-b48f-dbc1bf678f55', - 'kernel_append_params': 'kernel-params'} - image_props_mock.return_value = {'kernel_id': 'kernel_uuid', - 'ramdisk_id': 'ramdisk_uuid'} - - boot_mode_mock.return_value = 'uefi' - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._prepare_boot_iso(task, 'root-uuid') - - self.assertFalse(fetch_mock.called) - deploy_info_mock.assert_called_once_with(task.node) - image_props_mock.assert_called_once_with( - task.context, 'image-uuid', ['kernel_id', 'ramdisk_id']) - create_boot_iso_mock.assert_called_once_with( - task.context, - '/remote_image_share_root/' - "boot-%s.iso" % self.node.uuid, - 'kernel_uuid', 'ramdisk_uuid', - deploy_iso_href='02f9d414-2ce0-4cf5-b48f-dbc1bf678f55', - root_uuid='root-uuid', kernel_params='kernel-params', - boot_mode='uefi') - task.node.refresh() - self.assertEqual("boot-%s.iso" % self.node.uuid, - task.node.driver_internal_info['boot_iso']) - - def test__get_floppy_image_name(self, check_share_fs_mounted_mock): - actual = irmc_boot._get_floppy_image_name(self.node) - expected = "image-%s.img" % self.node.uuid - self.assertEqual(expected, actual) - - @mock.patch.object(shutil, 'copyfile', spec_set=True, autospec=True) - @mock.patch.object(images, 'create_vfat_image', spec_set=True, - autospec=True) - @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True, - autospec=True) - def test__prepare_floppy_image(self, - tempfile_mock, - create_vfat_image_mock, - copyfile_mock, - check_share_fs_mounted_mock): - mock_image_file_handle = mock.MagicMock(spec=io.BytesIO) - mock_image_file_obj = mock.MagicMock() - mock_image_file_obj.name = 'image-tmp-file' - mock_image_file_handle.__enter__.return_value = mock_image_file_obj - tempfile_mock.side_effect = [mock_image_file_handle] - - deploy_args = {'arg1': 'val1', 'arg2': 'val2'} - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._prepare_floppy_image(task, deploy_args) - - create_vfat_image_mock.assert_called_once_with( - 'image-tmp-file', parameters=deploy_args) - copyfile_mock.assert_called_once_with( - 'image-tmp-file', - '/remote_image_share_root/' + "image-%s.img" % self.node.uuid) - - @mock.patch.object(shutil, 'copyfile', spec_set=True, autospec=True) - @mock.patch.object(images, 'create_vfat_image', spec_set=True, - autospec=True) - @mock.patch.object(tempfile, 'NamedTemporaryFile', spec_set=True, - autospec=True) - def test__prepare_floppy_image_exception(self, - tempfile_mock, - create_vfat_image_mock, - copyfile_mock, - check_share_fs_mounted_mock): - mock_image_file_handle = mock.MagicMock(spec=io.BytesIO) - mock_image_file_obj = mock.MagicMock() - mock_image_file_obj.name = 'image-tmp-file' - mock_image_file_handle.__enter__.return_value = mock_image_file_obj - tempfile_mock.side_effect = [mock_image_file_handle] - - deploy_args = {'arg1': 'val1', 'arg2': 'val2'} - copyfile_mock.side_effect = IOError("fake error") - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_boot._prepare_floppy_image, - task, - deploy_args) - - create_vfat_image_mock.assert_called_once_with( - 'image-tmp-file', parameters=deploy_args) - copyfile_mock.assert_called_once_with( - 'image-tmp-file', - '/remote_image_share_root/' + "image-%s.img" % self.node.uuid) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - def test_attach_boot_iso_if_needed( - self, - setup_vmedia_mock, - set_boot_device_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.provision_state = states.ACTIVE - task.node.driver_internal_info['boot_iso'] = 'boot-iso' - irmc_boot.attach_boot_iso_if_needed(task) - setup_vmedia_mock.assert_called_once_with(task, 'boot-iso') - set_boot_device_mock.assert_called_once_with( - task, boot_devices.CDROM) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - def test_attach_boot_iso_if_needed_deprecated( - self, - setup_vmedia_mock, - set_boot_device_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.provision_state = states.ACTIVE - task.node.driver_internal_info['irmc_boot_iso'] = 'boot-iso' - irmc_boot.attach_boot_iso_if_needed(task) - setup_vmedia_mock.assert_called_once_with(task, 'boot-iso') - set_boot_device_mock.assert_called_once_with( - task, boot_devices.CDROM) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - def test_attach_boot_iso_if_needed_on_rebuild( - self, - setup_vmedia_mock, - set_boot_device_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.provision_state = states.DEPLOYING - task.node.driver_internal_info['boot_iso'] = 'boot-iso' - irmc_boot.attach_boot_iso_if_needed(task) - self.assertFalse(setup_vmedia_mock.called) - self.assertFalse(set_boot_device_mock.called) - - @mock.patch.object(irmc_boot, '_attach_virtual_cd', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_attach_virtual_fd', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_prepare_floppy_image', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_detach_virtual_fd', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_detach_virtual_cd', spec_set=True, - autospec=True) - def test__setup_vmedia_for_boot_with_parameters( - self, - _detach_virtual_cd_mock, - _detach_virtual_fd_mock, - _prepare_floppy_image_mock, - _attach_virtual_fd_mock, - _attach_virtual_cd_mock, - check_share_fs_mounted_mock): - parameters = {'a': 'b'} - iso_filename = 'deploy_iso_or_boot_iso' - _prepare_floppy_image_mock.return_value = 'floppy_file_name' - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._setup_vmedia_for_boot(task, iso_filename, parameters) - - _detach_virtual_cd_mock.assert_called_once_with(task.node) - _detach_virtual_fd_mock.assert_called_once_with(task.node) - _prepare_floppy_image_mock.assert_called_once_with(task, - parameters) - _attach_virtual_fd_mock.assert_called_once_with(task.node, - 'floppy_file_name') - _attach_virtual_cd_mock.assert_called_once_with(task.node, - iso_filename) - - @mock.patch.object(irmc_boot, '_attach_virtual_cd', autospec=True) - @mock.patch.object(irmc_boot, '_detach_virtual_fd', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_detach_virtual_cd', spec_set=True, - autospec=True) - def test__setup_vmedia_for_boot_without_parameters( - self, - _detach_virtual_cd_mock, - _detach_virtual_fd_mock, - _attach_virtual_cd_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._setup_vmedia_for_boot(task, 'bootable_iso_filename') - - _detach_virtual_cd_mock.assert_called_once_with(task.node) - _detach_virtual_fd_mock.assert_called_once_with(task.node) - _attach_virtual_cd_mock.assert_called_once_with( - task.node, - 'bootable_iso_filename') - - @mock.patch.object(irmc_boot, '_get_iso_name', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_get_floppy_image_name', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_remove_share_file', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_detach_virtual_fd', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_detach_virtual_cd', spec_set=True, - autospec=True) - def test__cleanup_vmedia_boot_ok(self, - _detach_virtual_cd_mock, - _detach_virtual_fd_mock, - _remove_share_file_mock, - _get_floppy_image_name_mock, - _get_iso_name_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._cleanup_vmedia_boot(task) - - _detach_virtual_cd_mock.assert_called_once_with(task.node) - _detach_virtual_fd_mock.assert_called_once_with(task.node) - _get_floppy_image_name_mock.assert_called_once_with(task.node) - _get_iso_name_mock.assert_has_calls( - [mock.call(task.node, label='deploy'), - mock.call(task.node, label='rescue')]) - self.assertEqual(3, _remove_share_file_mock.call_count) - _remove_share_file_mock.assert_has_calls( - [mock.call(_get_floppy_image_name_mock(task.node)), - mock.call(_get_iso_name_mock(task.node, label='deploy')), - mock.call(_get_iso_name_mock(task.node, label='rescue'))]) - - @mock.patch.object(utils, 'unlink_without_raise', spec_set=True, - autospec=True) - def test__remove_share_file(self, unlink_without_raise_mock, - check_share_fs_mounted_mock): - CONF.set_override('remote_image_share_root', '/share', 'irmc') - - irmc_boot._remove_share_file("boot.iso") - - unlink_without_raise_mock.assert_called_once_with('/share/boot.iso') - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__attach_virtual_cd_ok(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - irmc_boot.scci.get_virtual_cd_set_params_cmd = ( - mock.MagicMock(sepc_set=[])) - cd_set_params = (irmc_boot.scci - .get_virtual_cd_set_params_cmd.return_value) - - CONF.set_override('remote_image_server', '10.20.30.40', 'irmc') - CONF.set_override('remote_image_share_type', 'NFS', 'irmc') - CONF.set_override('remote_image_share_name', 'share', 'irmc') - CONF.set_override('remote_image_user_name', 'admin', 'irmc') - CONF.set_override('remote_image_user_password', 'admin0', 'irmc') - CONF.set_override('remote_image_user_domain', 'local', 'irmc') - - irmc_boot.scci.get_share_type.return_value = 0 - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._attach_virtual_cd(task.node, 'iso_filename') - - get_irmc_client_mock.assert_called_once_with(task.node) - (irmc_boot.scci.get_virtual_cd_set_params_cmd - .assert_called_once_with)('10.20.30.40', - 'local', - 0, - 'share', - 'iso_filename', - 'admin', - 'admin0') - irmc_client.assert_has_calls( - [mock.call(cd_set_params, do_async=False), - mock.call(irmc_boot.scci.MOUNT_CD, do_async=False)]) - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__attach_virtual_cd_fail(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - irmc_client.side_effect = Exception("fake error") - irmc_boot.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - e = self.assertRaises(exception.IRMCOperationError, - irmc_boot._attach_virtual_cd, - task.node, - 'iso_filename') - get_irmc_client_mock.assert_called_once_with(task.node) - self.assertEqual("iRMC Inserting virtual cdrom failed. " - "Reason: fake error", str(e)) - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__detach_virtual_cd_ok(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._detach_virtual_cd(task.node) - - irmc_client.assert_called_once_with(irmc_boot.scci.UNMOUNT_CD) - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__detach_virtual_cd_fail(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - irmc_client.side_effect = Exception("fake error") - irmc_boot.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - e = self.assertRaises(exception.IRMCOperationError, - irmc_boot._detach_virtual_cd, - task.node) - self.assertEqual("iRMC Ejecting virtual cdrom failed. " - "Reason: fake error", str(e)) - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__attach_virtual_fd_ok(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - irmc_boot.scci.get_virtual_fd_set_params_cmd = ( - mock.MagicMock(sepc_set=[])) - fd_set_params = (irmc_boot.scci - .get_virtual_fd_set_params_cmd.return_value) - - CONF.set_override('remote_image_server', '10.20.30.40', 'irmc') - CONF.set_override('remote_image_share_type', 'NFS', 'irmc') - CONF.set_override('remote_image_share_name', 'share', 'irmc') - CONF.set_override('remote_image_user_name', 'admin', 'irmc') - CONF.set_override('remote_image_user_password', 'admin0', 'irmc') - CONF.set_override('remote_image_user_domain', 'local', 'irmc') - - irmc_boot.scci.get_share_type.return_value = 0 - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._attach_virtual_fd(task.node, - 'floppy_image_filename') - - get_irmc_client_mock.assert_called_once_with(task.node) - (irmc_boot.scci.get_virtual_fd_set_params_cmd - .assert_called_once_with)('10.20.30.40', - 'local', - 0, - 'share', - 'floppy_image_filename', - 'admin', - 'admin0') - irmc_client.assert_has_calls( - [mock.call(fd_set_params, do_async=False), - mock.call(irmc_boot.scci.MOUNT_FD, do_async=False)]) - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__attach_virtual_fd_fail(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - irmc_client.side_effect = Exception("fake error") - irmc_boot.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - e = self.assertRaises(exception.IRMCOperationError, - irmc_boot._attach_virtual_fd, - task.node, - 'iso_filename') - get_irmc_client_mock.assert_called_once_with(task.node) - self.assertEqual("iRMC Inserting virtual floppy failed. " - "Reason: fake error", str(e)) - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__detach_virtual_fd_ok(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_boot._detach_virtual_fd(task.node) - - irmc_client.assert_called_once_with(irmc_boot.scci.UNMOUNT_FD) - - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__detach_virtual_fd_fail(self, get_irmc_client_mock, - check_share_fs_mounted_mock): - irmc_client = get_irmc_client_mock.return_value - irmc_client.side_effect = Exception("fake error") - irmc_boot.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - e = self.assertRaises(exception.IRMCOperationError, - irmc_boot._detach_virtual_fd, - task.node) - self.assertEqual("iRMC Ejecting virtual floppy failed. " - "Reason: fake error", str(e)) - - @mock.patch.object(irmc_boot, '_parse_config_option', spec_set=True, - autospec=True) - def test_check_share_fs_mounted_ok(self, parse_conf_mock, - check_share_fs_mounted_mock): - # Note(naohirot): mock.patch.stop() and mock.patch.start() don't work. - # therefore monkey patching is used to - # irmc_boot.check_share_fs_mounted. - # irmc_boot.check_share_fs_mounted is mocked in - # third_party_driver_mocks.py. - # irmc_boot.check_share_fs_mounted_orig is the real function. - CONF.set_override('remote_image_share_root', '/', 'irmc') - CONF.set_override('remote_image_share_type', 'nfs', 'irmc') - result = irmc_boot.check_share_fs_mounted_orig() - - parse_conf_mock.assert_called_once_with() - self.assertIsNone(result) - - @mock.patch.object(irmc_boot, '_parse_config_option', spec_set=True, - autospec=True) - def test_check_share_fs_mounted_exception(self, parse_conf_mock, - check_share_fs_mounted_mock): - # Note(naohirot): mock.patch.stop() and mock.patch.start() don't work. - # therefore monkey patching is used to - # irmc_boot.check_share_fs_mounted. - # irmc_boot.check_share_fs_mounted is mocked in - # third_party_driver_mocks.py. - # irmc_boot.check_share_fs_mounted_orig is the real function. - CONF.set_override('remote_image_share_root', '/etc', 'irmc') - CONF.set_override('remote_image_share_type', 'cifs', 'irmc') - - self.assertRaises(exception.IRMCSharedFileSystemNotMounted, - irmc_boot.check_share_fs_mounted_orig) - parse_conf_mock.assert_called_once_with() - - -@mock.patch.object(irmc_boot, 'check_share_fs_mounted', spec_set=True, - autospec=True) -class IRMCVirtualMediaBootTestCase(test_common.BaseIRMCTest): - boot_interface = 'irmc-virtual-media' - - def setUp(self): - super(IRMCVirtualMediaBootTestCase, self).setUp() - - @mock.patch.object(deploy_utils, 'validate_image_properties', - spec_set=True, autospec=True) - @mock.patch.object(service_utils, 'is_glance_image', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_parse_deploy_info', spec_set=True, - autospec=True) - def test_validate(self, deploy_info_mock, is_glance_image_mock, - validate_prop_mock, check_share_fs_mounted_mock): - d_info = {'image_source': '733d1c44-a2ea-414b-aca7-69decf20d810'} - deploy_info_mock.return_value = d_info - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.validate(task) - - self.assertEqual(check_share_fs_mounted_mock.call_count, 2) - deploy_info_mock.assert_called_once_with(task.node) - self.assertFalse(is_glance_image_mock.called) - validate_prop_mock.assert_called_once_with(task, d_info) - - @mock.patch.object(irmc_management, 'backup_bios_config', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia', - spec_set=True, autospec=True) - @mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id', - spec_set=True, autospec=True) - def _test_prepare_ramdisk(self, - get_single_nic_with_vif_port_id_mock, - _setup_vmedia_mock, - mock_backup_bios, - mode='deploy'): - instance_info = self.node.instance_info - instance_info['boot_iso'] = 'glance://abcdef' - instance_info['image_source'] = '6b2f0c0c-79e8-4db6-842e-43c9764204af' - self.node.instance_info = instance_info - self.node.save() - - ramdisk_params = {'a': 'b'} - get_single_nic_with_vif_port_id_mock.return_value = '12:34:56:78:90:ab' - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_ramdisk(task, ramdisk_params) - - expected_ramdisk_opts = {'a': 'b', 'BOOTIF': '12:34:56:78:90:ab', - 'ipa-agent-token': mock.ANY} - - get_single_nic_with_vif_port_id_mock.assert_called_once_with( - task) - _setup_vmedia_mock.assert_called_once_with( - task, mode, expected_ramdisk_opts) - self.assertEqual('glance://abcdef', - self.node.instance_info['boot_iso']) - provision_state = task.node.provision_state - self.assertEqual(1 if provision_state == states.DEPLOYING else 0, - mock_backup_bios.call_count) - - def test_prepare_ramdisk_glance_image_deploying( - self, check_share_fs_mounted_mock): - self.node.provision_state = states.DEPLOYING - self.node.save() - self._test_prepare_ramdisk() - - def test_prepare_ramdisk_glance_image_rescuing( - self, check_share_fs_mounted_mock): - self.node.provision_state = states.RESCUING - self.node.save() - self._test_prepare_ramdisk(mode='rescue') - - def test_prepare_ramdisk_glance_image_cleaning( - self, check_share_fs_mounted_mock): - self.node.provision_state = states.CLEANING - self.node.save() - self._test_prepare_ramdisk() - - @mock.patch.object(irmc_boot, '_setup_vmedia', spec_set=True, - autospec=True) - def test_prepare_ramdisk_not_deploying_not_cleaning( - self, mock_is_image, check_share_fs_mounted_mock): - """Ensure deploy ops are blocked when not deploying and not cleaning""" - - for state in states.STABLE_STATES: - mock_is_image.reset_mock() - self.node.provision_state = state - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertIsNone( - task.driver.boot.prepare_ramdisk(task, None)) - self.assertFalse(mock_is_image.called) - - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_clean_up_ramdisk(self, _cleanup_vmedia_boot_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.clean_up_ramdisk(task) - _cleanup_vmedia_boot_mock.assert_called_once_with(task) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_prepare_instance_whole_disk_image( - self, _cleanup_vmedia_boot_mock, set_boot_device_mock, - check_share_fs_mounted_mock): - self.node.driver_internal_info = {'is_whole_disk_image': True} - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_instance(task) - - _cleanup_vmedia_boot_mock.assert_called_once_with(task) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.DISK, - persistent=True) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_prepare_instance_partition_image( - self, _cleanup_vmedia_boot_mock, set_boot_device_mock, - check_share_fs_mounted_mock): - self.node.driver_internal_info = {'root_uuid_or_disk_id': "some_uuid"} - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_instance(task) - - _cleanup_vmedia_boot_mock.assert_called_once_with(task) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.DISK, - persistent=True) - - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_remove_share_file', spec_set=True, - autospec=True) - def test_clean_up_instance(self, _remove_share_file_mock, - _cleanup_vmedia_boot_mock, - check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.instance_info['boot_iso'] = 'glance://deploy_iso' - task.node.driver_internal_info['boot_iso'] = 'irmc_boot.iso' - - task.driver.boot.clean_up_instance(task) - - _remove_share_file_mock.assert_called_once_with( - irmc_boot._get_iso_name(task.node, label='boot')) - self.assertNotIn('boot_iso', task.node.driver_internal_info) - _cleanup_vmedia_boot_mock.assert_called_once_with(task) - - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_setup_vmedia_for_boot', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_prepare_boot_iso', spec_set=True, - autospec=True) - def test__configure_vmedia_boot(self, - _prepare_boot_iso_mock, - _setup_vmedia_for_boot_mock, - node_set_boot_device, - check_share_fs_mounted_mock): - root_uuid_or_disk_id = {'root uuid': 'root_uuid'} - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_internal_info['boot_iso'] = 'boot.iso' - task.driver.boot._configure_vmedia_boot( - task, root_uuid_or_disk_id) - - _prepare_boot_iso_mock.assert_called_once_with( - task, root_uuid_or_disk_id) - _setup_vmedia_for_boot_mock.assert_called_once_with( - task, 'boot.iso') - node_set_boot_device.assert_called_once_with( - task, boot_devices.CDROM, persistent=True) - - def test_remote_image_share_type_values( - self, check_share_fs_mounted_mock): - cfg.CONF.set_override('remote_image_share_type', 'cifs', 'irmc') - cfg.CONF.set_override('remote_image_share_type', 'nfs', 'irmc') - self.assertRaises(ValueError, cfg.CONF.set_override, - 'remote_image_share_type', 'fake', 'irmc') - - # NOTE(TheJulia): https://bugs.launchpad.net/ironic/+bug/2025424 - # Disabling until we can figure out what exactly is going on. - @unittest.skip("bug #2025424") - @mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot.IRMCVirtualMediaBoot, - '_configure_vmedia_boot', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_prepare_instance_with_secure_boot(self, - mock_cleanup_vmedia_boot, - mock_configure_vmedia_boot, - mock_set_secure_boot_mode, - check_share_fs_mounted_mock): - self.node.driver_internal_info = {'root_uuid_or_disk_id': "12312642"} - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - self.node.deploy_interface = 'ramdisk' - self.node.instance_info = { - 'capabilities': { - "secure_boot": "true" - } - } - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_instance(task) - mock_cleanup_vmedia_boot.assert_called_once_with(task) - mock_set_secure_boot_mode.assert_called_once_with(task.node, - enable=True) - mock_configure_vmedia_boot.assert_called_once_with(mock.ANY, task, - "12312642") - - @mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot.IRMCVirtualMediaBoot, - '_configure_vmedia_boot', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_prepare_instance_with_secure_boot_false( - self, mock_cleanup_vmedia_boot, mock_configure_vmedia_boot, - mock_set_secure_boot_mode, check_share_fs_mounted_mock): - self.node.driver_internal_info = {'root_uuid_or_disk_id': "12312642"} - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - self.node.deploy_interface = 'ramdisk' - self.node.instance_info = { - 'capabilities': { - "secure_boot": "false" - } - } - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_instance(task) - mock_cleanup_vmedia_boot.assert_called_once_with(task) - self.assertFalse(mock_set_secure_boot_mode.called) - mock_configure_vmedia_boot.assert_called_once_with(mock.ANY, task, - "12312642") - - @mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot.IRMCVirtualMediaBoot, - '_configure_vmedia_boot', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_prepare_instance_without_secure_boot( - self, mock_cleanup_vmedia_boot, mock_configure_vmedia_boot, - mock_set_secure_boot_mode, check_share_fs_mounted_mock): - self.node.driver_internal_info = {'root_uuid_or_disk_id': "12312642"} - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - self.node.deploy_interface = 'ramdisk' - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_instance(task) - mock_cleanup_vmedia_boot.assert_called_once_with(task) - self.assertFalse(mock_set_secure_boot_mode.called) - mock_configure_vmedia_boot.assert_called_once_with(mock.ANY, task, - "12312642") - - # NOTE(TheJulia): https://bugs.launchpad.net/ironic/+bug/2025424 - # Disabling until we can figure out what exactly is going on. - @unittest.skip("bug #2025424") - @mock.patch.object(irmc_boot, '_remove_share_file', autospec=True) - @mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_clean_up_instance_with_secure_boot(self, mock_cleanup_vmedia_boot, - mock_set_secure_boot_mode, - mock_remove_share_file, - check_share_fs_mounted_mock): - self.node.provision_state = states.DELETING - self.node.target_provision_state = states.AVAILABLE - self.node.instance_info = { - 'capabilities': { - "secure_boot": "true" - } - } - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.clean_up_instance(task) - mock_set_secure_boot_mode.assert_called_once_with(task.node, - enable=False) - mock_cleanup_vmedia_boot.assert_called_once_with(task) - mock_remove_share_file.assert_called_once_with( - 'boot-%s.iso' % task.node.uuid) - - @mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_clean_up_instance_with_secure_boot_false( - self, mock_cleanup_vmedia_boot, mock_set_secure_boot_mode, - check_share_fs_mounted_mock): - self.node.provision_state = states.DELETING - self.node.target_provision_state = states.AVAILABLE - self.node.instance_info = { - 'capabilities': { - "secure_boot": "false" - } - } - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.clean_up_instance(task) - self.assertFalse(mock_set_secure_boot_mode.called) - mock_cleanup_vmedia_boot.assert_called_once_with(task) - - @mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', spec_set=True, - autospec=True) - def test_clean_up_instance_without_secure_boot( - self, mock_cleanup_vmedia_boot, mock_set_secure_boot_mode, - check_share_fs_mounted_mock): - self.node.provision_state = states.DELETING - self.node.target_provision_state = states.AVAILABLE - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.clean_up_instance(task) - self.assertFalse(mock_set_secure_boot_mode.called) - mock_cleanup_vmedia_boot.assert_called_once_with(task) - - @mock.patch.object(os.path, 'isfile', return_value=True, - autospec=True) - def test_validate_rescue(self, mock_isfile, check_share_fs_mounted_mock): - driver_info = self.node.driver_info - driver_info['rescue_iso'] = 'rescue.iso' - self.node.driver_info = driver_info - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.boot.validate_rescue(task) - - def test_validate_rescue_no_rescue_ramdisk( - self, check_share_fs_mounted_mock): - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaisesRegex(exception.MissingParameterValue, - 'Missing.*rescue_iso', - task.driver.boot.validate_rescue, task) - - @mock.patch.object(os.path, 'isfile', return_value=False, - autospec=True) - def test_validate_rescue_ramdisk_not_exist( - self, mock_isfile, check_share_fs_mounted_mock): - driver_info = self.node.driver_info - driver_info['rescue_iso'] = 'rescue.iso' - self.node.driver_info = driver_info - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaisesRegex(exception.InvalidParameterValue, - 'Rescue ISO file, .*' - 'not found for node: .*', - task.driver.boot.validate_rescue, task) - - -class IRMCPXEBootTestCase(test_common.BaseIRMCTest): - - @mock.patch.object(irmc_management, 'backup_bios_config', spec_set=True, - autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True, - autospec=True) - def test_prepare_ramdisk_with_backup_bios(self, mock_parent_prepare, - mock_backup_bios): - self.node.provision_state = states.DEPLOYING - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_ramdisk(task, {}) - mock_backup_bios.assert_called_once_with(task) - mock_parent_prepare.assert_called_once_with( - task.driver.boot, task, {}) - - @mock.patch.object(irmc_management, 'backup_bios_config', spec_set=True, - autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', spec_set=True, - autospec=True) - def test_prepare_ramdisk_without_backup_bios(self, mock_parent_prepare, - mock_backup_bios): - self.node.provision_state = states.CLEANING - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_ramdisk(task, {}) - self.assertFalse(mock_backup_bios.called) - mock_parent_prepare.assert_called_once_with( - task.driver.boot, task, {}) - - @mock.patch.object(pxe.PXEBoot, 'prepare_instance', spec_set=True, - autospec=True) - def test_prepare_instance(self, mock_prepare_instance): - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.prepare_instance(task) - mock_prepare_instance.assert_called_once_with( - task.driver.boot, task) - - @mock.patch.object(pxe.PXEBoot, 'clean_up_instance', spec_set=True, - autospec=True) - def test_clean_up_instance(self, mock_clean_up_instance): - self.node.provision_state = states.CLEANING - self.node.target_provision_state = states.AVAILABLE - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.boot.clean_up_instance(task) - mock_clean_up_instance.assert_called_once_with( - task.driver.boot, task) - - -@mock.patch.object(irmc_boot, 'check_share_fs_mounted', spec_set=True, - autospec=True) -@mock.patch.object(irmc_boot, 'viom', - spec_set=mock_specs.SCCICLIENT_VIOM_SPEC) -class IRMCVirtualMediaBootWithVolumeTestCase(test_common.BaseIRMCTest): - boot_interface = 'irmc-virtual-media' - - def setUp(self): - super(IRMCVirtualMediaBootWithVolumeTestCase, self).setUp() - driver_info = INFO_DICT - d_in_info = dict(boot_from_volume='volume-uuid') - self.config(enabled_storage_interfaces=['cinder']) - self.node = obj_utils.create_test_node(self.context, - driver='irmc', - driver_info=driver_info, - storage_interface='cinder', - driver_internal_info=d_in_info) - - def _create_mock_conf(self, mock_viom): - mock_conf = mock.Mock(spec_set=mock_specs.SCCICLIENT_VIOM_CONF_SPEC) - mock_viom.VIOMConfiguration.return_value = mock_conf - return mock_conf - - def _add_pci_physical_id(self, uuid, physical_id): - driver_info = self.node.driver_info - ids = driver_info.get('irmc_pci_physical_ids', {}) - ids[uuid] = physical_id - driver_info['irmc_pci_physical_ids'] = ids - self.node.driver_info = driver_info - self.node.save() - - def _create_port(self, physical_id='LAN0-1', **kwargs): - uuid = uuidutils.generate_uuid() - obj_utils.create_test_port(self.context, - uuid=uuid, - node_id=self.node.id, - **kwargs) - if physical_id: - self._add_pci_physical_id(uuid, physical_id) - - def _create_iscsi_iqn_connector(self, physical_id='CNA1-1'): - uuid = uuidutils.generate_uuid() - obj_utils.create_test_volume_connector( - self.context, - uuid=uuid, - type='iqn', - node_id=self.node.id, - connector_id='iqn.initiator') - if physical_id: - self._add_pci_physical_id(uuid, physical_id) - - def _create_iscsi_ip_connector(self, physical_id=None, network_size='24'): - uuid = uuidutils.generate_uuid() - obj_utils.create_test_volume_connector( - self.context, - uuid=uuid, - type='ip', - node_id=self.node.id, - connector_id='192.168.11.11') - if physical_id: - self._add_pci_physical_id(uuid, physical_id) - if network_size: - driver_info = self.node.driver_info - driver_info['irmc_storage_network_size'] = network_size - self.node.driver_info = driver_info - self.node.save() - - def _create_iscsi_target(self, target_info=None, boot_index=0, **kwargs): - target_properties = { - 'target_portal': '192.168.22.22:3260', - 'target_iqn': 'iqn.target', - 'target_lun': 1, - } - if target_info: - target_properties.update(target_info) - obj_utils.create_test_volume_target( - self.context, - volume_type='iscsi', - node_id=self.node.id, - boot_index=boot_index, - properties=target_properties, - **kwargs) - - def _create_iscsi_resources(self): - self._create_iscsi_iqn_connector() - self._create_iscsi_ip_connector() - self._create_iscsi_target() - - def _create_fc_connector(self): - uuid = uuidutils.generate_uuid() - obj_utils.create_test_volume_connector( - self.context, - uuid=uuid, - type='wwnn', - node_id=self.node.id, - connector_id='11:22:33:44:55') - self._add_pci_physical_id(uuid, 'FC2-1') - obj_utils.create_test_volume_connector( - self.context, - uuid=uuidutils.generate_uuid(), - type='wwpn', - node_id=self.node.id, - connector_id='11:22:33:44:56') - - def _create_fc_target(self): - target_properties = { - 'target_wwn': 'aa:bb:cc:dd:ee', - 'target_lun': 2, - } - obj_utils.create_test_volume_target( - self.context, - volume_type='fibre_channel', - node_id=self.node.id, - boot_index=0, - properties=target_properties) - - def _create_fc_resources(self): - self._create_fc_connector() - self._create_fc_target() - - def _call_validate(self): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.boot.validate(task) - - def test_validate_iscsi(self, mock_viom, check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_resources() - self._call_validate() - self.assertEqual([mock.call('LAN0-1'), mock.call('CNA1-1')], - mock_viom.validate_physical_port_id.call_args_list) - - def test_validate_no_physical_id_in_lan_port(self, mock_viom, - check_share_fs_mounted_mock): - self._create_port(physical_id=None) - self._create_iscsi_resources() - self.assertRaises(exception.MissingParameterValue, - self._call_validate) - - @mock.patch.object(irmc_boot, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - def test_validate_invalid_physical_id_in_lan_port( - self, mock_scci, mock_viom, check_share_fs_mounted_mock): - self._create_port(physical_id='wrong-id') - self._create_iscsi_resources() - - mock_viom.validate_physical_port_id.side_effect = ( - Exception('fake error')) - mock_scci.SCCIInvalidInputError = Exception - self.assertRaises(exception.InvalidParameterValue, - self._call_validate) - - def test_validate_iscsi_connector_no_ip(self, mock_viom, - check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_iqn_connector() - self._create_iscsi_target() - - self.assertRaises(exception.MissingParameterValue, - self._call_validate) - - def test_validate_iscsi_connector_no_iqn(self, mock_viom, - check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_ip_connector(physical_id='CNA1-1') - self._create_iscsi_target() - - self.assertRaises(exception.MissingParameterValue, - self._call_validate) - - def test_validate_iscsi_connector_no_netmask(self, mock_viom, - check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_iqn_connector() - self._create_iscsi_ip_connector(network_size=None) - self._create_iscsi_target() - - self.assertRaises(exception.MissingParameterValue, - self._call_validate) - - def test_validate_iscsi_connector_invalid_netmask( - self, mock_viom, check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_iqn_connector() - self._create_iscsi_ip_connector(network_size='worng-netmask') - self._create_iscsi_target() - - self.assertRaises(exception.InvalidParameterValue, - self._call_validate) - - def test_validate_iscsi_connector_too_small_netmask( - self, mock_viom, check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_iqn_connector() - self._create_iscsi_ip_connector(network_size='0') - self._create_iscsi_target() - - self.assertRaises(exception.InvalidParameterValue, - self._call_validate) - - def test_validate_iscsi_connector_too_large_netmask( - self, mock_viom, check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_iqn_connector() - self._create_iscsi_ip_connector(network_size='32') - self._create_iscsi_target() - - self.assertRaises(exception.InvalidParameterValue, - self._call_validate) - - def test_validate_iscsi_connector_no_physical_id( - self, mock_viom, check_share_fs_mounted_mock): - self._create_port() - self._create_iscsi_iqn_connector(physical_id=None) - self._create_iscsi_ip_connector() - self._create_iscsi_target() - - self.assertRaises(exception.MissingParameterValue, - self._call_validate) - - @mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id', - autospec=True) - def test_prepare_ramdisk_skip(self, mock_nic, mock_viom, - check_share_fs_mounted_mock): - self._create_iscsi_resources() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.provision_state = states.DEPLOYING - task.driver.boot.prepare_ramdisk(task, {}) - mock_nic.assert_not_called() - - @mock.patch.object(irmc_boot, '_cleanup_vmedia_boot', autospec=True) - def test_prepare_instance(self, mock_clean, mock_viom, - check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - self._create_port() - self._create_iscsi_resources() - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.boot.prepare_instance(task) - mock_clean.assert_not_called() - - mock_conf.set_iscsi_volume.assert_called_once_with( - 'CNA1-1', - 'iqn.initiator', - initiator_ip='192.168.11.11', - initiator_netmask=24, - target_iqn='iqn.target', - target_ip='192.168.22.22', - target_port=3260, - target_lun=1, - boot_prio=1, - chap_user=None, - chap_secret=None) - mock_conf.set_lan_port.assert_called_once_with('LAN0-1') - mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1') - self._assert_viom_apply(mock_viom, mock_conf) - - def _call__configure_boot_from_volume(self): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.boot._configure_boot_from_volume(task) - - def _assert_viom_apply(self, mock_viom, mock_conf): - mock_conf.apply.assert_called_once_with() - mock_conf.dump_json.assert_called_once_with() - mock_viom.VIOMConfiguration.assert_called_once_with( - PARSED_IFNO, identification=self.node.uuid) - - def test__configure_boot_from_volume_iscsi(self, mock_viom, - check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - self._create_port() - self._create_iscsi_resources() - - self._call__configure_boot_from_volume() - - mock_conf.set_iscsi_volume.assert_called_once_with( - 'CNA1-1', - 'iqn.initiator', - initiator_ip='192.168.11.11', - initiator_netmask=24, - target_iqn='iqn.target', - target_ip='192.168.22.22', - target_port=3260, - target_lun=1, - boot_prio=1, - chap_user=None, - chap_secret=None) - mock_conf.set_lan_port.assert_called_once_with('LAN0-1') - mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1') - self._assert_viom_apply(mock_viom, mock_conf) - - def test__configure_boot_from_volume_multi_lan_ports( - self, mock_viom, check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - self._create_port() - self._create_port(physical_id='LAN0-2', - address='52:54:00:cf:2d:32') - self._create_iscsi_resources() - - self._call__configure_boot_from_volume() - - mock_conf.set_iscsi_volume.assert_called_once_with( - 'CNA1-1', - 'iqn.initiator', - initiator_ip='192.168.11.11', - initiator_netmask=24, - target_iqn='iqn.target', - target_ip='192.168.22.22', - target_port=3260, - target_lun=1, - boot_prio=1, - chap_user=None, - chap_secret=None) - self.assertEqual([mock.call('LAN0-1'), mock.call('LAN0-2')], - mock_conf.set_lan_port.call_args_list) - mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1') - self._assert_viom_apply(mock_viom, mock_conf) - - def test__configure_boot_from_volume_iscsi_no_portal_port( - self, mock_viom, check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - self._create_port() - self._create_iscsi_iqn_connector() - self._create_iscsi_ip_connector() - self._create_iscsi_target( - target_info=dict(target_portal='192.168.22.23')) - - self._call__configure_boot_from_volume() - - mock_conf.set_iscsi_volume.assert_called_once_with( - 'CNA1-1', - 'iqn.initiator', - initiator_ip='192.168.11.11', - initiator_netmask=24, - target_iqn='iqn.target', - target_ip='192.168.22.23', - target_port=None, - target_lun=1, - boot_prio=1, - chap_user=None, - chap_secret=None) - mock_conf.set_lan_port.assert_called_once_with('LAN0-1') - mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1') - self._assert_viom_apply(mock_viom, mock_conf) - - def test__configure_boot_from_volume_iscsi_chap( - self, mock_viom, check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - self._create_port() - self._create_iscsi_iqn_connector() - self._create_iscsi_ip_connector() - self._create_iscsi_target( - target_info=dict(auth_method='CHAP', - auth_username='chapuser', - auth_password='chappass')) - - self._call__configure_boot_from_volume() - - mock_conf.set_iscsi_volume.assert_called_once_with( - 'CNA1-1', - 'iqn.initiator', - initiator_ip='192.168.11.11', - initiator_netmask=24, - target_iqn='iqn.target', - target_ip='192.168.22.22', - target_port=3260, - target_lun=1, - boot_prio=1, - chap_user='chapuser', - chap_secret='chappass') - mock_conf.set_lan_port.assert_called_once_with('LAN0-1') - mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1') - self._assert_viom_apply(mock_viom, mock_conf) - - def test__configure_boot_from_volume_fc(self, mock_viom, - check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - self._create_port() - self._create_fc_connector() - self._create_fc_target() - - self._call__configure_boot_from_volume() - - mock_conf.set_fc_volume.assert_called_once_with( - 'FC2-1', - 'aa:bb:cc:dd:ee', - 2, - boot_prio=1) - mock_conf.set_lan_port.assert_called_once_with('LAN0-1') - mock_viom.validate_physical_port_id.assert_called_once_with('FC2-1') - self._assert_viom_apply(mock_viom, mock_conf) - - @mock.patch.object(irmc_boot, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - def test__configure_boot_from_volume_apply_error( - self, mock_scci, mock_viom, check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - self._create_port() - self._create_fc_connector() - self._create_fc_target() - mock_conf.apply.side_effect = Exception('fake scci error') - mock_scci.SCCIError = Exception - - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.IRMCOperationError, - task.driver.boot._configure_boot_from_volume, - task) - - mock_conf.set_fc_volume.assert_called_once_with( - 'FC2-1', - 'aa:bb:cc:dd:ee', - 2, - boot_prio=1) - mock_conf.set_lan_port.assert_called_once_with('LAN0-1') - mock_viom.validate_physical_port_id.assert_called_once_with('FC2-1') - self._assert_viom_apply(mock_viom, mock_conf) - - def test_clean_up_instance(self, mock_viom, check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.boot.clean_up_instance(task) - - mock_viom.VIOMConfiguration.assert_called_once_with(PARSED_IFNO, - self.node.uuid) - mock_conf.terminate.assert_called_once_with(reboot=False) - - def test_clean_up_instance_error(self, mock_viom, - check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - mock_conf.terminate.side_effect = Exception('fake error') - irmc_boot.scci.SCCIError = Exception - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.IRMCOperationError, - task.driver.boot.clean_up_instance, - task) - - mock_viom.VIOMConfiguration.assert_called_once_with(PARSED_IFNO, - self.node.uuid) - mock_conf.terminate.assert_called_once_with(reboot=False) - - def test__cleanup_boot_from_volume(self, mock_viom, - check_share_fs_mounted_mock): - mock_conf = self._create_mock_conf(mock_viom) - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.boot._cleanup_boot_from_volume(task) - - mock_viom.VIOMConfiguration.assert_called_once_with(PARSED_IFNO, - self.node.uuid) - mock_conf.terminate.assert_called_once_with(reboot=False) - - -class IRMCPXEBootBasicTestCase(test_pxe.PXEBootTestCase): - boot_interface = 'irmc-pxe' - # NOTE(etingof): add driver-specific configuration - driver_info = dict(test_pxe.PXEBootTestCase.driver_info) - driver_info.update(PARSED_IFNO) - - def test_get_properties(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - properties = task.driver.get_properties() - for p in pxe_base.COMMON_PROPERTIES: - self.assertIn(p, properties) - - -class IsImageHrefOrdinaryFileNameTestCase(base.TestCase): - - def test_is_image_href_ordinary_file_name_true(self): - image = u"\u0111eploy.iso" - result = irmc_boot._is_image_href_ordinary_file_name(image) - self.assertTrue(result) - - def test_is_image_href_ordinary_file_name_false(self): - for image in ('733d1c44-a2ea-414b-aca7-69decf20d810', - u'glance://\u0111eploy_iso', - u'http://\u0111eploy_iso', - u'https://\u0111eploy_iso', - u'file://\u0111eploy_iso',): - result = irmc_boot._is_image_href_ordinary_file_name(image) - self.assertFalse(result) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_common.py b/ironic/tests/unit/drivers/modules/irmc/test_common.py deleted file mode 100644 index 322b9514b0..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_common.py +++ /dev/null @@ -1,585 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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 iRMC modules. -""" - -import os -from unittest import mock - -from oslo_config import cfg -from oslo_utils import uuidutils - -from ironic.common import exception -from ironic.common import utils -from ironic.conductor import task_manager -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules import snmp -from ironic.tests.unit.db import base as db_base -from ironic.tests.unit.db import utils as db_utils -from ironic.tests.unit.drivers import third_party_driver_mock_specs \ - as mock_specs -from ironic.tests.unit.objects import utils as obj_utils - - -class BaseIRMCTest(db_base.DbTestCase): - - boot_interface = 'irmc-pxe' - inspect_interface = 'irmc' - power_interface = 'irmc' - - def setUp(self): - super(BaseIRMCTest, self).setUp() - self.config(enabled_hardware_types=['irmc', 'fake-hardware'], - enabled_power_interfaces=['irmc', 'fake'], - enabled_management_interfaces=['irmc', 'fake'], - enabled_bios_interfaces=['irmc', 'no-bios', 'fake'], - enabled_boot_interfaces=[self.boot_interface, 'fake'], - enabled_inspect_interfaces=['irmc', 'no-inspect', 'fake']) - self.info = db_utils.get_test_irmc_info() - self.node = obj_utils.create_test_node( - self.context, - driver='irmc', - boot_interface=self.boot_interface, - inspect_interface=self.inspect_interface, - power_interface=self.power_interface, - driver_info=self.info, - uuid=uuidutils.generate_uuid()) - - -class IRMCValidateParametersTestCase(BaseIRMCTest): - - @mock.patch.object(utils, 'is_fips_enabled', - return_value=False, autospec=True) - def test_parse_driver_info(self, mock_check_fips): - info = irmc_common.parse_driver_info(self.node) - - self.assertEqual('1.2.3.4', info['irmc_address']) - self.assertEqual('admin0', info['irmc_username']) - self.assertEqual('fake0', info['irmc_password']) - self.assertEqual(60, info['irmc_client_timeout']) - self.assertEqual(80, info['irmc_port']) - self.assertEqual('digest', info['irmc_auth_method']) - self.assertEqual('ipmitool', info['irmc_sensor_method']) - self.assertEqual(snmp.SNMP_V2C, info['irmc_snmp_version']) - self.assertEqual(161, info['irmc_snmp_port']) - self.assertEqual('public', info['irmc_snmp_community']) - self.assertTrue(info['irmc_verify_ca']) - - @mock.patch.object(utils, 'is_fips_enabled', - return_value=False, autospec=True) - def test_parse_snmp_driver_info_with_snmp(self, mock_check_fips): - test_list = [{'interfaces': [{'interface': 'inspect_interface', - 'impl': 'irmc'}, - {'interface': 'power_interface', - 'impl': 'irmc'}], - 'snmp': True}, - {'interfaces': [{'interface': 'inspect_interface', - 'impl': 'inspector'}, - {'interface': 'power_interface', - 'impl': 'irmc'}], - 'snmp': True}, - {'interfaces': [{'interface': 'inspect_interface', - 'impl': 'irmc'}, - {'interface': 'power_interface', - 'impl': 'ipmitool'}], - 'snmp': True}, - {'interfaces': [{'interface': 'inspect_interface', - 'impl': 'inspector'}, - {'interface': 'power_interface', - 'impl': 'ipmitool'}], - 'snmp': False} - ] - - for t_conf in test_list: - with self.subTest(t_conf=t_conf): - for int_conf in t_conf['interfaces']: - setattr(self.node, int_conf['interface'], int_conf['impl']) - irmc_common.parse_driver_info(self.node) - - if t_conf['snmp']: - mock_check_fips.assert_called() - else: - mock_check_fips.assert_not_called() - - mock_check_fips.reset_mock() - - def test_parse_driver_info_snmpv3(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - info = irmc_common.parse_driver_info(self.node) - - self.assertEqual('1.2.3.4', info['irmc_address']) - self.assertEqual('admin0', info['irmc_username']) - self.assertEqual('fake0', info['irmc_password']) - self.assertEqual(60, info['irmc_client_timeout']) - self.assertEqual(80, info['irmc_port']) - self.assertEqual('digest', info['irmc_auth_method']) - self.assertEqual('ipmitool', info['irmc_sensor_method']) - self.assertEqual(snmp.SNMP_V3, info['irmc_snmp_version']) - self.assertEqual(161, info['irmc_snmp_port']) - self.assertEqual('public', info['irmc_snmp_community']) - self.assertEqual('admin0', info['irmc_snmp_user']) - self.assertEqual(snmp.snmp_auth_protocols['sha'], - info['irmc_snmp_auth_proto']) - self.assertEqual('valid_key', info['irmc_snmp_auth_password']) - self.assertEqual(snmp.snmp_priv_protocols['aes'], - info['irmc_snmp_priv_proto']) - self.assertEqual('valid_key', info['irmc_snmp_priv_password']) - - @mock.patch.object(utils, 'is_fips_enabled', - return_value=False, autospec=True) - def test_parse_driver_option_default(self, mock_check_fips): - self.node.driver_info = { - "irmc_address": "1.2.3.4", - "irmc_username": "admin0", - "irmc_password": "fake0", - } - info = irmc_common.parse_driver_info(self.node) - - self.assertEqual('basic', info['irmc_auth_method']) - self.assertEqual(443, info['irmc_port']) - self.assertEqual(60, info['irmc_client_timeout']) - self.assertEqual('ipmitool', info['irmc_sensor_method']) - self.assertEqual(True, info['irmc_verify_ca']) - - def test_parse_driver_info_missing_address(self): - del self.node.driver_info['irmc_address'] - self.assertRaises(exception.MissingParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_missing_username(self): - del self.node.driver_info['irmc_username'] - self.assertRaises(exception.MissingParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_missing_password(self): - del self.node.driver_info['irmc_password'] - self.assertRaises(exception.MissingParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_timeout(self): - self.node.driver_info['irmc_client_timeout'] = 'qwe' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_port(self): - self.node.driver_info['irmc_port'] = 'qwe' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_auth_method(self): - self.node.driver_info['irmc_auth_method'] = 'qwe' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_sensor_method(self): - self.node.driver_info['irmc_sensor_method'] = 'qwe' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_missing_multiple_params(self): - del self.node.driver_info['irmc_password'] - del self.node.driver_info['irmc_address'] - e = self.assertRaises(exception.MissingParameterValue, - irmc_common.parse_driver_info, self.node) - self.assertIn('irmc_password', str(e)) - self.assertIn('irmc_address', str(e)) - - def test_parse_driver_info_invalid_snmp_version(self): - self.node.driver_info['irmc_snmp_version'] = 'v3x' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - @mock.patch.object(utils, 'is_fips_enabled', - return_value=True, autospec=True) - def test_parse_driver_info_invalid_snmp_version_fips(self, - mock_check_fips): - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - self.assertEqual(1, mock_check_fips.call_count) - - def test_parse_driver_info_invalid_snmp_port(self): - self.node.driver_info['irmc_snmp_port'] = '161p' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_snmp_community(self): - self.node.driver_info['irmc_snmp_version'] = 'v2c' - self.node.driver_info['irmc_snmp_community'] = 100 - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_missing_snmp_user(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.assertRaises(exception.MissingParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_missing_snmp_auth_password(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.assertRaises(exception.MissingParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_missing_snmp_priv_password(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.assertRaises(exception.MissingParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_using_snmp_security(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_security'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - info = irmc_common.parse_driver_info(self.node) - self.assertEqual('admin0', info['irmc_snmp_user']) - - def test_parse_driver_info_invalid_snmp_security(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_security'] = 100 - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_snmp_user(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 100 - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_snmp_auth_password(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 100 - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_short_snmp_auth_password(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'short' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_snmp_priv_password(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 100 - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_short_snmp_priv_password(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'short' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_snmp_auth_proto(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_auth_proto'] = 'invalid' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - def test_parse_driver_info_invalid_snmp_priv_proto(self): - self.node.driver_info['irmc_snmp_version'] = 'v3' - self.node.driver_info['irmc_snmp_user'] = 'admin0' - self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key' - self.node.driver_info['irmc_snmp_priv_proto'] = 'invalid' - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - @mock.patch.object(os.path, 'isabs', return_value=True, autospec=True) - @mock.patch.object(os.path, 'isdir', return_value=True, autospec=True) - def test_parse_driver_info_dir_path_verify_ca(self, mock_isdir, - mock_isabs): - fake_path = 'absolute/path/to/a/valid/CA' - self.node.driver_info['irmc_verify_ca'] = fake_path - info = irmc_common.parse_driver_info(self.node) - self.assertEqual(fake_path, info['irmc_verify_ca']) - mock_isdir.assert_called_once_with(fake_path) - mock_isabs.assert_called_once_with(fake_path) - - @mock.patch.object(os.path, 'isabs', return_value=True, autospec=True) - @mock.patch.object(os.path, 'isfile', return_value=True, autospec=True) - def test_parse_driver_info_file_path_verify_ca(self, mock_isfile, - mock_isabs): - fake_path = 'absolute/path/to/a/valid/ca.pem' - self.node.driver_info['irmc_verify_ca'] = fake_path - info = irmc_common.parse_driver_info(self.node) - self.assertEqual(fake_path, info['irmc_verify_ca']) - mock_isfile.assert_called_once_with(fake_path) - mock_isabs.assert_called_once_with(fake_path) - - def test_parse_driver_info_string_bool_verify_ca(self): - self.node.driver_info['irmc_verify_ca'] = "False" - info = irmc_common.parse_driver_info(self.node) - self.assertFalse(info['irmc_verify_ca']) - - def test_parse_driver_info_invalid_verify_ca(self): - self.node.driver_info['irmc_verify_ca'] = "1234" - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - self.node.driver_info['irmc_verify_ca'] = 1234 - self.assertRaises(exception.InvalidParameterValue, - irmc_common.parse_driver_info, self.node) - - -class IRMCCommonMethodsTestCase(BaseIRMCTest): - - @mock.patch.object(irmc_common, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - def test_get_irmc_client(self, mock_scci): - self.info['irmc_port'] = 80 - self.info['irmc_auth_method'] = 'digest' - self.info['irmc_client_timeout'] = 60 - self.info['irmc_verify_ca'] = True - mock_scci.get_client.return_value = 'get_client' - returned_mock_scci_get_client = irmc_common.get_irmc_client(self.node) - mock_scci.get_client.assert_called_with( - self.info['irmc_address'], - self.info['irmc_username'], - self.info['irmc_password'], - port=self.info['irmc_port'], - auth_method=self.info['irmc_auth_method'], - verify=self.info['irmc_verify_ca'], - client_timeout=self.info['irmc_client_timeout']) - self.assertEqual('get_client', returned_mock_scci_get_client) - - def test_update_ipmi_properties(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - ipmi_info = { - "ipmi_address": "1.2.3.4", - "ipmi_username": "admin0", - "ipmi_password": "fake0", - } - task.node.driver_info = self.info - irmc_common.update_ipmi_properties(task) - actual_info = task.node.driver_info - expected_info = dict(self.info, **ipmi_info) - self.assertEqual(expected_info, actual_info) - - @mock.patch.object(irmc_common, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - def test_get_irmc_report(self, mock_scci): - self.info['irmc_port'] = 80 - self.info['irmc_auth_method'] = 'digest' - self.info['irmc_client_timeout'] = 60 - self.info['irmc_verify_ca'] = True - mock_scci.get_report.return_value = 'get_report' - returned_mock_scci_get_report = irmc_common.get_irmc_report(self.node) - mock_scci.get_report.assert_called_with( - self.info['irmc_address'], - self.info['irmc_username'], - self.info['irmc_password'], - port=self.info['irmc_port'], - auth_method=self.info['irmc_auth_method'], - verify=self.info['irmc_verify_ca'], - client_timeout=self.info['irmc_client_timeout']) - self.assertEqual('get_report', returned_mock_scci_get_report) - - def test_out_range_port(self): - self.assertRaises(ValueError, cfg.CONF.set_override, - 'port', 60, 'irmc') - - def test_out_range_auth_method(self): - self.assertRaises(ValueError, cfg.CONF.set_override, - 'auth_method', 'fake', 'irmc') - - def test_out_range_sensor_method(self): - self.assertRaises(ValueError, cfg.CONF.set_override, - 'sensor_method', 'fake', 'irmc') - - @mock.patch.object(irmc_common, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - def test_set_secure_boot_mode_enable(self, mock_elcm): - mock_elcm.set_secure_boot_mode.return_value = 'set_secure_boot_mode' - info = irmc_common.parse_driver_info(self.node) - irmc_common.set_secure_boot_mode(self.node, True) - mock_elcm.set_secure_boot_mode.assert_called_once_with( - info, True) - - @mock.patch.object(irmc_common, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - def test_set_secure_boot_mode_disable(self, mock_elcm): - mock_elcm.set_secure_boot_mode.return_value = 'set_secure_boot_mode' - info = irmc_common.parse_driver_info(self.node) - irmc_common.set_secure_boot_mode(self.node, False) - mock_elcm.set_secure_boot_mode.assert_called_once_with( - info, False) - - @mock.patch.object(irmc_common, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - @mock.patch.object(irmc_common, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - def test_set_secure_boot_mode_fail(self, mock_scci, mock_elcm): - irmc_common.scci.SCCIError = Exception - mock_elcm.set_secure_boot_mode.side_effect = Exception - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_common.set_secure_boot_mode, - task.node, True) - info = irmc_common.parse_driver_info(task.node) - mock_elcm.set_secure_boot_mode.assert_called_once_with( - info, True) - - @mock.patch.object(irmc_common, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - def test_check_elcm_license_success_with_200(self, elcm_mock): - elcm_req_mock = elcm_mock.elcm_request - json_data = ('{ "eLCMStatus" : { "EnabledAndLicenced" : "true" , ' - '"SDCardMounted" : "false" } }') - func_return_value = {'active': True, 'status_code': 200} - response_mock = elcm_req_mock.return_value - response_mock.status_code = 200 - response_mock.text = json_data - self.assertEqual(irmc_common.check_elcm_license(self.node), - func_return_value) - - @mock.patch.object(irmc_common, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - def test_check_elcm_license_success_with_500(self, elcm_mock): - elcm_req_mock = elcm_mock.elcm_request - json_data = '' - func_return_value = {'active': False, 'status_code': 500} - response_mock = elcm_req_mock.return_value - response_mock.status_code = 500 - response_mock.text = json_data - self.assertEqual(irmc_common.check_elcm_license(self.node), - func_return_value) - - @mock.patch.object(irmc_common, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - def test_check_elcm_license_fail_invalid_json(self, elcm_mock, scci_mock): - scci_mock.SCCIError = Exception - elcm_req_mock = elcm_mock.elcm_request - json_data = '' - response_mock = elcm_req_mock.return_value - response_mock.status_code = 200 - response_mock.text = json_data - self.assertRaises(exception.IRMCOperationError, - irmc_common.check_elcm_license, self.node) - - @mock.patch.object(irmc_common, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - def test_check_elcm_license_fail_elcm_error(self, elcm_mock, scci_mock): - scci_mock.SCCIError = Exception - elcm_req_mock = elcm_mock.elcm_request - elcm_req_mock.side_effect = scci_mock.SCCIError - self.assertRaises(exception.IRMCOperationError, - irmc_common.check_elcm_license, self.node) - - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(irmc_common, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - def test_set_irmc_version_success(self, scci_mock, get_report_mock): - version_str = 'iRMC S6/2.00' - scci_mock.get_irmc_version_str.return_value = version_str.split('/') - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_common.set_irmc_version(task) - self.assertEqual(version_str, - task.node.driver_internal_info['irmc_fw_version']) - - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(irmc_common, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - def test_set_irmc_version_fail(self, scci_mock, get_report_mock): - scci_mock.SCCIError = Exception - get_report_mock.side_effect = scci_mock.SCCIError - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_common.set_irmc_version, task) - - def test_within_version_ranges_success(self): - self.node.set_driver_internal_info('irmc_fw_version', 'iRMC S6/2.00') - ver_range_list = [ - {'4': {'upper': '1.05'}, - '6': {'min': '1.95', 'upper': '2.01'} - }, - {'4': {'upper': '1.05'}, - '6': {'min': '1.95', 'upper': None} - }, - {'4': {'upper': '1.05'}, - '6': {'min': '1.95'} - }, - {'4': {'upper': '1.05'}, - '6': {} - }, - {'4': {'upper': '1.05'}, - '6': None - }] - for range_dict in ver_range_list: - with self.subTest(): - self.assertTrue(irmc_common.within_version_ranges(self.node, - range_dict)) - - def test_within_version_ranges_success_out_range(self): - self.node.set_driver_internal_info('irmc_fw_version', 'iRMC S6/2.00') - ver_range_list = [ - {'4': {'upper': '1.05'}, - '6': {'min': '1.95', 'upper': '2.00'} - }, - {'4': {'upper': '1.05'}, - '6': {'min': '1.95', 'upper': '1.99'} - }, - {'4': {'upper': '1.05'}, - }] - for range_dict in ver_range_list: - with self.subTest(): - self.assertFalse(irmc_common.within_version_ranges(self.node, - range_dict)) - - def test_within_version_ranges_fail_no_match(self): - self.node.set_driver_internal_info('irmc_fw_version', 'ver/2.00') - ver_range = { - '4': {'upper': '1.05'}, - '6': {'min': '1.95', 'upper': '2.01'} - } - self.assertFalse(irmc_common.within_version_ranges(self.node, - ver_range)) - - def test_within_version_ranges_fail_no_version_set(self): - ver_range = { - '4': {'upper': '1.05'}, - '6': {'min': '1.95', 'upper': '2.01'} - } - self.assertFalse(irmc_common.within_version_ranges(self.node, - ver_range)) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_inspect.py b/ironic/tests/unit/drivers/modules/irmc/test_inspect.py deleted file mode 100644 index e8762391ff..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_inspect.py +++ /dev/null @@ -1,719 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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 iRMC Inspection Driver -""" - -from unittest import mock - -from pysnmp.proto import rfc1902 - -from ironic.common import exception -from ironic.common import states -from ironic.common import utils -from ironic.conductor import task_manager -from ironic.conductor import utils as manager_utils -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.irmc import inspect as irmc_inspect -from ironic.drivers.modules.irmc import power as irmc_power -from ironic import objects -from ironic.tests.unit.drivers import ( - third_party_driver_mock_specs as mock_specs -) -from ironic.tests.unit.drivers.modules.irmc import test_common - - -class IRMCInspectInternalMethodsTestCase(test_common.BaseIRMCTest): - - @mock.patch('ironic.drivers.modules.irmc.inspect.snmp.SNMPClient', - spec_set=True, autospec=True) - def test__get_mac_addresses(self, snmpclient_mock): - - # NOTE(yushiro): In pysnmp 4.4.12, SNMPClient returns following type: - # node classes: pysnmp.proto.rfc1902.Integer32 - # mac addresses: pysnmp.proto.rfc1902.OctetString - snmpclient_mock.return_value = mock.Mock( - **{'get_next.side_effect': [ - [ - rfc1902.Integer32(2), - rfc1902.Integer32(2), - rfc1902.Integer32(7) - ], [ - rfc1902.OctetString('\x90\x1b\x0e\xa5\x70\x37'), - rfc1902.OctetString('\x90\x1b\x0e\xa5\x70\x38'), - rfc1902.OctetString('\x90\x1b\x0e\xa5\x70\x39') - ]]} - ) - inspected_macs = ['90:1b:0e:a5:70:37', '90:1b:0e:a5:70:38'] - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - result = irmc_inspect._get_mac_addresses(task.node) - self.assertEqual(inspected_macs, result) - - @mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True, - autospec=True) - @mock.patch.object(irmc_inspect.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test__inspect_hardware_ipmi( - self, get_irmc_report_mock, scci_mock, _get_mac_addresses_mock): - # Set config flags - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] - cpu_fpgas = ['0x1000/0x0179', '0x2100/0x0180'] - self.config(gpu_ids=gpu_ids, group='irmc') - self.config(fpga_ids=cpu_fpgas, group='irmc') - kwargs = {'sleep_flag': False} - - inspected_props = { - 'memory_mb': '1024', - 'local_gb': 10, - 'cpu_arch': 'x86_64'} - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1, - 'cpu_fpga': 1} - new_traits = ['CUSTOM_CPU_FPGA'] - existing_traits = [] - - inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] - report = 'fake_report' - get_irmc_report_mock.return_value = report - scci_mock.get_essential_properties.return_value = inspected_props - scci_mock.get_capabilities_properties.return_value = ( - inspected_capabilities) - _get_mac_addresses_mock.return_value = inspected_macs - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - result = irmc_inspect._inspect_hardware(task.node, - existing_traits, - **kwargs) - get_irmc_report_mock.assert_called_once_with(task.node) - scci_mock.get_essential_properties.assert_called_once_with( - report, irmc_inspect.IRMCInspect.ESSENTIAL_PROPERTIES) - scci_mock.get_capabilities_properties.assert_called_once_with( - mock.ANY, irmc_inspect.CAPABILITIES_PROPERTIES, - gpu_ids, fpga_ids=cpu_fpgas, **kwargs) - - expected_props = dict(inspected_props) - inspected_capabilities = utils.get_updated_capabilities( - '', inspected_capabilities) - expected_props['capabilities'] = inspected_capabilities - self.assertEqual((expected_props, inspected_macs, new_traits), - result) - - @mock.patch.object( - irmc_inspect, '_get_capabilities_properties_without_ipmi', - autospec=True) - @mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True, - autospec=True) - @mock.patch.object(irmc_inspect.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test__inspect_hardware_redfish( - self, get_irmc_report_mock, scci_mock, _get_mac_addresses_mock, - _get_cap_prop_without_ipmi_mock): - # Set config flags - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - - kwargs = {'sleep_flag': False} - - parsed_info = irmc_common.parse_driver_info(self.node) - inspected_props = { - 'memory_mb': '1024', - 'local_gb': 10, - 'cpu_arch': 'x86_64'} - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S6-2.00S', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - formatted_caps = utils.get_updated_capabilities( - '', inspected_capabilities) - existing_traits = ['EXISTING_TRAIT'] - passed_cap_prop = {'irmc_firmware_version', - 'rom_firmware_version', 'server_model'} - inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] - report = 'fake_report' - get_irmc_report_mock.return_value = report - scci_mock.get_essential_properties.return_value = inspected_props - _get_cap_prop_without_ipmi_mock.return_value = { - 'capabilities': formatted_caps, - **inspected_props} - _get_mac_addresses_mock.return_value = inspected_macs - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - result = irmc_inspect._inspect_hardware(task.node, - existing_traits, - **kwargs) - get_irmc_report_mock.assert_called_once_with(task.node) - scci_mock.get_essential_properties.assert_called_once_with( - report, irmc_inspect.IRMCInspect.ESSENTIAL_PROPERTIES) - _get_cap_prop_without_ipmi_mock.assert_called_once_with( - parsed_info, passed_cap_prop, '', inspected_props) - - expected_props = dict(inspected_props) - inspected_capabilities = utils.get_updated_capabilities( - '', inspected_capabilities) - expected_props['capabilities'] = inspected_capabilities - self.assertEqual((expected_props, inspected_macs, existing_traits), - result) - - @mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True, - autospec=True) - @mock.patch.object(irmc_inspect.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test__inspect_hardware_exception( - self, get_irmc_report_mock, scci_mock, _get_mac_addresses_mock): - report = 'fake_report' - kwargs = {'sleep_flag': False} - get_irmc_report_mock.return_value = report - side_effect = exception.SNMPFailure("fake exception") - scci_mock.get_essential_properties.side_effect = side_effect - irmc_inspect.irmc.scci.SCCIInvalidInputError = Exception - irmc_inspect.irmc.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.HardwareInspectionFailure, - irmc_inspect._inspect_hardware, - task.node, **kwargs) - get_irmc_report_mock.assert_called_once_with(task.node) - self.assertFalse(_get_mac_addresses_mock.called) - - -class IRMCInspectTestCase(test_common.BaseIRMCTest): - - def test_get_properties(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - properties = task.driver.get_properties() - for prop in irmc_common.COMMON_PROPERTIES: - self.assertIn(prop, properties) - - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate(self, parse_driver_info_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.driver.inspect.validate(task) - parse_driver_info_mock.assert_called_once_with(task.node) - - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_fail(self, parse_driver_info_mock): - side_effect = exception.InvalidParameterValue("Invalid Input") - parse_driver_info_mock.side_effect = side_effect - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - task.driver.inspect.validate, - task) - - def test__init_fail_invalid_gpu_ids_input(self): - # Set config flags - self.config(gpu_ids='100/x079,0x20/', group='irmc') - self.assertRaises(exception.InvalidParameterValue, - irmc_inspect.IRMCInspect) - - def test__init_fail_invalid_fpga_ids_input(self): - # Set config flags - self.config(fpga_ids='100/x079,0x20/', group='irmc') - self.assertRaises(exception.InvalidParameterValue, - irmc_inspect.IRMCInspect) - - @mock.patch.object(irmc_inspect.LOG, 'info', spec_set=True, autospec=True) - @mock.patch('ironic.drivers.modules.irmc.inspect.objects.Port', - spec_set=True, autospec=True) - @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_inspect_hardware(self, power_state_mock, _inspect_hardware_mock, - port_mock, info_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - inspected_props = { - 'memory_mb': '1024', - 'local_gb': 10, - 'cpu_arch': 'x86_64'} - inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] - new_traits = ['CUSTOM_CPU_FPGA'] - existing_traits = [] - power_state_mock.return_value = states.POWER_ON - _inspect_hardware_mock.return_value = (inspected_props, - inspected_macs, - new_traits) - new_port_mock1 = objects.Port - new_port_mock2 = objects.Port - - port_mock.side_effect = [new_port_mock1, new_port_mock2] - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - result = task.driver.inspect.inspect_hardware(task) - - node_id = task.node.id - _inspect_hardware_mock.assert_called_once_with(task.node, - existing_traits) - - port_mock.assert_has_calls([ - mock.call(task.context, address=inspected_macs[0], - node_id=node_id), - mock.call.create(), - mock.call(task.context, address=inspected_macs[1], - node_id=node_id), - mock.call.create() - ], any_order=False) - - self.assertTrue(info_mock.called) - task.node.refresh() - self.assertEqual(inspected_props, task.node.properties) - self.assertEqual(states.MANAGEABLE, result) - - @mock.patch.object(manager_utils, 'node_power_action', spec_set=True, - autospec=True) - @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(irmc_inspect.LOG, 'info', spec_set=True, autospec=True) - @mock.patch.object(irmc_inspect.objects, 'Port', - spec_set=True, autospec=True) - @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_inspect_hardware_with_power_off(self, power_state_mock, - _inspect_hardware_mock, - port_mock, info_mock, - set_boot_device_mock, - power_action_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - inspected_props = { - 'memory_mb': '1024', - 'local_gb': 10, - 'cpu_arch': 'x86_64'} - inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] - new_traits = ['CUSTOM_CPU_FPGA'] - existing_traits = [] - power_state_mock.return_value = states.POWER_OFF - _inspect_hardware_mock.return_value = (inspected_props, - inspected_macs, - new_traits) - - new_port_mock1 = objects.Port - new_port_mock2 = objects.Port - - port_mock.side_effect = [new_port_mock1, new_port_mock2] - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - result = task.driver.inspect.inspect_hardware(task) - - node_id = task.node.id - _inspect_hardware_mock.assert_called_once_with(task.node, - existing_traits, - sleep_flag=True) - - port_mock.assert_has_calls([ - mock.call(task.context, address=inspected_macs[0], - node_id=node_id), - mock.call.create(), - mock.call(task.context, address=inspected_macs[1], - node_id=node_id), - mock.call.create() - ], any_order=False) - - self.assertTrue(info_mock.called) - task.node.refresh() - self.assertEqual(inspected_props, task.node.properties) - self.assertEqual(states.MANAGEABLE, result) - self.assertEqual(power_action_mock.called, True) - self.assertEqual(power_action_mock.call_count, 2) - - @mock.patch('ironic.objects.Port', spec_set=True, autospec=True) - @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_inspect_hardware_inspect_exception( - self, power_state_mock, _inspect_hardware_mock, port_mock): - self.node.set_driver_internal_info('irmc_fw_version', 'iRMC S4/7.82F') - self.node.save() - - side_effect = exception.HardwareInspectionFailure("fake exception") - _inspect_hardware_mock.side_effect = side_effect - power_state_mock.return_value = states.POWER_ON - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertRaises(exception.HardwareInspectionFailure, - task.driver.inspect.inspect_hardware, - task) - self.assertFalse(port_mock.called) - - @mock.patch.object(objects.trait.TraitList, - 'get_trait_names', - spec_set=True, - autospec=True) - @mock.patch.object(irmc_inspect.LOG, 'warn', spec_set=True, autospec=True) - @mock.patch('ironic.objects.Port', spec_set=True, autospec=True) - @mock.patch.object(irmc_inspect, '_inspect_hardware', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_inspect_hardware_mac_already_exist( - self, power_state_mock, _inspect_hardware_mock, - port_mock, warn_mock, trait_mock): - self.node.set_driver_internal_info('irmc_fw_version', 'iRMC S4/7.82F') - self.node.save() - - inspected_props = { - 'memory_mb': '1024', - 'local_gb': 10, - 'cpu_arch': 'x86_64'} - inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] - existing_traits = ['CUSTOM_CPU_FPGA'] - new_traits = list(existing_traits) - _inspect_hardware_mock.return_value = (inspected_props, - inspected_macs, - new_traits) - power_state_mock.return_value = states.POWER_ON - side_effect = exception.MACAlreadyExists("fake exception") - new_port_mock = port_mock.return_value - new_port_mock.create.side_effect = side_effect - trait_mock.return_value = existing_traits - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - result = task.driver.inspect.inspect_hardware(task) - - _inspect_hardware_mock.assert_called_once_with(task.node, - existing_traits) - self.assertEqual(2, port_mock.call_count) - task.node.refresh() - self.assertEqual(inspected_props, task.node.properties) - self.assertEqual(states.MANAGEABLE, result) - - @mock.patch.object(objects.trait.TraitList, 'get_trait_names', - spec_set=True, autospec=True) - @mock.patch.object(irmc_inspect, '_get_mac_addresses', spec_set=True, - autospec=True) - @mock.patch.object(irmc_inspect.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def _test_inspect_hardware_props(self, gpu_ids, - fpga_ids, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits, - get_irmc_report_mock, - scci_mock, - _get_mac_addresses_mock, - trait_mock): - capabilities_props = set(irmc_inspect.CAPABILITIES_PROPERTIES) - - # if gpu_ids = [], pci_gpu_devices will not be inspected - if len(gpu_ids) == 0: - capabilities_props.remove('pci_gpu_devices') - - # if fpga_ids = [], cpu_fpga will not be inspected - if fpga_ids is None or len(fpga_ids) == 0: - capabilities_props.remove('cpu_fpga') - - self.config(gpu_ids=gpu_ids, group='irmc') - self.config(fpga_ids=fpga_ids, group='irmc') - kwargs = {'sleep_flag': False} - - inspected_props = { - 'memory_mb': '1024', - 'local_gb': 10, - 'cpu_arch': 'x86_64'} - - inspected_macs = ['aa:aa:aa:aa:aa:aa', 'bb:bb:bb:bb:bb:bb'] - report = 'fake_report' - - get_irmc_report_mock.return_value = report - scci_mock.get_essential_properties.return_value = inspected_props - scci_mock.get_capabilities_properties.return_value = \ - inspected_capabilities - _get_mac_addresses_mock.return_value = inspected_macs - trait_mock.return_value = existed_traits - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.properties[u'capabilities'] =\ - ",".join('%(k)s:%(v)s' % {'k': k, 'v': v} - for k, v in existed_capabilities.items()) - result = irmc_inspect._inspect_hardware(task.node, - existed_traits, - **kwargs) - get_irmc_report_mock.assert_called_once_with(task.node) - scci_mock.get_essential_properties.assert_called_once_with( - report, irmc_inspect.IRMCInspect.ESSENTIAL_PROPERTIES) - scci_mock.get_capabilities_properties.assert_called_once_with( - mock.ANY, capabilities_props, - gpu_ids, fpga_ids=fpga_ids, **kwargs) - expected_capabilities = utils.get_updated_capabilities( - '', expected_capabilities) - - set1 = set(expected_capabilities.split(',')) - set2 = set(result[0]['capabilities'].split(',')) - self.assertEqual(set1, set2) - self.assertEqual(expected_traits, result[2]) - - def test_inspect_hardware_existing_cap_in_props(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - # Set config flags - gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] - cpu_fpgas = ['0x1000/0x0179', '0x2100/0x0180'] - existed_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1 - } - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1, - 'cpu_fpga': 1 - } - expected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1 - } - existed_traits = [] - expected_traits = ['CUSTOM_CPU_FPGA'] - - self._test_inspect_hardware_props(gpu_ids, - cpu_fpgas, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits) - - def test_inspect_hardware_props_empty_gpu_ids_fpga_ids(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - # Set config flags - gpu_ids = [] - cpu_fpgas = [] - existed_capabilities = {} - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - expected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - existed_traits = [] - expected_traits = [] - - self._test_inspect_hardware_props(gpu_ids, - cpu_fpgas, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits) - - def test_inspect_hardware_props_pci_gpu_devices_return_zero(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - # Set config flags - gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] - cpu_fpgas = ['0x1000/0x0179', '0x2100/0x0180'] - existed_capabilities = {} - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 0, - 'cpu_fpga': 0 - } - expected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - - existed_traits = [] - expected_traits = [] - - self._test_inspect_hardware_props(gpu_ids, - cpu_fpgas, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits) - - def test_inspect_hardware_props_empty_gpu_ids_fpga_id_sand_existing_cap( - self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - # Set config flags - gpu_ids = [] - cpu_fpgas = [] - existed_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1} - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - expected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - - existed_traits = [] - expected_traits = [] - - self._test_inspect_hardware_props(gpu_ids, - cpu_fpgas, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits) - - def test_inspect_hardware_props_gpu_cpu_fpgas_zero_and_existing_cap( - self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - # Set config flags - gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] - cpu_fpgas = ['0x1000/0x0179', '0x2100/0x0180'] - existed_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1} - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 0, - 'cpu_fpga': 0} - expected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - - existed_traits = ['CUSTOM_CPU_FPGA'] - expected_traits = [] - - self._test_inspect_hardware_props(gpu_ids, - cpu_fpgas, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits) - - def test_inspect_hardware_props_trusted_boot_removed(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - # Set config flags - gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] - cpu_fpgas = ['0x1000/0x0179', '0x2100/0x0180'] - existed_capabilities = {} - inspected_capabilities = { - 'trusted_boot': True, - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1, - 'cpu_fpga': 1} - expected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1} - - existed_traits = [] - expected_traits = ['CUSTOM_CPU_FPGA'] - - self._test_inspect_hardware_props(gpu_ids, - cpu_fpgas, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits) - - def test_inspect_hardware_props_gpu_and_cpu_fpgas_results_are_different( - self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - # Set config flags - gpu_ids = ['0x1000/0x0079', '0x2100/0x0080'] - cpu_fpgas = ['0x1000/0x0179', '0x2100/0x0180'] - existed_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 1} - inspected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x', - 'pci_gpu_devices': 0, - 'cpu_fpga': 1} - expected_capabilities = { - 'irmc_firmware_version': 'iRMC S4-7.82F', - 'server_model': 'TX2540M1F5', - 'rom_firmware_version': 'V4.6.5.4 R1.15.0 for D3099-B1x'} - - existed_traits = [] - expected_traits = ['CUSTOM_CPU_FPGA'] - - self._test_inspect_hardware_props(gpu_ids, - cpu_fpgas, - existed_capabilities, - inspected_capabilities, - expected_capabilities, - existed_traits, - expected_traits) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_management.py b/ironic/tests/unit/drivers/modules/irmc/test_management.py deleted file mode 100644 index 9e70e04bfd..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_management.py +++ /dev/null @@ -1,815 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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 iRMC Management Driver -""" - -import os -from unittest import mock -import xml.etree.ElementTree as ET - -from ironic.common import boot_devices -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 fake -from ironic.drivers.modules import ipmitool -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.irmc import management as irmc_management -from ironic.drivers.modules.irmc import power as irmc_power -from ironic.drivers.modules.redfish import management as redfish_management -from ironic.drivers.modules.redfish import utils as redfish_util -from ironic.drivers import utils as driver_utils -from ironic.tests.unit.drivers.modules.irmc import test_common -from ironic.tests.unit.drivers import third_party_driver_mock_specs \ - as mock_specs - - -@mock.patch.object(irmc_management.irmc, 'elcm', - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) -@mock.patch.object(manager_utils, 'node_power_action', - specset=True, autospec=True) -@mock.patch.object(irmc_power.IRMCPower, 'get_power_state', - return_value=states.POWER_ON, - specset=True, autospec=True) -class IRMCManagementFunctionsTestCase(test_common.BaseIRMCTest): - def setUp(self): - super(IRMCManagementFunctionsTestCase, self).setUp() - self.info = irmc_common.parse_driver_info(self.node) - - irmc_management.irmc.scci.SCCIError = Exception - irmc_management.irmc.scci.SCCIInvalidInputError = ValueError - - def test_backup_bios_config(self, mock_get_power, mock_power_action, - mock_elcm): - self.config(clean_priority_restore_irmc_bios_config=10, group='irmc') - bios_config = {'Server': {'System': {'BiosConfig': {'key1': 'val1'}}}} - mock_elcm.backup_bios_config.return_value = { - 'bios_config': bios_config} - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_management.backup_bios_config(task) - - self.assertEqual(bios_config, task.node.driver_internal_info[ - 'irmc_bios_config']) - self.assertEqual(1, mock_elcm.backup_bios_config.call_count) - - def test_backup_bios_config_skipped(self, mock_get_power, - mock_power_action, mock_elcm): - self.config(clean_priority_restore_irmc_bios_config=0, group='irmc') - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - irmc_management.backup_bios_config(task) - - self.assertNotIn('irmc_bios_config', - task.node.driver_internal_info) - self.assertFalse(mock_elcm.backup_bios_config.called) - - def test_backup_bios_config_failed(self, mock_get_power, - mock_power_action, mock_elcm): - self.config(clean_priority_restore_irmc_bios_config=10, group='irmc') - mock_elcm.backup_bios_config.side_effect = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_management.backup_bios_config, - task) - self.assertNotIn('irmc_bios_config', - task.node.driver_internal_info) - self.assertEqual(1, mock_elcm.backup_bios_config.call_count) - - def test__restore_bios_config(self, mock_get_power, mock_power_action, - mock_elcm): - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - # Set bios data for the node info - task.node.driver_internal_info['irmc_bios_config'] = 'data' - irmc_management._restore_bios_config(task) - - self.assertEqual(1, mock_elcm.restore_bios_config.call_count) - - def test__restore_bios_config_failed(self, mock_get_power, - mock_power_action, - mock_elcm): - mock_elcm.restore_bios_config.side_effect = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - # Set bios data for the node info - task.node.driver_internal_info['irmc_bios_config'] = 'data' - - self.assertRaises(exception.IRMCOperationError, - irmc_management._restore_bios_config, - task) - # Backed up BIOS config is still in the node object - self.assertEqual('data', task.node.driver_internal_info[ - 'irmc_bios_config']) - self.assertTrue(mock_elcm.restore_bios_config.called) - - def test__restore_bios_config_corrupted(self, mock_get_power, - mock_power_action, - mock_elcm): - mock_elcm.restore_bios_config.side_effect = \ - irmc_management.irmc.scci.SCCIInvalidInputError - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - # Set bios data for the node info - task.node.driver_internal_info['irmc_bios_config'] = 'data' - - self.assertRaises(exception.IRMCOperationError, - irmc_management._restore_bios_config, - task) - # Backed up BIOS config is removed from the node object - self.assertNotIn('irmc_bios_config', - task.node.driver_internal_info) - self.assertTrue(mock_elcm.restore_bios_config.called) - - -class IRMCManagementTestCase(test_common.BaseIRMCTest): - def setUp(self): - super(IRMCManagementTestCase, self).setUp() - self.info = irmc_common.parse_driver_info(self.node) - - def test_get_properties(self): - expected = irmc_common.COMMON_PROPERTIES - expected.update(ipmitool.COMMON_PROPERTIES) - expected.update(ipmitool.CONSOLE_PROPERTIES) - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - # Remove the boot and deploy interfaces properties - task.driver.boot = fake.FakeBoot() - task.driver.deploy = fake.FakeDeploy() - self.assertEqual(expected, task.driver.get_properties()) - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_ipmi_success(self, mock_drvinfo, redfish_parsedr_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.driver.management.validate(task) - mock_drvinfo.assert_called_once_with(task.node) - redfish_parsedr_mock.assert_not_called() - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_ipmi_fail(self, mock_drvinfo, redfish_parsedr_mock): - side_effect = exception.InvalidParameterValue("Invalid Input") - mock_drvinfo.side_effect = side_effect - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - task.driver.management.validate, - task) - mock_drvinfo.assert_called_once_with(task.node) - redfish_parsedr_mock.assert_not_called() - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_redfish_success( - self, mock_drvinfo, redfish_parsedr_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.driver.management.validate(task) - redfish_parsedr_mock.assert_called_once_with(task.node) - mock_drvinfo.assert_called_once_with(task.node) - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_redfish_fail(self, mock_drvinfo, redfish_parsedr_mock): - side_effect = exception.InvalidParameterValue("Invalid Input") - redfish_parsedr_mock.side_effect = side_effect - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - mock_drvinfo.assert_not_called() - self.assertRaises(exception.InvalidParameterValue, - task.driver.management.validate, - task) - redfish_parsedr_mock.assert_called_once_with(task.node) - - def test_management_interface_get_supported_boot_devices_ipmi(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - expected = [boot_devices.PXE, boot_devices.DISK, - boot_devices.CDROM, boot_devices.BIOS, - boot_devices.SAFE] - self.assertEqual(sorted(expected), sorted(task.driver.management. - get_supported_boot_devices(task))) - - def test_management_interface_get_supported_boot_devices_redfish(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - expected = list(redfish_management.BOOT_DEVICE_MAP_REV) - self.assertEqual(sorted(expected), sorted(task.driver.management. - get_supported_boot_devices(task))) - - @mock.patch.object(irmc_management.ipmitool, "send_raw", spec_set=True, - autospec=True) - def _test_management_interface_set_boot_device_ok( - self, boot_mode, params, expected_raw_code, send_raw_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - send_raw_mock.return_value = [None, None] - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = '' - if boot_mode: - driver_utils.add_node_capability(task, 'boot_mode', boot_mode) - irmc_management.IRMCManagement().set_boot_device(task, **params) - send_raw_mock.assert_has_calls([ - mock.call(task, "0x00 0x08 0x03 0x08"), - mock.call(task, expected_raw_code)]) - - def test_management_interface_set_boot_device_ok_pxe_ipmi(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - params = {'device': boot_devices.PXE, 'persistent': False} - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xa0 0x04 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0x80 0x04 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xa0 0x04 0x00 0x00 0x00") - - params['persistent'] = True - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xe0 0x04 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0xc0 0x04 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xe0 0x04 0x00 0x00 0x00") - - def test_management_interface_set_boot_device_ok_disk_ipmi(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - params = {'device': boot_devices.DISK, 'persistent': False} - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xa0 0x08 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0x80 0x08 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xa0 0x08 0x00 0x00 0x00") - - params['persistent'] = True - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xe0 0x08 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0xc0 0x08 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xe0 0x08 0x00 0x00 0x00") - - def test_management_interface_set_boot_device_ok_cdrom_ipmi(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - params = {'device': boot_devices.CDROM, 'persistent': False} - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xa0 0x20 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0x80 0x20 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xa0 0x20 0x00 0x00 0x00") - - params['persistent'] = True - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xe0 0x20 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0xc0 0x20 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xe0 0x20 0x00 0x00 0x00") - - def test_management_interface_set_boot_device_ok_bios_ipmi(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - params = {'device': boot_devices.BIOS, 'persistent': False} - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xa0 0x18 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0x80 0x18 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xa0 0x18 0x00 0x00 0x00") - - params['persistent'] = True - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xe0 0x18 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0xc0 0x18 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xe0 0x18 0x00 0x00 0x00") - - def test_management_interface_set_boot_device_ok_safe_ipmi(self): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - params = {'device': boot_devices.SAFE, 'persistent': False} - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xa0 0x0c 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0x80 0x0c 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xa0 0x0c 0x00 0x00 0x00") - - params['persistent'] = True - self._test_management_interface_set_boot_device_ok( - None, - params, - "0x00 0x08 0x05 0xe0 0x0c 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'bios', - params, - "0x00 0x08 0x05 0xc0 0x0c 0x00 0x00 0x00") - self._test_management_interface_set_boot_device_ok( - 'uefi', - params, - "0x00 0x08 0x05 0xe0 0x0c 0x00 0x00 0x00") - - @mock.patch.object(irmc_management.ipmitool, "send_raw", spec_set=True, - autospec=True) - def test_management_interface_set_boot_device_ng_ipmi(self, send_raw_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - - """uefi mode, next boot only, unknown device.""" - send_raw_mock.return_value = [None, None] - - with task_manager.acquire(self.context, self.node.uuid) as task: - driver_utils.add_node_capability(task, 'boot_mode', 'uefi') - self.assertRaises(exception.InvalidParameterValue, - irmc_management.IRMCManagement().set_boot_device, - task, - "unknown") - - @mock.patch.object(irmc_management.ipmitool, 'send_raw', autospec=True) - @mock.patch.object(redfish_management.RedfishManagement, 'set_boot_device', - autospec=True) - def test_management_interfase_set_boot_device_success_redfish( - self, redfish_set_boot_dev_mock, ipmi_raw_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - ipmi_raw_mock.side_effect = exception.IPMIFailure - management_inst = irmc_management.IRMCManagement() - with task_manager.acquire(self.context, self.node.uuid) as task: - params = ['pxe', True] - management_inst.set_boot_device(task, *params) - redfish_set_boot_dev_mock.assert_called_once_with( - management_inst, task, *params) - - @mock.patch.object(redfish_management.RedfishManagement, 'set_boot_device', - autospec=True) - def test_management_interfase_set_boot_device_fail_redfish( - self, redfish_set_boot_dev_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - management_inst = irmc_management.IRMCManagement() - with task_manager.acquire(self.context, self.node.uuid) as task: - params = [task, 'safe', True] - self.assertRaises(exception.InvalidParameterValue, - management_inst.set_boot_device, *params) - redfish_set_boot_dev_mock.assert_not_called() - - @mock.patch.object(irmc_management.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test_management_interface_get_sensors_data_scci_ok_ipmi( - self, mock_get_irmc_report, mock_scci): - """'irmc_sensor_method' = 'scci' specified and OK data.""" - with open(os.path.join(os.path.dirname(__file__), - 'fake_sensors_data_ok.xml'), "r") as report: - fake_txt = report.read() - fake_xml = ET.fromstring(fake_txt) - - mock_get_irmc_report.return_value = fake_xml - mock_scci.get_sensor_data.return_value = fake_xml.find( - "./System/SensorDataRecords") - - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['irmc_sensor_method'] = 'scci' - sensor_dict = irmc_management.IRMCManagement().get_sensors_data( - task) - - expected = { - 'Fan (4)': { - 'FAN1 SYS (29)': { - 'Units': 'RPM', - 'Sensor ID': 'FAN1 SYS (29)', - 'Sensor Reading': '600 RPM' - }, - 'FAN2 SYS (29)': { - 'Units': 'None', - 'Sensor ID': 'FAN2 SYS (29)', - 'Sensor Reading': 'None None' - } - }, - 'Temperature (1)': { - 'Systemboard 1 (7)': { - 'Units': 'degree C', - 'Sensor ID': 'Systemboard 1 (7)', - 'Sensor Reading': '80 degree C' - }, - 'Ambient (55)': { - 'Units': 'degree C', - 'Sensor ID': 'Ambient (55)', - 'Sensor Reading': '42 degree C' - } - } - } - self.assertEqual(expected, sensor_dict) - - @mock.patch.object(irmc_management.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test_management_interface_get_sensors_data_scci_ok_redfish( - self, mock_get_irmc_report, mock_scci): - """'irmc_sensor_method' = 'scci' specified and OK data.""" - with open(os.path.join(os.path.dirname(__file__), - 'fake_sensors_data_ok.xml'), "r") as report: - fake_txt = report.read() - fake_xml = ET.fromstring(fake_txt) - - mock_get_irmc_report.return_value = fake_xml - mock_scci.get_sensor_data.return_value = fake_xml.find( - "./System/SensorDataRecords") - - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['irmc_sensor_method'] = 'scci' - sensor_dict = irmc_management.IRMCManagement().get_sensors_data( - task) - - expected = { - 'Fan (4)': { - 'FAN1 SYS (29)': { - 'Units': 'RPM', - 'Sensor ID': 'FAN1 SYS (29)', - 'Sensor Reading': '600 RPM' - }, - 'FAN2 SYS (29)': { - 'Units': 'None', - 'Sensor ID': 'FAN2 SYS (29)', - 'Sensor Reading': 'None None' - } - }, - 'Temperature (1)': { - 'Systemboard 1 (7)': { - 'Units': 'degree C', - 'Sensor ID': 'Systemboard 1 (7)', - 'Sensor Reading': '80 degree C' - }, - 'Ambient (55)': { - 'Units': 'degree C', - 'Sensor ID': 'Ambient (55)', - 'Sensor Reading': '42 degree C' - } - } - } - self.assertEqual(expected, sensor_dict) - - @mock.patch.object(irmc_management.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test_management_interface_get_sensors_data_scci_ng_ipmi( - self, mock_get_irmc_report, mock_scci): - """'irmc_sensor_method' = 'scci' specified and NG data.""" - with open(os.path.join(os.path.dirname(__file__), - 'fake_sensors_data_ng.xml'), "r") as report: - fake_txt = report.read() - fake_xml = ET.fromstring(fake_txt) - - mock_get_irmc_report.return_value = fake_xml - mock_scci.get_sensor_data.return_value = fake_xml.find( - "./System/SensorDataRecords") - - self.node.set_driver_internal_info('irmc_fw_version', 'iRMC S5/2.00S') - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['irmc_sensor_method'] = 'scci' - sensor_dict = irmc_management.IRMCManagement().get_sensors_data( - task) - - self.assertEqual(len(sensor_dict), 0) - - @mock.patch.object(irmc_management.irmc, 'scci', - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC) - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test_management_interface_get_sensors_data_scci_ng_redfish( - self, mock_get_irmc_report, mock_scci): - """'irmc_sensor_method' = 'scci' specified and NG data.""" - with open(os.path.join(os.path.dirname(__file__), - 'fake_sensors_data_ng.xml'), "r") as report: - fake_txt = report.read() - fake_xml = ET.fromstring(fake_txt) - - mock_get_irmc_report.return_value = fake_xml - mock_scci.get_sensor_data.return_value = fake_xml.find( - "./System/SensorDataRecords") - - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['irmc_sensor_method'] = 'scci' - sensor_dict = irmc_management.IRMCManagement().get_sensors_data( - task) - - self.assertEqual(len(sensor_dict), 0) - - @mock.patch.object(ipmitool.IPMIManagement, 'get_sensors_data', - spec_set=True, autospec=True) - def test_management_interface_get_sensors_data_ipmitool_ok_ipmi( - self, - get_sensors_data_mock): - """'irmc_sensor_method' = 'ipmitool' specified.""" - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['irmc_sensor_method'] = 'ipmitool' - task.driver.management.get_sensors_data(task) - get_sensors_data_mock.assert_called_once_with( - task.driver.management, task) - - @mock.patch.object(ipmitool.IPMIManagement, 'get_sensors_data', - spec_set=True, autospec=True) - def test_management_interface_get_sensors_data_ipmitool_ng_redfish( - self, - get_sensors_data_mock): - """'irmc_sensor_method' = 'ipmitool' specified.""" - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['irmc_sensor_method'] = 'ipmitool' - self.assertRaises(exception.InvalidParameterValue, - task.driver.management.get_sensors_data, task) - - @mock.patch.object(irmc_common, 'get_irmc_report', spec_set=True, - autospec=True) - def test_management_interface_get_sensors_data_exception( - self, - get_irmc_report_mock): - """'FailedToGetSensorData Exception.""" - - get_irmc_report_mock.side_effect = exception.InvalidParameterValue( - "Fake Error") - irmc_management.irmc.scci.SCCIInvalidInputError = Exception - irmc_management.irmc.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_info['irmc_sensor_method'] = 'scci' - e = self.assertRaises( - exception.FailedToGetSensorData, - irmc_management.IRMCManagement().get_sensors_data, task) - self.assertEqual("Failed to get sensor data for node %s. " - "Error: Fake Error" % self.node.uuid, str(e)) - - @mock.patch.object(redfish_management.RedfishManagement, 'detect_vendor', - spec_set=True, autospec=True) - @mock.patch.object(ipmitool.IPMIManagement, 'detect_vendor', - spec_set=True, autospec=True) - def test_management_interface_detect_vendor_ipmi(self, - ipmimgmt_detectv_mock, - redfishmgmt_detectv_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - irmc_mgmt_inst = irmc_management.IRMCManagement() - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_mgmt_inst.detect_vendor(task) - ipmimgmt_detectv_mock.assert_called_once_with(irmc_mgmt_inst, task) - redfishmgmt_detectv_mock.assert_not_called() - - @mock.patch.object(redfish_management.RedfishManagement, 'detect_vendor', - spec_set=True, autospec=True) - @mock.patch.object(ipmitool.IPMIManagement, 'detect_vendor', - spec_set=True, autospec=True) - def test_management_interface_detect_vendor_redfish( - self, ipmimgmt_detectv_mock, redfishmgmt_detectv_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - ipmimgmt_detectv_mock.side_effect = exception.IPMIFailure - irmc_mgmt_inst = irmc_management.IRMCManagement() - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_mgmt_inst.detect_vendor(task) - redfishmgmt_detectv_mock.assert_called_once_with( - irmc_mgmt_inst, task) - - @mock.patch.object(irmc_management.LOG, 'error', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test_management_interface_inject_nmi_ok(self, mock_get_irmc_client, - mock_log): - irmc_client = mock_get_irmc_client.return_value - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_management.IRMCManagement().inject_nmi(task) - - irmc_client.assert_called_once_with( - irmc_management.irmc.scci.POWER_RAISE_NMI) - self.assertFalse(mock_log.called) - - @mock.patch.object(irmc_management.LOG, 'error', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test_management_interface_inject_nmi_fail(self, mock_get_irmc_client, - mock_log): - irmc_client = mock_get_irmc_client.return_value - irmc_client.side_effect = Exception() - irmc_management.irmc.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_management.IRMCManagement().inject_nmi, - task) - - irmc_client.assert_called_once_with( - irmc_management.irmc.scci.POWER_RAISE_NMI) - self.assertTrue(mock_log.called) - - @mock.patch.object(irmc_management, '_restore_bios_config', - spec_set=True, autospec=True) - def test_management_interface_restore_irmc_bios_config(self, - mock_restore_bios): - with task_manager.acquire(self.context, self.node.uuid) as task: - result = task.driver.management.restore_irmc_bios_config(task) - self.assertIsNone(result) - mock_restore_bios.assert_called_once_with(task) - - @mock.patch.object(irmc_common, 'set_irmc_version', autospec=True) - @mock.patch.object(irmc_common, 'check_elcm_license', autospec=True) - def test_verify_http_s_connection_and_fw_ver_success(self, - check_elcm_mock, - set_irmc_ver_mock): - check_elcm_mock.return_value = {'active': True, - 'status_code': 200} - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_mng = irmc_management.IRMCManagement() - irmc_mng.verify_http_https_connection_and_fw_version(task) - check_elcm_mock.assert_called_with(task.node) - set_irmc_ver_mock.assert_called_with(task) - - @mock.patch.object(irmc_common, 'set_irmc_version', autospec=True) - @mock.patch.object(irmc_common, 'check_elcm_license', autospec=True) - def test_verify_http_s_connection_and_fw_ver_raise_http_success( - self, check_elcm_mock, set_irmc_ver_mock): - error_msg_http = ('iRMC establishing connection to REST API ' - 'failed. Reason: ' - 'Access to REST API returns unexpected ' - 'status code. Check driver_info parameter ' - 'or version of iRMC because iRMC does not ' - 'support HTTP connection to iRMC REST API ' - 'since iRMC S6 2.00.') - - check_elcm_mock.return_value = {'active': False, - 'status_code': 404} - - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_mng = irmc_management.IRMCManagement() - - task.node.driver_info['irmc_port'] = 80 - self.assertRaisesRegex( - exception.IRMCOperationError, - error_msg_http, - irmc_mng.verify_http_https_connection_and_fw_version, - task) - check_elcm_mock.assert_called_with(task.node) - set_irmc_ver_mock.assert_not_called() - - @mock.patch.object(irmc_common, 'set_irmc_version', autospec=True) - @mock.patch.object(irmc_common, 'check_elcm_license', autospec=True) - def test_verify_http_s_connection_and_fw_ver_raise_https_success( - self, check_elcm_mock, set_irmc_ver_mock): - error_msg_https = ('iRMC establishing connection to REST API ' - 'failed. Reason: ' - 'Access to REST API returns unexpected ' - 'status code. Check driver_info parameter ' - 'related to iRMC driver') - - check_elcm_mock.return_value = {'active': False, - 'status_code': 404} - - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_mng = irmc_management.IRMCManagement() - task.node.driver_info['irmc_port'] = 443 - self.assertRaisesRegex( - exception.IRMCOperationError, - error_msg_https, - irmc_mng.verify_http_https_connection_and_fw_version, - task) - check_elcm_mock.assert_called_with(task.node) - set_irmc_ver_mock.assert_not_called() - - @mock.patch.object(irmc_common, 'set_irmc_version', autospec=True) - @mock.patch.object(irmc_common, 'check_elcm_license', autospec=True) - def test_verify_http_s_connection_and_fw_ver_fail_invalid( - self, check_elcm_mock, set_irmc_ver_mock): - check_elcm_mock.side_effect = exception.InvalidParameterValue - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_mng = irmc_management.IRMCManagement() - self.assertRaises( - exception.IRMCOperationError, - irmc_mng.verify_http_https_connection_and_fw_version, - task) - check_elcm_mock.assert_called_with(task.node) - - @mock.patch.object(irmc_common, 'set_irmc_version', autospec=True) - @mock.patch.object(irmc_common, 'check_elcm_license', autospec=True) - def test_verify_http_s_connection_and_fw_ver_fail_missing( - self, check_elcm_mock, set_irmc_ver_mock): - check_elcm_mock.side_effect = exception.MissingParameterValue - with task_manager.acquire(self.context, self.node.uuid) as task: - irmc_mng = irmc_management.IRMCManagement() - self.assertRaises( - exception.IRMCOperationError, - irmc_mng.verify_http_https_connection_and_fw_version, - task) - check_elcm_mock.assert_called_with(task.node) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_periodic_task.py b/ironic/tests/unit/drivers/modules/irmc/test_periodic_task.py deleted file mode 100644 index a066dbf47e..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_periodic_task.py +++ /dev/null @@ -1,333 +0,0 @@ -# Copyright 2018 FUJITSU LIMITED -# -# 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 iRMC periodic tasks -""" - -from unittest import mock - -from oslo_utils import uuidutils - -from ironic.conductor import task_manager -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.irmc import raid as irmc_raid -from ironic.tests.unit.drivers.modules.irmc import test_common -from ironic.tests.unit.objects import utils as obj_utils - - -class iRMCPeriodicTaskTestCase(test_common.BaseIRMCTest): - - def setUp(self): - super(iRMCPeriodicTaskTestCase, self).setUp() - self.node_2 = obj_utils.create_test_node( - self.context, driver='fake-hardware', - uuid=uuidutils.generate_uuid()) - self.driver = mock.Mock(raid=irmc_raid.IRMCRAID()) - self.raid_config = { - 'logical_disks': [ - {'controller': 'RAIDAdapter0'}, - {'irmc_raid_info': - {' size': {'#text': 465, '@Unit': 'GB'}, - 'logical_drive_number': 0, - 'name': 'LogicalDrive_0', - 'raid_level': '1'}}]} - self.target_raid_config = { - 'logical_disks': [ - { - 'key': 'value' - }]} - self.node.raid_config = self.raid_config - self.node.target_raid_config = self.target_raid_config - - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - def test__query_raid_config_fgi_status_without_node( - self, report_mock): - mock_manager = mock.Mock() - node_list = [] - mock_manager.iter_nodes.return_value = node_list - raid_object = irmc_raid.IRMCRAID() - raid_object._query_raid_config_fgi_status(mock_manager, None) - self.assertEqual(0, report_mock.call_count) - - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_without_input( - self, mock_acquire, report_mock): - mock_manager = mock.Mock() - raid_config = self.raid_config - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - # Set none target_raid_config input - task.node.target_raid_config = None - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, report_mock.call_count) - - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_without_raid_config( - self, mock_acquire, report_mock): - mock_manager = mock.Mock() - raid_config = {} - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, report_mock.call_count) - - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_without_fgi_status( - self, mock_acquire, report_mock): - mock_manager = mock.Mock() - raid_config = { - 'logical_disks': [ - {'controller': 'RAIDAdapter0'}, - {'irmc_raid_info': - {' size': {'#text': 465, '@Unit': 'GB'}, - 'logical_drive_number': 0, - 'name': 'LogicalDrive_0', - 'raid_level': '1'}}]} - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, report_mock.call_count) - - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_other_clean_state( - self, mock_acquire, report_mock): - mock_manager = mock.Mock() - raid_config = self.raid_config - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - # Set provision state value - task.node.provision_state = 'cleaning' - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, report_mock.call_count) - - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._set_clean_failed', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_fgi_status', - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_completing_status( - self, mock_acquire, report_mock, fgi_mock, clean_fail_mock): - mock_manager = mock.Mock() - fgi_mock.return_value = 'completing' - node_list = [(self.node.uuid, 'irmc', '', self.raid_config)] - mock_manager.iter_nodes.return_value = node_list - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - # Set provision state value - task.node.provision_state = 'clean wait' - task.node.target_raid_config = self.target_raid_config - task.node.raid_config = self.raid_config - task.node.save() - - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, clean_fail_mock.call_count) - report_mock.assert_called_once_with(task.node) - fgi_mock.assert_called_once_with(report_mock.return_value, - self.node.uuid) - - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._set_clean_failed', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_fgi_status', - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_with_clean_fail( - self, mock_acquire, report_mock, fgi_mock, clean_fail_mock): - mock_manager = mock.Mock() - raid_config = self.raid_config - fgi_mock.return_value = None - fgi_status_dict = None - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - # Set provision state value - task.node.provision_state = 'clean wait' - task.node.target_raid_config = self.target_raid_config - task.node.raid_config = self.raid_config - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - clean_fail_mock.assert_called_once_with(mock.ANY, task, - fgi_status_dict) - report_mock.assert_called_once_with(task.node) - fgi_mock.assert_called_once_with(report_mock.return_value, - self.node.uuid) - - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._resume_cleaning', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._set_clean_failed', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_fgi_status', - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_with_complete_cleaning( - self, mock_acquire, report_mock, fgi_mock, clean_fail_mock, - clean_mock): - mock_manager = mock.Mock() - raid_config = self.raid_config - fgi_mock.return_value = {'0': 'Idle', '1': 'Idle'} - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - # Set provision state value - task.node.provision_state = 'clean wait' - task.node.target_raid_config = self.target_raid_config - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, clean_fail_mock.call_count) - report_mock.assert_called_once_with(task.node) - fgi_mock.assert_called_once_with(report_mock.return_value, - self.node.uuid) - clean_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._resume_cleaning', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._set_clean_failed', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_fgi_status', - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_with_two_nodes_without_raid_config( - self, mock_acquire, report_mock, fgi_mock, clean_fail_mock, - clean_mock): - mock_manager = mock.Mock() - raid_config = self.raid_config - raid_config_2 = {} - fgi_mock.return_value = {'0': 'Idle', '1': 'Idle'} - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - node_list = [(self.node_2.uuid, 'irmc', '', raid_config_2), - (self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - # Set provision state value - task.node.provision_state = 'clean wait' - task.node.target_raid_config = self.target_raid_config - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, clean_fail_mock.call_count) - report_mock.assert_called_once_with(task.node) - fgi_mock.assert_called_once_with(report_mock.return_value, - self.node.uuid) - clean_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._resume_cleaning', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._set_clean_failed', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_fgi_status', - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_with_two_nodes_with_fgi_status_none( - self, mock_acquire, report_mock, fgi_mock, clean_fail_mock, - clean_mock): - mock_manager = mock.Mock() - raid_config = self.raid_config - raid_config_2 = self.raid_config.copy() - self.node_2.raid_config = raid_config_2 - fgi_status_dict = {} - fgi_mock.side_effect = [{}, {'0': 'Idle', '1': 'Idle'}] - node_list = [(self.node_2.uuid, 'fake-hardware', '', raid_config_2), - (self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - task = mock.Mock(node=self.node_2, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - task.node.provision_state = 'clean wait' - task.node.target_raid_config = self.target_raid_config - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - report_mock.assert_has_calls( - [mock.call(task.node), mock.call(task.node)]) - fgi_mock.assert_has_calls([mock.call(report_mock.return_value, - self.node_2.uuid), - mock.call(report_mock.return_value, - self.node_2.uuid)]) - clean_fail_mock.assert_called_once_with(mock.ANY, - task, fgi_status_dict) - clean_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._resume_cleaning', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid.IRMCRAID._set_clean_failed', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_fgi_status', - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_report', autospec=True) - @mock.patch.object(task_manager, 'acquire', autospec=True) - def test__query_raid_config_fgi_status_avoid_repeatedly_resume_cleaning( - self, mock_acquire, report_mock, fgi_mock, clean_fail_mock, - clean_mock): - mock_manager = mock.Mock() - raid_config = self.raid_config - fgi_mock.return_value = {'0': 'Idle', '1': 'Idle'} - task = mock.Mock(node=self.node, driver=self.driver) - mock_acquire.return_value = mock.MagicMock( - __enter__=mock.MagicMock(return_value=task)) - task.node.raid_config = raid_config - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - # Set provision state value - task.node.provision_state = 'clean wait' - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - raid_config = task.node.raid_config - node_list = [(self.node.uuid, 'irmc', '', raid_config)] - mock_manager.iter_nodes.return_value = node_list - task.node.provision_state = 'clean wait' - task.node.save() - task.driver.raid._query_raid_config_fgi_status(mock_manager, - self.context) - self.assertEqual(0, clean_fail_mock.call_count) - report_mock.assert_called_once_with(task.node) - fgi_mock.assert_called_once_with(report_mock.return_value, - self.node.uuid) - clean_mock.assert_called_once_with(mock.ANY, task) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_power.py b/ironic/tests/unit/drivers/modules/irmc/test_power.py deleted file mode 100644 index 2c84606aa0..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_power.py +++ /dev/null @@ -1,473 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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 iRMC Power Driver -""" - -from unittest import mock - -from ironic.common import exception -from ironic.common import states -from ironic.conductor import task_manager -from ironic.drivers.modules.irmc import boot as irmc_boot -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.irmc import power as irmc_power -from ironic.drivers.modules.redfish import power as redfish_power -from ironic.drivers.modules.redfish import utils as redfish_util -from ironic.tests.unit.drivers.modules.irmc import test_common - - -class IRMCPowerInternalMethodsTestCase(test_common.BaseIRMCTest): - - def test__is_expected_power_state(self): - target_state = states.SOFT_POWER_OFF - boot_status_value = irmc_power.BOOT_STATUS_VALUE['unknown'] - self.assertTrue(irmc_power._is_expected_power_state( - target_state, boot_status_value)) - - target_state = states.SOFT_POWER_OFF - boot_status_value = irmc_power.BOOT_STATUS_VALUE['off'] - self.assertTrue(irmc_power._is_expected_power_state( - target_state, boot_status_value)) - - target_state = states.SOFT_REBOOT - boot_status_value = irmc_power.BOOT_STATUS_VALUE['os-running'] - self.assertTrue(irmc_power._is_expected_power_state( - target_state, boot_status_value)) - - target_state = states.SOFT_POWER_OFF - boot_status_value = irmc_power.BOOT_STATUS_VALUE['os-running'] - self.assertFalse(irmc_power._is_expected_power_state( - target_state, boot_status_value)) - - @mock.patch('ironic.drivers.modules.irmc.power.snmp.SNMPClient', - spec_set=True, autospec=True) - def test__wait_power_state_soft_power_off(self, snmpclient_mock): - target_state = states.SOFT_POWER_OFF - self.config(snmp_polling_interval=1, group='irmc') - self.config(soft_power_off_timeout=3, group='conductor') - snmpclient_mock.return_value = mock.Mock( - **{'get.side_effect': [8, 8, 2]}) - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - irmc_power._wait_power_state(task, target_state) - - task.node.refresh() - self.assertIsNone(task.node.last_error) - self.assertEqual(states.POWER_OFF, task.node.power_state) - self.assertEqual(states.NOSTATE, task.node.target_power_state) - - @mock.patch('ironic.drivers.modules.irmc.power.snmp.SNMPClient', - spec_set=True, autospec=True) - def test__wait_power_state_soft_reboot(self, snmpclient_mock): - target_state = states.SOFT_REBOOT - self.config(snmp_polling_interval=1, group='irmc') - self.config(soft_power_off_timeout=3, group='conductor') - snmpclient_mock.return_value = mock.Mock( - **{'get.side_effect': [10, 6, 8]}) - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - irmc_power._wait_power_state(task, target_state) - - task.node.refresh() - self.assertIsNone(task.node.last_error) - self.assertEqual(states.POWER_ON, task.node.power_state) - self.assertEqual(states.NOSTATE, task.node.target_power_state) - - @mock.patch('ironic.drivers.modules.irmc.power.snmp.SNMPClient', - spec_set=True, autospec=True) - def test__wait_power_state_timeout(self, snmpclient_mock): - target_state = states.SOFT_POWER_OFF - self.config(snmp_polling_interval=1, group='irmc') - self.config(soft_power_off_timeout=2, group='conductor') - snmpclient_mock.return_value = mock.Mock( - **{'get.side_effect': [8, 8, 8]}) - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_power._wait_power_state, - task, - target_state, - timeout=None) - - task.node.refresh() - self.assertIsNotNone(task.node.last_error) - self.assertEqual(states.ERROR, task.node.power_state) - self.assertEqual(states.NOSTATE, task.node.target_power_state) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed', - autospec=True) - def test__set_power_state_power_on_ok( - self, - attach_boot_iso_if_needed_mock, - get_irmc_client_mock, - _wait_power_state_mock): - irmc_client = get_irmc_client_mock.return_value - target_state = states.POWER_ON - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - irmc_power._set_power_state(task, target_state) - attach_boot_iso_if_needed_mock.assert_called_once_with(task) - irmc_client.assert_called_once_with(irmc_power.scci.POWER_ON) - self.assertFalse(_wait_power_state_mock.called) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - def test__set_power_state_power_off_ok(self, - get_irmc_client_mock, - _wait_power_state_mock): - irmc_client = get_irmc_client_mock.return_value - target_state = states.POWER_OFF - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - irmc_power._set_power_state(task, target_state) - irmc_client.assert_called_once_with(irmc_power.scci.POWER_OFF) - self.assertFalse(_wait_power_state_mock.called) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed', - autospec=True) - def test__set_power_state_reboot_ok( - self, - attach_boot_iso_if_needed_mock, - get_irmc_client_mock, - _wait_power_state_mock): - irmc_client = get_irmc_client_mock.return_value - target_state = states.REBOOT - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - irmc_power._set_power_state(task, target_state) - attach_boot_iso_if_needed_mock.assert_called_once_with(task) - irmc_client.assert_called_once_with(irmc_power.scci.POWER_RESET) - self.assertFalse(_wait_power_state_mock.called) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed', - autospec=True) - def test__set_power_state_soft_reboot_ok( - self, - attach_boot_iso_if_needed_mock, - get_irmc_client_mock, - _wait_power_state_mock): - irmc_client = get_irmc_client_mock.return_value - target_state = states.SOFT_REBOOT - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - irmc_power._set_power_state(task, target_state) - attach_boot_iso_if_needed_mock.assert_called_once_with(task) - irmc_client.assert_called_once_with(irmc_power.scci.POWER_SOFT_CYCLE) - _wait_power_state_mock.assert_has_calls( - [mock.call(task, states.SOFT_POWER_OFF, timeout=None), - mock.call(task, states.SOFT_REBOOT, timeout=None)]) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed', - autospec=True) - def test__set_power_state_soft_power_off_ok(self, - attach_boot_iso_if_needed_mock, - get_irmc_client_mock, - _wait_power_state_mock): - irmc_client = get_irmc_client_mock.return_value - target_state = states.SOFT_POWER_OFF - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - irmc_power._set_power_state(task, target_state) - self.assertFalse(attach_boot_iso_if_needed_mock.called) - irmc_client.assert_called_once_with(irmc_power.scci.POWER_SOFT_OFF) - _wait_power_state_mock.assert_called_once_with(task, target_state, - timeout=None) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed', - autospec=True) - def test__set_power_state_invalid_target_state( - self, - attach_boot_iso_if_needed_mock, - _wait_power_state_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - irmc_power._set_power_state, - task, - states.ERROR) - self.assertFalse(attach_boot_iso_if_needed_mock.called) - self.assertFalse(_wait_power_state_mock.called) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed', - autospec=True) - def test__set_power_state_scci_exception(self, - attach_boot_iso_if_needed_mock, - get_irmc_client_mock, - _wait_power_state_mock): - irmc_client = get_irmc_client_mock.return_value - irmc_client.side_effect = Exception() - irmc_power.scci.SCCIClientError = Exception - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_power._set_power_state, - task, - states.POWER_ON) - attach_boot_iso_if_needed_mock.assert_called_once_with( - task) - self.assertFalse(_wait_power_state_mock.called) - - @mock.patch.object(irmc_power, '_wait_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_common, 'get_irmc_client', spec_set=True, - autospec=True) - @mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed', - autospec=True) - def test__set_power_state_snmp_exception(self, - attach_boot_iso_if_needed_mock, - get_irmc_client_mock, - _wait_power_state_mock): - target_state = states.SOFT_REBOOT - _wait_power_state_mock.side_effect = exception.SNMPFailure( - "fake exception") - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - irmc_power._set_power_state, - task, - target_state) - attach_boot_iso_if_needed_mock.assert_called_once_with( - task) - get_irmc_client_mock.return_value.assert_called_once_with( - irmc_power.STATES_MAP[target_state]) - _wait_power_state_mock.assert_called_once_with( - task, states.SOFT_POWER_OFF, timeout=None) - - -class IRMCPowerTestCase(test_common.BaseIRMCTest): - - def test_get_properties(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - properties = task.driver.get_properties() - for prop in irmc_common.COMMON_PROPERTIES: - self.assertIn(prop, properties) - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_default(self, mock_drvinfo, redfish_parsedr_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.driver.power.validate(task) - mock_drvinfo.assert_called_once_with(task.node) - redfish_parsedr_mock.assert_not_called() - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_ipmi(self, mock_drvinfo, redfish_parsedr_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.driver.power.validate(task) - mock_drvinfo.assert_called_once_with(task.node) - redfish_parsedr_mock.assert_not_called() - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_fail_ipmi(self, mock_drvinfo, redfish_parsedr_mock): - side_effect = exception.InvalidParameterValue("Invalid Input") - mock_drvinfo.side_effect = side_effect - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - task.driver.power.validate, - task) - redfish_parsedr_mock.assert_not_called() - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_redfish(self, mock_drvinfo, redfish_parsedr_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.driver.power.validate(task) - mock_drvinfo.assert_called_once_with(task.node) - redfish_parsedr_mock.assert_called_once_with(task.node) - - @mock.patch.object(redfish_util, 'parse_driver_info', autospec=True) - @mock.patch.object(irmc_common, 'parse_driver_info', spec_set=True, - autospec=True) - def test_validate_fail_redfish(self, mock_drvinfo, redfish_parsedr_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - side_effect = exception.InvalidParameterValue("Invalid Input") - redfish_parsedr_mock.side_effect = side_effect - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - task.driver.power.validate, - task) - mock_drvinfo.assert_called_once_with(task.node) - - @mock.patch.object(redfish_power.RedfishPower, 'get_power_state', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.power.ipmitool.IPMIPower', - spec_set=True, autospec=True) - def test_get_power_state_default(self, mock_IPMIPower, redfish_getpw_mock): - ipmi_power = mock_IPMIPower.return_value - ipmi_power.get_power_state.return_value = states.POWER_ON - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertEqual(states.POWER_ON, - task.driver.power.get_power_state(task)) - ipmi_power.get_power_state.assert_called_once_with(task) - redfish_getpw_mock.assert_not_called() - - @mock.patch.object(redfish_power.RedfishPower, 'get_power_state', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.power.ipmitool.IPMIPower', - spec_set=True, autospec=True) - def test_get_power_state_ipmi(self, mock_IPMIPower, redfish_getpw_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', True) - self.node.save() - ipmi_power = mock_IPMIPower.return_value - ipmi_power.get_power_state.return_value = states.POWER_ON - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertEqual(states.POWER_ON, - task.driver.power.get_power_state(task)) - ipmi_power.get_power_state.assert_called_once_with(task) - redfish_getpw_mock.assert_not_called() - - @mock.patch.object(redfish_power.RedfishPower, 'get_power_state', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.power.ipmitool.IPMIPower', - spec_set=True, autospec=True) - def test_get_power_state_redfish(self, mock_IPMIPower, redfish_getpw_mock): - self.node.set_driver_internal_info('irmc_ipmi_succeed', False) - self.node.save() - ipmipw_instance = mock_IPMIPower() - ipmipw_instance.get_power_state.side_effect = exception.IPMIFailure - redfish_getpw_mock.return_value = states.POWER_ON - irmc_power_inst = irmc_power.IRMCPower() - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertEqual(states.POWER_ON, - irmc_power_inst.get_power_state(task)) - ipmipw_instance.get_power_state.assert_called() - redfish_getpw_mock.assert_called_once_with(irmc_power_inst, task) - - @mock.patch.object(irmc_power, '_set_power_state', spec_set=True, - autospec=True) - def test_set_power_state(self, mock_set_power): - mock_set_power.return_value = states.POWER_ON - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.power.set_power_state(task, states.POWER_ON) - mock_set_power.assert_called_once_with(task, states.POWER_ON, - timeout=None) - - @mock.patch.object(irmc_power, '_set_power_state', spec_set=True, - autospec=True) - def test_set_power_state_timeout(self, mock_set_power): - mock_set_power.return_value = states.POWER_ON - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.power.set_power_state(task, states.POWER_ON, - timeout=2) - mock_set_power.assert_called_once_with(task, states.POWER_ON, - timeout=2) - - @mock.patch.object(irmc_power, '_set_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_reboot_reboot(self, mock_get_power, mock_set_power): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - mock_get_power.return_value = states.POWER_ON - task.driver.power.reboot(task) - mock_get_power.assert_called_once_with( - task.driver.power, task) - mock_set_power.assert_called_once_with(task, states.REBOOT, - timeout=None) - - @mock.patch.object(irmc_power, '_set_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_reboot_reboot_timeout(self, mock_get_power, mock_set_power): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - mock_get_power.return_value = states.POWER_ON - task.driver.power.reboot(task, timeout=2) - mock_get_power.assert_called_once_with( - task.driver.power, task) - mock_set_power.assert_called_once_with(task, states.REBOOT, - timeout=2) - - @mock.patch.object(irmc_power, '_set_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_reboot_power_on(self, mock_get_power, mock_set_power): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - mock_get_power.return_value = states.POWER_OFF - task.driver.power.reboot(task) - mock_get_power.assert_called_once_with( - task.driver.power, task) - mock_set_power.assert_called_once_with(task, states.POWER_ON, - timeout=None) - - @mock.patch.object(irmc_power, '_set_power_state', spec_set=True, - autospec=True) - @mock.patch.object(irmc_power.IRMCPower, 'get_power_state', spec_set=True, - autospec=True) - def test_reboot_power_on_timeout(self, mock_get_power, mock_set_power): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - mock_get_power.return_value = states.POWER_OFF - task.driver.power.reboot(task, timeout=2) - mock_get_power.assert_called_once_with( - task.driver.power, task) - mock_set_power.assert_called_once_with(task, states.POWER_ON, - timeout=2) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_raid.py b/ironic/tests/unit/drivers/modules/irmc/test_raid.py deleted file mode 100644 index 54a0a0dd62..0000000000 --- a/ironic/tests/unit/drivers/modules/irmc/test_raid.py +++ /dev/null @@ -1,856 +0,0 @@ -# Copyright 2018 FUJITSU LIMITED -# -# 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 IRMC RAID configuration -""" - -from unittest import mock - -from ironic.common import exception -from ironic.conductor import task_manager -from ironic import drivers as ironic_drivers -from ironic.drivers.modules import deploy_utils -from ironic.drivers.modules.irmc import common as irmc_common -from ironic.drivers.modules.irmc import raid -from ironic.tests.unit.drivers.modules.irmc import test_common - - -class IRMCRaidConfigurationInternalMethodsTestCase(test_common.BaseIRMCTest): - - def setUp(self): - super(IRMCRaidConfigurationInternalMethodsTestCase, self).setUp() - self.raid_adapter_profile = { - "Server": { - "HWConfigurationIrmc": { - "Adapters": { - "RAIDAdapter": [ - { - "@AdapterId": "RAIDAdapter0", - "@ConfigurationType": "Addressing", - "Arrays": None, - "LogicalDrives": None, - "PhysicalDisks": { - "PhysicalDisk": [ - { - "@Number": "0", - "@Action": "None", - "Slot": 0, - }, - { - "@Number": "1", - "@Action": "None", - "Slot": 1 - }, - { - "@Number": "2", - "@Action": "None", - "Slot": 2 - }, - { - "@Number": "3", - "@Action": "None", - "Slot": 3 - } - ] - } - } - ] - } - } - } - } - - self.valid_disk_slots = { - "PhysicalDisk": [ - { - "@Number": "0", - "Slot": 0, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - }, - { - "@Number": "1", - "Slot": 1, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - }, - { - "@Number": "2", - "Slot": 2, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - }, - { - "@Number": "3", - "Slot": 3, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - }, - { - "@Number": "4", - "Slot": 4, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - }, - { - "@Number": "5", - "Slot": 5, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - }, - { - "@Number": "6", - "Slot": 6, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - }, - { - "@Number": "7", - "Slot": 7, - "Size": { - "@Unit": "GB", - "#text": 1000 - } - } - ] - } - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test___fail_validation_with_none_raid_adapter_profile( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = None - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "0" - } - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test___fail_validation_without_raid_level( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50" - } - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test___fail_validation_with_raid_level_is_none(self, - get_raid_adapter_mock, - get_physical_disk_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "" - } - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_without_physical_disks( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = { - "Server": { - "HWConfigurationIrmc": { - "Adapters": { - "RAIDAdapter": [ - { - "@AdapterId": "RAIDAdapter0", - "@ConfigurationType": "Addressing", - "Arrays": None, - "LogicalDrives": None, - "PhysicalDisks": None - } - ] - } - } - } - } - - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "1" - } - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test___fail_validation_with_raid_level_outside_list( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "2" - } - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch( - 'ironic.drivers.modules.irmc.raid._validate_logical_drive_capacity', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_not_enough_valid_disks( - self, get_raid_adapter_mock, get_physical_disk_mock, - capacity_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "5" - }, - { - "size_gb": "50", - "raid_level": "1" - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_physical_disk_insufficient( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "1", - "physical_disks": [ - "0", - "1", - "2" - ] - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_physical_disk_not_enough_disks( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "5", - "physical_disks": [ - "0", - "1" - ] - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_physical_disk_incorrect_valid_disks( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "10", - "physical_disks": [ - "0", - "1", - "2", - "3", - "4" - ] - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_physical_disk_outside_valid_disks_1( - self, get_raid_adapter_mock, get_physical_disk_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "1", - "physical_disks": [ - "4", - "5" - ] - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch( - 'ironic.drivers.modules.irmc.raid._validate_logical_drive_capacity', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_physical_disk_outside_valid_slots_2( - self, get_raid_adapter_mock, get_physical_disk_mock, - capacity_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "5", - "physical_disks": [ - "0", - "1", - "2" - ] - }, - { - "size_gb": "50", - "raid_level": "0", - "physical_disks": [ - "4" - ] - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch( - 'ironic.drivers.modules.irmc.raid._validate_logical_drive_capacity', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_physical_disk', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_duplicated_physical_disks( - self, get_raid_adapter_mock, get_physical_disk_mock, - capacity_mock): - get_raid_adapter_mock.return_value = self.raid_adapter_profile - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "1", - "physical_disks": [ - "0", - "1" - ] - }, - { - "size_gb": "50", - "raid_level": "1", - "physical_disks": [ - "1", - "2" - ] - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - @mock.patch('ironic.drivers.modules.irmc.raid._get_raid_adapter', - autospec=True) - def test__fail_validation_with_difference_physical_disks_type( - self, get_raid_adapter_mock): - get_raid_adapter_mock.return_value = { - "Server": { - "HWConfigurationIrmc": { - "Adapters": { - "RAIDAdapter": [ - { - "@AdapterId": "RAIDAdapter0", - "@ConfigurationType": "Addressing", - "Arrays": None, - "LogicalDrives": None, - "PhysicalDisks": { - "PhysicalDisk": [ - { - "@Number": "0", - "Slot": 0, - "Type": "HDD", - }, - { - "@Number": "1", - "Slot": 1, - "Type": "SSD", - } - ] - } - } - ] - } - } - } - } - target_raid_config = { - "logical_disks": [ - { - "size_gb": "50", - "raid_level": "1", - "physical_disks": [ - "0", - "1" - ] - } - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.IRMCOperationError, - raid._validate_physical_disks, - task.node, target_raid_config['logical_disks']) - - def test__fail_validate_capacity_raid_0(self): - disk = { - "size_gb": 3000, - "raid_level": "0" - } - self.assertRaises(exception.InvalidParameterValue, - raid._validate_logical_drive_capacity, - disk, self.valid_disk_slots) - - def test__fail_validate_capacity_raid_1(self): - disk = { - "size_gb": 3000, - "raid_level": "1" - } - self.assertRaises(exception.InvalidParameterValue, - raid._validate_logical_drive_capacity, - disk, self.valid_disk_slots) - - def test__fail_validate_capacity_raid_5(self): - disk = { - "size_gb": 3000, - "raid_level": "5" - } - self.assertRaises(exception.InvalidParameterValue, - raid._validate_logical_drive_capacity, - disk, self.valid_disk_slots) - - def test__fail_validate_capacity_raid_6(self): - disk = { - "size_gb": 3000, - "raid_level": "6" - } - self.assertRaises(exception.InvalidParameterValue, - raid._validate_logical_drive_capacity, - disk, self.valid_disk_slots) - - def test__fail_validate_capacity_raid_10(self): - disk = { - "size_gb": 3000, - "raid_level": "10" - } - self.assertRaises(exception.InvalidParameterValue, - raid._validate_logical_drive_capacity, - disk, self.valid_disk_slots) - - def test__fail_validate_capacity_raid_50(self): - disk = { - "size_gb": 5000, - "raid_level": "50" - } - self.assertRaises(exception.InvalidParameterValue, - raid._validate_logical_drive_capacity, - disk, self.valid_disk_slots) - - def test__fail_validate_capacity_with_physical_disk(self): - disk = { - "size_gb": 4000, - "raid_level": "5", - "physical_disks": [ - "0", - "1", - "3", - "4" - ] - } - self.assertRaises(exception.InvalidParameterValue, - raid._validate_logical_drive_capacity, - disk, self.valid_disk_slots) - - @mock.patch('ironic.common.raid.update_raid_info', autospec=True) - @mock.patch.object(deploy_utils, 'set_async_step_flags', autospec=True) - def test__commit_raid_config_with_logical_drives( - self, set_async_step_flags_mock, update_raid_info_mock): - if not mock._is_instance_mock( - ironic_drivers.modules.irmc.raid.client.elcm): - mock.patch.object(ironic_drivers.modules.irmc.raid.client, - 'elcm', autospec=True).start() - get_raid_adapter_mock = ( - ironic_drivers.modules.irmc.raid.client.elcm.get_raid_adapter) - get_raid_adapter_mock.return_value = { - "Server": { - "HWConfigurationIrmc": { - "Adapters": { - "RAIDAdapter": [ - { - "@AdapterId": "RAIDAdapter0", - "@ConfigurationType": "Addressing", - "Arrays": { - "Array": [ - { - "@Number": 0, - "@ConfigurationType": "Addressing", - "PhysicalDiskRefs": { - "PhysicalDiskRef": [ - { - "@Number": "0" - }, - { - "@Number": "1" - } - ] - } - } - ] - }, - "LogicalDrives": { - "LogicalDrive": [ - { - "@Number": 0, - "@Action": "None", - "RaidLevel": "1", - "Name": "LogicalDrive_0", - "Size": { - "@Unit": "GB", - "#text": 465 - }, - "ArrayRefs": { - "ArrayRef": [ - { - "@Number": 0 - } - ] - } - } - ] - }, - "PhysicalDisks": { - "PhysicalDisk": [ - { - "@Number": "0", - "@Action": "None", - "Slot": 0, - "PDStatus": "Operational" - }, - { - "@Number": "1", - "@Action": "None", - "Slot": 1, - "PDStatus": "Operational" - } - ] - } - } - ] - } - } - } - } - - expected_raid_config = [ - {'controller': 'RAIDAdapter0'}, - {'irmc_raid_info': {' size': {'#text': 465, '@Unit': 'GB'}, - 'logical_drive_number': 0, - 'name': 'LogicalDrive_0', - 'raid_level': '1'}}, - {'physical_drives': {'physical_drive': {'@Action': 'None', - '@Number': '0', - 'PDStatus': 'Operational', - 'Slot': 0}}}, - {'physical_drives': {'physical_drive': {'@Action': 'None', - '@Number': '1', - 'PDStatus': 'Operational', - 'Slot': 1}}}] - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - raid._commit_raid_config(task) - irmc_info = irmc_common.parse_driver_info(task.node) - get_raid_adapter_mock.assert_called_once_with(irmc_info) - update_raid_info_mock.assert_called_once_with( - task.node, task.node.raid_config) - set_async_step_flags_mock.assert_called_once_with( - task.node, reboot=True, skip_current_step=True, polling=True) - self.assertEqual(task.node.raid_config['logical_disks'], - expected_raid_config) - - -class IRMCRaidConfigurationTestCase(test_common.BaseIRMCTest): - - def setUp(self): - super(IRMCRaidConfigurationTestCase, self).setUp() - self.config(enabled_raid_interfaces=['irmc']) - self.raid_adapter_profile = { - "Server": { - "HWConfigurationIrmc": { - "Adapters": { - "RAIDAdapter": [ - { - "@AdapterId": "RAIDAdapter0", - "@ConfigurationType": "Addressing", - "Arrays": None, - "LogicalDrives": None, - "PhysicalDisks": { - "PhysicalDisk": [ - { - "@Number": "0", - "@Action": "None", - "Slot": 0, - }, - { - "@Number": "1", - "@Action": "None", - "Slot": 1 - }, - { - "@Number": "2", - "@Action": "None", - "Slot": 2 - }, - { - "@Number": "3", - "@Action": "None", - "Slot": 3 - } - ] - } - } - ] - } - } - } - } - - def test_fail_create_raid_without_target_raid_config(self): - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - - task.node.target_raid_config = {} - raid_configuration = raid.IRMCRAID() - - self.assertRaises(exception.MissingParameterValue, - raid_configuration.create_configuration, task) - - @mock.patch('ironic.drivers.modules.irmc.raid._validate_physical_disks', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._create_raid_adapter', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._commit_raid_config', - autospec=True) - def test_create_raid_with_raid_1_and_0(self, commit_mock, - create_raid_mock, validation_mock): - expected_input = { - "logical_disks": [ - { - "raid_level": "10" - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.target_raid_config = { - "logical_disks": [ - { - "raid_level": "1+0" - }, - ] - } - - task.driver.raid.create_configuration(task) - create_raid_mock.assert_called_once_with(task.node) - validation_mock.assert_called_once_with( - task.node, expected_input['logical_disks']) - commit_mock.assert_called_once_with(task) - - @mock.patch('ironic.drivers.modules.irmc.raid._validate_physical_disks', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._create_raid_adapter', - autospec=True) - @mock.patch('ironic.drivers.modules.irmc.raid._commit_raid_config', - autospec=True) - def test_create_raid_with_raid_5_and_0(self, commit_mock, - create_raid_mock, validation_mock): - expected_input = { - "logical_disks": [ - { - "raid_level": "50" - }, - ] - } - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.target_raid_config = { - "logical_disks": [ - { - "raid_level": "5+0" - }, - ] - } - - task.driver.raid.create_configuration(task) - create_raid_mock.assert_called_once_with(task.node) - validation_mock.assert_called_once_with( - task.node, expected_input['logical_disks']) - commit_mock.assert_called_once_with(task) - - @mock.patch('ironic.drivers.modules.irmc.raid._delete_raid_adapter', - autospec=True) - def test_delete_raid_configuration(self, delete_raid_mock): - - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.driver.raid.delete_configuration(task) - delete_raid_mock.assert_called_once_with(task.node) - - @mock.patch('ironic.drivers.modules.irmc.raid._delete_raid_adapter', - autospec=True) - def test_delete_raid_configuration_return_cleared_raid_config( - self, delete_raid_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - - expected_raid_config = {} - - task.driver.raid.delete_configuration(task) - self.assertEqual(expected_raid_config, task.node.raid_config) - delete_raid_mock.assert_called_once_with(task.node) diff --git a/ironic/tests/unit/drivers/test_irmc.py b/ironic/tests/unit/drivers/test_irmc.py deleted file mode 100644 index c26eae8d64..0000000000 --- a/ironic/tests/unit/drivers/test_irmc.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright 2015 FUJITSU LIMITED -# -# 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 iRMC Deploy Driver -""" - -from unittest import mock - -from ironic.conductor import task_manager -from ironic.drivers import irmc -from ironic.drivers.modules import agent -from ironic.drivers.modules import inspector -from ironic.drivers.modules import ipmitool -from ironic.drivers.modules import ipxe -from ironic.drivers.modules.irmc import bios as irmc_bios -from ironic.drivers.modules.irmc import boot as irmc_boot -from ironic.drivers.modules.irmc import raid -from ironic.drivers.modules import noop -from ironic.tests.unit.db import base as db_base -from ironic.tests.unit.objects import utils as obj_utils - - -@mock.patch.object(irmc_boot, 'check_share_fs_mounted', spec_set=True, - autospec=True) -class IRMCHardwareTestCase(db_base.DbTestCase): - - def setUp(self): - super(IRMCHardwareTestCase, self).setUp() - self.config_temp_dir('http_root', group='deploy') - self.config(enabled_hardware_types=['irmc'], - enabled_boot_interfaces=['irmc-virtual-media', 'ipxe'], - enabled_console_interfaces=['ipmitool-socat'], - enabled_deploy_interfaces=['ansible', 'direct'], - enabled_inspect_interfaces=['irmc'], - enabled_management_interfaces=['irmc'], - enabled_power_interfaces=['irmc', 'ipmitool'], - enabled_raid_interfaces=['no-raid', 'agent', 'irmc'], - enabled_rescue_interfaces=['no-rescue', 'agent'], - enabled_bios_interfaces=['irmc', 'no-bios', 'fake']) - - def test_default_interfaces(self, check_share_fs_mounted_mock): - node = obj_utils.create_test_node(self.context, driver='irmc') - with task_manager.acquire(self.context, node.id) as task: - self.assertIsInstance(task.driver.boot, - irmc.boot.IRMCVirtualMediaBoot) - self.assertIsInstance(task.driver.console, - ipmitool.IPMISocatConsole) - self.assertIsInstance(task.driver.deploy, - agent.AgentDeploy) - self.assertIsInstance(task.driver.inspect, - irmc.inspect.IRMCInspect) - self.assertIsInstance(task.driver.management, - irmc.management.IRMCManagement) - self.assertIsInstance(task.driver.power, - irmc.power.IRMCPower) - self.assertIsInstance(task.driver.raid, - noop.NoRAID) - self.assertIsInstance(task.driver.rescue, - noop.NoRescue) - self.assertIsInstance(task.driver.bios, - irmc_bios.IRMCBIOS) - - def test_override_with_inspector(self, check_share_fs_mounted_mock): - self.config(enabled_inspect_interfaces=['agent', 'irmc']) - node = obj_utils.create_test_node( - self.context, driver='irmc', - inspect_interface='agent', - raid_interface='agent') - with task_manager.acquire(self.context, node.id) as task: - self.assertIsInstance(task.driver.boot, - irmc.boot.IRMCVirtualMediaBoot) - self.assertIsInstance(task.driver.console, - ipmitool.IPMISocatConsole) - self.assertIsInstance(task.driver.deploy, - agent.AgentDeploy) - self.assertIsInstance(task.driver.inspect, - inspector.AgentInspect) - self.assertIsInstance(task.driver.management, - irmc.management.IRMCManagement) - self.assertIsInstance(task.driver.power, - irmc.power.IRMCPower) - self.assertIsInstance(task.driver.raid, - agent.AgentRAID) - self.assertIsInstance(task.driver.rescue, - noop.NoRescue) - - def test_override_with_agent_rescue(self, check_share_fs_mounted_mock): - node = obj_utils.create_test_node( - self.context, driver='irmc', - rescue_interface='agent', - raid_interface='agent') - with task_manager.acquire(self.context, node.id) as task: - self.assertIsInstance(task.driver.boot, - irmc.boot.IRMCVirtualMediaBoot) - self.assertIsInstance(task.driver.console, - ipmitool.IPMISocatConsole) - self.assertIsInstance(task.driver.deploy, - agent.AgentDeploy) - self.assertIsInstance(task.driver.inspect, - irmc.inspect.IRMCInspect) - self.assertIsInstance(task.driver.management, - irmc.management.IRMCManagement) - self.assertIsInstance(task.driver.power, - irmc.power.IRMCPower) - self.assertIsInstance(task.driver.raid, - agent.AgentRAID) - self.assertIsInstance(task.driver.rescue, - agent.AgentRescue) - - def test_override_with_ipmitool_power(self, check_share_fs_mounted_mock): - node = obj_utils.create_test_node( - self.context, driver='irmc', power_interface='ipmitool') - with task_manager.acquire(self.context, node.id) as task: - self.assertIsInstance(task.driver.boot, - irmc.boot.IRMCVirtualMediaBoot) - self.assertIsInstance(task.driver.console, - ipmitool.IPMISocatConsole) - self.assertIsInstance(task.driver.deploy, - agent.AgentDeploy) - self.assertIsInstance(task.driver.inspect, - irmc.inspect.IRMCInspect) - self.assertIsInstance(task.driver.management, - irmc.management.IRMCManagement) - self.assertIsInstance(task.driver.power, - ipmitool.IPMIPower) - self.assertIsInstance(task.driver.raid, - noop.NoRAID) - self.assertIsInstance(task.driver.rescue, - noop.NoRescue) - - def test_override_with_raid_configuration(self, - check_share_fs_mounted_mock): - node = obj_utils.create_test_node( - self.context, driver='irmc', - rescue_interface='agent', - raid_interface='irmc') - with task_manager.acquire(self.context, node.id) as task: - self.assertIsInstance(task.driver.boot, - irmc.boot.IRMCVirtualMediaBoot) - self.assertIsInstance(task.driver.console, - ipmitool.IPMISocatConsole) - self.assertIsInstance(task.driver.deploy, - agent.AgentDeploy) - self.assertIsInstance(task.driver.inspect, - irmc.inspect.IRMCInspect) - self.assertIsInstance(task.driver.management, - irmc.management.IRMCManagement) - self.assertIsInstance(task.driver.power, - irmc.power.IRMCPower) - self.assertIsInstance(task.driver.raid, - raid.IRMCRAID) - self.assertIsInstance(task.driver.rescue, - agent.AgentRescue) - - def test_override_with_bios_configuration(self, - check_share_fs_mounted_mock): - node = obj_utils.create_test_node( - self.context, driver='irmc', - rescue_interface='agent', - bios_interface='no-bios') - with task_manager.acquire(self.context, node.id) as task: - self.assertIsInstance(task.driver.boot, - irmc.boot.IRMCVirtualMediaBoot) - self.assertIsInstance(task.driver.console, - ipmitool.IPMISocatConsole) - self.assertIsInstance(task.driver.deploy, - agent.AgentDeploy) - self.assertIsInstance(task.driver.inspect, - irmc.inspect.IRMCInspect) - self.assertIsInstance(task.driver.management, - irmc.management.IRMCManagement) - self.assertIsInstance(task.driver.power, - irmc.power.IRMCPower) - self.assertIsInstance(task.driver.bios, - noop.NoBIOS) - self.assertIsInstance(task.driver.rescue, - agent.AgentRescue) - - def test_override_with_boot_configuration(self, - check_share_fs_mounted_mock): - node = obj_utils.create_test_node( - self.context, driver='irmc', - boot_interface='ipxe') - with task_manager.acquire(self.context, node.id) as task: - self.assertIsInstance(task.driver.boot, ipxe.iPXEBoot) diff --git a/ironic/tests/unit/drivers/test_utils.py b/ironic/tests/unit/drivers/test_utils.py index 74562ed20a..d715734c61 100644 --- a/ironic/tests/unit/drivers/test_utils.py +++ b/ironic/tests/unit/drivers/test_utils.py @@ -556,11 +556,6 @@ def test_default_verify_is_unspecified(self): 'config_group': 'drac', 'driver_info_key': 'redfish_verify_ca', }, - { - 'driver': 'irmc', - 'config_group': 'irmc', - 'driver_info_key': 'irmc_verify_ca', - }, { 'driver': 'redfish', 'config_group': 'redfish', @@ -588,10 +583,6 @@ def test_default_verify_is_specified(self): 'driver': 'idrac', 'config_group': 'redfish', }, - { - 'driver': 'irmc', - 'config_group': 'irmc', - }, { 'driver': 'redfish', 'config_group': 'redfish', diff --git a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py index f61ce42b29..f5fe587c95 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mock_specs.py +++ b/ironic/tests/unit/drivers/third_party_driver_mock_specs.py @@ -30,56 +30,6 @@ 'error', ) -# scciclient -SCCICLIENT_SPEC = ( - 'irmc', -) -SCCICLIENT_IRMC_SCCI_SPEC = ( - 'POWER_OFF', - 'POWER_ON', - 'POWER_RESET', - 'POWER_SOFT_CYCLE', - 'POWER_SOFT_OFF', - 'MOUNT_CD', - 'POWER_RAISE_NMI', - 'UNMOUNT_CD', - 'MOUNT_FD', - 'UNMOUNT_FD', - 'SCCIError', - 'SCCIClientError', - 'SCCIError', - 'SCCIInvalidInputError', - 'get_share_type', - 'get_client', - 'get_report', - 'get_sensor_data', - 'get_virtual_cd_set_params_cmd', - 'get_virtual_fd_set_params_cmd', - 'get_essential_properties', - 'get_capabilities_properties', - 'get_irmc_version_str', -) -SCCICLIENT_IRMC_ELCM_SPEC = ( - 'backup_bios_config', - 'elcm_request', - 'restore_bios_config', - 'set_secure_boot_mode', -) - -SCCICLIENT_VIOM_SPEC = ( - 'validate_physical_port_id', - 'VIOMConfiguration', -) - -SCCICLIENT_VIOM_CONF_SPEC = ( - 'set_lan_port', - 'set_iscsi_volume', - 'set_fc_volume', - 'apply', - 'dump_json', - 'terminate', -) - REDFISH_SPEC = ( 'redfish', ) diff --git a/ironic/tests/unit/drivers/third_party_driver_mocks.py b/ironic/tests/unit/drivers/third_party_driver_mocks.py index d7c52c8422..209d6130af 100644 --- a/ironic/tests/unit/drivers/third_party_driver_mocks.py +++ b/ironic/tests/unit/drivers/third_party_driver_mocks.py @@ -25,7 +25,6 @@ - proliantutils - pysnmp -- scciclient """ import importlib @@ -96,39 +95,6 @@ importlib.reload(sys.modules['ironic.drivers.modules.snmp']) -# attempt to load the external 'scciclient' library, which is required by -# the optional drivers.modules.irmc module -scciclient = importutils.try_import('scciclient') -if not scciclient: - mock_scciclient = mock.MagicMock(spec_set=mock_specs.SCCICLIENT_SPEC) - sys.modules['scciclient'] = mock_scciclient - sys.modules['scciclient.irmc'] = mock_scciclient.irmc - sys.modules['scciclient.irmc.scci'] = mock.MagicMock( - spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC, - POWER_OFF=mock.sentinel.POWER_OFF, - POWER_ON=mock.sentinel.POWER_ON, - POWER_RESET=mock.sentinel.POWER_RESET, - MOUNT_CD=mock.sentinel.MOUNT_CD, - UNMOUNT_CD=mock.sentinel.UNMOUNT_CD, - MOUNT_FD=mock.sentinel.MOUNT_FD, - UNMOUNT_FD=mock.sentinel.UNMOUNT_FD) - sys.modules['scciclient.irmc.elcm'] = mock.MagicMock( - spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC) - - -# if anything has loaded the iRMC driver yet, reload it now that the -# external library has been mocked -if 'ironic.drivers.modules.irmc' in sys.modules: - importlib.reload(sys.modules['ironic.drivers.modules.irmc']) - - -# install mock object to prevent the irmc-virtual-media boot interface from -# checking whether NFS/CIFS share file system is mounted or not. -irmc_boot = importutils.import_module( - 'ironic.drivers.modules.irmc.boot') -irmc_boot.check_share_fs_mounted_orig = irmc_boot.check_share_fs_mounted - - # attempt to load the external 'networking_generic_switch' library, which is # required by the optional drivers.modules.switch.generic_switch module networking_generic_switch = importutils.try_import('networking_generic_switch') diff --git a/pyproject.toml b/pyproject.toml index efae2c39d0..efba0a6393 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,6 @@ none = "ironic.dhcp.none:NoneDHCPApi" fake = "ironic.drivers.modules.fake:FakeBIOS" idrac-redfish = "ironic.drivers.modules.drac.bios:DracRedfishBIOS" ilo = "ironic.drivers.modules.ilo.bios:IloBIOS" -irmc = "ironic.drivers.modules.irmc.bios:IRMCBIOS" no-bios = "ironic.drivers.modules.noop:NoBIOS" redfish = "ironic.drivers.modules.redfish.bios:RedfishBIOS" @@ -62,8 +61,6 @@ ilo-ipxe = "ironic.drivers.modules.ilo.boot:IloiPXEBoot" ilo-virtual-media = "ironic.drivers.modules.ilo.boot:IloVirtualMediaBoot" ilo-uefi-https = "ironic.drivers.modules.ilo.boot:IloUefiHttpsBoot" ipxe = "ironic.drivers.modules.ipxe:iPXEBoot" -irmc-pxe = "ironic.drivers.modules.irmc.boot:IRMCPXEBoot" -irmc-virtual-media = "ironic.drivers.modules.irmc.boot:IRMCVirtualMediaBoot" pxe = "ironic.drivers.modules.pxe:PXEBoot" redfish-virtual-media = "ironic.drivers.modules.redfish.boot:RedfishVirtualMediaBoot" redfish-https = "ironic.drivers.modules.redfish.boot:RedfishHttpsBoot" @@ -98,7 +95,6 @@ agent = "ironic.drivers.modules.inspector:AgentInspect" fake = "ironic.drivers.modules.fake:FakeInspect" idrac-redfish = "ironic.drivers.modules.drac.inspect:DracRedfishInspect" ilo = "ironic.drivers.modules.ilo.inspect:IloInspect" -irmc = "ironic.drivers.modules.irmc.inspect:IRMCInspect" no-inspect = "ironic.drivers.modules.noop:NoInspect" redfish = "ironic.drivers.modules.redfish.inspect:RedfishInspect" @@ -109,7 +105,6 @@ ilo = "ironic.drivers.modules.ilo.management:IloManagement" ilo5 = "ironic.drivers.modules.ilo.management:Ilo5Management" intel-ipmitool = "ironic.drivers.modules.intel_ipmi.management:IntelIPMIManagement" ipmitool = "ironic.drivers.modules.ipmitool:IPMIManagement" -irmc = "ironic.drivers.modules.irmc.management:IRMCManagement" noop = "ironic.drivers.modules.noop_mgmt:NoopManagement" redfish = "ironic.drivers.modules.redfish.management:RedfishManagement" @@ -125,7 +120,6 @@ fake = "ironic.drivers.modules.fake:FakePower" idrac-redfish = "ironic.drivers.modules.drac.power:DracRedfishPower" ilo = "ironic.drivers.modules.ilo.power:IloPower" ipmitool = "ironic.drivers.modules.ipmitool:IPMIPower" -irmc = "ironic.drivers.modules.irmc.power:IRMCPower" redfish = "ironic.drivers.modules.redfish.power:RedfishPower" snmp = "ironic.drivers.modules.snmp:SNMPPower" @@ -134,7 +128,6 @@ agent = "ironic.drivers.modules.agent:AgentRAID" fake = "ironic.drivers.modules.fake:FakeRAID" idrac-redfish = "ironic.drivers.modules.drac.raid:DracRedfishRAID" ilo5 = "ironic.drivers.modules.ilo.raid:Ilo5RAID" -irmc = "ironic.drivers.modules.irmc.raid:IRMCRAID" no-raid = "ironic.drivers.modules.noop:NoRAID" redfish = "ironic.drivers.modules.redfish.raid:RedfishRAID" @@ -153,7 +146,6 @@ external = "ironic.drivers.modules.storage.external:ExternalStorage" fake = "ironic.drivers.modules.fake:FakeVendorB" idrac-redfish = "ironic.drivers.modules.drac.vendor_passthru:DracRedfishVendorPassthru" ilo = "ironic.drivers.modules.ilo.vendor:VendorPassthru" -irmc = "ironic.drivers.modules.irmc.vendor:IRMCVendorPassthru" ipmitool = "ironic.drivers.modules.ipmitool:VendorPassthru" no-vendor = "ironic.drivers.modules.noop:NoVendor" redfish = "ironic.drivers.modules.redfish.vendor:RedfishVendorPassthru" @@ -165,7 +157,6 @@ ilo = "ironic.drivers.ilo:IloHardware" ilo5 = "ironic.drivers.ilo:Ilo5Hardware" intel-ipmi = "ironic.drivers.intel_ipmi:IntelIPMIHardware" ipmi = "ironic.drivers.ipmi:IPMIHardware" -irmc = "ironic.drivers.irmc:IRMCHardware" manual-management = "ironic.drivers.generic:ManualManagementHardware" redfish = "ironic.drivers.redfish:RedfishHardware" snmp = "ironic.drivers.snmp:SNMPHardware" diff --git a/releasenotes/notes/remove-irmc-driver-a1b2c3d4e5f6a7b8.yaml b/releasenotes/notes/remove-irmc-driver-a1b2c3d4e5f6a7b8.yaml new file mode 100644 index 0000000000..54ba6a24f1 --- /dev/null +++ b/releasenotes/notes/remove-irmc-driver-a1b2c3d4e5f6a7b8.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The iRMC hardware type and all its interfaces have been removed. + Users of the iRMC driver should migrate to a supported hardware + type, preferably ``redfish``. From e78ad987a3c74bd6ba794ea0d3c173e95db684f4 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Thu, 26 Mar 2026 10:19:22 -0500 Subject: [PATCH 02/24] make target_datetime optional for set_bmc_clock step Allow the ``set_bmc_clock`` management setup in the redfish interface to set the time to the conductor if the time is not explicitly provided. This makes it possible to call this step and get an accurate time. For Ironic to manage the BMC, the time must be inline with the conductor so this is a better reasonable default. This also makes it possible to use the ``set_bmc_clock`` in runbooks, as originally intended, because users cannot supply a time for the runbooks without this fix the runbook could only ever set the time to a fixed value which isn't useful. Closes-Bug: 2146355 Change-Id: I54ce06b7025f174946b957df2c6fc2bedff11c37 Signed-off-by: Doug Goldstein --- doc/source/admin/cleaning.rst | 12 ++++++++ doc/source/admin/steps.rst | 3 +- ironic/drivers/modules/redfish/management.py | 15 +++++++--- .../modules/redfish/test_management.py | 28 +++++++++++++++++++ ...edfish-set-bmc-clock-97a7132356f30485.yaml | 12 ++++++++ 5 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/redfish-set-bmc-clock-97a7132356f30485.yaml diff --git a/doc/source/admin/cleaning.rst b/doc/source/admin/cleaning.rst index 8d1e07064f..b8178d370b 100644 --- a/doc/source/admin/cleaning.rst +++ b/doc/source/admin/cleaning.rst @@ -378,6 +378,18 @@ An example is setting the BMC clock using the Redfish management interface:: }] } +If ``target_datetime`` is omitted, Ironic uses the current UTC time on the +conductor when the step executes. This makes the step suitable for automated +cleaning runbooks without hardcoding a timestamp:: + + { + "target": "clean", + "clean_steps": [{ + "interface": "management", + "step": "set_bmc_clock" + }] + } + This step requires the node to use the ``redfish`` management interface and that the Redfish service exposes the ``DateTime`` and ``DateTimeLocalOffset`` fields under the Manager Resource. diff --git a/doc/source/admin/steps.rst b/doc/source/admin/steps.rst index 975fb578c4..9e8e1a7600 100644 --- a/doc/source/admin/steps.rst +++ b/doc/source/admin/steps.rst @@ -175,4 +175,5 @@ node registration, before inspection and deployment. is not interruptible. - This is different from the manual ``clean`` step ``set_bmc_clock`` - which allows explicit datatime setting through the API. + which allows explicit datetime setting through the API, but also defaults + to the current conductor UTC time when ``target_datetime`` is omitted. diff --git a/ironic/drivers/modules/redfish/management.py b/ironic/drivers/modules/redfish/management.py index c2a8ace4e7..8b43dd0084 100644 --- a/ironic/drivers/modules/redfish/management.py +++ b/ironic/drivers/modules/redfish/management.py @@ -580,8 +580,10 @@ def _sensor2dict(resource, *fields): @base.clean_step(priority=0, abortable=False, argsinfo={ 'target_datetime': { - 'description': 'The datetime to set in ISO8601 format', - 'required': True + 'description': ('The datetime to set in ISO8601 format. If ' + 'omitted, the current conductor UTC time is ' + 'used.'), + 'required': False }, 'datetime_local_offset': { 'description': 'The local time offset from UTC', @@ -589,17 +591,22 @@ def _sensor2dict(resource, *fields): } }) @task_manager.require_exclusive_lock - def set_bmc_clock(self, task, target_datetime, datetime_local_offset=None): + def set_bmc_clock(self, task, target_datetime=None, + datetime_local_offset=None): """Set the BMC clock using Redfish Manager resource. :param task: a TaskManager instance containing the node to act on. - :param target_datetime: The datetime to set in ISO8601 format + :param target_datetime: The datetime to set in ISO8601 format. + Defaults to the current conductor UTC time. :param datetime_local_offset: The local time offset from UTC (optional) :raises: RedfishError if the operation fails """ try: system = redfish_utils.get_system(task.node) manager = redfish_utils.get_manager(task.node, system) + if target_datetime is None: + target_datetime = timeutils.utcnow().replace( + tzinfo=timezone.utc).isoformat() # if the Redfish manager interface does not have microseconds, # we cannot send microseconds in our update so strip them out if not parser.isoparse(manager.datetime).microsecond: diff --git a/ironic/tests/unit/drivers/modules/redfish/test_management.py b/ironic/tests/unit/drivers/modules/redfish/test_management.py index 5d45850e34..a045a4179e 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_management.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_management.py @@ -660,6 +660,34 @@ def test_set_bmc_clock_success(self, mock_get_system, mock_get_manager): target_datetime, None) mock_manager.refresh.assert_called_once() + @mock.patch.object(redfish_utils, 'get_manager', autospec=True) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) + def test_set_bmc_clock_defaults_to_conductor_time(self, mock_get_system, + mock_get_manager): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + current_datetime = datetime.datetime( + 2025, 6, 27, 12, 0, 0, 123456) + expected_datetime = current_datetime.replace( + tzinfo=datetime.timezone.utc, + microsecond=0).isoformat() + + mock_system = mock.Mock() + mock_manager = mock.Mock() + mock_manager.datetime = '2025-06-27T12:00:00+00:00' + + mock_get_system.return_value = mock_system + mock_get_manager.return_value = mock_manager + + with mock.patch.object(redfish_mgmt.timeutils, 'utcnow', + autospec=True, + return_value=current_datetime): + task.driver.management.set_bmc_clock(task) + + mock_manager.set_datetime.assert_called_once_with( + expected_datetime, None) + mock_manager.refresh.assert_called_once() + @mock.patch.object(redfish_utils, 'get_manager', autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True) def test_set_bmc_clock_datetime_mismatch_raises(self, mock_get_system, diff --git a/releasenotes/notes/redfish-set-bmc-clock-97a7132356f30485.yaml b/releasenotes/notes/redfish-set-bmc-clock-97a7132356f30485.yaml new file mode 100644 index 0000000000..d0a8bddc3e --- /dev/null +++ b/releasenotes/notes/redfish-set-bmc-clock-97a7132356f30485.yaml @@ -0,0 +1,12 @@ +--- +fixes: + - | + Allow the ``set_bmc_clock`` management setup in the redfish interface to + set the time to the conductor if the time is not explicitly provided. This + makes it possible to call this step and get an accurate time. For Ironic to + manage the BMC, the time must be inline with the conductor so this is a + better reasonable default. This also makes it possible to use the ``set_bmc_clock`` + in runbooks, as originally intended, because users cannot supply a time + for the runbooks without this fix the runbook could only ever set the time + to a fixed value which isn't useful. + Fixes `bug 2146355 `_. From 2df342dc4e840acf068ef0c0fda0ca7d770d562c Mon Sep 17 00:00:00 2001 From: MahnoorAsghar Date: Tue, 24 Mar 2026 18:17:33 +0100 Subject: [PATCH 03/24] Soft power off before ejecting virtual media regardless of CONF.inspector.power_off Signed-off-by: Mahnoor Asghar Change-Id: I5dd5161a913a345b670e763bd95268f1d5098669 --- ironic/conf/inspector.py | 5 ++- ironic/drivers/modules/inspector/interface.py | 40 +++++++++++-------- ...-ejection-inspection-367bae07c5d5fc77.yaml | 11 +++++ 3 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/soft-power-off-before-vmedia-ejection-inspection-367bae07c5d5fc77.yaml diff --git a/ironic/conf/inspector.py b/ironic/conf/inspector.py index d59ddf7859..21b038f747 100644 --- a/ironic/conf/inspector.py +++ b/ironic/conf/inspector.py @@ -58,7 +58,10 @@ cfg.BoolOpt('power_off', default=True, help=_('whether to power off a node after inspection ' 'finishes. Ignored for nodes that have fast ' - 'track mode enabled.')), + 'track mode enabled. Note that the node may be powered ' + 'off at the end of inspection for other reasons, e.g. ' + 'for safe ejection of virtual media without corrupting ' + 'the filesystem.')), cfg.StrOpt('callback_endpoint_override', deprecated_for_removal=True, deprecated_reason=_('This option was used by inspector ' diff --git a/ironic/drivers/modules/inspector/interface.py b/ironic/drivers/modules/inspector/interface.py index 007dc5e293..cfab356201 100644 --- a/ironic/drivers/modules/inspector/interface.py +++ b/ironic/drivers/modules/inspector/interface.py @@ -33,30 +33,37 @@ def tear_down_managed_boot(task, always_power_off=False): errors = [] ironic_manages_boot = utils.pop_node_nested_field( task.node, 'driver_internal_info', _IRONIC_MANAGES_BOOT) - power_off_done = False if ironic_manages_boot: - # First, perform a graceful shutdown BEFORE ejecting virtual media - # to avoid filesystem corruption errors on the node. The OS needs - # access to the virtual media to shut down cleanly. - if (CONF.inspector.power_off - and not utils.fast_track_enabled(task.node) - and not task.node.disable_power_off): + # If fast_track is truly active (config and agent is alive), we skip + # cleanup entirely to allow the ramdisk to be reused for the next + # operation. Similarly, if disable_power_off is set, the user wants + # the node to stay running, so we can't eject media from under the + # running OS. + # If neither condition applies, we must do soft power off before + # ejecting media to avoid filesystem corruption. + if cond_utils.is_fast_track(task) or task.node.disable_power_off: + LOG.debug('Skipping inspection cleanup for node %s (fast_track=%s,' + ' disable_power_off=%s)', task.node.uuid, + cond_utils.is_fast_track(task), + task.node.disable_power_off) + else: try: + LOG.info('Performing soft power off for node %s before ' + 'ejecting virtual media', task.node.uuid) cond_utils.node_power_action(task, states.SOFT_POWER_OFF) - power_off_done = True except Exception as exc: errors.append(_('unable to power off the node: %s') % exc) LOG.exception('Unable to power off node %s for inspection', task.node.uuid) - # Now it's safe to eject virtual media since the OS has shut down - try: - task.driver.boot.clean_up_ramdisk(task) - except Exception as exc: - errors.append(_('unable to clean up ramdisk boot: %s') % exc) - LOG.exception('Unable to clean up ramdisk boot for node %s', - task.node.uuid) + try: + task.driver.boot.clean_up_ramdisk(task) + except Exception as exc: + errors.append(_('unable to clean up ramdisk boot: %s') % exc) + LOG.exception('Unable to clean up ramdisk boot for node %s', + task.node.uuid) + try: with cond_utils.power_state_for_network_configuration(task): task.driver.network.remove_inspection_network(task) @@ -67,8 +74,7 @@ def tear_down_managed_boot(task, always_power_off=False): if ((ironic_manages_boot or always_power_off) and CONF.inspector.power_off - and not utils.fast_track_enabled(task.node) - and not power_off_done): + and not utils.fast_track_enabled(task.node)): if task.node.disable_power_off: LOG.debug('Rebooting node %s instead of powering it off because ' 'disable_power_off is set to True', task.node.uuid) diff --git a/releasenotes/notes/soft-power-off-before-vmedia-ejection-inspection-367bae07c5d5fc77.yaml b/releasenotes/notes/soft-power-off-before-vmedia-ejection-inspection-367bae07c5d5fc77.yaml new file mode 100644 index 0000000000..3704ba3c92 --- /dev/null +++ b/releasenotes/notes/soft-power-off-before-vmedia-ejection-inspection-367bae07c5d5fc77.yaml @@ -0,0 +1,11 @@ +fixes: + - | + Issue a soft power off before ejecting virtual media after inspection, + regardless of the value of the ``[inspector]power_off`` configuration + option. + Powering off before ejecting virtual media is needed to avoid filesystem + corruption errors on nodes (particularly visible on iDRAC consoles) when + the operating system loses access to its boot media during shutdown. This + power off is needed regardless of the value of the + ``[inspector]power_off`` configuration option, which is about whether a + node should be powered off after inspection. From 4ee0cbaf5fa57e4df9469acf93e5f1f862a608ba Mon Sep 17 00:00:00 2001 From: haseeb Date: Fri, 27 Mar 2026 23:05:23 +0530 Subject: [PATCH 04/24] Fix idrac-redfish SKU to serial_number override to run before rule evaluation iDRAC devices report their Service Tag in the Redfish SKU field, which should be treated as the chassis serial number. The existing override corrected serial_number after inspect_hardware had already stored inventory and evaluated inspection rules, so rules matching on serial_number would see the motherboard serial instead of the service tag. Extract system vendor gathering into _get_system_vendor_info so the idrac-redfish inspect interface can override it inline. The SKU is now substituted before inventory is stored and before rules run, removing the redundant get/store round-trip Closes-Bug: #2146585 Change-Id: I6813c71dc02845f106c8df5dea9a17dc6d031909 Assisted-by: Claude Opus 4.6 Signed-off-by: haseeb --- ironic/drivers/modules/drac/inspect.py | 40 +++++------ ironic/drivers/modules/redfish/inspect.py | 36 ++++++---- .../unit/drivers/modules/drac/test_inspect.py | 67 ++++++------------- .../drivers/modules/redfish/test_inspect.py | 34 ++++++++++ .../idrac-redfish-sku-bad1ce14db1b980c.yaml | 11 +++ 5 files changed, 104 insertions(+), 84 deletions(-) create mode 100644 releasenotes/notes/idrac-redfish-sku-bad1ce14db1b980c.yaml diff --git a/ironic/drivers/modules/drac/inspect.py b/ironic/drivers/modules/drac/inspect.py index b8a666f307..33608fcb91 100644 --- a/ironic/drivers/modules/drac/inspect.py +++ b/ironic/drivers/modules/drac/inspect.py @@ -53,29 +53,23 @@ def inspect_hardware(self, task): ethernet_interfaces_mac = list(self._get_mac_address(task).values()) inspect_utils.create_ports_if_not_exist(task, ethernet_interfaces_mac) - # Get the SKU before calling parent - Dell uses SKU for service tag - # which is the actual system serial number, while serial_number - # contains the motherboard serial. - system = redfish_utils.get_system(task.node) - sku = system.sku - - result = super(DracRedfishInspect, self).inspect_hardware(task) - - # Update serial_number to use SKU (Dell service tag) if available - if sku: - inspection_data = inspect_utils.get_inspection_data( - task.node, task.context) - inventory = inspection_data.get('inventory', {}) - system_vendor = inventory.get('system_vendor', {}) - if system_vendor: - system_vendor['serial_number'] = str(sku) - inspect_utils.store_inspection_data(task.node, - inventory, - inspection_data.get( - 'plugin_data', {}), - task.context) - - return result + return super(DracRedfishInspect, self).inspect_hardware(task) + + def _get_system_vendor_info(self, task, system): + """Get system vendor information for Dell systems. + + Overrides the parent to use SKU (Dell service tag) as the + serial number instead of the Redfish SerialNumber field, + which on Dell systems contains the motherboard serial. + + :param task: a TaskManager instance. + :param system: a Redfish system object. + :returns: a dictionary of system vendor information. + """ + system_vendor = super()._get_system_vendor_info(task, system) + if system.sku: + system_vendor['serial_number'] = str(system.sku) + return system_vendor def _get_mac_address(self, task): """Get a list of MAC addresses diff --git a/ironic/drivers/modules/redfish/inspect.py b/ironic/drivers/modules/redfish/inspect.py index 4c88f3c847..3db6e033b8 100644 --- a/ironic/drivers/modules/redfish/inspect.py +++ b/ironic/drivers/modules/redfish/inspect.py @@ -152,19 +152,7 @@ def inspect_hardware(self, task): if pcie_devices: inventory['pci_devices'] = pcie_devices - system_vendor = {} - if system.model: - system_vendor['product_name'] = str(system.model) - - if system.serial_number: - system_vendor['serial_number'] = str(system.serial_number) - - if system.manufacturer: - system_vendor['manufacturer'] = str(system.manufacturer) - - if system.uuid: - system_vendor['system_uuid'] = str(system.uuid) - + system_vendor = self._get_system_vendor_info(task, system) if system_vendor: inventory['system_vendor'] = system_vendor @@ -386,6 +374,28 @@ def _get_storage_controllers(self, task, system): return controllers + def _get_system_vendor_info(self, task, system): + """Get system vendor information. + + :param task: a TaskManager instance. + :param system: a Redfish system object. + :returns: a dictionary of system vendor information. + """ + system_vendor = {} + if system.model: + system_vendor['product_name'] = str(system.model) + + if system.serial_number: + system_vendor['serial_number'] = str(system.serial_number) + + if system.manufacturer: + system_vendor['manufacturer'] = str(system.manufacturer) + + if system.uuid: + system_vendor['system_uuid'] = str(system.uuid) + + return system_vendor + @staticmethod def _enum_to_str(value): """Convert an enum value to string, or return as-is.""" diff --git a/ironic/tests/unit/drivers/modules/drac/test_inspect.py b/ironic/tests/unit/drivers/modules/drac/test_inspect.py index 28466650b2..179e3bf2aa 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_inspect.py +++ b/ironic/tests/unit/drivers/modules/drac/test_inspect.py @@ -180,80 +180,51 @@ def test__get_mac_address_without_ethernet_interfaces(self, shared=True) as task: return_value = task.driver.inspect._get_mac_address(task) self.assertEqual(expected_value, return_value) - @mock.patch.object(inspect_utils, 'store_inspection_data', autospec=True) - @mock.patch.object(inspect_utils, 'get_inspection_data', autospec=True) - @mock.patch.object(redfish_inspect.RedfishInspect, 'inspect_hardware', - autospec=True) - @mock.patch.object(inspect_utils, 'create_ports_if_not_exist', - autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True) - def test_inspect_hardware_sku_as_serial( - self, mock_get_system, mock_create_ports, - mock_parent_inspect, mock_get_data, mock_store_data): + def test_get_system_vendor_info_sku_as_serial(self, mock_get_system): """Test that Dell SKU (service tag) is used as serial_number. Dell systems report the motherboard serial in serial_number and the actual service tag in SKU. This test verifies that iDRAC inspection - uses the SKU as the serial_number in the inspection data. + uses the SKU as the serial_number via _get_system_vendor_info. """ system_mock = self.init_system_mock(mock_get_system.return_value) - # Dell reports motherboard serial in serial_number system_mock.serial_number = 'MOBO123456' - # Dell service tag is in SKU - this is what we want as serial + system_mock.manufacturer = 'Dell Inc.' + system_mock.model = 'PowerEdge R640' + system_mock.uuid = '1234-5678' system_mock.sku = 'DELL-SVC-TAG' - mock_parent_inspect.return_value = states.MANAGEABLE - mock_get_data.return_value = { - 'inventory': { - 'system_vendor': { - 'serial_number': 'MOBO123456', - 'manufacturer': 'Dell Inc.', - 'product_name': 'PowerEdge R640' - } - }, - 'plugin_data': {} - } - with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: - task.driver.inspect._get_mac_address = mock.Mock(return_value={}) - result = task.driver.inspect.inspect_hardware(task) + result = task.driver.inspect._get_system_vendor_info( + task, system_mock) - self.assertEqual(states.MANAGEABLE, result) - # Verify store_inspection_data was called with SKU as serial - mock_store_data.assert_called_once() - call_args = mock_store_data.call_args - inventory = call_args[0][1] - self.assertEqual('DELL-SVC-TAG', - inventory['system_vendor']['serial_number']) + self.assertEqual('DELL-SVC-TAG', result['serial_number']) + self.assertEqual('Dell Inc.', result['manufacturer']) + self.assertEqual('PowerEdge R640', result['product_name']) - @mock.patch.object(redfish_inspect.RedfishInspect, 'inspect_hardware', - autospec=True) - @mock.patch.object(inspect_utils, 'create_ports_if_not_exist', - autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True) - def test_inspect_hardware_no_sku( - self, mock_get_system, mock_create_ports, mock_parent_inspect): - """Test inspection when SKU is not available. + def test_get_system_vendor_info_no_sku(self, mock_get_system): + """Test that original serial_number is preserved when SKU is None. When SKU is not available, the original serial_number from Redfish should be preserved. """ system_mock = self.init_system_mock(mock_get_system.return_value) system_mock.serial_number = 'MOBO123456' + system_mock.manufacturer = 'Dell Inc.' + system_mock.model = 'PowerEdge R640' + system_mock.uuid = '1234-5678' system_mock.sku = None - mock_parent_inspect.return_value = states.MANAGEABLE - with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: - task.driver.inspect._get_mac_address = mock.Mock(return_value={}) - result = task.driver.inspect.inspect_hardware(task) + result = task.driver.inspect._get_system_vendor_info( + task, system_mock) - self.assertEqual(states.MANAGEABLE, result) - # Parent inspect_hardware called but store_inspection_data - # should not be called again since SKU is None - mock_parent_inspect.assert_called_once() + # Original serial_number should be preserved when SKU is None + self.assertEqual('MOBO123456', result['serial_number']) @mock.patch.object(redfish_utils, 'get_system', autospec=True) def test_collect_lldp_data_successful_dell_oem(self, mock_get_system): diff --git a/ironic/tests/unit/drivers/modules/redfish/test_inspect.py b/ironic/tests/unit/drivers/modules/redfish/test_inspect.py index 3a49f57740..504d9701b7 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_inspect.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_inspect.py @@ -790,6 +790,40 @@ def test_get_pxe_port_macs(self): self.assertEqual(expected_properties, task.driver.inspect._get_pxe_port_macs(task)) + def test_get_system_vendor_info(self): + """Test _get_system_vendor_info returns correct vendor data.""" + mock_system = mock.Mock() + mock_system.model = 'PowerEdge R1234' + mock_system.serial_number = '123456' + mock_system.manufacturer = 'Sushy Emulator' + mock_system.uuid = '12345678-1234-1234-1234-12345' + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + result = task.driver.inspect._get_system_vendor_info( + task, mock_system) + + self.assertEqual('PowerEdge R1234', result['product_name']) + self.assertEqual('123456', result['serial_number']) + self.assertEqual('Sushy Emulator', result['manufacturer']) + self.assertEqual('12345678-1234-1234-1234-12345', + result['system_uuid']) + + def test_get_system_vendor_info_empty(self): + """Test _get_system_vendor_info with no vendor data.""" + mock_system = mock.Mock() + mock_system.model = None + mock_system.serial_number = None + mock_system.manufacturer = None + mock_system.uuid = None + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + result = task.driver.inspect._get_system_vendor_info( + task, mock_system) + + self.assertEqual({}, result) + @mock.patch.object(redfish_utils, 'get_enabled_macs', autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True) def test_inspect_hardware_ignore_missing_pcie_devices( diff --git a/releasenotes/notes/idrac-redfish-sku-bad1ce14db1b980c.yaml b/releasenotes/notes/idrac-redfish-sku-bad1ce14db1b980c.yaml new file mode 100644 index 0000000000..dd5b5681b2 --- /dev/null +++ b/releasenotes/notes/idrac-redfish-sku-bad1ce14db1b980c.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + [`Bug 2146585 `_] + iDRAC devices report their Service Tag in the Redfish SKU field, + which should be treated as the chassis serial number. The + idrac-redfish driver already overrode serial_number with the SKU + value, but the fix-up ran after inventory was stored and inspection + rules were evaluated, causing rules to see the wrong serial_number. + The override now happens inline via ``_get_system_vendor_info`` so + the correct value is present before storage and rule evaluation. From f895e18a22939feef5cd5d662a6f357d5c868a91 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Fri, 5 May 2023 14:34:41 +0200 Subject: [PATCH 05/24] UPSTREAM: : add OWNERS and test dockerfile back again --- OWNERS | 18 ++++++++++++++++++ openstack-ironic-tester.Dockerfile | 8 ++++++++ 2 files changed, 26 insertions(+) create mode 100644 OWNERS create mode 100644 openstack-ironic-tester.Dockerfile diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..c547a4f7a3 --- /dev/null +++ b/OWNERS @@ -0,0 +1,18 @@ +approvers: + - derekhiggins + - dtantsur + - elfosardo + - iurygregory + - rhjanders + - zaneb +reviewers: + - derekhiggins + - dtantsur + - elfosardo + - iurygregory + - rhjanders + - zaneb + +# Bugzilla Information +component: "Bare Metal Hardware Provisioning" +subcomponent: "ironic" diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile new file mode 100644 index 0000000000..4bca82e0c3 --- /dev/null +++ b/openstack-ironic-tester.Dockerfile @@ -0,0 +1,8 @@ +FROM ubi9 + +RUN dnf upgrade -y \ + && dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ + && dnf clean all \ + && rm -rf /var/cache/yum \ + && python3 -m pip install tox + From 30f2760d143e99014988e96814785e46b4ac5774 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Wed, 24 May 2023 13:56:46 +0200 Subject: [PATCH 06/24] UPSTREAM: : Do not upgrade test image In general we should not upgrade the base image when building a container as the base image is already rebuilt regularly. Also setting the base image explicitely to one recommended by ART. --- openstack-ironic-tester.Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index 4bca82e0c3..b68e5b5c7c 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,7 +1,6 @@ -FROM ubi9 +FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.14 -RUN dnf upgrade -y \ - && dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ +RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ && dnf clean all \ && rm -rf /var/cache/yum \ && python3 -m pip install tox From 5fb767e077568c6ca6eab8e4fdf6e93b62239abd Mon Sep 17 00:00:00 2001 From: Julia Kreger Date: Tue, 11 Apr 2023 15:59:47 -0700 Subject: [PATCH 07/24] DPU modeling - parent_node DB/Model/API Adds the parent node support and tests in one change including all DB/Model/API changes along with RBAC and basic API tests. * Updates the API version to 1.83 * Adds parent_node and related index to the nodes table. * Adds new API parameters to list by parent node relationship. Depends-On: https://review.opendev.org/c/openstack/ironic/+/883967 Change-Id: I8d64fee7105718199986db4994e13352d639f04f --- api-ref/source/parameters.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 5d00ebe393..a3b8420a4f 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -221,7 +221,7 @@ fields_for_conductor: type: array include_children: description: | - Whether to show child nodes in the node list, which are normally hidden. + Wheter to show child nodes in the node list, which are normally hidden. in: query required: false type: boolean @@ -1540,8 +1540,9 @@ owner: type: string parent_node: description: | - An optional UUID which can be used to denote the "parent" baremetal - node. + A UUID representing a node which is a parent_node to the present + node. If populated, the node will disappear from the normal node + list, as it is *not* intended for *normal* usage directly. in: body required: false type: string From 863f4df3101c47085aa14c8c6e9d33f47bf8b682 Mon Sep 17 00:00:00 2001 From: Julia Kreger Date: Wed, 24 May 2023 15:17:56 -0700 Subject: [PATCH 08/24] follow-up on DPU change api-ref Change-Id: I22c8aae89d24d3ff330f10f1e0d43461fd6e52d4 --- api-ref/source/parameters.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index a3b8420a4f..5d00ebe393 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -221,7 +221,7 @@ fields_for_conductor: type: array include_children: description: | - Wheter to show child nodes in the node list, which are normally hidden. + Whether to show child nodes in the node list, which are normally hidden. in: query required: false type: boolean @@ -1540,9 +1540,8 @@ owner: type: string parent_node: description: | - A UUID representing a node which is a parent_node to the present - node. If populated, the node will disappear from the normal node - list, as it is *not* intended for *normal* usage directly. + An optional UUID which can be used to denote the "parent" baremetal + node. in: body required: false type: string From 78c09825ad475fbbf3b8fb3678277f7e6f60eb77 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Wed, 18 Oct 2023 14:19:09 +0200 Subject: [PATCH 09/24] UPSTREAM: : update base image for OCP 4.15 --- openstack-ironic-tester.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index b68e5b5c7c..40db4857ea 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.14 +FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.15 RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ && dnf clean all \ From cb5b37565b05e8cd180e7744902535197f00d141 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Tue, 12 Dec 2023 17:58:04 +0100 Subject: [PATCH 10/24] UPSTREAM: : add ci-operator config --- .ci-operator.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .ci-operator.yaml diff --git a/.ci-operator.yaml b/.ci-operator.yaml new file mode 100644 index 0000000000..e2fbe81824 --- /dev/null +++ b/.ci-operator.yaml @@ -0,0 +1,4 @@ +build_root_image: + name: release + namespace: openshift + tag: rhel-9-release-golang-1.20-openshift-4.16 From a4ad34c230f1eb144cdae038fd0c496e89b53c5b Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Wed, 13 Dec 2023 16:57:48 +0100 Subject: [PATCH 11/24] UPSTREAM: : install distro pbr --- openstack-ironic-tester.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index 40db4857ea..e568329e43 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,6 +1,7 @@ FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.15 -RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ +RUN curl http://base-4-16-rhel-9-ironic-prevalidation.ocp.svc > /etc/yum.repos.d/base-4-16-rhel-9-ironic-prevalidation.repo \ + && dnf install -y python3-pbr python3-devel python3-pip libpq-devel glibc-langpack-en \ && dnf clean all \ && rm -rf /var/cache/yum \ && python3 -m pip install tox From 268ffd2c7348e4605c5223eb75340c30065cba40 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Thu, 14 Dec 2023 09:37:54 +0100 Subject: [PATCH 12/24] Revert "UPSTREAM: : install distro pbr" --- openstack-ironic-tester.Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index e568329e43..40db4857ea 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,7 +1,6 @@ FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.15 -RUN curl http://base-4-16-rhel-9-ironic-prevalidation.ocp.svc > /etc/yum.repos.d/base-4-16-rhel-9-ironic-prevalidation.repo \ - && dnf install -y python3-pbr python3-devel python3-pip libpq-devel glibc-langpack-en \ +RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ && dnf clean all \ && rm -rf /var/cache/yum \ && python3 -m pip install tox From 5b3555d339da02c3287a38aaea76b0e35a2efae6 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Thu, 14 Dec 2023 10:07:11 +0100 Subject: [PATCH 13/24] UPSTREAM: : fix ci operator config --- .ci-operator.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci-operator.yaml b/.ci-operator.yaml index e2fbe81824..841b62393e 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -1,4 +1,4 @@ build_root_image: - name: release - namespace: openshift - tag: rhel-9-release-golang-1.20-openshift-4.16 + name: builder + namespace: ocp + tag: rhel-9-golang-1.20-openshift-4.15 From a0e3c312f5af9bb1ee2787279b9b47d3436b2a96 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Mon, 18 Mar 2024 14:55:01 +0100 Subject: [PATCH 14/24] UPSTREAM: : update base image for OCP 4.16 --- .ci-operator.yaml | 2 +- openstack-ironic-tester.Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci-operator.yaml b/.ci-operator.yaml index 841b62393e..cec615d8d2 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -1,4 +1,4 @@ build_root_image: name: builder namespace: ocp - tag: rhel-9-golang-1.20-openshift-4.15 + tag: rhel-9-golang-1.20-openshift-4.16 diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index 40db4857ea..8e080b74f1 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.15 +FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.16 RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ && dnf clean all \ From eb919a5966e71500b7a0f0a102a857271bd30a36 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Mon, 5 May 2025 14:25:09 +0200 Subject: [PATCH 15/24] UPSTREAM: : pin upper-constraints We need to pin libraries that are still compatible with python 3.9 as upstream has already dropped compatibility. --- openstack-ironic-tester.Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index 8e080b74f1..b79169185f 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,5 +1,7 @@ FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.16 +ENV TOX_CONSTRAINTS_FILE="https://releases.openstack.org/constraints/upper/2025.1" + RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ && dnf clean all \ && rm -rf /var/cache/yum \ From b91f7430597f5dc9f3a2a27f21ab9e0d48c63ef3 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Fri, 9 May 2025 11:02:58 +0200 Subject: [PATCH 16/24] UPSTREAM: : update base image for tests --- openstack-ironic-tester.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index b79169185f..ae6e7fed26 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ci.openshift.org/ocp/builder:rhel-9-base-openshift-4.16 +FROM registry.ci.openshift.org/ocp/4.20:base-rhel9 ENV TOX_CONSTRAINTS_FILE="https://releases.openstack.org/constraints/upper/2025.1" From a5d95f211a56daa38b00230652ff306456cd1386 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Fri, 9 May 2025 11:47:08 +0200 Subject: [PATCH 17/24] UPSTREAM: : Run tests using Python 3.12 --- openstack-ironic-tester.Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index ae6e7fed26..0ab81d5f01 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,9 +1,7 @@ FROM registry.ci.openshift.org/ocp/4.20:base-rhel9 -ENV TOX_CONSTRAINTS_FILE="https://releases.openstack.org/constraints/upper/2025.1" - -RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ +RUN dnf install -y python3.12-devel python3.12-pip libpq-devel glibc-langpack-en \ && dnf clean all \ && rm -rf /var/cache/yum \ - && python3 -m pip install tox + && python3.12 -m pip install tox From 4d421aed34528c20aa6b3c777dead5db13671084 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Fri, 11 Jul 2025 14:20:36 +0200 Subject: [PATCH 18/24] UPSTREAM: : unpin upper-constraints --- openstack-ironic-tester.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index 0ab81d5f01..d14c065939 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,6 +1,6 @@ FROM registry.ci.openshift.org/ocp/4.20:base-rhel9 -RUN dnf install -y python3.12-devel python3.12-pip libpq-devel glibc-langpack-en \ +RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ && dnf clean all \ && rm -rf /var/cache/yum \ && python3.12 -m pip install tox From cc4192913c18425d593ce6d98240c517659f56d1 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Fri, 9 May 2025 11:47:08 +0200 Subject: [PATCH 19/24] UPSTREAM: : Run tests using Python 3.12 (cherry picked from commit aa7dfab64655324e4a8979004937c26ff740d2c2) --- openstack-ironic-tester.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index d14c065939..0ab81d5f01 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,6 +1,6 @@ FROM registry.ci.openshift.org/ocp/4.20:base-rhel9 -RUN dnf install -y python3-devel python3-pip libpq-devel glibc-langpack-en \ +RUN dnf install -y python3.12-devel python3.12-pip libpq-devel glibc-langpack-en \ && dnf clean all \ && rm -rf /var/cache/yum \ && python3.12 -m pip install tox From 72f451000baac7ba039a4647961e78e50a8b65da Mon Sep 17 00:00:00 2001 From: Julia Kreger Date: Fri, 8 Aug 2025 14:37:06 -0700 Subject: [PATCH 20/24] Fix the ability to escape service fail Back when we developed service, we expected operators to iterate to fix their issues, but we also put in abort code. We just never wired in the abort code to the abort verb. It really seems like we really should have done that, and this change changes API and Conductor code path to make this happen. Closes-Bug: 2119989 Assisted-By: Claude Clode - Claude Sonnet 4 Change-Id: Ic02ba87485a676e77563057427ab94953bea2cc2 Signed-off-by: Julia Kreger (cherry picked from commit 1eda80766c321c607e69af02bfbeee8943ab5df2) --- ironic/conductor/manager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py index 9c0b697628..b52e77dce1 100644 --- a/ironic/conductor/manager.py +++ b/ironic/conductor/manager.py @@ -1440,8 +1440,6 @@ def do_provisioning_action(self, context, node_id, action): states.CLEANHOLD, states.DEPLOYWAIT, states.DEPLOYHOLD, - states.SERVICEWAIT, - states.SERVICEHOLD, states.SERVICEFAIL)): self._do_abort(task) return From 755203e2a20320c39123d5eb5657c86a7e32eb5a Mon Sep 17 00:00:00 2001 From: Jacob Anders Date: Wed, 13 Aug 2025 10:50:36 +1000 Subject: [PATCH 21/24] Fix servicing abort to respect abortable flag Currently, Ironic codebase allows aborting servicing state regardless of whether a servicing step has abortable flag set or not. This patch fixes this by adding handling of service wait states to abort code paths and adding the missing state machine transition. Generated-By: Claude Code Sonnet 3.5 Change-Id: Ie07490bdb9c6461bd6ac7a6315773dcfb13592f9 Signed-off-by: Jacob Anders (cherry picked from commit fa8a8fbb4b57db45007d1ff967d73a6d33cea4e9) --- ironic/conductor/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ironic/conductor/manager.py b/ironic/conductor/manager.py index b52e77dce1..9c0b697628 100644 --- a/ironic/conductor/manager.py +++ b/ironic/conductor/manager.py @@ -1440,6 +1440,8 @@ def do_provisioning_action(self, context, node_id, action): states.CLEANHOLD, states.DEPLOYWAIT, states.DEPLOYHOLD, + states.SERVICEWAIT, + states.SERVICEHOLD, states.SERVICEFAIL)): self._do_abort(task) return From f81af6e77cad4b1d48bc631ee0b8544c36ea1445 Mon Sep 17 00:00:00 2001 From: Iury Gregory Melo Ferreira Date: Mon, 8 Sep 2025 01:03:16 -0300 Subject: [PATCH 22/24] UPSTREAM: : update base image for tests --- openstack-ironic-tester.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index 0ab81d5f01..9526021dfd 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ci.openshift.org/ocp/4.20:base-rhel9 +FROM registry.ci.openshift.org/ocp/4.21:base-rhel9 RUN dnf install -y python3.12-devel python3.12-pip libpq-devel glibc-langpack-en \ && dnf clean all \ From 9c03a76737697c0fa94cfa8ea9a7f8905cc145e4 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Tue, 20 Jan 2026 11:46:09 +0100 Subject: [PATCH 23/24] UPSTREAM: : Update test base image for 4.22 --- openstack-ironic-tester.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstack-ironic-tester.Dockerfile b/openstack-ironic-tester.Dockerfile index 9526021dfd..6657b7d99a 100644 --- a/openstack-ironic-tester.Dockerfile +++ b/openstack-ironic-tester.Dockerfile @@ -1,4 +1,4 @@ -FROM registry.ci.openshift.org/ocp/4.21:base-rhel9 +FROM registry.ci.openshift.org/ocp/4.22:base-rhel9 RUN dnf install -y python3.12-devel python3.12-pip libpq-devel glibc-langpack-en \ && dnf clean all \ From c344f6bad1c59e91f6e35ca034f896c2761c7f4f Mon Sep 17 00:00:00 2001 From: Jacob Anders Date: Thu, 22 Jan 2026 15:08:12 +0100 Subject: [PATCH 24/24] UPSTREAM: : Update username user by Jacob. --- OWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OWNERS b/OWNERS index c547a4f7a3..c9ffd11dbc 100644 --- a/OWNERS +++ b/OWNERS @@ -3,14 +3,14 @@ approvers: - dtantsur - elfosardo - iurygregory - - rhjanders + - jacob-anders - zaneb reviewers: - derekhiggins - dtantsur - elfosardo - iurygregory - - rhjanders + - jacob-anders - zaneb # Bugzilla Information