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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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. 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 `_. 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``. 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.