diff options
107 files changed, 1155 insertions, 2136 deletions
diff --git a/devstack/lib/ironic b/devstack/lib/ironic index 90842cfaf..ab96638c0 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -681,12 +681,6 @@ if [[ "$IRONIC_BOOT_MODE" == "uefi" ]]; then fi fi -# TODO(dtantsur): change this when we change the default value. -IRONIC_DEFAULT_BOOT_OPTION=${IRONIC_DEFAULT_BOOT_OPTION:-local} -if [ $IRONIC_DEFAULT_BOOT_OPTION != "netboot" ] && [ $IRONIC_DEFAULT_BOOT_OPTION != "local" ]; then - die $LINENO "Supported values for IRONIC_DEFAULT_BOOT_OPTION are 'netboot' and 'local' only." -fi - # TODO(pas-ha) find a way to (cross-)sign the custom CA bundle used by tls-proxy # with default iPXE cert - for reference see http://ipxe.org/crypto if is_service_enabled tls-proxy && [[ "$IRONIC_IPXE_USE_SWIFT" == "True" ]]; then @@ -1855,8 +1849,6 @@ function configure_ironic_conductor { iniset $IRONIC_CONF_FILE dhcp dhcp_provider $IRONIC_DHCP_PROVIDER - iniset $IRONIC_CONF_FILE deploy default_boot_option $IRONIC_DEFAULT_BOOT_OPTION - isolinux=$(find -L /usr -type f -name "isolinux.bin" | head -1) if [[ -n "$isolinux" ]]; then iniset $IRONIC_CONF_FILE DEFAULT isolinux_bin "$isolinux" @@ -2903,8 +2895,7 @@ function upload_image_if_needed { # Change the default image only if the provided settings prevent the # default cirros image from working. - if [[ "$IRONIC_TEMPEST_WHOLE_DISK_IMAGE" != True \ - && "$IRONIC_DEFAULT_BOOT_OPTION" == local ]]; then + if [[ "$IRONIC_TEMPEST_WHOLE_DISK_IMAGE" != True ]]; then IRONIC_IMAGE_NAME=$IRONIC_PARTITIONED_IMAGE_NAME DEFAULT_IMAGE_NAME=$IRONIC_IMAGE_NAME fi @@ -3242,7 +3233,9 @@ function ironic_configure_tempest { # Driver for API tests iniset $TEMPEST_CONFIG baremetal driver fake-hardware - iniset $TEMPEST_CONFIG baremetal default_boot_option $IRONIC_DEFAULT_BOOT_OPTION + # NOTE(dtantsur): remove this when the tempest plugin no longer supports + # netboot (i.e. when Zed is the oldest supported branch). + iniset $TEMPEST_CONFIG baremetal default_boot_option local local adjusted_root_disk_size_gb if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then diff --git a/doc/source/admin/adoption.rst b/doc/source/admin/adoption.rst index 570b36072..3a9d1d14a 100644 --- a/doc/source/admin/adoption.rst +++ b/doc/source/admin/adoption.rst @@ -135,8 +135,7 @@ from the ``manageable`` state to ``active`` state:: baremetal port create <node_mac_address> --node <node_uuid> baremetal node set testnode \ - --instance-info image_source="http://localhost:8080/blankimage" \ - --instance-info capabilities="{\"boot_option\": \"local\"}" + --instance-info image_source="http://localhost:8080/blankimage" baremetal node manage testnode --wait diff --git a/doc/source/admin/anaconda-deploy-interface.rst b/doc/source/admin/anaconda-deploy-interface.rst index e715a1b23..2c686506a 100644 --- a/doc/source/admin/anaconda-deploy-interface.rst +++ b/doc/source/admin/anaconda-deploy-interface.rst @@ -187,7 +187,8 @@ as it accounts for the particular stages and appropriate callbacks to Ironic. .. warning:: - The default template expects a ``instance_info\liveimg_url`` setting to + The default template (for the kickstart 'liveimg' command) expects an + ``instance_info\image_info`` setting to be provided by the user, which serves as a base operating system image. In the context of the anaconda driver, it should be thought of almost like "stage3". If you're using a custom template, it may not be required, @@ -201,12 +202,13 @@ Ironic. --instance_info ks_template=<URL> If you do choose to use a liveimg with a customized template, or if you wish -to use the stock template with a liveimg, you will need to provide parameter. +to use the stock template with a liveimg, you will need to provide this +setting. .. code-block:: shell baremetal node set <node> \ - --instance_info liveimg_url=<URL> + --instance_info image_info=<URL> .. warning:: This is required if you do *not* utilize a customised template. As in use diff --git a/doc/source/admin/dhcp-less.rst b/doc/source/admin/dhcp-less.rst index 089829e25..a14c2499d 100644 --- a/doc/source/admin/dhcp-less.rst +++ b/doc/source/admin/dhcp-less.rst @@ -127,3 +127,14 @@ the service catalog or configured in the ``[service_catalog]`` section: [deploy] external_callback_url = <Bare Metal API URL with a routable IP address> + + +In case you need specific URLs for each node, you can use the +``driver_info[external_http_url]`` node property. When used it overrides the +``[deploy]http_url`` and ``[deploy]external_http_url`` settings in the +configuration file. + +.. code-block:: bash + + baremetal node set node-0 \ + --driver-info external_http_url="<your_node_external_url>" diff --git a/doc/source/admin/drivers/ilo.rst b/doc/source/admin/drivers/ilo.rst index 7a2fae8bc..f764a6d89 100644 --- a/doc/source/admin/drivers/ilo.rst +++ b/doc/source/admin/drivers/ilo.rst @@ -296,13 +296,8 @@ Node configuration - ``deploy_iso``: The glance UUID of the deploy ramdisk ISO image. - ``instance info/boot_iso`` property to be either boot iso - Glance UUID or a HTTP(S) URL. This is optional property and is used when - ``boot_option`` is set to ``netboot`` or ``ramdisk``. - - .. note:: - When ``boot_option`` is set to ``ramdisk``, the ironic node must be - configured to use ``ramdisk`` deploy interface. See :ref:`ramdisk-deploy` - for details. + Glance UUID or a HTTP(S) URL. This is optional property and is used with + :doc:`/admin/ramdisk-boot`. .. note:: The ``boot_iso`` property used to be called ``ilo_boot_iso`` before @@ -1043,9 +1038,7 @@ to send management information and images in encrypted channel over HTTPS. Deploy Process ~~~~~~~~~~~~~~ -Please refer to `Netboot in swiftless deploy for intermediate images`_ for -partition image support and `Localboot in swiftless deploy for intermediate images`_ -for whole disk image support. +Please refer to `Swiftless deploy for intermediate images`_. HTTP(S) Based Deploy Support ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1063,8 +1056,7 @@ must be reachable by the conductor and the bare metal nodes. Deploy Process ~~~~~~~~~~~~~~ -Please refer to `Netboot with HTTP(S) based deploy`_ for partition image boot -and `Localboot with HTTP(S) based deploy`_ for whole disk image boot. +Please refer to `HTTP(S) based deploy`_. Support for iLO driver with Standalone Ironic @@ -1084,58 +1076,8 @@ intermediate images on conductor as described in Deploy Process ============== -.. note:: - Network boot is deprecated and will be removed in the Zed release. - -.. TODO(dtantsur): review these diagrams to exclude netboot. - -Netboot with glance and swift -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. seqdiag:: - :scale: 80 - - diagram { - Glance; Conductor; Baremetal; Swift; IPA; iLO; - activation = none; - span_height = 1; - edge_length = 250; - default_note_color = white; - default_fontsize = 14; - - Conductor -> iLO [label = "Powers off the node"]; - Conductor -> Glance [label = "Download user image"]; - Conductor -> Glance [label = "Get the metadata for deploy ISO"]; - Conductor -> Conductor [label = "Generates swift tempURL for deploy ISO"]; - Conductor -> Conductor [label = "Creates the FAT32 image containing Ironic API URL and driver name"]; - Conductor -> Swift [label = "Uploads the FAT32 image"]; - Conductor -> Conductor [label = "Generates swift tempURL for FAT32 image"]; - Conductor -> iLO [label = "Attaches the FAT32 image swift tempURL as virtual media floppy"]; - Conductor -> iLO [label = "Attaches the deploy ISO swift tempURL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets one time boot to CDROM"]; - Conductor -> iLO [label = "Reboot the node"]; - iLO -> Swift [label = "Downloads deploy ISO"]; - Baremetal -> iLO [label = "Boots deploy kernel/ramdisk from iLO virtual media CDROM"]; - IPA -> Conductor [label = "Lookup node"]; - Conductor -> IPA [label = "Provides node UUID"]; - IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Sends the user image HTTP(S) URL"]; - IPA -> Swift [label = "Retrieves the user image on bare metal"]; - IPA -> IPA [label = "Writes user image to disk"]; - Conductor -> Conductor [label = "Generates the boot ISO"]; - Conductor -> Swift [label = "Uploads the boot ISO"]; - Conductor -> Conductor [label = "Generates swift tempURL for boot ISO"]; - Conductor -> iLO [label = "Attaches boot ISO swift tempURL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets boot device to CDROM"]; - Conductor -> IPA [label = "Power off the node"]; - Conductor -> iLO [label = "Power on the node"]; - iLO -> Swift [label = "Downloads boot ISO"]; - iLO -> Baremetal [label = "Boots the instance kernel/ramdisk from iLO virtual media CDROM"]; - Baremetal -> Baremetal [label = "Instance kernel finds root partition and continues booting from disk"]; - } - -Localboot with glance and swift for partition images -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Glance and swift for partition images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. seqdiag:: :scale: 80 @@ -1176,8 +1118,8 @@ Localboot with glance and swift for partition images } -Localboot with glance and swift -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Glance and swift with whole-disk images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. seqdiag:: :scale: 80 @@ -1216,52 +1158,8 @@ Localboot with glance and swift Baremetal -> Baremetal [label = "Boot user image from disk"]; } -Netboot in swiftless deploy for intermediate images -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. seqdiag:: - :scale: 80 - - diagram { - Glance; Conductor; Baremetal; ConductorWebserver; IPA; iLO; - activation = none; - span_height = 1; - edge_length = 250; - default_note_color = white; - default_fontsize = 14; - - Conductor -> iLO [label = "Powers off the node"]; - Conductor -> Glance [label = "Download user image"]; - Conductor -> Glance [label = "Get the metadata for deploy ISO"]; - Conductor -> Conductor [label = "Generates swift tempURL for deploy ISO"]; - Conductor -> Conductor [label = "Creates the FAT32 image containing Ironic API URL and driver name"]; - Conductor -> ConductorWebserver [label = "Uploads the FAT32 image"]; - Conductor -> iLO [label = "Attaches the FAT32 image URL as virtual media floppy"]; - Conductor -> iLO [label = "Attaches the deploy ISO swift tempURL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets one time boot to CDROM"]; - Conductor -> iLO [label = "Reboot the node"]; - iLO -> Swift [label = "Downloads deploy ISO"]; - Baremetal -> iLO [label = "Boots deploy kernel/ramdisk from iLO virtual media CDROM"]; - IPA -> Conductor [label = "Lookup node"]; - Conductor -> IPA [label = "Provides node UUID"]; - IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Sends the user image HTTP(S) URL"]; - IPA -> ConductorWebserver [label = "Retrieves the user image on bare metal"]; - IPA -> IPA [label = "Writes user image to root partition"]; - Conductor -> Conductor [label = "Generates the boot ISO"]; - Conductor -> ConductorWebserver [label = "Uploads the boot ISO"]; - Conductor -> iLO [label = "Attaches boot ISO URL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets boot device to CDROM"]; - Conductor -> IPA [label = "Power off the node"]; - Conductor -> iLO [label = "Power on the node"]; - iLO -> ConductorWebserver [label = "Downloads boot ISO"]; - iLO -> Baremetal [label = "Boots the instance kernel/ramdisk from iLO virtual media CDROM"]; - Baremetal -> Baremetal [label = "Instance kernel finds root partition and continues booting from disk"]; - } - - -Localboot in swiftless deploy for intermediate images -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Swiftless deploy +^^^^^^^^^^^^^^^^ .. seqdiag:: :scale: 80 @@ -1299,51 +1197,8 @@ Localboot in swiftless deploy for intermediate images Baremetal -> Baremetal [label = "Boot user image from disk"]; } -Netboot with HTTP(S) based deploy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. seqdiag:: - :scale: 80 - - diagram { - Webserver; Conductor; Baremetal; Swift; IPA; iLO; - activation = none; - span_height = 1; - edge_length = 250; - default_note_color = white; - default_fontsize = 14; - - Conductor -> iLO [label = "Powers off the node"]; - Conductor -> Webserver [label = "Download user image"]; - Conductor -> Conductor [label = "Creates the FAT32 image containing Ironic API URL and driver name"]; - Conductor -> Swift [label = "Uploads the FAT32 image"]; - Conductor -> Conductor [label = "Generates swift tempURL for FAT32 image"]; - Conductor -> iLO [label = "Attaches the FAT32 image swift tempURL as virtual media floppy"]; - Conductor -> iLO [label = "Attaches the deploy ISO URL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets one time boot to CDROM"]; - Conductor -> iLO [label = "Reboot the node"]; - iLO -> Webserver [label = "Downloads deploy ISO"]; - Baremetal -> iLO [label = "Boots deploy kernel/ramdisk from iLO virtual media CDROM"]; - IPA -> Conductor [label = "Lookup node"]; - Conductor -> IPA [label = "Provides node UUID"]; - IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Sends the user image HTTP(S) URL"]; - IPA -> Swift [label = "Retrieves the user image on bare metal"]; - IPA -> IPA [label = "Writes user image to disk"]; - Conductor -> Conductor [label = "Generates the boot ISO"]; - Conductor -> Swift [label = "Uploads the boot ISO"]; - Conductor -> Conductor [label = "Generates swift tempURL for boot ISO"]; - Conductor -> iLO [label = "Attaches boot ISO swift tempURL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets boot device to CDROM"]; - Conductor -> IPA [label = "Power off the node"]; - Conductor -> iLO [label = "Power on the node"]; - iLO -> Swift [label = "Downloads boot ISO"]; - iLO -> Baremetal [label = "Boots the instance kernel/ramdisk from iLO virtual media CDROM"]; - Baremetal -> Baremetal [label = "Instance kernel finds root partition and continues booting from disk"]; - } - -Localboot with HTTP(S) based deploy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +HTTP(S) based deploy +^^^^^^^^^^^^^^^^^^^^ .. seqdiag:: :scale: 80 @@ -1379,49 +1234,8 @@ Localboot with HTTP(S) based deploy Baremetal -> Baremetal [label = "Boot user image from disk"]; } -Netboot in standalone ironic -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. seqdiag:: - :scale: 80 - - diagram { - Webserver; Conductor; Baremetal; ConductorWebserver; IPA; iLO; - activation = none; - span_height = 1; - edge_length = 250; - default_note_color = white; - default_fontsize = 14; - - Conductor -> iLO [label = "Powers off the node"]; - Conductor -> Webserver [label = "Download user image"]; - Conductor -> Conductor [label = "Creates the FAT32 image containing Ironic API URL and driver name"]; - Conductor -> ConductorWebserver[label = "Uploads the FAT32 image"]; - Conductor -> iLO [label = "Attaches the FAT32 image URL as virtual media floppy"]; - Conductor -> iLO [label = "Attaches the deploy ISO URL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets one time boot to CDROM"]; - Conductor -> iLO [label = "Reboot the node"]; - iLO -> Webserver [label = "Downloads deploy ISO"]; - Baremetal -> iLO [label = "Boots deploy kernel/ramdisk from iLO virtual media CDROM"]; - IPA -> Conductor [label = "Lookup node"]; - Conductor -> IPA [label = "Provides node UUID"]; - IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Sends the user image HTTP(S) URL"]; - IPA -> ConductorWebserver [label = "Retrieves the user image on bare metal"]; - IPA -> IPA [label = "Writes user image to root partition"]; - Conductor -> Conductor [label = "Generates the boot ISO"]; - Conductor -> ConductorWebserver [label = "Uploads the boot ISO"]; - Conductor -> iLO [label = "Attaches boot ISO URL as virtual media CDROM"]; - Conductor -> iLO [label = "Sets boot device to CDROM"]; - Conductor -> IPA [label = "Power off the node"]; - Conductor -> iLO [label = "Power on the node"]; - iLO -> ConductorWebserver [label = "Downloads boot ISO"]; - iLO -> Baremetal [label = "Boots the instance kernel/ramdisk from iLO virtual media CDROM"]; - Baremetal -> Baremetal [label = "Instance kernel finds root partition and continues booting from disk"]; - } - -Localboot in standalone ironic -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Standalone ironic +^^^^^^^^^^^^^^^^^ .. seqdiag:: :scale: 80 diff --git a/doc/source/admin/drivers/irmc.rst b/doc/source/admin/drivers/irmc.rst index 15d245c10..17b8d8644 100644 --- a/doc/source/admin/drivers/irmc.rst +++ b/doc/source/admin/drivers/irmc.rst @@ -111,6 +111,9 @@ Here is a command example to enroll a node with ``irmc`` hardware type. Node configuration ^^^^^^^^^^^^^^^^^^ +Configuration via ``driver_info`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Each node is configured for ``irmc`` hardware type by setting the following ironic node object's properties: @@ -126,14 +129,52 @@ Node configuration UEFI Secure Boot is required. Please refer to `UEFI Secure Boot Support`_ for more information. +* 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 recongnize certification file, Ironic user + must run ``openssl rehash <path_to_dir>``. + + - 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 optional - property when ``boot_option`` is set to ``netboot``. + 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 @@ -150,6 +191,9 @@ Node configuration - ``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 ``ironic.conf`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * All of the nodes are configured by setting the following configuration options in the ``[irmc]`` section of ``/etc/ironic/ironic.conf``: @@ -198,6 +242,10 @@ Node configuration ``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``: @@ -215,6 +263,7 @@ Node configuration - ``driver_info/irmc_snmp_priv_proto`` property overrides ``snmp_priv_proto``. + Optional functionalities for the ``irmc`` hardware type ======================================================= diff --git a/doc/source/admin/interfaces/deploy.rst b/doc/source/admin/interfaces/deploy.rst index f2ee5d95d..7db5a24ff 100644 --- a/doc/source/admin/interfaces/deploy.rst +++ b/doc/source/admin/interfaces/deploy.rst @@ -131,8 +131,6 @@ Anaconda deploy The ``anaconda`` deploy interface is another option for highly customized deployments. See :doc:`/admin/anaconda-deploy-interface` for more details. -.. _ramdisk-deploy: - Ramdisk deploy ============== diff --git a/doc/source/admin/ramdisk-boot.rst b/doc/source/admin/ramdisk-boot.rst index 29708b781..5e9900f08 100644 --- a/doc/source/admin/ramdisk-boot.rst +++ b/doc/source/admin/ramdisk-boot.rst @@ -11,8 +11,9 @@ It is suported by ``pxe``, ``ipxe``, ``redfish-virtual-media`` and Configuration ------------- -Ramdisk/ISO boot requires using the ``ramdisk`` deploy interface. As with most -non-default interfaces, it must be enabled and set for a node to be utilized: +Ramdisk/ISO boot requires using the ``ramdisk`` deploy interface. It is enabled +by default starting with the Zed release cycle. On an earlier release, it must +be enabled explicitly: .. code-block:: ini diff --git a/doc/source/admin/report.txt b/doc/source/admin/report.txt index 1f1fc4d8e..a1c96e2cc 100644 --- a/doc/source/admin/report.txt +++ b/doc/source/admin/report.txt @@ -321,7 +321,6 @@ default: deploy: continue_if_disk_secure_erase_fails = False - default_boot_option = local erase_devices_metadata_priority = None erase_devices_priority = 0 http_root = /opt/stack/data/ironic/httpboot diff --git a/doc/source/admin/secure-rbac.rst b/doc/source/admin/secure-rbac.rst index 639cfcb23..7721211b6 100644 --- a/doc/source/admin/secure-rbac.rst +++ b/doc/source/admin/secure-rbac.rst @@ -267,3 +267,16 @@ restrictive and an ``owner`` may revoke access to ``lessee``. Access to the underlying baremetal node is not exclusive between the ``owner`` and ``lessee``, and this use model expects that some level of communication takes place between the appropriate parties. + +Can I, a project admin, create a node? +-------------------------------------- + +Starting in API version ``1.80``, the capability was added +to allow users with an ``admin`` role to be able to create and +delete their own nodes in Ironic. + +This functionality is enabled by default, and automatically +imparts ``owner`` privileges to the created Bare Metal node. + +This functionality can be disabled by setting +``[api]project_admin_can_manage_own_nodes`` to ``False``. diff --git a/doc/source/admin/troubleshooting.rst b/doc/source/admin/troubleshooting.rst index 8cf49392f..fa04d3006 100644 --- a/doc/source/admin/troubleshooting.rst +++ b/doc/source/admin/troubleshooting.rst @@ -138,7 +138,7 @@ A few things should be checked in this case: +------------+----------------------------------+ | Field | Value | +------------+----------------------------------+ - | properties | capabilities:boot_option='local' | + | properties | capabilities:boot_mode='uefi' | +------------+----------------------------------+ But in Ironic node:: @@ -147,7 +147,7 @@ A few things should be checked in this case: +------------+-----------------------------------------+ | Property | Value | +------------+-----------------------------------------+ - | properties | {u'capabilities': u'boot_option:local'} | + | properties | {u'capabilities': u'boot_mode:uefi'} | +------------+-----------------------------------------+ #. After making changes to nodes in Ironic, it takes time for those changes diff --git a/doc/source/contributor/webapi-version-history.rst b/doc/source/contributor/webapi-version-history.rst index f90cb3c6c..c395bdcbe 100644 --- a/doc/source/contributor/webapi-version-history.rst +++ b/doc/source/contributor/webapi-version-history.rst @@ -2,8 +2,20 @@ REST API Version History ======================== +1.80 (Zed) +---------- + +This verison is a signifier of additional RBAC functionality allowing +a project scoped ``admin`` to create or delete nodes in Ironic. + +1.79 (Zed, 21.0) +---------------------- +A node with the same name as the allocation ``name`` is moved to the +start of the derived candidate list. + 1.78 (Xena, 18.2) ---------------------- + Add endpoints to allow history events for nodes to be retrieved via the REST API. diff --git a/doc/source/install/advanced.rst b/doc/source/install/advanced.rst index ebb0b99e2..8e8c8fa50 100644 --- a/doc/source/install/advanced.rst +++ b/doc/source/install/advanced.rst @@ -3,8 +3,6 @@ Advanced features ================= -.. include:: include/local-boot-partition-images.inc - .. include:: include/root-device-hints.inc .. include:: include/kernel-boot-parameters.inc diff --git a/doc/source/install/include/local-boot-partition-images.inc b/doc/source/install/include/local-boot-partition-images.inc deleted file mode 100644 index 32ee200ce..000000000 --- a/doc/source/install/include/local-boot-partition-images.inc +++ /dev/null @@ -1,56 +0,0 @@ -.. _local-boot-partition-images: - -Local boot with partition images --------------------------------- - -The Bare Metal service supports local boot with partition images, meaning that -after the deployment the node's subsequent reboots won't happen via PXE or -Virtual Media. Instead, it will boot from a local boot loader installed on -the disk. - -.. note:: Whole disk images, on the contrary, support only local boot, and use - it by default. - -It's important to note that in order for this to work the image being -deployed with Bare Metal service **must** contain ``grub2`` installed within it. - -Enabling the local boot is different when Bare Metal service is used with -Compute service and without it. -The following sections will describe both methods. - -.. _ironic-python-agent: https://docs.openstack.org/ironic-python-agent/latest/ - - -Enabling local boot with Compute service -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To enable local boot we need to set a capability on the bare metal node, -for example:: - - baremetal node set <node-uuid> --property capabilities="boot_option:local" - - -Nodes having ``boot_option`` set to ``local`` may be requested by adding -an ``extra_spec`` to the Compute service flavor, for example:: - - openstack flavor set baremetal --property capabilities:boot_option="local" - - -.. note:: - If the node is configured to use ``UEFI``, Bare Metal service will create - an ``EFI partition`` on the disk and switch the partition table format to - ``gpt``. The ``EFI partition`` will be used later by the boot loader - (which is installed from the deploy ramdisk). - -.. _local-boot-without-compute: - -Enabling local boot without Compute -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Since adding ``capabilities`` to the node's properties is only used by -the nova scheduler to perform more advanced scheduling of instances, -we need a way to enable local boot when Compute is not present. To do that -we can simply specify the capability via the ``instance_info`` attribute -of the node, for example:: - - baremetal node set <node-uuid> --instance-info capabilities='{"boot_option": "local"}' diff --git a/doc/source/install/refarch/common.rst b/doc/source/install/refarch/common.rst index ec2a0dbbb..800632fd5 100644 --- a/doc/source/install/refarch/common.rst +++ b/doc/source/install/refarch/common.rst @@ -135,44 +135,9 @@ The Bare Metal service can deploy two types of images: .. warning:: Partition images are only supported with GNU/Linux operating systems. - .. warning:: - If you plan on using local boot, your partition images must contain GRUB2 - bootloader tools to enable ironic to set up the bootloader during deploy. - -Local vs network boot ---------------------- - -The Bare Metal service supports booting user instances either using a local -bootloader or using the driver's boot interface (e.g. via PXE_ or iPXE_ -protocol in case of the ``pxe`` interface). - -Network boot cannot be used with certain architectures (for example, when no -tenant networks have access to the control plane). - -Additional considerations are related to the ``pxe`` boot interface, and other -boot interfaces based on it: - -* Local boot makes node's boot process independent of the Bare Metal conductor - managing it. Thus, nodes are able to reboot correctly, even if the Bare Metal - TFTP or HTTP service is down. - -* Network boot (and iPXE) must be used when booting nodes from remote volumes, - if the driver does not support attaching volumes out-of-band. - -The default boot option for the cloud can be changed via the Bare Metal service -configuration file, for example: - -.. code-block:: ini - - [deploy] - default_boot_option = local - -This default can be overridden by setting the ``boot_option`` capability on a -node. See :ref:`local-boot-partition-images` for details. - -.. note:: - Currently, local boot is used by default. It's safer to set - the ``default_boot_option`` explicitly. + For the Bare Metal service to set up the bootloader during deploy, your + partition images must container either GRUB2 bootloader or ready-to-use + EFI artifacts. .. _refarch-common-networking: diff --git a/doc/source/user/architecture.rst b/doc/source/user/architecture.rst index de86f47de..1b034fbb4 100644 --- a/doc/source/user/architecture.rst +++ b/doc/source/user/architecture.rst @@ -158,8 +158,9 @@ remain the same. #. The ironic node's management interface issues commands to enable network boot of a node. -#. The ironic node's deploy interface caches the instance image, kernel and - ramdisk if needed (it is needed in case of netboot for example). +#. The ironic node's deploy interface caches the instance image (normal + deployment), kernel and ramdisk (``ramdisk`` deploy) or ISO (``ramdisk`` + deploy with virtual media). #. The ironic node's power interface instructs the node to power on. diff --git a/doc/source/user/deploy.rst b/doc/source/user/deploy.rst index 9b4609289..7499786cb 100644 --- a/doc/source/user/deploy.rst +++ b/doc/source/user/deploy.rst @@ -186,14 +186,6 @@ Capabilities previously ironic used a separate ``instance_info/deploy_boot_mode`` field instead. -* To override the :ref:`boot option <local-boot-partition-images>` used for - this instance, set the ``boot_option`` capability: - - .. code-block:: shell - - baremetal node set $NODE_UUID \ - --instance-info capabilities='{"boot_option": "local"}' - * Starting with the Ussuri release, you can set :ref:`root device hints <root-device-hints>` per instance: diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py index dab134258..59b166db4 100644 --- a/ironic/api/controllers/v1/node.py +++ b/ironic/api/controllers/v1/node.py @@ -2462,7 +2462,15 @@ class NodesController(rest.RestController): raise exception.OperationNotPermitted() context = api.request.context - api_utils.check_policy('baremetal:node:create') + owned_node = False + if CONF.api.project_admin_can_manage_own_nodes: + owned_node = api_utils.check_policy_true( + 'baremetal:node:create:self_owned_node') + else: + owned_node = False + + if not owned_node: + api_utils.check_policy('baremetal:node:create') reject_fields_in_newer_versions(node) @@ -2486,6 +2494,28 @@ class NodesController(rest.RestController): if not node.get('resource_class'): node['resource_class'] = CONF.default_resource_class + cdict = context.to_policy_values() + if cdict.get('system_scope') != 'all' and owned_node: + # This only applies when the request is not system + # scoped. + + # First identify what was requested, and if there is + # a project ID to use. + project_id = None + requested_owner = node.get('owner', None) + if cdict.get('project_id', False): + project_id = cdict.get('project_id') + + if requested_owner and requested_owner != project_id: + # Translation: If project scoped, and an owner has been + # requested, and that owner does not match the requestor's + # project ID value. + msg = _("Cannot create a node as a project scoped admin " + "with an owner other than your own project.") + raise exception.Invalid(msg) + # Finally, note the project ID + node['owner'] = project_id + chassis = _replace_chassis_uuid_with_id(node) chassis_uuid = chassis and chassis.uuid or None @@ -2739,8 +2769,16 @@ class NodesController(rest.RestController): raise exception.OperationNotPermitted() context = api.request.context - rpc_node = api_utils.check_node_policy_and_retrieve( - 'baremetal:node:delete', node_ident, with_suffix=True) + try: + rpc_node = api_utils.check_node_policy_and_retrieve( + 'baremetal:node:delete', node_ident, with_suffix=True) + except exception.HTTPForbidden: + if not CONF.api.project_admin_can_manage_own_nodes: + raise + else: + rpc_node = api_utils.check_node_policy_and_retrieve( + 'baremetal:node:delete:self_owned_node', node_ident, + with_suffix=True) chassis_uuid = _get_chassis_uuid(rpc_node) notify.emit_start_notification(context, rpc_node, 'delete', diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py index 04525ff65..8de2d156d 100644 --- a/ironic/api/controllers/v1/utils.py +++ b/ironic/api/controllers/v1/utils.py @@ -86,11 +86,13 @@ STANDARD_TRAITS = os_traits.get_traits() CUSTOM_TRAIT_PATTERN = "^%s[A-Z0-9_]+$" % os_traits.CUSTOM_NAMESPACE CUSTOM_TRAIT_REGEX = re.compile(CUSTOM_TRAIT_PATTERN) -TRAITS_SCHEMA = {'anyOf': [ - {'type': 'string', 'minLength': 1, 'maxLength': 255, - 'pattern': CUSTOM_TRAIT_PATTERN}, - {'type': 'string', 'enum': STANDARD_TRAITS}, -]} +TRAITS_SCHEMA = { + 'type': 'string', 'minLength': 1, 'maxLength': 255, + 'anyOf': [ + {'pattern': CUSTOM_TRAIT_PATTERN}, + {'enum': STANDARD_TRAITS}, + ] +} LOCAL_LINK_BASE_SCHEMA = { 'type': 'object', diff --git a/ironic/api/controllers/v1/versions.py b/ironic/api/controllers/v1/versions.py index 7fc80bc97..763d92389 100644 --- a/ironic/api/controllers/v1/versions.py +++ b/ironic/api/controllers/v1/versions.py @@ -117,7 +117,7 @@ BASE_VERSION = 1 # v1.77: Add fields selector to drivers list and driver detail. # v1.78: Add node history endpoint # v1.79: Change allocation behaviour to prefer node name match - +# v1.80: Marker to represent self service node creation/deletion MINOR_0_JUNO = 0 MINOR_1_INITIAL_VERSION = 1 MINOR_2_AVAILABLE_STATE = 2 @@ -198,6 +198,7 @@ MINOR_76_NODE_CHANGE_BOOT_MODE = 76 MINOR_77_DRIVER_FIELDS_SELECTOR = 77 MINOR_78_NODE_HISTORY = 78 MINOR_79_ALLOCATION_NODE_NAME = 79 +MINOR_80_PROJECT_CREATE_DELETE_NODE = 80 # When adding another version, update: # - MINOR_MAX_VERSION @@ -205,7 +206,7 @@ MINOR_79_ALLOCATION_NODE_NAME = 79 # explanation of what changed in the new version # - common/release_mappings.py, RELEASE_MAPPING['master']['api'] -MINOR_MAX_VERSION = MINOR_79_ALLOCATION_NODE_NAME +MINOR_MAX_VERSION = MINOR_80_PROJECT_CREATE_DELETE_NODE # String representations of the minor and maximum versions _MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) diff --git a/ironic/common/args.py b/ironic/common/args.py index 94cfe8841..bd13e3eaf 100755 --- a/ironic/common/args.py +++ b/ironic/common/args.py @@ -211,12 +211,17 @@ def _validate_schema(name, value, schema): try: jsonschema.validate(value, schema) except jsonschema.exceptions.ValidationError as e: - - # The error message includes the whole schema which can be very - # large and unhelpful, so truncate it to be brief and useful - error_msg = ' '.join(str(e).split("\n")[:3])[:-1] - raise exception.InvalidParameterValue( - _('Schema error for %s: %s') % (name, error_msg)) + error_msg = _('Schema error for %s: %s') % (name, e.message) + # Sometimes the root message is too generic, try to find a possible + # root cause: + cause = None + current = e + while current.context: + current = jsonschema.exceptions.best_match(current.context) + cause = current.message + if cause is not None: + error_msg += _('. Possible root cause: %s') % cause + raise exception.InvalidParameterValue(error_msg) return value diff --git a/ironic/common/policy.py b/ironic/common/policy.py index a56257e0f..7fdd398f9 100644 --- a/ironic/common/policy.py +++ b/ironic/common/policy.py @@ -437,12 +437,20 @@ node_policies = [ policy.DocumentedRuleDefault( name='baremetal:node:create', check_str=SYSTEM_ADMIN, - scope_types=['system'], + scope_types=['system', 'project'], description='Create Node records', operations=[{'path': '/nodes', 'method': 'POST'}], deprecated_rule=deprecated_node_create ), policy.DocumentedRuleDefault( + name='baremetal:node:create:self_owned_node', + check_str=('role:admin'), + scope_types=['project'], + description='Create node records which will be tracked ' + 'as owned by the associated user project.', + operations=[{'path': '/nodes', 'method': 'POST'}], + ), + policy.DocumentedRuleDefault( name='baremetal:node:list', check_str=API_READER, scope_types=['system', 'project'], @@ -663,7 +671,14 @@ node_policies = [ operations=[{'path': '/nodes/{node_ident}', 'method': 'DELETE'}], deprecated_rule=deprecated_node_delete ), - + policy.DocumentedRuleDefault( + name='baremetal:node:delete:self_owned_node', + check_str=PROJECT_ADMIN, + scope_types=['project'], + description='Delete node records which are associated with ' + 'the requesting project.', + operations=[{'path': '/nodes/{node_ident}', 'method': 'DELETE'}], + ), policy.DocumentedRuleDefault( name='baremetal:node:validate', check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN, diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py index 88c55d6d7..1849aaa7d 100644 --- a/ironic/common/pxe_utils.py +++ b/ironic/common/pxe_utils.py @@ -681,8 +681,10 @@ def get_instance_image_info(task, ipxe_enabled=False): def _get_image_properties(): nonlocal image_properties if not image_properties: - glance_service = service.GlanceImageService(context=ctx) - image_properties = glance_service.show( + i_service = service.get_image_service( + d_info['image_source'], + context=ctx) + image_properties = i_service.show( d_info['image_source'])['properties'] labels = ('kernel', 'ramdisk') @@ -691,10 +693,13 @@ def get_instance_image_info(task, ipxe_enabled=False): # we won't use any of them. We'll use the values specified # with the image, which we assume have been set. _get_image_properties() - for label in labels: - i_info[label] = str(image_properties[label + '_id']) - node.instance_info = i_info - node.save() + if image_properties: + # This is intended for Glance usage, but all image properties + # should be routed through the image service request routing. + for label in labels: + i_info[label] = str(image_properties[label + '_id']) + node.instance_info = i_info + node.save() anaconda_labels = () if deploy_utils.get_boot_option(node) == 'kickstart': @@ -996,8 +1001,10 @@ def build_kickstart_config_options(task): manager_utils.add_secret_token(node, pregenerated=True) node.save() params['liveimg_url'] = node.instance_info['image_url'] + if node.driver_internal_info.get('is_source_a_path', False): + # Record a value so it matches as the template opts in. + params['is_source_a_path'] = 'true' params['agent_token'] = node.driver_internal_info['agent_secret_token'] - heartbeat_url = '%s/v1/heartbeat/%s' % ( deploy_utils.get_ironic_api_url().rstrip('/'), node.uuid diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index aaf423bfe..940321870 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -470,7 +470,7 @@ RELEASE_MAPPING = { 'VolumeTarget': ['1.0'], } }, - 'master': { + '21.0': { 'api': '1.79', 'rpc': '1.55', 'objects': { @@ -490,6 +490,26 @@ RELEASE_MAPPING = { 'VolumeTarget': ['1.0'], } }, + 'master': { + 'api': '1.80', + 'rpc': '1.55', + 'objects': { + 'Allocation': ['1.1'], + 'BIOSSetting': ['1.1'], + 'Node': ['1.36'], + 'NodeHistory': ['1.0'], + 'Conductor': ['1.3'], + 'Chassis': ['1.3'], + 'Deployment': ['1.0'], + 'DeployTemplate': ['1.1'], + 'Port': ['1.10'], + 'Portgroup': ['1.4'], + 'Trait': ['1.0'], + 'TraitList': ['1.0'], + 'VolumeConnector': ['1.0'], + 'VolumeTarget': ['1.0'], + } + }, } # NOTE(xek): Assign each named release to the appropriate semver. diff --git a/ironic/conductor/verify.py b/ironic/conductor/verify.py index 50180e2b8..812472b83 100644 --- a/ironic/conductor/verify.py +++ b/ironic/conductor/verify.py @@ -63,7 +63,7 @@ def do_node_verify(task): except Exception as e: error = ('Node %(node)s failed verify step %(step)s ' 'with unexpected error: %(err)s' % - {'node': node.uuid, 'step': node.verify_step, + {'node': node.uuid, 'step': step['step'], 'err': e}) utils.verifying_error_handler( task, error, diff --git a/ironic/conf/api.py b/ironic/conf/api.py index 2b0e9a824..cf59fa006 100644 --- a/ironic/conf/api.py +++ b/ironic/conf/api.py @@ -86,6 +86,11 @@ opts = [ 'network_data_schema', default='$pybasedir/api/controllers/v1/network-data-schema.json', help=_("Schema for network data used by this deployment.")), + cfg.BoolOpt('project_admin_can_manage_own_nodes', + default=True, + mutable=True, + help=_('If a project scoped administrative user is permitted ' + 'to create/delte baremetal nodes in their project.')), ] opt_group = cfg.OptGroup(name='api', diff --git a/ironic/conf/default.py b/ironic/conf/default.py index 66555d146..0e3c32bd1 100644 --- a/ironic/conf/default.py +++ b/ironic/conf/default.py @@ -111,7 +111,7 @@ driver_opts = [ cfg.StrOpt('default_console_interface', help=_DEFAULT_IFACE_HELP.format('console')), cfg.ListOpt('enabled_deploy_interfaces', - default=['direct'], + default=['direct', 'ramdisk'], help=_ENABLED_IFACE_HELP.format('deploy')), cfg.StrOpt('default_deploy_interface', help=_DEFAULT_IFACE_HELP.format('deploy')), diff --git a/ironic/conf/deploy.py b/ironic/conf/deploy.py index 7a7fb37d7..6ae080c83 100644 --- a/ironic/conf/deploy.py +++ b/ironic/conf/deploy.py @@ -120,18 +120,6 @@ opts = [ mutable=True, help=_('Whether to power off a node after deploy failure. ' 'Defaults to True.')), - cfg.StrOpt('default_boot_option', - choices=[('netboot', _('boot from a network')), - ('local', _('local boot'))], - default='local', - mutable=True, - help=_('Default boot option to use when no boot option is ' - 'requested in node\'s driver_info. Defaults to ' - '"local". Prior to the Ussuri release, the default ' - 'was "netboot".'), - deprecated_for_removal=True, - deprecated_reason=_('Support for network boot will be removed ' - 'after the Yoga release.')), cfg.StrOpt('default_boot_mode', choices=[(boot_modes.UEFI, _('UEFI boot mode')), (boot_modes.LEGACY_BIOS, _('Legacy BIOS boot mode'))], diff --git a/ironic/conf/molds.py b/ironic/conf/molds.py index 4cec1749a..53724598e 100644 --- a/ironic/conf/molds.py +++ b/ironic/conf/molds.py @@ -26,11 +26,11 @@ opts = [ cfg.StrOpt('password', help=_('Password for "http" Basic auth. By default set ' 'empty.')), - cfg.StrOpt('retry_attempts', + cfg.IntOpt('retry_attempts', default=3, help=_('Retry attempts for saving or getting configuration ' 'molds.')), - cfg.StrOpt('retry_interval', + cfg.IntOpt('retry_interval', default=3, help=_('Retry interval for saving or getting configuration ' 'molds.')) diff --git a/ironic/drivers/ilo.py b/ironic/drivers/ilo.py index 10676b411..d8bbafb9e 100644 --- a/ironic/drivers/ilo.py +++ b/ironic/drivers/ilo.py @@ -37,7 +37,7 @@ class IloHardware(generic.GenericHardware): @property def supported_boot_interfaces(self): """List of supported boot interfaces.""" - return [boot.IloVirtualMediaBoot, boot.IloPXEBoot, boot.IloiPXEBoot] + return [boot.IloVirtualMediaBoot, boot.IloiPXEBoot, boot.IloPXEBoot] @property def supported_bios_interfaces(self): diff --git a/ironic/drivers/modules/agent.py b/ironic/drivers/modules/agent.py index c171f81b1..1c0f5465e 100644 --- a/ironic/drivers/modules/agent.py +++ b/ironic/drivers/modules/agent.py @@ -16,7 +16,6 @@ from urllib import parse as urlparse from ironic_lib import metrics_utils from oslo_log import log -from oslo_utils import excutils from oslo_utils import units from ironic.common import exception @@ -325,33 +324,7 @@ class CustomAgentDeploy(agent_base.AgentBaseMixin, agent_base.AgentDeployMixin, if node.provision_state == states.DEPLOYING: # Validate network interface to ensure that it supports boot # options configured on the node. - try: - task.driver.network.validate(task) - except exception.InvalidParameterValue: - # For 'neutron' network interface validation will fail - # if node is using 'netboot' boot option while provisioning - # a whole disk image. Updating 'boot_option' in node's - # 'instance_info' to 'local for backward compatibility. - # TODO(stendulker): Fail here once the default boot - # option is local. - # NOTE(TheJulia): Fixing the default boot mode only - # masks the failure as the lack of a user definition - # can be perceived as both an invalid configuration and - # reliance upon the default configuration. The reality - # being that in most scenarios, users do not want network - # booting, so the changed default should be valid. - with excutils.save_and_reraise_exception(reraise=False) as ctx: - instance_info = node.instance_info - capabilities = utils.parse_instance_info_capabilities(node) - if 'boot_option' not in capabilities: - capabilities['boot_option'] = 'local' - instance_info['capabilities'] = capabilities - node.instance_info = instance_info - node.save() - # Re-validate the network interface - task.driver.network.validate(task) - else: - ctx.reraise = True + task.driver.network.validate(task) # Determine if this is a fast track sequence fast_track_deploy = manager_utils.is_fast_track(task) if fast_track_deploy: @@ -597,13 +570,6 @@ class AgentDeploy(CustomAgentDeploy): iwdi = task.node.driver_internal_info.get('is_whole_disk_image') cpu_arch = task.node.properties.get('cpu_arch') - # If `boot_option` is set to `netboot`, PXEBoot.prepare_instance() - # would need root_uuid of the whole disk image to add it into the - # pxe config to perform chain boot. - # IPA would have returned us the 'root_uuid_or_disk_id' if image - # being provisioned is a whole disk image. IPA would also provide us - # 'efi_system_partition_uuid' if the image being provisioned is a - # partition image. # In case of local boot using partition image, we need both # 'root_uuid_or_disk_id' and 'efi_system_partition_uuid' to configure # bootloader for local boot. diff --git a/ironic/drivers/modules/agent_base.py b/ironic/drivers/modules/agent_base.py index 582c36d90..ff2a454ea 100644 --- a/ironic/drivers/modules/agent_base.py +++ b/ironic/drivers/modules/agent_base.py @@ -1217,12 +1217,12 @@ class AgentDeployMixin(HeartbeatMixin, AgentOobStepsMixin): """ node = task.node - if deploy_utils.get_boot_option(node) == "local": - # Install the boot loader - self.configure_local_boot( - task, root_uuid=root_uuid, - efi_system_part_uuid=efi_sys_uuid, - prep_boot_part_uuid=prep_boot_part_uuid) + # Install the boot loader + self.configure_local_boot( + task, root_uuid=root_uuid, + efi_system_part_uuid=efi_sys_uuid, + prep_boot_part_uuid=prep_boot_part_uuid) + try: task.driver.boot.prepare_instance(task) except Exception as e: diff --git a/ironic/drivers/modules/agent_config.template b/ironic/drivers/modules/agent_config.template deleted file mode 100644 index bf9f5f4b4..000000000 --- a/ironic/drivers/modules/agent_config.template +++ /dev/null @@ -1,13 +0,0 @@ -default deploy - -label deploy -kernel {{ pxe_options.deployment_aki_path }} -append initrd={{ pxe_options.deployment_ari_path }} text {{ pxe_options.pxe_append_params }} - -label boot_partition -kernel {{ pxe_options.aki_path }} -append initrd={{ pxe_options.ari_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} - -label boot_whole_disk -COM32 chain.c32 -append mbr:{{ DISK_IDENTIFIER }} diff --git a/ironic/drivers/modules/ansible/deploy.py b/ironic/drivers/modules/ansible/deploy.py index cd3f4c68f..d7cf49412 100644 --- a/ironic/drivers/modules/ansible/deploy.py +++ b/ironic/drivers/modules/ansible/deploy.py @@ -396,12 +396,6 @@ class AnsibleDeploy(agent_base.HeartbeatMixin, task.driver.boot.validate(task) node = task.node - iwdi = node.driver_internal_info.get('is_whole_disk_image') - if not iwdi and deploy_utils.get_boot_option(node) == "netboot": - raise exception.InvalidParameterValue(_( - "Node %(node)s is configured to use the ansible deploy " - "interface, which does not support netboot.") % - {'node': node.uuid}) params = {} image_source = node.instance_info.get('image_source') diff --git a/ironic/drivers/modules/boot.ipxe b/ironic/drivers/modules/boot.ipxe index 4ed58497c..95d95686a 100644 --- a/ironic/drivers/modules/boot.ipxe +++ b/ironic/drivers/modules/boot.ipxe @@ -10,6 +10,12 @@ isset ${net${netid}/mac} || goto loop_done echo Attempting to boot from MAC ${net${netid}/mac:hexhyp} chain {{ ipxe_for_mac_uri }}${net${netid}/mac:hexhyp} || goto loop +# If we've got here the chained config returned success +# suggesting "sanboot" in boot_whole_disk failed (some UEFI cases) +# exit 0 so the bios continues to the next device +echo Exiting pxe config to allow boot to continue on next device +exit 0 + :loop_done {% if ipxe_fallback_script -%} chain {{ ipxe_fallback_script }} | goto boot_failed diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py index bcefd2323..13f91e9cd 100644 --- a/ironic/drivers/modules/deploy_utils.py +++ b/ironic/drivers/modules/deploy_utils.py @@ -55,7 +55,6 @@ LOG = logging.getLogger(__name__) METRICS = metrics_utils.get_metrics_logger(__name__) SUPPORTED_CAPABILITIES = { - 'boot_option': ('local', 'netboot', 'ramdisk', 'kickstart'), 'boot_mode': ('bios', 'uefi'), 'secure_boot': ('true', 'false'), 'disk_label': ('msdos', 'gpt'), @@ -159,6 +158,9 @@ def _replace_disk_identifier(path, disk_identifier): # NOTE(TheJulia): This should likely be migrated to pxe_utils. +# TODO(dtantsur): with the removal of netboot, root_uuid_or_disk_id and +# the logic of replacing ROOT can be dropped, while is_whole_disk_image can +# be renamed to something like netboot_fallback. def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode, is_whole_disk_image, iscsi_boot=False, ramdisk_boot=False, ipxe_enabled=False, @@ -616,17 +618,11 @@ def get_boot_option(node): :returns: A string representing the boot option type. Defaults to configuration setting [deploy]default_boot_mode. """ - - # NOTE(TheJulia): Software raid always implies local deployment - if is_software_raid(node): - return 'local' if is_anaconda_deploy(node): return 'kickstart' if is_ramdisk_deploy(node): return 'ramdisk' - capabilities = utils.parse_instance_info_capabilities(node) - return capabilities.get('boot_option', - CONF.deploy.default_boot_option).lower() + return 'local' # FIXME(dtantsur): relying on deploy interface name is an anti-pattern. @@ -1462,7 +1458,12 @@ def reboot_to_finish_step(task): disable_ramdisk = task.node.driver_internal_info.get( 'cleaning_disable_ramdisk') if not disable_ramdisk: + if manager_utils.is_fast_track(task): + LOG.debug('Forcing power off on node %s for a clean reboot into ' + 'the agent image', task.node) + manager_utils.node_power_action(task, states.POWER_OFF) prepare_agent_boot(task) + manager_utils.node_power_action(task, states.REBOOT) return get_async_step_return_state(task.node) diff --git a/ironic/drivers/modules/ilo/boot.py b/ironic/drivers/modules/ilo/boot.py index 7f5c5adcf..e29852981 100644 --- a/ironic/drivers/modules/ilo/boot.py +++ b/ironic/drivers/modules/ilo/boot.py @@ -320,7 +320,7 @@ class IloVirtualMediaBoot(base.BootInterface): except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error("Virtual media deploy with 'ramdisk' " - "boot_option accepts only Glance images or " + "deploy accepts only Glance images or " "HTTP(S) URLs as " "instance_info['boot_iso']. Either %s " "is not a valid HTTP(S) URL or is not " @@ -460,21 +460,8 @@ class IloVirtualMediaBoot(base.BootInterface): boot_devices.CDROM, persistent=True) else: - # Boot from disk every time if the image deployed is - # a whole disk image. - 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: - drv_int_info = node.driver_internal_info - root_uuid_or_disk_id = drv_int_info.get('root_uuid_or_disk_id') - if root_uuid_or_disk_id: - self._configure_vmedia_boot(task, root_uuid_or_disk_id) - else: - LOG.warning("The UUID for the root partition could not " - "be found for node %s", node.uuid) + manager_utils.node_set_boot_device(task, boot_devices.DISK, + persistent=True) # Set boot mode ilo_common.update_boot_mode(task) # Need to enable secure boot, if being requested @@ -590,8 +577,7 @@ class IloPXEBoot(pxe.PXEBoot): """Prepares the boot of instance. This method prepares the boot of the instance after reading - relevant information from the node's instance_info. In case of netboot, - it updates the dhcp entries and switches the PXE config. In case of + relevant information from the node's instance_info. In case of localboot, it cleans up the PXE config. In case of 'boot from volume', it updates the iSCSI info onto iLO and sets the node to boot from 'UefiTarget' boot device. @@ -683,8 +669,7 @@ class IloiPXEBoot(ipxe.iPXEBoot): """Prepares the boot of instance. This method prepares the boot of the instance after reading - relevant information from the node's instance_info. In case of netboot, - it updates the dhcp entries and switches the PXE config. In case of + relevant information from the node's instance_info. In case of localboot, it cleans up the PXE config. In case of 'boot from volume', it updates the iSCSI info onto iLO and sets the node to boot from 'UefiTarget' boot device. @@ -904,7 +889,7 @@ class IloUefiHttpsBoot(base.BootInterface): except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error("UEFI-HTTPS boot with 'ramdisk' " - "boot_option accepts only Glance images or " + "deploy accepts only Glance images or " "HTTPS URLs as " "instance_info['boot_iso']. Either %s " "is not a valid HTTPS URL or is not " diff --git a/ironic/drivers/modules/ilo/power.py b/ironic/drivers/modules/ilo/power.py index ee8fcc794..a1363fb52 100644 --- a/ironic/drivers/modules/ilo/power.py +++ b/ironic/drivers/modules/ilo/power.py @@ -44,9 +44,8 @@ def _attach_boot_iso_if_needed(task): This method checks the instance info of the baremetal node for a boot iso. If the instance info has a value of key 'boot_iso', - it indicates that 'boot_option' is 'netboot'. Therefore it attaches - the boot ISO on the baremetal node and then sets the node to boot from - virtual media cdrom. + it indicates ramdisk deploy. Therefore it attaches the boot ISO on the + baremetal node and then sets the node to boot from virtual media cdrom. :param task: a TaskManager instance containing the node to act on. """ diff --git a/ironic/drivers/modules/image_cache.py b/ironic/drivers/modules/image_cache.py index 17dfba9cf..5ca053f36 100644 --- a/ironic/drivers/modules/image_cache.py +++ b/ironic/drivers/modules/image_cache.py @@ -237,6 +237,7 @@ class ImageCache(object): """ threshold = time.time() - self._cache_ttl survived = [] + count = 0 for file_name, last_used, stat in listing: if last_used < threshold: try: @@ -246,6 +247,7 @@ class ImageCache(object): "master image cache: %(exc)s", {'name': file_name, 'exc': exc}) else: + count += 1 if amount is not None: amount -= stat.st_size if amount <= 0: @@ -253,6 +255,9 @@ class ImageCache(object): break else: survived.append((file_name, last_used, stat)) + if count: + LOG.debug('Removed %(count)d expired file(s) from %(dir)s', + {'count': count, 'dir': self.master_dir}) return survived, amount def _clean_up_ensure_cache_size(self, listing, amount): @@ -275,6 +280,7 @@ class ImageCache(object): for f in os.listdir(self.master_dir)) total_size = sum(os.path.getsize(f) for f in total_listing) + count = 0 while listing and (total_size > self._cache_size or (amount is not None and amount > 0)): file_name, last_used, stat = listing.pop() @@ -286,6 +292,7 @@ class ImageCache(object): {'name': file_name, 'exc': exc}) else: total_size -= stat.st_size + count += 1 if amount is not None: amount -= stat.st_size @@ -295,6 +302,10 @@ class ImageCache(object): "threshold %(expected)d", {'dir': self.master_dir, 'actual': total_size, 'expected': self._cache_size}) + elif count: + LOG.debug( + 'Removed %(count)d file(s) from %(dir)s to free up space', + {'count': count, 'dir': self.master_dir}) return max(amount, 0) if amount is not None else 0 diff --git a/ironic/drivers/modules/image_utils.py b/ironic/drivers/modules/image_utils.py index bb0dfa166..304c199bf 100644 --- a/ironic/drivers/modules/image_utils.py +++ b/ironic/drivers/modules/image_utils.py @@ -169,7 +169,7 @@ class ImageHandler(object): return urlparse.urlunparse(parsed_url) - def publish_image(self, image_file, object_name): + def publish_image(self, image_file, object_name, node_http_url=None): """Make image file downloadable. Depending on ironic settings, pushes given file into Swift or copies @@ -178,6 +178,9 @@ class ImageHandler(object): :param image_file: path to file to publish :param object_name: name of the published file + :param node_http_url: a url to be used to publish the image. If set, + the values from external_http_url and http_url + from CONF.deploy won't be used. :return: a URL to download published file """ @@ -220,7 +223,8 @@ class ImageHandler(object): shutil.copyfile(image_file, published_file) os.chmod(published_file, self._file_permission) - http_url = CONF.deploy.external_http_url or CONF.deploy.http_url + http_url = (node_http_url or CONF.deploy.external_http_url + or CONF.deploy.http_url) image_url = os.path.join(http_url, self._image_subdir, object_name) return image_url @@ -302,8 +306,9 @@ def prepare_floppy_image(task, params=None): images.create_vfat_image(vfat_image_tmpfile, parameters=params) img_handler = ImageHandler(task.node.driver) - - image_url = img_handler.publish_image(vfat_image_tmpfile, object_name) + node_http_url = task.node.driver_info.get("external_http_url") + image_url = img_handler.publish_image(vfat_image_tmpfile, object_name, + node_http_url) LOG.debug("Created floppy image %(name)s in Swift for node %(node)s, " "exposed as temporary URL " diff --git a/ironic/drivers/modules/ipxe_config.template b/ironic/drivers/modules/ipxe_config.template index bca63c982..650083869 100644 --- a/ironic/drivers/modules/ipxe_config.template +++ b/ironic/drivers/modules/ipxe_config.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} initrd=ramdisk || goto boot_partition -initrd {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.ari_path }} || goto boot_partition -boot - :boot_anaconda imgfree kernel {% if pxe_options.ipxe_timeout > 0 %}--timeout {{ pxe_options.ipxe_timeout }} {% endif %}{{ pxe_options.aki_path }} text {{ pxe_options.pxe_append_params|default("", true) }} inst.ks={{ pxe_options.ks_cfg_url }} {% if pxe_options.repo_url %}inst.repo={{ pxe_options.repo_url }}{% else %}inst.stage2={{ pxe_options.stage2_url }}{% endif %} initrd=ramdisk || goto boot_anaconda @@ -72,4 +66,4 @@ goto boot_iscsi {%- endif %} :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/drivers/modules/irmc/boot.py b/ironic/drivers/modules/irmc/boot.py index 84964bd2f..11153a6f1 100644 --- a/ironic/drivers/modules/irmc/boot.py +++ b/ironic/drivers/modules/irmc/boot.py @@ -376,9 +376,8 @@ def attach_boot_iso_if_needed(task): 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 that 'boot_option' is 'netboot'. Threfore it attaches - the boot ISO on the bare metal node and then sets the node to boot from - virtual media cdrom. + 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. diff --git a/ironic/drivers/modules/irmc/common.py b/ironic/drivers/modules/irmc/common.py index 00b7c0625..7a8fc0f1d 100644 --- a/ironic/drivers/modules/irmc/common.py +++ b/ironic/drivers/modules/irmc/common.py @@ -15,8 +15,11 @@ """ Common functionalities shared between different iRMC modules. """ +import os + from oslo_log import log as logging from oslo_utils import importutils +from oslo_utils import strutils from ironic.common import exception from ironic.common.i18n import _ @@ -46,6 +49,16 @@ OPTIONAL_PROPERTIES = { "'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 " @@ -84,6 +97,7 @@ SNMP_V3_DEPRECATED_PROPERTIES = { 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) @@ -116,7 +130,9 @@ def parse_driver_info(node): # corresponding config names don't have 'irmc_' prefix opt = {param: info.get(param, CONF.irmc.get(param[len('irmc_'):])) for param in OPTIONAL_PROPERTIES} - d_info = dict(req, **opt) + 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') @@ -137,6 +153,38 @@ def parse_driver_info(node): error_msgs.append( _("Value '%s' is not supported for 'irmc_sensor_method'.") % d_info['irmc_sensor_method']) + + verify_ca = 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)) @@ -287,6 +335,7 @@ def get_irmc_client(node): :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) @@ -296,6 +345,7 @@ def get_irmc_client(node): 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 @@ -338,6 +388,7 @@ def get_irmc_report(node): 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']) diff --git a/ironic/drivers/modules/ks.cfg.template b/ironic/drivers/modules/ks.cfg.template index 825ea38c8..ca799953a 100644 --- a/ironic/drivers/modules/ks.cfg.template +++ b/ironic/drivers/modules/ks.cfg.template @@ -15,11 +15,24 @@ zerombr clearpart --all --initlabel autopart -# Downloading and installing OS image using liveimg section is mandatory -# in a *default* ironic configuration. Users (infrastructure operators) +# Downloading and installing OS image using "liveimg" section is the +# default mode of operation for an OpenStack-integrated Ironic +# deployment where Glance is in use. Users (infrastructure operators) # may choose to customize this pattern, or use release specific kickstart # configurations which may already point to a mirror. +# +# An alternative is "url", which points to a repository of files used for +# the deploy, similar to mounting an ISO media and exposing the files. + +{% if 'is_source_a_path' in ks_options -%} +url --url {{ks_options.liveimg_url }} + +# If packages are not selected, a URL based auto-deployment fails. +%packages --ignoremissing +%end +{% else -%} liveimg --url {{ ks_options.liveimg_url }} +{% endif -%} # Following %pre and %onerror sections are mandatory %pre diff --git a/ironic/drivers/modules/network/neutron.py b/ironic/drivers/modules/network/neutron.py index 3e4dcbfd6..2693b603e 100644 --- a/ironic/drivers/modules/network/neutron.py +++ b/ironic/drivers/modules/network/neutron.py @@ -20,9 +20,7 @@ from oslo_log import log from ironic.common import exception from ironic.common.i18n import _ from ironic.common import neutron -from ironic.common import states from ironic.drivers import base -from ironic.drivers.modules import deploy_utils from ironic.drivers.modules.network import common LOG = log.getLogger(__name__) @@ -61,15 +59,6 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin, """ self.get_cleaning_network_uuid(task) self.get_provisioning_network_uuid(task) - node = task.node - if (node.provision_state == states.DEPLOYING - and node.driver_internal_info.get('is_whole_disk_image') - and deploy_utils.get_boot_option(node) == 'netboot'): - error_msg = (_('The node %s cannot perform "local" boot for ' - 'whole disk image when node is using "neutron" ' - 'network and is configured with "netboot" boot ' - 'option.') % node.uuid) - raise exception.InvalidParameterValue(error_msg) def _add_network(self, task, network, security_groups, process): # If we have left over ports from a previous process, remove them diff --git a/ironic/drivers/modules/pxe_base.py b/ironic/drivers/modules/pxe_base.py index a8a768b8b..daa90ba8d 100644 --- a/ironic/drivers/modules/pxe_base.py +++ b/ironic/drivers/modules/pxe_base.py @@ -261,50 +261,6 @@ class PXEBaseMixin(object): anaconda_boot=(boot_option == "kickstart")) boot_device = boot_devices.PXE - elif boot_option != "local": - if task.driver.storage.should_write_image(task): - # Make sure that the instance kernel/ramdisk is cached. - # This is for the takeover scenario for active nodes. - instance_image_info = pxe_utils.get_instance_image_info( - task, ipxe_enabled=self.ipxe_enabled) - pxe_utils.cache_ramdisk_kernel(task, instance_image_info, - ipxe_enabled=self.ipxe_enabled) - - # If it's going to PXE boot we need to update the DHCP server - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=self.ipxe_enabled, ip_version=4) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=self.ipxe_enabled, ip_version=6) - provider = dhcp_factory.DHCPFactory() - provider.update_dhcp(task, dhcp_opts) - - iwdi = task.node.driver_internal_info.get('is_whole_disk_image') - try: - root_uuid_or_disk_id = task.node.driver_internal_info[ - 'root_uuid_or_disk_id' - ] - except KeyError: - if not task.driver.storage.should_write_image(task): - pass - elif not iwdi: - LOG.warning("The UUID for the root partition can't be " - "found, unable to switch the pxe config from " - "deployment mode to service (boot) mode for " - "node %(node)s", {"node": task.node.uuid}) - else: - LOG.warning("The disk id for the whole disk image can't " - "be found, unable to switch the pxe config " - "from deployment mode to service (boot) mode " - "for node %(node)s. Booting the instance " - "from disk.", {"node": task.node.uuid}) - pxe_utils.clean_up_pxe_config( - task, ipxe_enabled=self.ipxe_enabled) - boot_device = boot_devices.DISK - else: - pxe_utils.build_service_pxe_config( - task, instance_image_info, root_uuid_or_disk_id, - ipxe_enabled=self.ipxe_enabled) - boot_device = boot_devices.PXE else: # NOTE(dtantsur): create a PXE configuration as a safety net for # hardware uncapable of persistent boot. If on a reboot it will try diff --git a/ironic/drivers/modules/pxe_config.template b/ironic/drivers/modules/pxe_config.template index 9b773b2ba..bf4cec11a 100644 --- a/ironic/drivers/modules/pxe_config.template +++ b/ironic/drivers/modules/pxe_config.template @@ -5,12 +5,6 @@ kernel {{ pxe_options.deployment_aki_path }} append initrd={{ pxe_options.deployment_ari_path }} selinux=0 troubleshoot=0 text {{ pxe_options.pxe_append_params|default("", true) }} ipappend 2 - -label boot_partition -kernel {{ pxe_options.aki_path }} -append initrd={{ pxe_options.ari_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} - - label boot_whole_disk COM32 chain.c32 append mbr:{{ DISK_IDENTIFIER }} diff --git a/ironic/drivers/modules/pxe_grub_config.template b/ironic/drivers/modules/pxe_grub_config.template index d33cbb8cd..d8fc48673 100644 --- a/ironic/drivers/modules/pxe_grub_config.template +++ b/ironic/drivers/modules/pxe_grub_config.template @@ -7,11 +7,6 @@ menuentry "deploy" { initrdefi {{ pxe_options.deployment_ari_path }} } -menuentry "boot_partition" { - linuxefi {{ pxe_options.aki_path }} root={{ ROOT }} ro text {{ pxe_options.pxe_append_params|default("", true) }} boot_server={{pxe_options.tftp_server}} - initrdefi {{ pxe_options.ari_path }} -} - menuentry "boot_ramdisk" { linuxefi {{ pxe_options.aki_path }} root=/dev/ram0 text {{ pxe_options.pxe_append_params|default("", true) }} {{ pxe_options.ramdisk_opts|default('', true) }} initrdefi {{ pxe_options.ari_path }} diff --git a/ironic/drivers/modules/redfish/bios.py b/ironic/drivers/modules/redfish/bios.py index c2eb8fcbc..44742795e 100644 --- a/ironic/drivers/modules/redfish/bios.py +++ b/ironic/drivers/modules/redfish/bios.py @@ -19,7 +19,6 @@ from oslo_utils import importutils from ironic.common import exception from ironic.common.i18n import _ from ironic.common import states -from ironic.conductor import task_manager from ironic.conductor import utils as manager_utils from ironic.drivers import base from ironic.drivers.modules import deploy_utils @@ -55,20 +54,23 @@ class RedfishBIOS(base.BIOSInterface): driver='redfish', reason=_("Unable to import the sushy library")) - def _parse_allowable_values(self, allowable_values): + def _parse_allowable_values(self, node, allowable_values): """Convert the BIOS registry allowable_value list to expected strings :param allowable_values: list of dicts of valid values for enumeration :returns: list containing only allowable value names """ - # Get name from ValueName if it exists, otherwise use DisplayValueName + # Get name from ValueName if it exists, otherwise use ValueDisplayName new_list = [] for dic in allowable_values: - for key in dic: - if key == 'ValueName' or key == 'DisplayValueName': - new_list.append(dic[key]) - break + key = dic.get('ValueName') or dic.get('ValueDisplayName') + if key: + new_list.append(key) + else: + LOG.warning('Cannot detect the value name for enumeration ' + 'item %(item)s for node %(node)s', + {'item': dic, 'node': node.uuid}) return new_list @@ -130,7 +132,8 @@ class RedfishBIOS(base.BIOSInterface): setting[k] = getattr(reg, k, None) if k == "allowable_values" and isinstance(setting[k], list): - setting[k] = self._parse_allowable_values(setting[k]) + setting[k] = self._parse_allowable_values( + task.node, setting[k]) LOG.debug('Cache BIOS settings for node %(node_uuid)s', {'node_uuid': task.node.uuid}) @@ -185,9 +188,8 @@ class RedfishBIOS(base.BIOSInterface): LOG.error(error_msg) raise exception.RedfishError(error=error_msg) - self.post_reset(task) self._set_reboot(task) - return deploy_utils.get_async_step_return_state(task.node) + return self.post_reset(task) else: current_attrs = bios.attributes LOG.debug('Post factory reset, BIOS configuration for node ' @@ -244,9 +246,8 @@ class RedfishBIOS(base.BIOSInterface): LOG.error(error_msg) raise exception.RedfishError(error=error_msg) - self.post_configuration(task, settings) self._set_reboot_requested(task, attributes) - return deploy_utils.get_async_step_return_state(task.node) + return self.post_configuration(task, settings) else: # Step 2: Verify requested BIOS settings applied requested_attrs = info.get('requested_bios_attrs') @@ -267,8 +268,7 @@ class RedfishBIOS(base.BIOSInterface): :param task: a TaskManager instance containing the node to act on. """ - deploy_utils.prepare_agent_boot(task) - self._reboot(task) + return deploy_utils.reboot_to_finish_step(task) def post_configuration(self, task, settings): """Perform post configuration action to store the BIOS settings. @@ -281,8 +281,7 @@ class RedfishBIOS(base.BIOSInterface): :param task: a TaskManager instance containing the node to act on. :param settings: a list of BIOS settings to be updated. """ - deploy_utils.prepare_agent_boot(task) - self._reboot(task) + return deploy_utils.reboot_to_finish_step(task) def get_properties(self): """Return the properties of the interface. @@ -322,17 +321,6 @@ class RedfishBIOS(base.BIOSInterface): LOG.debug('Verification of BIOS settings for node %(node_uuid)s ' 'successful.', {'node_uuid': task.node.uuid}) - @task_manager.require_exclusive_lock - def _reboot(self, task): - """Reboot the target Redfish service. - - :param task: a TaskManager instance containing the node to act on. - :raises: InvalidParameterValue when the wrong state is specified - or the wrong driver info is specified. - :raises: RedfishError on an error from the Sushy library - """ - manager_utils.node_power_action(task, states.REBOOT) - def _set_reboot(self, task): """Set driver_internal_info flags for deployment or cleaning reboot. diff --git a/ironic/drivers/modules/redfish/boot.py b/ironic/drivers/modules/redfish/boot.py index 164425eee..a321c08ec 100644 --- a/ironic/drivers/modules/redfish/boot.py +++ b/ironic/drivers/modules/redfish/boot.py @@ -53,7 +53,11 @@ OPTIONAL_PROPERTIES = { "used by ironic when building UEFI-bootable ISO " "out of kernel and ramdisk. Required for UEFI " "when deploy_iso is not provided."), - + 'external_http_url': _("External URL that is used when the image could " + "be served outside of the provisioning network. " + "If set it will have priority over the following " + "configs: CONF.deploy.external_http_url and " + "CONF.deploy.http_url. Defaults to None.") } RESCUE_PROPERTIES = { diff --git a/ironic/drivers/modules/redfish/raid.py b/ironic/drivers/modules/redfish/raid.py index 77abdef0e..809ec59c6 100644 --- a/ironic/drivers/modules/redfish/raid.py +++ b/ironic/drivers/modules/redfish/raid.py @@ -693,6 +693,7 @@ def update_raid_config(node): """ system = redfish_utils.get_system(node) logical_disks = [] + vol_no_raid_type = [] for stor in system.storage.get_members(): for vol in stor.volumes.get_members(): if vol.raid_type: @@ -705,7 +706,14 @@ def update_raid_config(node): key for key, value in RAID_LEVELS.items() if value['raid_type'] == vol.raid_type.value) } - logical_disks.append(logical_disk) + logical_disks.append(logical_disk) + else: + vol_no_raid_type.append(vol.identity) + + if vol_no_raid_type: + LOG.warning("Unable to update raid_config for volumes missing RAID " + "type: %(vol_no_raid_type)s", + {'vol_no_raid_type': ", ".join(vol_no_raid_type)}) raid_common.update_raid_info(node, {'logical_disks': logical_disks}) diff --git a/ironic/tests/base.py b/ironic/tests/base.py index ba43461b6..4b34ef0a4 100644 --- a/ironic/tests/base.py +++ b/ironic/tests/base.py @@ -159,7 +159,7 @@ class TestCase(oslo_test_base.BaseTestCase): values = ['fake'] if iface == 'deploy': - values.extend(['direct', 'anaconda']) + values.extend(['direct', 'ramdisk', 'anaconda']) elif iface == 'boot': values.append('pxe') elif iface == 'storage': diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py index d7a3d474e..6531f36e7 100644 --- a/ironic/tests/unit/api/controllers/v1/test_node.py +++ b/ironic/tests/unit/api/controllers/v1/test_node.py @@ -4898,13 +4898,39 @@ class TestPost(test_api_base.BaseApiTest): ndict = test_api_utils.post_get_test_node(owner='cowsay') response = self.post_json('/nodes', ndict, headers={api_base.Version.string: - str(api_v1.max_version())}) + str(api_v1.max_version()), + 'X-Project-Id': 'cowsay'}) self.assertEqual(http_client.CREATED, response.status_int) result = self.get_json('/nodes/%s' % ndict['uuid'], headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual('cowsay', result['owner']) + def test_create_node_owner_system_scope(self): + ndict = test_api_utils.post_get_test_node(owner='catsay') + response = self.post_json('/nodes', ndict, + headers={api_base.Version.string: + str(api_v1.max_version()), + 'OpenStack-System-Scope': 'all', + 'X-Roles': 'admin'}) + self.assertEqual(http_client.CREATED, response.status_int) + result = self.get_json('/nodes/%s' % ndict['uuid'], + headers={api_base.Version.string: + str(api_v1.max_version())}) + self.assertEqual('catsay', result['owner']) + + def test_create_node_owner_recorded_project_scope(self): + ndict = test_api_utils.post_get_test_node() + response = self.post_json('/nodes', ndict, + headers={api_base.Version.string: + str(api_v1.max_version()), + 'X-Project-Id': 'ravensay'}) + self.assertEqual(http_client.CREATED, response.status_int) + result = self.get_json('/nodes/%s' % ndict['uuid'], + headers={api_base.Version.string: + str(api_v1.max_version())}) + self.assertEqual('ravensay', result['owner']) + def test_create_node_owner_old_api_version(self): headers = {api_base.Version.string: '1.32'} ndict = test_api_utils.post_get_test_node(owner='bob') diff --git a/ironic/tests/unit/api/test_acl.py b/ironic/tests/unit/api/test_acl.py index 5793e95a8..cdc20d477 100644 --- a/ironic/tests/unit/api/test_acl.py +++ b/ironic/tests/unit/api/test_acl.py @@ -81,10 +81,18 @@ class TestACLBase(base.BaseApiTest): body=None, assert_status=None, assert_dict_contains=None, assert_list_length=None, - deprecated=None): + deprecated=None, + self_manage_nodes=True): path = path.format(**self.format_data) self.mock_auth.side_effect = self._fake_process_request + # Set self management override + if not self_manage_nodes: + cfg.CONF.set_override( + 'project_admin_can_manage_own_nodes', + False, + 'api') + # always request the latest api version version = api_versions.max_version_string() rheaders = { diff --git a/ironic/tests/unit/api/test_audit.py b/ironic/tests/unit/api/test_audit.py index d85ed3e85..cedeabf17 100644 --- a/ironic/tests/unit/api/test_audit.py +++ b/ironic/tests/unit/api/test_audit.py @@ -35,7 +35,7 @@ class TestAuditMiddleware(base.BaseApiTest): @mock.patch.object(audit, 'AuditMiddleware', autospec=True) def test_enable_audit_request(self, mock_audit): - CONF.audit.enabled = True + CONF.set_override('enabled', True, 'audit') self._make_app() mock_audit.assert_called_once_with( mock.ANY, @@ -44,14 +44,13 @@ class TestAuditMiddleware(base.BaseApiTest): @mock.patch.object(audit, 'AuditMiddleware', autospec=True) def test_enable_audit_request_error(self, mock_audit): - CONF.audit.enabled = True + CONF.set_override('enabled', True, 'audit') mock_audit.side_effect = IOError("file access error") - self.assertRaises(exception.InputFileError, self._make_app) @mock.patch.object(audit, 'AuditMiddleware', autospec=True) def test_disable_audit_request(self, mock_audit): - CONF.audit.enabled = False + CONF.set_override('enabled', False, 'audit') self._make_app() self.assertFalse(mock_audit.called) diff --git a/ironic/tests/unit/api/test_ospmiddleware.py b/ironic/tests/unit/api/test_ospmiddleware.py index 555251dd7..f814d5688 100644 --- a/ironic/tests/unit/api/test_ospmiddleware.py +++ b/ironic/tests/unit/api/test_ospmiddleware.py @@ -32,12 +32,12 @@ class TestOsprofilerWsgiMiddleware(base.BaseApiTest): @mock.patch.object(web, 'WsgiMiddleware', autospec=True) def test_enable_osp_wsgi_request(self, mock_ospmiddleware): - CONF.profiler.enabled = True + CONF.set_override('enabled', True, 'profiler') self._make_app() mock_ospmiddleware.assert_called_once_with(mock.ANY) @mock.patch.object(web, 'WsgiMiddleware', autospec=True) def test_disable_osp_wsgi_request(self, mock_ospmiddleware): - CONF.profiler.enabled = False + CONF.set_override('enabled', False, 'profiler') self._make_app() self.assertFalse(mock_ospmiddleware.called) diff --git a/ironic/tests/unit/api/test_rbac_project_scoped.yaml b/ironic/tests/unit/api/test_rbac_project_scoped.yaml index 802600703..b55439ad1 100644 --- a/ironic/tests/unit/api/test_rbac_project_scoped.yaml +++ b/ironic/tests/unit/api/test_rbac_project_scoped.yaml @@ -89,35 +89,71 @@ owner_admin_cannot_post_nodes: body: &node_post_body name: node driver: fake-driverz - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +owner_admin_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *owner_admin_headers + body: *node_post_body + assert_status: 503 + self_manage_nodes: True owner_manager_cannot_post_nodes: path: '/v1/nodes' method: post headers: *owner_manager_headers body: *node_post_body - assert_status: 500 + assert_status: 403 lessee_admin_cannot_post_nodes: path: '/v1/nodes' method: post headers: *lessee_admin_headers body: *node_post_body - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +lessee_admin_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *lessee_admin_headers + body: *node_post_body + assert_status: 403 + self_manage_nodes: False lessee_manager_cannot_post_nodes: path: '/v1/nodes' method: post headers: *lessee_manager_headers body: *node_post_body - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +lessee_manager_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *lessee_manager_headers + body: *node_post_body + assert_status: 403 + self_manage_nodes: True third_party_admin_cannot_post_nodes: path: '/v1/nodes' method: post headers: *third_party_admin_headers body: *node_post_body - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +third_party_admin_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *third_party_admin_headers + body: *node_post_body + assert_status: 503 + self_manage_nodes: True # Based on nodes_post_member owner_member_cannot_post_nodes: @@ -125,7 +161,7 @@ owner_member_cannot_post_nodes: method: post headers: *owner_member_headers body: *node_post_body - assert_status: 500 + assert_status: 403 # Based on nodes_post_reader owner_reader_cannot_post_reader: @@ -133,7 +169,7 @@ owner_reader_cannot_post_reader: method: post headers: *owner_reader_headers body: *node_post_body - assert_status: 500 + assert_status: 403 # Based on nodes_get_admin # TODO: Create 3 nodes, 2 owned, 1 leased where it is also owned. @@ -671,6 +707,14 @@ owner_admin_cannot_delete_nodes: method: delete headers: *owner_admin_headers assert_status: 403 + self_manage_nodes: False + +owner_admin_can_delete_nodes: + path: '/v1/nodes/{owner_node_ident}' + method: delete + headers: *owner_admin_headers + assert_status: 503 + self_manage_nodes: True owner_manager_cannot_delete_nodes: path: '/v1/nodes/{owner_node_ident}' diff --git a/ironic/tests/unit/common/test_molds.py b/ironic/tests/unit/common/test_molds.py index bd2c37e47..810dd61bc 100644 --- a/ironic/tests/unit/common/test_molds.py +++ b/ironic/tests/unit/common/test_molds.py @@ -38,7 +38,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): mock_session = mock.Mock() mock_session.get_token.return_value = 'token' mock_swift.return_value = mock_session - cfg.CONF.molds.storage = 'swift' + cfg.CONF.set_override('storage', 'swift', 'molds') url = 'https://example.com/file1' data = {'key': 'value'} @@ -54,7 +54,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): mock_session = mock.Mock() mock_session.get_token.return_value = None mock_swift.return_value = mock_session - cfg.CONF.molds.storage = 'swift' + cfg.CONF.set_override('storage', 'swift', 'molds') url = 'https://example.com/file1' data = {'key': 'value'} @@ -66,9 +66,9 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'put', autospec=True) def test_save_configuration_http(self, mock_put): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') url = 'https://example.com/file1' data = {'key': 'value'} @@ -81,9 +81,9 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'put', autospec=True) def test_save_configuration_http_noauth(self, mock_put): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = None - cfg.CONF.molds.password = None + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', None, 'molds') + cfg.CONF.set_override('password', None, 'molds') url = 'https://example.com/file1' data = {'key': 'value'} @@ -95,9 +95,9 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'put', autospec=True) def test_save_configuration_http_error(self, mock_put): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') response = mock.MagicMock() response.status_code = 404 response.raise_for_status.side_effect = requests.exceptions.HTTPError @@ -116,11 +116,11 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'put', autospec=True) def test_save_configuration_connection_error(self, mock_put): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' - cfg.CONF.molds.retry_interval = 0 - cfg.CONF.molds.retry_attempts = 3 + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') + cfg.CONF.set_override('retry_interval', 0, 'molds') + cfg.CONF.set_override('retry_attempts', 3, 'molds') response = mock.MagicMock() mock_put.side_effect = [ requests.exceptions.ConnectTimeout, @@ -137,11 +137,11 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'put', autospec=True) def test_save_configuration_connection_error_exceeded(self, mock_put): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' - cfg.CONF.molds.retry_interval = 0 - cfg.CONF.molds.retry_attempts = 2 + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') + cfg.CONF.set_override('retry_interval', 0, 'molds') + cfg.CONF.set_override('retry_attempts', 2, 'molds') mock_put.side_effect = [ requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError] @@ -164,7 +164,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): mock_session = mock.Mock() mock_session.get_token.return_value = 'token' mock_swift.return_value = mock_session - cfg.CONF.molds.storage = 'swift' + cfg.CONF.set_override('storage', 'swift', 'molds') response = mock.MagicMock() response.status_code = 200 response.content = "{'key': 'value'}" @@ -185,7 +185,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): mock_session = mock.Mock() mock_session.get_token.return_value = None mock_swift.return_value = mock_session - cfg.CONF.molds.storage = 'swift' + cfg.CONF.set_override('storage', 'swift', 'molds') url = 'https://example.com/file1' with task_manager.acquire(self.context, self.node.uuid) as task: @@ -196,9 +196,9 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'get', autospec=True) def test_get_configuration_http(self, mock_get): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') response = mock.MagicMock() response.status_code = 200 response.content = "{'key': 'value'}" @@ -215,9 +215,9 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'get', autospec=True) def test_get_configuration_http_noauth(self, mock_get): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = None - cfg.CONF.molds.password = None + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', None, 'molds') + cfg.CONF.set_override('password', None, 'molds') response = mock.MagicMock() response.status_code = 200 response.content = "{'key': 'value'}" @@ -233,9 +233,9 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'get', autospec=True) def test_get_configuration_http_error(self, mock_get): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') response = mock.MagicMock() response.status_code = 404 response.raise_for_status.side_effect = requests.exceptions.HTTPError @@ -253,11 +253,11 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'get', autospec=True) def test_get_configuration_connection_error(self, mock_get): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' - cfg.CONF.molds.retry_interval = 0 - cfg.CONF.molds.retry_attempts = 3 + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') + cfg.CONF.set_override('retry_interval', 0, 'molds') + cfg.CONF.set_override('retry_attempts', 3, 'molds') response = mock.MagicMock() mock_get.side_effect = [ requests.exceptions.ConnectTimeout, @@ -274,11 +274,11 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'get', autospec=True) def test_get_configuration_mold_connection_error_exceeded(self, mock_get): - cfg.CONF.molds.storage = 'http' - cfg.CONF.molds.user = 'user' - cfg.CONF.molds.password = 'password' - cfg.CONF.molds.retry_interval = 0 - cfg.CONF.molds.retry_attempts = 2 + cfg.CONF.set_override('storage', 'http', 'molds') + cfg.CONF.set_override('user', 'user', 'molds') + cfg.CONF.set_override('password', 'password', 'molds') + cfg.CONF.set_override('retry_interval', 0, 'molds') + cfg.CONF.set_override('retry_attempts', 2, 'molds') mock_get.side_effect = [ requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError] @@ -296,7 +296,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'get', autospec=True) def test_get_configuration_empty(self, mock_get): - cfg.CONF.molds.storage = 'http' + cfg.CONF.set_override('storage', 'http', 'molds') response = mock.MagicMock() response.status_code = 200 response.content = '' @@ -309,7 +309,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase): @mock.patch.object(requests, 'get', autospec=True) def test_get_configuration_invalid_json(self, mock_get): - cfg.CONF.molds.storage = 'http' + cfg.CONF.set_override('storage', 'http', 'molds') response = mock.MagicMock() response.status_code = 200 response.content = 'not json' diff --git a/ironic/tests/unit/common/test_pxe_utils.py b/ironic/tests/unit/common/test_pxe_utils.py index c6dc9bffa..f9d781830 100644 --- a/ironic/tests/unit/common/test_pxe_utils.py +++ b/ironic/tests/unit/common/test_pxe_utils.py @@ -27,6 +27,7 @@ from oslo_utils import uuidutils from ironic.common import exception from ironic.common.glance_service import image_service +from ironic.common import image_service as base_image_service from ironic.common import pxe_utils from ironic.common import states from ironic.common import utils @@ -1295,25 +1296,6 @@ class PXEInterfacesTestCase(db_base.DbTestCase): self.assertEqual('instance_ramdisk_uuid', task.node.instance_info['ramdisk']) - def test_get_instance_image_info(self): - # Tests when 'is_whole_disk_image' exists in driver_internal_info - # NOTE(TheJulia): The method being tested is primarily geared for - # only netboot operation as the information should only need to be - # looked up again during network booting. - self.config(group="deploy", default_boot_option="netboot") - self._test_get_instance_image_info() - - def test_get_instance_image_info_without_is_whole_disk_image(self): - # NOTE(TheJulia): The method being tested is primarily geared for - # only netboot operation as the information should only need to be - # looked up again during network booting. - self.config(group="deploy", default_boot_option="netboot") - # Tests when 'is_whole_disk_image' doesn't exists in - # driver_internal_info - del self.node.driver_internal_info['is_whole_disk_image'] - self.node.save() - self._test_get_instance_image_info() - @mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option', return_value='local', autospec=True) def test_get_instance_image_info_localboot(self, boot_opt_mock): @@ -1465,6 +1447,65 @@ class PXEInterfacesTestCase(db_base.DbTestCase): @mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option', return_value='kickstart', autospec=True) + @mock.patch.object(base_image_service.HttpImageService, 'show', + autospec=True) + def test_get_instance_image_info_with_kickstart_url_http( + self, image_show_mock, boot_opt_mock): + properties = {'properties': {}} + expected_info = {'ramdisk': + ('http://fake.url/ramdisk', + os.path.join(CONF.pxe.tftp_root, + self.node.uuid, + 'ramdisk')), + 'kernel': + ('http://fake.url/kernel', + os.path.join(CONF.pxe.tftp_root, + self.node.uuid, + 'kernel')), + 'ks_template': + (CONF.anaconda.default_ks_template, + os.path.join(CONF.deploy.http_root, + self.node.uuid, + 'ks.cfg.template')), + 'ks_cfg': + ('', + os.path.join(CONF.deploy.http_root, + self.node.uuid, + 'ks.cfg'))} + image_show_mock.return_value = properties + self.context.auth_token = 'fake' + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + dii = task.node.driver_internal_info + dii['is_source_a_path'] = True + task.node.driver_internal_info = dii + i_info = task.node.instance_info + i_info['image_source'] = 'http://fake.url/path' + i_info['kernel'] = 'http://fake.url/kernel' + i_info['ramdisk'] = 'http://fake.url/ramdisk' + task.node.instance_info = i_info + task.node.save() + image_info = pxe_utils.get_instance_image_info( + task, ipxe_enabled=False) + self.assertEqual(expected_info, image_info) + # In the absense of kickstart template in both instance_info and + # image default kickstart template is used + self.assertEqual(CONF.anaconda.default_ks_template, + image_info['ks_template'][0]) + calls = [mock.call(task.node), mock.call(task.node)] + boot_opt_mock.assert_has_calls(calls) + # Instance info gets presedence over kickstart template on the + # image + properties['properties'] = {'ks_template': 'glance://template_id'} + task.node.instance_info['ks_template'] = 'https://server/fake.tmpl' + image_show_mock.return_value = properties + image_info = pxe_utils.get_instance_image_info( + task, ipxe_enabled=False) + self.assertEqual('https://server/fake.tmpl', + image_info['ks_template'][0]) + + @mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option', + return_value='kickstart', autospec=True) @mock.patch.object(image_service.GlanceImageService, 'show', autospec=True) def test_get_instance_image_info_kickstart_stage2_missing( self, image_show_mock, boot_opt_mock): @@ -1567,6 +1608,27 @@ class PXEBuildKickstartConfigOptionsTestCase(db_base.DbTestCase): self.assertTrue(params['ks_options'].pop('agent_token')) self.assertEqual(expected, params['ks_options']) + @mock.patch.object(deploy_utils, 'get_ironic_api_url', autospec=True) + def test_build_kickstart_config_options_pxe_source_path(self, + api_url_mock): + api_url_mock.return_value = 'http://ironic-api' + d_info = self.node.driver_internal_info + d_info['is_source_a_path'] = True + self.node.driver_internal_info = d_info + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + expected = {} + expected['liveimg_url'] = task.node.instance_info['image_url'] + expected['config_drive'] = '' + expected['heartbeat_url'] = ( + 'http://ironic-api/v1/heartbeat/%s' % task.node.uuid + ) + expected['is_source_a_path'] = 'true' + params = pxe_utils.build_kickstart_config_options(task) + self.assertTrue(params['ks_options'].pop('agent_token')) + self.assertEqual(expected, params['ks_options']) + @mock.patch('ironic.common.utils.render_template', autospec=True) def test_prepare_instance_kickstart_config_not_anaconda_boot(self, render_mock): diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index b433aa4a5..378d65f15 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -7319,7 +7319,6 @@ class DoNodeAdoptionTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): mock_take_over.assert_called_once_with(task.driver.deploy, task) self.assertFalse(mock_start_console.called) mock_boot_validate.assert_not_called() - self.assertNotIn('is_whole_disk_image', task.node.driver_internal_info) @mock.patch('ironic.common.image_service.HttpImageService.validate_href', autospec=True) @@ -7328,26 +7327,23 @@ class DoNodeAdoptionTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): @mock.patch('ironic.drivers.modules.fake.FakeBoot.validate', autospec=True) @mock.patch('ironic.drivers.modules.fake.FakeConsole.start_console', autospec=True) - @mock.patch('ironic.drivers.modules.fake.FakeDeploy.take_over', + @mock.patch('ironic.drivers.modules.ramdisk.RamdiskDeploy.take_over', autospec=True) - @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare', + @mock.patch('ironic.drivers.modules.ramdisk.RamdiskDeploy.prepare', autospec=True) - def test__do_adoption_with_netboot(self, - mock_prepare, - mock_take_over, - mock_start_console, - mock_boot_validate, - mock_power_validate, - mock_validate_href): + def test__do_adoption_ramdisk_deploy(self, + mock_prepare, + mock_take_over, + mock_start_console, + mock_boot_validate, + mock_power_validate, + mock_validate_href): """Test a successful node adoption""" self._start_service() node = obj_utils.create_test_node( self.context, driver='fake-hardware', - provision_state=states.ADOPTING, - instance_info={ - 'capabilities': {'boot_option': 'netboot'}, - 'image_source': 'http://127.0.0.1/image', - }) + deploy_interface='ramdisk', + provision_state=states.ADOPTING) task = task_manager.TaskManager(self.context, node.uuid) self.service._do_adoption(task) @@ -7360,10 +7356,6 @@ class DoNodeAdoptionTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): mock_take_over.assert_called_once_with(task.driver.deploy, task) self.assertFalse(mock_start_console.called) mock_boot_validate.assert_called_once_with(task.driver.boot, task) - self.assertTrue(task.node.driver_internal_info.get( - 'is_whole_disk_image')) - mock_validate_href.assert_called_once_with(mock.ANY, - 'http://127.0.0.1/image') @mock.patch('ironic.drivers.modules.fake.FakeBoot.validate', autospec=True) @mock.patch('ironic.drivers.modules.fake.FakeConsole.start_console', @@ -7410,9 +7402,9 @@ class DoNodeAdoptionTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): @mock.patch('ironic.drivers.modules.fake.FakeBoot.validate', autospec=True) @mock.patch('ironic.drivers.modules.fake.FakeConsole.start_console', autospec=True) - @mock.patch('ironic.drivers.modules.fake.FakeDeploy.take_over', + @mock.patch('ironic.drivers.modules.ramdisk.RamdiskDeploy.take_over', autospec=True) - @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare', + @mock.patch('ironic.drivers.modules.ramdisk.RamdiskDeploy.prepare', autospec=True) def test__do_adoption_boot_validate_failure(self, mock_prepare, @@ -7428,10 +7420,8 @@ class DoNodeAdoptionTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): self._start_service() node = obj_utils.create_test_node( self.context, driver='fake-hardware', - provision_state=states.ADOPTING, - instance_info={ - 'capabilities': {'boot_option': 'netboot'}, - }) + deploy_interface='ramdisk', + provision_state=states.ADOPTING) task = task_manager.TaskManager(self.context, node.uuid) self.service._do_adoption(task) diff --git a/ironic/tests/unit/drivers/boot-fallback.ipxe b/ironic/tests/unit/drivers/boot-fallback.ipxe index bf8ab414c..ada2646a6 100644 --- a/ironic/tests/unit/drivers/boot-fallback.ipxe +++ b/ironic/tests/unit/drivers/boot-fallback.ipxe @@ -10,6 +10,12 @@ isset ${net${netid}/mac} || goto loop_done echo Attempting to boot from MAC ${net${netid}/mac:hexhyp} chain pxelinux.cfg/${net${netid}/mac:hexhyp} || goto loop +# If we've got here the chained config returned success +# suggesting "sanboot" in boot_whole_disk failed (some UEFI cases) +# exit 0 so the bios continues to the next device +echo Exiting pxe config to allow boot to continue on next device +exit 0 + :loop_done chain inspector.ipxe | goto boot_failed diff --git a/ironic/tests/unit/drivers/boot.ipxe b/ironic/tests/unit/drivers/boot.ipxe index aa8ee9e51..006bcb4aa 100644 --- a/ironic/tests/unit/drivers/boot.ipxe +++ b/ironic/tests/unit/drivers/boot.ipxe @@ -10,6 +10,12 @@ isset ${net${netid}/mac} || goto loop_done echo Attempting to boot from MAC ${net${netid}/mac:hexhyp} chain pxelinux.cfg/${net${netid}/mac:hexhyp} || goto loop +# If we've got here the chained config returned success +# suggesting "sanboot" in boot_whole_disk failed (some UEFI cases) +# exit 0 so the bios continues to the next device +echo Exiting pxe config to allow boot to continue on next device +exit 0 + :loop_done echo PXE boot failed! No configuration found for any of the present NICs. echo Press any key to reboot... diff --git a/ironic/tests/unit/drivers/ipxe_config.template b/ironic/tests/unit/drivers/ipxe_config.template index 70f8a03f1..3005a73d1 100644 --- a/ironic/tests/unit/drivers/ipxe_config.template +++ b/ironic/tests/unit/drivers/ipxe_config.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda @@ -44,4 +38,4 @@ initrd http://1.2.3.4:1234/ramdisk || goto boot_ramdisk boot :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/ipxe_config_boot_from_anaconda.template b/ironic/tests/unit/drivers/ipxe_config_boot_from_anaconda.template index 7963b3883..0c2812e85 100644 --- a/ironic/tests/unit/drivers/ipxe_config_boot_from_anaconda.template +++ b/ironic/tests/unit/drivers/ipxe_config_boot_from_anaconda.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.repo=http://1.2.3.4/path/to/os/ initrd=ramdisk || goto boot_anaconda @@ -44,4 +38,4 @@ initrd http://1.2.3.4:1234/ramdisk || goto boot_ramdisk boot :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/ipxe_config_boot_from_iso.template b/ironic/tests/unit/drivers/ipxe_config_boot_from_iso.template index c7133c7b6..9c889854d 100644 --- a/ironic/tests/unit/drivers/ipxe_config_boot_from_iso.template +++ b/ironic/tests/unit/drivers/ipxe_config_boot_from_iso.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda @@ -42,4 +36,4 @@ imgfree sanboot http://1.2.3.4:1234/uuid/iso :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/ipxe_config_boot_from_ramdisk.template b/ironic/tests/unit/drivers/ipxe_config_boot_from_ramdisk.template index 70f8a03f1..3005a73d1 100644 --- a/ironic/tests/unit/drivers/ipxe_config_boot_from_ramdisk.template +++ b/ironic/tests/unit/drivers/ipxe_config_boot_from_ramdisk.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda @@ -44,4 +38,4 @@ initrd http://1.2.3.4:1234/ramdisk || goto boot_ramdisk boot :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_extra_volume.template b/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_extra_volume.template index 0a872804a..ee619b53e 100644 --- a/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_extra_volume.template +++ b/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_extra_volume.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda @@ -62,4 +56,4 @@ sleep 10 goto boot_iscsi :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_multipath.template b/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_multipath.template index 571216e39..ede0283c6 100644 --- a/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_multipath.template +++ b/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_multipath.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda @@ -59,4 +53,4 @@ sleep 10 goto boot_iscsi :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_no_extra_volumes.template b/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_no_extra_volumes.template index 6b7a4394d..61fbec756 100644 --- a/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_no_extra_volumes.template +++ b/ironic/tests/unit/drivers/ipxe_config_boot_from_volume_no_extra_volumes.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda @@ -59,4 +53,4 @@ sleep 10 goto boot_iscsi :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/ipxe_config_timeout.template b/ironic/tests/unit/drivers/ipxe_config_timeout.template index 2458f010b..af00f5490 100644 --- a/ironic/tests/unit/drivers/ipxe_config_timeout.template +++ b/ironic/tests/unit/drivers/ipxe_config_timeout.template @@ -25,12 +25,6 @@ echo Powering off in 30 seconds. sleep 30 poweroff -:boot_partition -imgfree -kernel --timeout 120 http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param initrd=ramdisk || goto boot_partition -initrd --timeout 120 http://1.2.3.4:1234/ramdisk || goto boot_partition -boot - :boot_anaconda imgfree kernel --timeout 120 http://1.2.3.4:1234/kernel text test_param inst.ks=http://fake/ks.cfg inst.stage2=http://fake/stage2 initrd=ramdisk || goto boot_anaconda @@ -44,4 +38,4 @@ initrd --timeout 120 http://1.2.3.4:1234/ramdisk || goto boot_ramdisk boot :boot_whole_disk -sanboot --no-describe +sanboot --no-describe || exit 0 diff --git a/ironic/tests/unit/drivers/modules/ansible/test_deploy.py b/ironic/tests/unit/drivers/modules/ansible/test_deploy.py index ed9199575..3f295c4d9 100644 --- a/ironic/tests/unit/drivers/modules/ansible/test_deploy.py +++ b/ironic/tests/unit/drivers/modules/ansible/test_deploy.py @@ -623,24 +623,6 @@ class TestAnsibleDeploy(AnsibleDeployTestCaseBase): {'instance_info.image_source': INSTANCE_INFO['image_source']}, mock.ANY) - @mock.patch.object(deploy_utils, 'get_boot_option', - return_value='netboot', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True) - def test_validate_not_iwdi_netboot(self, pxe_boot_validate_mock, - get_boot_mock): - driver_internal_info = dict(DRIVER_INTERNAL_INFO) - driver_internal_info['is_whole_disk_image'] = False - self.node.driver_internal_info = driver_internal_info - self.node.save() - - with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: - self.assertRaises(exception.InvalidParameterValue, - self.driver.validate, task) - pxe_boot_validate_mock.assert_called_once_with( - task.driver.boot, task) - get_boot_mock.assert_called_once_with(task.node) - @mock.patch.object(ansible_deploy, '_calculate_memory_req', autospec=True, return_value=2000) @mock.patch.object(utils, 'node_power_action', autospec=True) diff --git a/ironic/tests/unit/drivers/modules/ilo/test_boot.py b/ironic/tests/unit/drivers/modules/ilo/test_boot.py index 128f603c5..8aa6f78da 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_boot.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_boot.py @@ -116,9 +116,9 @@ class IloBootCommonMethodsTestCase(test_common.BaseIloTest): self.assertEqual(expected_driver_info, actual_driver_info) def test_parse_driver_info_deploy_config(self): - CONF.conductor.deploy_kernel = 'kernel' - CONF.conductor.deploy_ramdisk = 'ramdisk' - CONF.conductor.bootloader = 'bootloader' + CONF.set_override('deploy_kernel', 'kernel', 'conductor') + CONF.set_override('deploy_ramdisk', 'ramdisk', 'conductor') + CONF.set_override('bootloader', 'bootloader', 'conductor') expected_driver_info = {'deploy_kernel': 'kernel', 'deploy_ramdisk': 'ramdisk', 'bootloader': 'bootloader', @@ -128,10 +128,9 @@ class IloBootCommonMethodsTestCase(test_common.BaseIloTest): self.assertEqual(expected_driver_info, actual_driver_info) def test_parse_driver_info_rescue_config(self): - CONF.conductor.rescue_kernel = 'kernel' - CONF.conductor.rescue_ramdisk = 'ramdisk' - CONF.conductor.bootloader = 'bootloader' - + CONF.set_override('rescue_kernel', 'kernel', 'conductor') + CONF.set_override('rescue_ramdisk', 'ramdisk', 'conductor') + CONF.set_override('bootloader', 'bootloader', 'conductor') expected_driver_info = {'rescue_kernel': 'kernel', 'rescue_ramdisk': 'ramdisk', 'bootloader': 'bootloader', @@ -141,9 +140,8 @@ class IloBootCommonMethodsTestCase(test_common.BaseIloTest): self.assertEqual(expected_driver_info, actual_driver_info) def test_parse_driver_info_bootloader_none(self): - CONF.conductor.deploy_kernel = 'kernel' - CONF.conductor.deploy_ramdisk = 'ramdisk' - + CONF.set_override('deploy_kernel', 'kernel', 'conductor') + CONF.set_override('deploy_ramdisk', 'ramdisk', 'conductor') self.assertRaisesRegex(exception.MissingParameterValue, 'bootloader', ilo_boot.parse_driver_info, self.node) @@ -452,14 +450,14 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest): spec_set=True, autospec=True) @mock.patch.object(service_utils, 'is_glance_image', spec_set=True, autospec=True) - def test_validate_ramdisk_boot_option_glance(self, is_glance_image_mock, - validate_href_mock, - val_driver_info_mock): + def test_validate_ramdisk_deploy_glance(self, is_glance_image_mock, + validate_href_mock, + val_driver_info_mock): instance_info = self.node.instance_info boot_iso = '6b2f0c0c-79e8-4db6-842e-43c9764204af' instance_info['boot_iso'] = boot_iso - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' self.node.instance_info = instance_info + self.node.deploy_interface = 'ramdisk' self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -475,14 +473,14 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest): spec_set=True, autospec=True) @mock.patch.object(service_utils, 'is_glance_image', spec_set=True, autospec=True) - def test_validate_ramdisk_boot_option_webserver(self, is_glance_image_mock, - validate_href_mock, - val_driver_info_mock): + def test_validate_ramdisk_deploy_webserver(self, is_glance_image_mock, + validate_href_mock, + val_driver_info_mock): instance_info = self.node.instance_info boot_iso = 'http://myserver/boot.iso' instance_info['boot_iso'] = boot_iso - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' self.node.instance_info = instance_info + self.node.deploy_interface = 'ramdisk' self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -499,18 +497,18 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest): spec_set=True, autospec=True) @mock.patch.object(service_utils, 'is_glance_image', spec_set=True, autospec=True) - def test_validate_ramdisk_boot_option_webserver_exc(self, - is_glance_image_mock, - validate_href_mock, - val_driver_info_mock, - log_mock): + def test_validate_ramdisk_deploy_webserver_exc(self, + is_glance_image_mock, + validate_href_mock, + val_driver_info_mock, + log_mock): instance_info = self.node.instance_info validate_href_mock.side_effect = exception.ImageRefValidationFailed( image_href='http://myserver/boot.iso', reason='fail') boot_iso = 'http://myserver/boot.iso' instance_info['boot_iso'] = boot_iso - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' self.node.instance_info = instance_info + self.node.deploy_interface = 'ramdisk' self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -523,7 +521,7 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest): is_glance_image_mock.assert_called_once_with(boot_iso) validate_href_mock.assert_called_once_with(mock.ANY, boot_iso) self.assertFalse(val_driver_info_mock.called) - self.assertIn("Virtual media deploy with 'ramdisk' boot_option " + self.assertIn("Virtual media deploy with 'ramdisk' deploy " "accepts only Glance images or HTTP(S) URLs as " "instance_info['boot_iso'].", log_mock.call_args[0][0]) @@ -857,7 +855,7 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest): autospec=True) @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True, autospec=True) - def _test_prepare_instance_whole_disk_image( + def test_prepare_instance_whole_disk_image( self, cleanup_vmedia_boot_mock, set_boot_device_mock, update_boot_mode_mock, update_secure_boot_mode_mock, is_iscsi_boot_mock): @@ -877,41 +875,31 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest): self.assertIsNone(task.node.driver_internal_info.get( 'ilo_uefi_iscsi_boot')) - def test_prepare_instance_whole_disk_image_local(self): - self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} - self.node.save() - self._test_prepare_instance_whole_disk_image() - - def test_prepare_instance_whole_disk_image(self): - self._test_prepare_instance_whole_disk_image() - @mock.patch.object(deploy_utils, 'is_iscsi_boot', spec_set=True, autospec=True) @mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed', spec_set=True, autospec=True) @mock.patch.object(ilo_common, 'update_boot_mode', spec_set=True, autospec=True) - @mock.patch.object(ilo_boot.IloVirtualMediaBoot, - '_configure_vmedia_boot', spec_set=True, + @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, autospec=True) @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', spec_set=True, autospec=True) def test_prepare_instance_partition_image( - self, cleanup_vmedia_boot_mock, configure_vmedia_mock, + self, cleanup_vmedia_boot_mock, set_boot_device_mock, update_boot_mode_mock, update_secure_boot_mode_mock, is_iscsi_boot_mock): self.node.driver_internal_info = {'root_uuid_or_disk_id': ( "12312642-09d3-467f-8e09-12385826a123")} - self.node.instance_info = { - 'capabilities': {'boot_option': 'netboot'}} self.node.save() is_iscsi_boot_mock.return_value = False 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) - configure_vmedia_mock.assert_called_once_with( - mock.ANY, task, "12312642-09d3-467f-8e09-12385826a123") + set_boot_device_mock.assert_called_once_with(task, + boot_devices.DISK, + persistent=True) update_boot_mode_mock.assert_called_once_with(task) update_secure_boot_mode_mock.assert_called_once_with(task) self.assertIsNone(task.node.driver_internal_info.get( @@ -998,9 +986,7 @@ class IloVirtualMediaBootTestCase(test_common.BaseIloTest): cleanup_vmedia_boot_mock): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - instance_info = task.node.instance_info - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' - task.node.instance_info = instance_info + task.node.deploy_interface = 'ramdisk' task.node.save() is_iscsi_boot_mock.return_value = False url = 'http://myserver/boot.iso' @@ -1377,7 +1363,7 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): self.config(enabled_hardware_types=['ilo5'], enabled_boot_interfaces=['ilo-uefi-https'], enabled_console_interfaces=['ilo'], - enabled_deploy_interfaces=['direct'], + enabled_deploy_interfaces=['direct', 'ramdisk'], enabled_inspect_interfaces=['ilo'], enabled_management_interfaces=['ilo5'], enabled_power_interfaces=['ilo'], @@ -1653,16 +1639,16 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): spec_set=True, autospec=True) @mock.patch.object(service_utils, 'is_glance_image', spec_set=True, autospec=True) - def test_validate_ramdisk_boot_option_glance(self, is_glance_image_mock, - validate_href_mock, - val_driver_info_mock, - get_boot_mock): + def test_validate_ramdisk_deploy_glance(self, is_glance_image_mock, + validate_href_mock, + val_driver_info_mock, + get_boot_mock): get_boot_mock.return_value = 'UEFI' instance_info = self.node.instance_info boot_iso = '6b2f0c0c-79e8-4db6-842e-43c9764204af' instance_info['boot_iso'] = boot_iso - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' self.node.instance_info = instance_info + self.node.deploy_interface = 'ramdisk' self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1680,16 +1666,16 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): spec_set=True, autospec=True) @mock.patch.object(service_utils, 'is_glance_image', spec_set=True, autospec=True) - def test_validate_ramdisk_boot_option_webserver(self, is_glance_image_mock, - validate_href_mock, - val_driver_info_mock, - get_boot_mock): + def test_validate_ramdisk_deploy_webserver(self, is_glance_image_mock, + validate_href_mock, + val_driver_info_mock, + get_boot_mock): get_boot_mock.return_value = 'UEFI' instance_info = self.node.instance_info boot_iso = 'http://myserver/boot.iso' instance_info['boot_iso'] = boot_iso - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' self.node.instance_info = instance_info + self.node.deploy_interface = 'ramdisk' self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1708,7 +1694,7 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): spec_set=True, autospec=True) @mock.patch.object(service_utils, 'is_glance_image', spec_set=True, autospec=True) - def test_validate_ramdisk_boot_option_webserver_exc( + def test_validate_ramdisk_deploy_webserver_exc( self, is_glance_image_mock, validate_href_mock, val_driver_info_mock, log_mock, get_boot_mock): @@ -1718,8 +1704,8 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): image_href='http://myserver/boot.iso', reason='fail') boot_iso = 'http://myserver/boot.iso' instance_info['boot_iso'] = boot_iso - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' self.node.instance_info = instance_info + self.node.deploy_interface = 'ramdisk' self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -1732,7 +1718,7 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): is_glance_image_mock.assert_called_once_with(boot_iso) validate_href_mock.assert_called_once_with(mock.ANY, boot_iso) self.assertFalse(val_driver_info_mock.called) - self.assertIn("UEFI-HTTPS boot with 'ramdisk' boot_option " + self.assertIn("UEFI-HTTPS boot with 'ramdisk' deploy " "accepts only Glance images or HTTPS URLs as " "instance_info['boot_iso'].", log_mock.call_args[0][0]) @@ -1902,7 +1888,7 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): spec_set=True, autospec=True) @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, autospec=True) - def _test_prepare_instance_local_or_whole_disk_image( + def test_prepare_instance_local_or_whole_disk_image( self, set_boot_device_mock, parse_deploy_mock, prepare_iso_mock, setup_uefi_https_mock, cleanup_iso_mock, update_secureboot_mock): @@ -1919,16 +1905,6 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): prepare_iso_mock.assert_not_called() setup_uefi_https_mock.assert_not_called() - def test_prepare_instance_image_local(self): - self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} - self.node.save() - self._test_prepare_instance_local_or_whole_disk_image() - - def test_prepare_instance_whole_disk_image(self): - self.node.driver_internal_info = {'is_whole_disk_image': True} - self.node.save() - self._test_prepare_instance_local_or_whole_disk_image() - @mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed', spec_set=True, autospec=True) @mock.patch.object(image_utils, 'cleanup_iso_image', spec_set=True, @@ -1937,41 +1913,30 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): spec_set=True, autospec=True) @mock.patch.object(image_utils, 'prepare_boot_iso', spec_set=True, autospec=True) - @mock.patch.object(ilo_boot.IloUefiHttpsBoot, '_parse_deploy_info', - spec_set=True, autospec=True) @mock.patch.object(manager_utils, 'node_set_boot_device', spec_set=True, autospec=True) def test_prepare_instance_partition_image( self, set_boot_device_mock, - parse_deploy_mock, prepare_iso_mock, setup_uefi_https_mock, + prepare_iso_mock, setup_uefi_https_mock, cleanup_iso_mock, update_secureboot_mock): - self.node.instance_info = { - 'capabilities': '{"boot_option": "netboot"}' - } self.node.driver_internal_info = { 'root_uuid_or_disk_id': ( "12312642-09d3-467f-8e09-12385826a123") } self.node.driver_internal_info.update({'is_whole_disk_image': False}) self.node.save() - d_info = {'a': 'x', 'b': 'y'} - parse_deploy_mock.return_value = d_info - prepare_iso_mock.return_value = "recreated-iso" with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.boot.prepare_instance(task) cleanup_iso_mock.assert_called_once_with(task) - set_boot_device_mock.assert_not_called() - parse_deploy_mock.assert_called_once_with(mock.ANY, task.node) - prepare_iso_mock.assert_called_once_with( - task, d_info, root_uuid='12312642-09d3-467f-8e09-12385826a123') + set_boot_device_mock.assert_called_once_with(task, + boot_devices.DISK, + persistent=True) + prepare_iso_mock.assert_not_called() update_secureboot_mock.assert_called_once_with(task) - setup_uefi_https_mock.assert_called_once_with( - task, "recreated-iso", True) - self.assertEqual(task.node.instance_info['boot_iso'], - "recreated-iso") + setup_uefi_https_mock.assert_not_called() @mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed', spec_set=True, autospec=True) @@ -1998,9 +1963,7 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - instance_info = task.node.instance_info - instance_info['capabilities'] = '{"boot_option": "ramdisk"}' - task.node.instance_info = instance_info + task.node.deploy_interface = 'ramdisk' task.node.save() task.driver.boot.prepare_instance(task) diff --git a/ironic/tests/unit/drivers/modules/ilo/test_common.py b/ironic/tests/unit/drivers/modules/ilo/test_common.py index 605124b69..352eb0837 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_common.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_common.py @@ -434,8 +434,8 @@ class IloCommonMethodsTestCase(BaseIloTest): tempfile_mock.return_value = mock_image_file_handle self.config(use_web_server_for_images=True, group='ilo') deploy_args = {'arg1': 'val1', 'arg2': 'val2'} - CONF.deploy.http_url = "http://abc.com/httpboot" - CONF.deploy.http_root = "/httpboot" + CONF.set_override('http_url', 'http://abc.com/httpboot', 'deploy') + CONF.set_override('http_root', '/httpboot', 'deploy') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -676,8 +676,8 @@ class IloCommonMethodsTestCase(BaseIloTest): swift_obj_mock = swift_api_mock.return_value boot_iso = 'swift:object-name' swift_obj_mock.get_temp_url.return_value = 'image_url' - CONF.ilo.swift_ilo_container = 'ilo_cont' - CONF.ilo.swift_object_expiry_timeout = 1 + CONF.set_override('swift_ilo_container', 'ilo_cont', 'ilo') + CONF.set_override('swift_object_expiry_timeout', 1, 'ilo') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: ilo_common.setup_vmedia_for_boot(task, boot_iso) @@ -704,7 +704,7 @@ class IloCommonMethodsTestCase(BaseIloTest): def test_cleanup_vmedia_boot(self, get_name_mock, swift_api_mock, eject_mock): swift_obj_mock = swift_api_mock.return_value - CONF.ilo.swift_ilo_container = 'ilo_cont' + CONF.set_override('swift_ilo_container', 'ilo_cont', 'ilo') get_name_mock.return_value = 'image-node-uuid' @@ -727,7 +727,7 @@ class IloCommonMethodsTestCase(BaseIloTest): exc = exception.SwiftOperationError('error') swift_obj_mock = swift_api_mock.return_value swift_obj_mock.delete_object.side_effect = exc - CONF.ilo.swift_ilo_container = 'ilo_cont' + CONF.set_override('swift_ilo_container', 'ilo_cont', 'ilo') get_name_mock.return_value = 'image-node-uuid' @@ -752,7 +752,7 @@ class IloCommonMethodsTestCase(BaseIloTest): exc = exception.SwiftObjectNotFoundError('error') swift_obj_mock = swift_api_mock.return_value swift_obj_mock.delete_object.side_effect = exc - CONF.ilo.swift_ilo_container = 'ilo_cont' + CONF.set_override('swift_ilo_container', 'ilo_cont', 'ilo') get_name_mock.return_value = 'image-node-uuid' @@ -771,7 +771,7 @@ class IloCommonMethodsTestCase(BaseIloTest): def test_cleanup_vmedia_boot_for_webserver(self, destroy_image_mock, eject_mock): - CONF.ilo.use_web_server_for_images = True + CONF.set_override('use_web_server_for_images', True, 'ilo') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -932,8 +932,8 @@ class IloCommonMethodsTestCase(BaseIloTest): autospec=True) def test_copy_image_to_web_server(self, copy_mock, chmod_mock): - CONF.deploy.http_url = "http://x.y.z.a/webserver/" - CONF.deploy.http_root = "/webserver" + CONF.set_override('http_url', 'http://x.y.z.a/webserver/', 'deploy') + CONF.set_override('http_root', '/webserver', 'deploy') expected_url = "http://x.y.z.a/webserver/image-UUID" source = 'tmp_image_file' destination = "image-UUID" @@ -949,8 +949,8 @@ class IloCommonMethodsTestCase(BaseIloTest): autospec=True) def test_copy_image_to_web_server_fails(self, copy_mock, chmod_mock): - CONF.deploy.http_url = "http://x.y.z.a/webserver/" - CONF.deploy.http_root = "/webserver" + CONF.set_override('http_url', 'http://x.y.z.a/webserver/', 'deploy') + CONF.set_override('http_root', '/webserver', 'deploy') source = 'tmp_image_file' destination = "image-UUID" image_path = "/webserver/image-UUID" @@ -965,8 +965,8 @@ class IloCommonMethodsTestCase(BaseIloTest): @mock.patch.object(ilo_common, 'ironic_utils', autospec=True) def test_remove_image_from_web_server(self, utils_mock): # | GIVEN | - CONF.deploy.http_url = "http://x.y.z.a/webserver/" - CONF.deploy.http_root = "/webserver" + CONF.set_override('http_url', 'http://x.y.z.a/webserver/', 'deploy') + CONF.set_override('http_root', '/webserver', 'deploy') object_name = 'tmp_image_file' # | WHEN | ilo_common.remove_image_from_web_server(object_name) @@ -1076,7 +1076,7 @@ class IloCommonMethodsTestCase(BaseIloTest): def test_destroy_floppy_image_from_web_server(self, get_floppy_name_mock, utils_mock): get_floppy_name_mock.return_value = 'image-uuid' - CONF.deploy.http_root = "/webserver/" + CONF.set_override('http_root', '/webserver/', 'deploy') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: ilo_common.destroy_floppy_image_from_web_server(task.node) @@ -1220,7 +1220,7 @@ class IloCommonMethodsTestCase(BaseIloTest): autospec=True) def test__get_certificate_file_list_none(self, path_exists_mock): cl = None - CONF.webserver_verify_ca = '/file/path' + CONF.set_override('webserver_verify_ca', '/file/path') path_exists_mock.return_value = True expected = ['/file/path'] actual = ilo_common._get_certificate_file_list(cl) @@ -1230,7 +1230,7 @@ class IloCommonMethodsTestCase(BaseIloTest): autospec=True) def test__get_certificate_file_list_empty(self, path_exists_mock): cl = [] - CONF.webserver_verify_ca = '/file/path' + CONF.set_override('webserver_verify_ca', '/file/path') path_exists_mock.return_value = True expected = ['/file/path'] actual = ilo_common._get_certificate_file_list(cl) @@ -1240,7 +1240,7 @@ class IloCommonMethodsTestCase(BaseIloTest): autospec=True) def test__get_certificate_file_list_empty_no_path(self, path_exists_mock): cl = [] - CONF.webserver_verify_ca = '/file/path' + CONF.set_override('webserver_verify_ca', '/file/path') path_exists_mock.return_value = False expected = [] actual = ilo_common._get_certificate_file_list(cl) @@ -1248,14 +1248,14 @@ class IloCommonMethodsTestCase(BaseIloTest): def test__get_certificate_file_list(self): cl = ['file/path/a', 'file/path/b'] - CONF.webserver_verify_ca = '/file/path/c' + CONF.set_override('webserver_verify_ca', '/file/path/c') expected = cl actual = ilo_common._get_certificate_file_list(cl) self.assertEqual(expected, actual) def test__get_certificate_file_list_string_type(self): cl = 'file/path/a' - CONF.webserver_verify_ca = '/file/path/c' + CONF.set_override('webserver_verify_ca', '/file/path/c') self.assertRaisesRegex(exception.InvalidParameterValue, "List of files is .* \"<class 'str'>\" .*", ilo_common._get_certificate_file_list, cl) @@ -1355,7 +1355,7 @@ class IloCommonMethodsTestCase(BaseIloTest): autospec=True) def test_add_certificates_raises_ilo_error(self, get_ilo_object_mock, get_cl_mock): - CONF.webserver_verify_ca = False + CONF.set_override('webserver_verify_ca', False) ilo_mock_object = get_ilo_object_mock.return_value c_l = ['/file/path/a', '/file/path/b'] get_cl_mock.return_value = c_l diff --git a/ironic/tests/unit/drivers/modules/irmc/test_boot.py b/ironic/tests/unit/drivers/modules/irmc/test_boot.py index 1822b9965..6b57c7504 100644 --- a/ironic/tests/unit/drivers/modules/irmc/test_boot.py +++ b/ironic/tests/unit/drivers/modules/irmc/test_boot.py @@ -63,6 +63,7 @@ PARSED_IFNO = { 'irmc_snmp_port': 161, 'irmc_snmp_version': snmp.SNMP_V2C, 'irmc_sensor_method': 'ipmitool', + 'irmc_verify_ca': True, } @@ -74,13 +75,14 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): def setUp(self): super(IRMCDeployPrivateMethodsTestCase, self).setUp() - CONF.irmc.remote_image_share_root = '/remote_image_share_root' - CONF.irmc.remote_image_server = '10.20.30.40' - CONF.irmc.remote_image_share_type = 'NFS' - CONF.irmc.remote_image_share_name = 'share' - CONF.irmc.remote_image_user_name = 'admin' - CONF.irmc.remote_image_user_password = 'admin0' - CONF.irmc.remote_image_user_domain = 'local' + 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, @@ -95,7 +97,8 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): @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.irmc.remote_image_share_root = '/non_existed_root' + CONF.set_override('remote_image_share_root', '/non_existed_root', + 'irmc') isdir_mock.return_value = False self.assertRaises(exception.InvalidParameterValue, @@ -248,7 +251,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): def test__parse_instance_info_with_boot_iso_file_name_ok( self, check_share_fs_mounted_mock): """With optional 'boot_iso' file name.""" - CONF.irmc.remote_image_share_root = '/etc' + 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) @@ -258,7 +261,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): def test__parse_instance_info_with_boot_iso_deprecated( self, check_share_fs_mounted_mock): """With optional 'irmc_boot_iso' file name.""" - CONF.irmc.remote_image_share_root = '/etc' + 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) @@ -268,7 +271,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): def test__parse_instance_info_without_boot_iso_ok( self, check_share_fs_mounted_mock): """With optional no 'boot_iso' file name.""" - CONF.irmc.remote_image_share_root = '/etc' + CONF.set_override('remote_image_share_root', '/etc', 'irmc') self.node.instance_info['boot_iso'] = None instance_info_expected = {} @@ -335,7 +338,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): @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.irmc.remote_image_share_root = '/etc' + 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: @@ -357,7 +360,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): def test_parse_deploy_info_ok(self, mock_isfile, get_image_instance_info_mock, check_share_fs_mounted_mock): - CONF.irmc.remote_image_share_root = '/etc' + CONF.set_override('remote_image_share_root', '/etc', 'irmc') get_image_instance_info_mock.return_value = {'a': 'b'} driver_info_expected = { 'a': 'b', @@ -443,7 +446,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): setup_vmedia_mock, set_boot_device_mock, check_share_fs_mounted_mock): - CONF.irmc.remote_image_share_root = '/' + CONF.set_override('remote_image_share_root', '/', 'irmc') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -476,7 +479,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): setup_vmedia_mock, set_boot_device_mock, check_share_fs_mounted_mock): - CONF.irmc.remote_image_share_root = '/' + CONF.set_override('remote_image_share_root', '/', 'irmc') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: @@ -550,7 +553,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): boot_mode_mock, create_boot_iso_mock, check_share_fs_mounted_mock): - CONF.irmc.remote_image_share_root = '/' + 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} @@ -828,7 +831,7 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): autospec=True) def test__remove_share_file(self, unlink_without_raise_mock, check_share_fs_mounted_mock): - CONF.irmc.remote_image_share_root = '/share' + CONF.set_override('remote_image_share_root', '/share', 'irmc') irmc_boot._remove_share_file("boot.iso") @@ -844,12 +847,12 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): cd_set_params = (irmc_boot.scci .get_virtual_cd_set_params_cmd.return_value) - CONF.irmc.remote_image_server = '10.20.30.40' - CONF.irmc.remote_image_user_domain = 'local' - CONF.irmc.remote_image_share_type = 'NFS' - CONF.irmc.remote_image_share_name = 'share' - CONF.irmc.remote_image_user_name = 'admin' - CONF.irmc.remote_image_user_password = 'admin0' + 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 @@ -926,12 +929,12 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): fd_set_params = (irmc_boot.scci .get_virtual_fd_set_params_cmd.return_value) - CONF.irmc.remote_image_server = '10.20.30.40' - CONF.irmc.remote_image_user_domain = 'local' - CONF.irmc.remote_image_share_type = 'NFS' - CONF.irmc.remote_image_share_name = 'share' - CONF.irmc.remote_image_user_name = 'admin' - CONF.irmc.remote_image_user_password = 'admin0' + 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 @@ -1009,8 +1012,8 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): # 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.irmc.remote_image_share_root = '/' - CONF.irmc.remote_image_share_type = 'nfs' + 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() @@ -1026,8 +1029,8 @@ class IRMCDeployPrivateMethodsTestCase(test_common.BaseIRMCTest): # 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.irmc.remote_image_share_root = '/etc' - CONF.irmc.remote_image_share_type = 'cifs' + 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) @@ -1144,8 +1147,9 @@ class IRMCVirtualMediaBootTestCase(test_common.BaseIRMCTest): 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): + 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, @@ -1157,26 +1161,13 @@ class IRMCVirtualMediaBootTestCase(test_common.BaseIRMCTest): boot_devices.DISK, persistent=True) - def test_prepare_instance_whole_disk_image_local( - self, check_share_fs_mounted_mock): - self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} - self.node.save() - self._test_prepare_instance_whole_disk_image() - - def test_prepare_instance_whole_disk_image(self, - check_share_fs_mounted_mock): - self._test_prepare_instance_whole_disk_image() - - @mock.patch.object(irmc_boot.IRMCVirtualMediaBoot, - '_configure_vmedia_boot', spec_set=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, _configure_vmedia_mock, + self, _cleanup_vmedia_boot_mock, set_boot_device_mock, check_share_fs_mounted_mock): - self.node.instance_info = { - 'capabilities': {'boot_option': 'netboot'}} self.node.driver_internal_info = {'root_uuid_or_disk_id': "some_uuid"} self.node.save() with task_manager.acquire(self.context, self.node.uuid, @@ -1184,8 +1175,9 @@ class IRMCVirtualMediaBootTestCase(test_common.BaseIRMCTest): task.driver.boot.prepare_instance(task) _cleanup_vmedia_boot_mock.assert_called_once_with(task) - _configure_vmedia_mock.assert_called_once_with(mock.ANY, task, - "some_uuid") + 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) @@ -1253,9 +1245,10 @@ class IRMCVirtualMediaBootTestCase(test_common.BaseIRMCTest): 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", 'boot_option': 'netboot' + "secure_boot": "true" } } self.node.save() @@ -1281,9 +1274,10 @@ class IRMCVirtualMediaBootTestCase(test_common.BaseIRMCTest): 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", 'boot_option': 'netboot' + "secure_boot": "false" } } self.node.save() @@ -1308,11 +1302,7 @@ class IRMCVirtualMediaBootTestCase(test_common.BaseIRMCTest): 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.instance_info = { - 'capabilities': { - 'boot_option': 'netboot' - } - } + self.node.deploy_interface = 'ramdisk' self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: diff --git a/ironic/tests/unit/drivers/modules/irmc/test_common.py b/ironic/tests/unit/drivers/modules/irmc/test_common.py index 7598fc16b..9dbb380ba 100644 --- a/ironic/tests/unit/drivers/modules/irmc/test_common.py +++ b/ironic/tests/unit/drivers/modules/irmc/test_common.py @@ -16,6 +16,7 @@ Test class for common methods used by iRMC modules. """ +import os from unittest import mock from oslo_config import cfg @@ -71,6 +72,7 @@ class IRMCValidateParametersTestCase(BaseIRMCTest): 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']) def test_parse_driver_info_snmpv3(self): self.node.driver_info['irmc_snmp_version'] = 'v3' @@ -111,6 +113,7 @@ class IRMCValidateParametersTestCase(BaseIRMCTest): 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'] @@ -274,6 +277,41 @@ class IRMCValidateParametersTestCase(BaseIRMCTest): 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): @@ -283,6 +321,7 @@ class IRMCCommonMethodsTestCase(BaseIRMCTest): 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( @@ -291,6 +330,7 @@ class IRMCCommonMethodsTestCase(BaseIRMCTest): 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) @@ -314,6 +354,7 @@ class IRMCCommonMethodsTestCase(BaseIRMCTest): 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( @@ -322,6 +363,7 @@ class IRMCCommonMethodsTestCase(BaseIRMCTest): 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) diff --git a/ironic/tests/unit/drivers/modules/irmc/test_raid.py b/ironic/tests/unit/drivers/modules/irmc/test_raid.py index eefe7ff3a..54a0a0dd6 100644 --- a/ironic/tests/unit/drivers/modules/irmc/test_raid.py +++ b/ironic/tests/unit/drivers/modules/irmc/test_raid.py @@ -702,8 +702,8 @@ class IRMCRaidConfigurationInternalMethodsTestCase(test_common.BaseIRMCTest): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: raid._commit_raid_config(task) - get_raid_adapter_mock.assert_called_once_with( - irmc_common.parse_driver_info(task.node)) + 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( diff --git a/ironic/tests/unit/drivers/modules/network/test_neutron.py b/ironic/tests/unit/drivers/modules/network/test_neutron.py index 0b70446ce..665a674a6 100644 --- a/ironic/tests/unit/drivers/modules/network/test_neutron.py +++ b/ironic/tests/unit/drivers/modules/network/test_neutron.py @@ -19,7 +19,6 @@ from oslo_utils import uuidutils from ironic.common import exception from ironic.common import neutron as neutron_common -from ironic.common import states from ironic.conductor import task_manager from ironic.drivers import base as drivers_base from ironic.drivers.modules.network import neutron @@ -106,65 +105,6 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): context=task.context)], validate_mock.call_args_list) - @mock.patch.object(neutron_common, 'validate_network', autospec=True) - def test_validate_boot_option_netboot(self, validate_mock): - driver_internal_info = self.node.driver_internal_info - driver_internal_info['is_whole_disk_image'] = True - self.node.driver_internal_info = driver_internal_info - boot_option = {'capabilities': '{"boot_option": "netboot"}'} - self.node.instance_info = boot_option - self.node.provision_state = states.DEPLOYING - self.node.save() - with task_manager.acquire(self.context, self.node.id) as task: - self.assertRaisesRegex( - exception.InvalidParameterValue, - 'cannot perform "local" boot for whole disk image', - self.interface.validate, task) - self.assertEqual([mock.call(CONF.neutron.cleaning_network, - 'cleaning network', - context=task.context), - mock.call(CONF.neutron.provisioning_network, - 'provisioning network', - context=task.context)], - validate_mock.call_args_list) - - @mock.patch.object(neutron_common, 'validate_network', autospec=True) - def test_validate_boot_option_netboot_no_exc(self, validate_mock): - CONF.set_override('default_boot_option', 'netboot', 'deploy') - driver_internal_info = self.node.driver_internal_info - driver_internal_info['is_whole_disk_image'] = True - self.node.driver_internal_info = driver_internal_info - self.node.provision_state = states.AVAILABLE - self.node.save() - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.validate(task) - self.assertEqual([mock.call(CONF.neutron.cleaning_network, - 'cleaning network', - context=task.context), - mock.call(CONF.neutron.provisioning_network, - 'provisioning network', - context=task.context)], - validate_mock.call_args_list) - - @mock.patch.object(neutron_common, 'validate_network', autospec=True) - def test_validate_boot_option_local(self, validate_mock): - driver_internal_info = self.node.driver_internal_info - driver_internal_info['is_whole_disk_image'] = True - self.node.driver_internal_info = driver_internal_info - boot_option = {'capabilities': '{"boot_option": "local"}'} - self.node.instance_info = boot_option - self.node.provision_state = states.DEPLOYING - self.node.save() - with task_manager.acquire(self.context, self.node.id) as task: - self.interface.validate(task) - self.assertEqual([mock.call(CONF.neutron.cleaning_network, - 'cleaning network', - context=task.context), - mock.call(CONF.neutron.provisioning_network, - 'provisioning network', - context=task.context)], - validate_mock.call_args_list) - @mock.patch.object(neutron_common, 'validate_network', side_effect=lambda n, t, context=None: n, autospec=True) @mock.patch.object(neutron_common, 'rollback_ports', autospec=True) diff --git a/ironic/tests/unit/drivers/modules/redfish/test_bios.py b/ironic/tests/unit/drivers/modules/redfish/test_bios.py index cd6f9be5f..2ff3235fd 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_bios.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_bios.py @@ -165,13 +165,16 @@ class RedfishBiosTestCase(db_base.DbTestCase): mock_setting_list.delete.assert_called_once_with( task.context, task.node.id, delete_names) + @mock.patch.object(manager_utils, 'is_fast_track', autospec=True) @mock.patch.object(redfish_boot.RedfishVirtualMediaBoot, 'prepare_ramdisk', spec_set=True, autospec=True) @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True) @mock.patch.object(manager_utils, 'node_power_action', autospec=True) def _test_step_pre_reboot(self, mock_power_action, mock_get_system, - mock_build_agent_options, mock_prepare): + mock_build_agent_options, mock_prepare, + mock_fast_track, fast_track=False): + mock_fast_track.return_value = fast_track if self.node.clean_step: step_data = self.node.clean_step check_fields = ['cleaning_reboot', 'skip_current_clean_step'] @@ -197,7 +200,13 @@ class RedfishBiosTestCase(db_base.DbTestCase): bios.supported_apply_times = [] ret = task.driver.bios.apply_configuration(task, data) mock_get_system.assert_called_with(task.node) - mock_power_action.assert_called_once_with(task, states.REBOOT) + if fast_track: + mock_power_action.assert_has_calls([ + mock.call(task, states.POWER_OFF), + mock.call(task, states.REBOOT), + ]) + else: + mock_power_action.assert_called_once_with(task, states.REBOOT) if step == 'factory_reset': bios.reset_bios.assert_called_once() if step == 'apply_configuration': @@ -205,8 +214,8 @@ class RedfishBiosTestCase(db_base.DbTestCase): attributes, apply_time=None) mock_build_agent_options.assert_called_once_with(task.node) mock_prepare.assert_called_once_with(mock.ANY, task, {'a': 'b'}) - info = task.node.driver_internal_info - self.assertTrue(all(x in info for x in check_fields)) + for field in check_fields: + self.assertIn(field, task.node.driver_internal_info) self.assertEqual(expected_ret, ret) def test_factory_reset_step_pre_reboot_cleaning(self): @@ -221,6 +230,12 @@ class RedfishBiosTestCase(db_base.DbTestCase): self.node.save() self._test_step_pre_reboot() + def test_factory_reset_step_pre_reboot_fast_track(self): + self.node.clean_step = {'priority': 100, 'interface': 'bios', + 'step': 'factory_reset', 'argsinfo': {}} + self.node.save() + self._test_step_pre_reboot(fast_track=True) + def test_apply_conf_step_pre_reboot_cleaning(self): data = [{'name': 'ProcTurboMode', 'value': 'Disabled'}, {'name': 'NicBoot1', 'value': 'NetworkBoot'}] @@ -239,6 +254,15 @@ class RedfishBiosTestCase(db_base.DbTestCase): self.node.save() self._test_step_pre_reboot() + def test_apply_conf_step_pre_reboot_fast_track(self): + data = [{'name': 'ProcTurboMode', 'value': 'Disabled'}, + {'name': 'NicBoot1', 'value': 'NetworkBoot'}] + self.node.clean_step = {'priority': 100, 'interface': 'bios', + 'step': 'apply_configuration', + 'argsinfo': {'settings': data}} + self.node.save() + self._test_step_pre_reboot(fast_track=True) + @mock.patch.object(redfish_utils, 'get_system', autospec=True) def _test_step_post_reboot(self, mock_get_system, attributes_after_reboot=None): @@ -573,7 +597,8 @@ class RedfishBiosRegistryTestCase(db_base.DbTestCase): self.registry.registry_entries.attributes[1].read_only = False self.registry.registry_entries.attributes[1].allowable_values =\ [{'ValueName': 'Enabled', 'ValueDisplayName': 'Enabled'}, - {'ValueName': 'Disabled', 'ValueDisplayName': 'Disabled'}] + {'ValueDisplayName': 'Disabled'}, + {'Invalid': 'banana'}] self.registry.registry_entries.attributes[2].name = "BootDelay" self.registry.registry_entries.attributes[2].attribute_type = "Integer" self.registry.registry_entries.attributes[2].lower_bound = 5 diff --git a/ironic/tests/unit/drivers/modules/redfish/test_firmware_utils.py b/ironic/tests/unit/drivers/modules/redfish/test_firmware_utils.py index e2c6e75b2..61bc23e48 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_firmware_utils.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_firmware_utils.py @@ -262,9 +262,9 @@ class FirmwareUtilsTestCase(base.TestCase): @mock.patch.object(os, 'chmod', autospec=True) def test_stage_http(self, mock_chmod, mock_link, mock_copyfile, mock_makedirs): - CONF.deploy.http_url = 'http://10.0.0.2' - CONF.deploy.external_http_url = None - CONF.deploy.http_root = '/httproot' + CONF.set_override('http_url', 'http://10.0.0.2', 'deploy') + CONF.set_override('external_http_url', None, 'deploy') + CONF.set_override('http_root', '/httproot', 'deploy') node = mock.Mock(uuid='55cdaba0-1123-4622-8b37-bb52dd6285d3') staged_url, need_cleanup = firmware_utils.stage( @@ -291,9 +291,9 @@ class FirmwareUtilsTestCase(base.TestCase): @mock.patch.object(os, 'chmod', autospec=True) def test_stage_http_copyfile(self, mock_chmod, mock_link, mock_copyfile, mock_makedirs): - CONF.deploy.http_url = 'http://10.0.0.2' - CONF.deploy.external_http_url = None - CONF.deploy.http_root = '/httproot' + CONF.set_override('http_url', 'http://10.0.0.2', 'deploy') + CONF.set_override('external_http_url', None, 'deploy') + CONF.set_override('http_root', '/httproot', 'deploy') node = mock.Mock(uuid='55cdaba0-1123-4622-8b37-bb52dd6285d3') mock_link.side_effect = OSError @@ -323,9 +323,9 @@ class FirmwareUtilsTestCase(base.TestCase): @mock.patch.object(os, 'chmod', autospec=True) def test_stage_http_copyfile_fails(self, mock_chmod, mock_link, mock_copyfile, mock_makedirs): - CONF.deploy.http_url = 'http://10.0.0.2' - CONF.deploy.external_http_url = None - CONF.deploy.http_root = '/httproot' + CONF.set_override('http_url', 'http://10.0.0.2', 'deploy') + CONF.set_override('external_http_url', None, 'deploy') + CONF.set_override('http_root', '/httproot', 'deploy') node = mock.Mock(uuid='55cdaba0-1123-4622-8b37-bb52dd6285d3') mock_link.side_effect = OSError mock_copyfile.side_effect = IOError @@ -352,9 +352,9 @@ class FirmwareUtilsTestCase(base.TestCase): @mock.patch.object(os, 'chmod', autospec=True) def test_stage_local_external(self, mock_chmod, mock_link, mock_rmtree, mock_copyfile, mock_makedirs): - CONF.deploy.http_url = 'http://10.0.0.2' - CONF.deploy.external_http_url = 'http://90.0.0.9' - CONF.deploy.http_root = '/httproot' + CONF.set_override('http_url', 'http://10.0.0.2', 'deploy') + CONF.set_override('external_http_url', 'http://90.0.0.9', 'deploy') + CONF.set_override('http_root', '/httproot', 'deploy') node = mock.Mock(uuid='55cdaba0-1123-4622-8b37-bb52dd6285d3') staged_url, need_cleanup = firmware_utils.stage( @@ -402,7 +402,7 @@ class FirmwareUtilsTestCase(base.TestCase): @mock.patch.object(swift, 'SwiftAPI', autospec=True) def test_cleanup(self, mock_swift_api, mock_gettempdir, mock_rmtree): mock_gettempdir.return_value = '/tmp' - CONF.deploy.http_root = '/httproot' + CONF.set_override('http_root', '/httproot', 'deploy') node = mock.Mock( uuid='55cdaba0-1123-4622-8b37-bb52dd6285d3', driver_internal_info={'firmware_cleanup': ['http', 'swift']}) diff --git a/ironic/tests/unit/drivers/modules/redfish/test_management.py b/ironic/tests/unit/drivers/modules/redfish/test_management.py index 93aae5de8..f8c82949a 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_management.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_management.py @@ -836,7 +836,7 @@ class RedfishManagementTestCase(db_base.DbTestCase): mock_update_service = mock.Mock() mock_update_service.simple_update.return_value = mock_task_monitor mock_get_update_service.return_value = mock_update_service - CONF.redfish.firmware_source = 'http' + CONF.set_override('firmware_source', 'http', 'redfish') with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.node.save = mock.Mock() @@ -1346,7 +1346,7 @@ class RedfishManagementTestCase(db_base.DbTestCase): {'task_monitor': '/task/123', 'url': 'http://test1'}, {'url': 'http://test2'}]} self.node.driver_internal_info = driver_internal_info - CONF.redfish.firmware_source = 'http' + CONF.set_override('firmware_source', 'http', 'redfish') management = redfish_mgmt.RedfishManagement() with task_manager.acquire(self.context, self.node.uuid, @@ -1375,7 +1375,7 @@ class RedfishManagementTestCase(db_base.DbTestCase): @mock.patch.object(firmware_utils, 'stage', autospec=True) def test__stage_firmware_file_https(self, mock_stage, mock_verify_checksum, mock_download_to_temp): - CONF.redfish.firmware_source = 'local' + CONF.set_override('firmware_source', 'local', 'redfish') firmware_update = {'url': 'https://test1', 'checksum': 'abc'} node = mock.Mock() mock_download_to_temp.return_value = '/tmp/test1' @@ -1399,7 +1399,7 @@ class RedfishManagementTestCase(db_base.DbTestCase): def test__stage_firmware_file_swift( self, mock_get_swift_temp_url, mock_stage, mock_verify_checksum, mock_download_to_temp): - CONF.redfish.firmware_source = 'swift' + CONF.set_override('firmware_source', 'swift', 'redfish') firmware_update = {'url': 'swift://container/bios.exe'} node = mock.Mock() mock_get_swift_temp_url.return_value = 'http://temp' @@ -1423,7 +1423,7 @@ class RedfishManagementTestCase(db_base.DbTestCase): mock_download_to_temp, mock_cleanup): node = mock.Mock() firmware_update = {'url': 'https://test1'} - CONF.redfish.firmware_source = 'local' + CONF.set_override('firmware_source', 'local', 'redfish') firmware_update = {'url': 'https://test1'} node = mock.Mock() mock_download_to_temp.return_value = '/tmp/test1' diff --git a/ironic/tests/unit/drivers/modules/redfish/test_raid.py b/ironic/tests/unit/drivers/modules/redfish/test_raid.py index b2d3a0a0e..dfb3c1473 100644 --- a/ironic/tests/unit/drivers/modules/redfish/test_raid.py +++ b/ironic/tests/unit/drivers/modules/redfish/test_raid.py @@ -1483,3 +1483,22 @@ class RedfishRAIDTestCase(db_base.DbTestCase): mock_reboot.assert_not_called() # Not yet updated as in progress self.assertEqual({}, task.node.raid_config) + + @mock.patch.object(redfish_raid, 'LOG', autospec=True) + def test_update_raid_config_missing_raid_type( + self, mock_log, mock_get_system): + volumes = [ + _mock_volume( + '1', raid_type=None, + capacity_bytes=100 * units.Gi), + _mock_volume( + '2', raid_type=None, + capacity_bytes=500 * units.Gi)] + self.mock_storage.volumes.get_members.return_value = volumes + mock_get_system.return_value.storage.get_members.return_value = [ + self.mock_storage] + + redfish_raid.update_raid_config(self.node) + + self.assertEqual([], self.node.raid_config['logical_disks']) + mock_log.warning.assert_called_once() diff --git a/ironic/tests/unit/drivers/modules/test_agent.py b/ironic/tests/unit/drivers/modules/test_agent.py index 4b6d271d4..653359f33 100644 --- a/ironic/tests/unit/drivers/modules/test_agent.py +++ b/ironic/tests/unit/drivers/modules/test_agent.py @@ -18,7 +18,6 @@ from oslo_config import cfg from ironic.common import dhcp_factory from ironic.common import exception -from ironic.common import image_service from ironic.common import images from ironic.common import raid from ironic.common import states @@ -848,212 +847,6 @@ class TestAgentDeploy(CommonTestsMixin, db_base.DbTestCase): @mock.patch('ironic.drivers.modules.agent.check_image_size', autospec=True) - @mock.patch.object(noop_storage.NoopStorage, 'attach_volumes', - autospec=True) - @mock.patch.object(deploy_utils, 'populate_storage_driver_internal_info', - autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True) - @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True) - @mock.patch.object(image_service.HttpImageService, 'validate_href', - autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, 'validate', - spec_set=True, autospec=True) - def test_prepare_with_neutron_net_capabilities_as_string( - self, validate_net_mock, - unconfigure_tenant_net_mock, add_provisioning_net_mock, - validate_href_mock, build_options_mock, - pxe_prepare_ramdisk_mock, storage_driver_info_mock, - storage_attach_volumes_mock, check_image_size_mock): - node = self.node - node.network_interface = 'neutron' - instance_info = node.instance_info - instance_info['capabilities'] = '{"lion": "roar"}' - node.instance_info = instance_info - node.save() - validate_net_mock.side_effect = [ - exception.InvalidParameterValue('invalid'), None] - with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: - task.node.provision_state = states.DEPLOYING - build_options_mock.return_value = {'a': 'b'} - self.driver.prepare(task) - storage_driver_info_mock.assert_called_once_with(task) - self.assertEqual(2, validate_net_mock.call_count) - add_provisioning_net_mock.assert_called_once_with(mock.ANY, task) - unconfigure_tenant_net_mock.assert_called_once_with(mock.ANY, task) - storage_attach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - validate_href_mock.assert_called_once_with(mock.ANY, 'fake-image', - secret=False) - build_options_mock.assert_called_once_with(task.node) - pxe_prepare_ramdisk_mock.assert_called_once_with( - task.driver.boot, task, {'a': 'b'}) - check_image_size_mock.assert_called_once_with(task) - self.node.refresh() - capabilities = self.node.instance_info['capabilities'] - self.assertEqual('local', capabilities['boot_option']) - self.assertEqual('roar', capabilities['lion']) - - @mock.patch('ironic.drivers.modules.agent.check_image_size', - autospec=True) - @mock.patch.object(noop_storage.NoopStorage, 'attach_volumes', - autospec=True) - @mock.patch.object(deploy_utils, 'populate_storage_driver_internal_info', - autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True) - @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True) - @mock.patch.object(image_service.HttpImageService, 'validate_href', - autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, 'validate', - spec_set=True, autospec=True) - def test_prepare_with_neutron_net_exc_no_capabilities( - self, validate_net_mock, - unconfigure_tenant_net_mock, add_provisioning_net_mock, - validate_href_mock, build_options_mock, - pxe_prepare_ramdisk_mock, storage_driver_info_mock, - storage_attach_volumes_mock, check_image_size_mock): - node = self.node - node.network_interface = 'neutron' - node.save() - validate_net_mock.side_effect = [ - exception.InvalidParameterValue('invalid'), None] - with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: - task.node.provision_state = states.DEPLOYING - build_options_mock.return_value = {'a': 'b'} - self.driver.prepare(task) - storage_driver_info_mock.assert_called_once_with(task) - self.assertEqual(2, validate_net_mock.call_count) - add_provisioning_net_mock.assert_called_once_with(mock.ANY, task) - unconfigure_tenant_net_mock.assert_called_once_with(mock.ANY, task) - storage_attach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - validate_href_mock.assert_called_once_with(mock.ANY, 'fake-image', - secret=False) - build_options_mock.assert_called_once_with(task.node) - pxe_prepare_ramdisk_mock.assert_called_once_with( - task.driver.boot, task, {'a': 'b'}) - check_image_size_mock.assert_called_once_with(task) - self.node.refresh() - capabilities = self.node.instance_info['capabilities'] - self.assertEqual('local', capabilities['boot_option']) - - @mock.patch('ironic.drivers.modules.agent.check_image_size', - autospec=True) - @mock.patch.object(noop_storage.NoopStorage, 'attach_volumes', - autospec=True) - @mock.patch.object(deploy_utils, 'populate_storage_driver_internal_info', - autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True) - @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True) - @mock.patch.object(image_service.HttpImageService, 'validate_href', - autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, 'validate', - spec_set=True, autospec=True) - def test_prepare_with_neutron_net_exc_no_capabilities_overwrite( - self, validate_net_mock, - unconfigure_tenant_net_mock, add_provisioning_net_mock, - validate_href_mock, build_options_mock, - pxe_prepare_ramdisk_mock, storage_driver_info_mock, - storage_attach_volumes_mock, check_image_size_mock): - node = self.node - node.network_interface = 'neutron' - instance_info = node.instance_info - instance_info['capabilities'] = {"cat": "meow"} - node.instance_info = instance_info - node.save() - validate_net_mock.side_effect = [ - exception.InvalidParameterValue('invalid'), None] - with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: - task.node.provision_state = states.DEPLOYING - build_options_mock.return_value = {'a': 'b'} - self.driver.prepare(task) - storage_driver_info_mock.assert_called_once_with(task) - self.assertEqual(2, validate_net_mock.call_count) - add_provisioning_net_mock.assert_called_once_with(mock.ANY, task) - unconfigure_tenant_net_mock.assert_called_once_with(mock.ANY, task) - storage_attach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - validate_href_mock.assert_called_once_with(mock.ANY, 'fake-image', - secret=False) - build_options_mock.assert_called_once_with(task.node) - pxe_prepare_ramdisk_mock.assert_called_once_with( - task.driver.boot, task, {'a': 'b'}) - check_image_size_mock.assert_called_once_with(task) - self.node.refresh() - capabilities = self.node.instance_info['capabilities'] - self.assertEqual('local', capabilities['boot_option']) - self.assertEqual('meow', capabilities['cat']) - - @mock.patch.object(noop_storage.NoopStorage, 'attach_volumes', - autospec=True) - @mock.patch.object(deploy_utils, 'populate_storage_driver_internal_info', - autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True) - @mock.patch.object(deploy_utils, 'build_agent_options', autospec=True) - @mock.patch.object(deploy_utils, 'build_instance_info_for_deploy', - autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - @mock.patch.object(neutron_network.NeutronNetwork, 'validate', - spec_set=True, autospec=True) - def test_prepare_with_neutron_net_exc_reraise( - self, validate_net_mock, - unconfigure_tenant_net_mock, add_provisioning_net_mock, - build_instance_info_mock, build_options_mock, - pxe_prepare_ramdisk_mock, storage_driver_info_mock, - storage_attach_volumes_mock): - node = self.node - node.network_interface = 'neutron' - instance_info = node.instance_info - instance_info['capabilities'] = {"boot_option": "netboot"} - node.instance_info = instance_info - node.save() - validate_net_mock.side_effect = ( - exception.InvalidParameterValue('invalid')) - with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: - task.node.provision_state = states.DEPLOYING - self.assertRaises(exception.InvalidParameterValue, - task.driver.deploy.prepare, - task) - storage_driver_info_mock.assert_called_once_with(task) - validate_net_mock.assert_called_once_with(mock.ANY, task) - self.assertFalse(add_provisioning_net_mock.called) - self.assertFalse(unconfigure_tenant_net_mock.called) - self.assertFalse(storage_attach_volumes_mock.called) - self.assertFalse(build_instance_info_mock.called) - self.assertFalse(build_options_mock.called) - self.assertFalse(pxe_prepare_ramdisk_mock.called) - self.node.refresh() - capabilities = self.node.instance_info['capabilities'] - self.assertEqual('netboot', capabilities['boot_option']) - - @mock.patch('ironic.drivers.modules.agent.check_image_size', - autospec=True) @mock.patch.object(flat_network.FlatNetwork, 'add_provisioning_network', spec_set=True, autospec=True) @mock.patch.object(flat_network.FlatNetwork, 'validate', @@ -1643,8 +1436,6 @@ class TestAgentDeploy(CommonTestsMixin, db_base.DbTestCase): def test_prepare_instance_boot_partition_image(self, prepare_instance_mock, uuid_mock, boot_mode_mock, log_mock): - self.node.instance_info = { - 'capabilities': {'boot_option': 'netboot'}} uuid_mock.return_value = { 'command_result': {'root uuid': 'root_uuid'} } diff --git a/ironic/tests/unit/drivers/modules/test_agent_base.py b/ironic/tests/unit/drivers/modules/test_agent_base.py index 97daca79f..f8b23e9fb 100644 --- a/ironic/tests/unit/drivers/modules/test_agent_base.py +++ b/ironic/tests/unit/drivers/modules/test_agent_base.py @@ -1426,40 +1426,11 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True) @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True) - @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True) @mock.patch.object(agent_base.AgentDeployMixin, 'configure_local_boot', autospec=True) - def test_prepare_instance_to_boot_netboot(self, configure_mock, - boot_option_mock, - prepare_instance_mock, - failed_state_mock): - boot_option_mock.return_value = 'netboot' - prepare_instance_mock.return_value = None - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - self.node.save() - root_uuid = 'root_uuid' - efi_system_part_uuid = 'efi_sys_uuid' - with task_manager.acquire(self.context, self.node['uuid'], - shared=False) as task: - self.deploy.prepare_instance_to_boot(task, root_uuid, - efi_system_part_uuid) - self.assertFalse(configure_mock.called) - boot_option_mock.assert_called_once_with(task.node) - prepare_instance_mock.assert_called_once_with(task.driver.boot, - task) - self.assertFalse(failed_state_mock.called) - - @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True) - @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True) - @mock.patch.object(agent_base.AgentDeployMixin, - 'configure_local_boot', autospec=True) - def test_prepare_instance_to_boot_localboot(self, configure_mock, - boot_option_mock, - prepare_instance_mock, - failed_state_mock): - boot_option_mock.return_value = 'local' + def test_prepare_instance_to_boot(self, configure_mock, + prepare_instance_mock, + failed_state_mock): prepare_instance_mock.return_value = None self.node.provision_state = states.DEPLOYING self.node.target_provision_state = states.ACTIVE @@ -1475,20 +1446,16 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): root_uuid=root_uuid, efi_system_part_uuid=efi_system_part_uuid, prep_boot_part_uuid=None) - boot_option_mock.assert_called_once_with(task.node) prepare_instance_mock.assert_called_once_with(task.driver.boot, task) self.assertFalse(failed_state_mock.called) @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True) @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True) - @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True) @mock.patch.object(agent_base.AgentDeployMixin, 'configure_local_boot', autospec=True) def test_prepare_instance_to_boot_localboot_prep_partition( - self, configure_mock, boot_option_mock, - prepare_instance_mock, failed_state_mock): - boot_option_mock.return_value = 'local' + self, configure_mock, prepare_instance_mock, failed_state_mock): prepare_instance_mock.return_value = None self.node.provision_state = states.DEPLOYING self.node.target_provision_state = states.ACTIVE @@ -1506,21 +1473,17 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): root_uuid=root_uuid, efi_system_part_uuid=efi_system_part_uuid, prep_boot_part_uuid=prep_boot_part_uuid) - boot_option_mock.assert_called_once_with(task.node) prepare_instance_mock.assert_called_once_with(task.driver.boot, task) self.assertFalse(failed_state_mock.called) @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True) @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True) - @mock.patch.object(deploy_utils, 'get_boot_option', autospec=True) @mock.patch.object(agent_base.AgentDeployMixin, 'configure_local_boot', autospec=True) def test_prepare_instance_to_boot_configure_fails(self, configure_mock, - boot_option_mock, prepare_mock, failed_state_mock): - boot_option_mock.return_value = 'local' self.node.provision_state = states.DEPLOYING self.node.target_provision_state = states.ACTIVE self.node.save() @@ -1542,7 +1505,6 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest): root_uuid=root_uuid, efi_system_part_uuid=efi_system_part_uuid, prep_boot_part_uuid=None) - boot_option_mock.assert_called_once_with(task.node) self.assertFalse(prepare_mock.called) self.assertFalse(failed_state_mock.called) diff --git a/ironic/tests/unit/drivers/modules/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/test_deploy_utils.py index 2bcdf1cb6..1177e9743 100644 --- a/ironic/tests/unit/drivers/modules/test_deploy_utils.py +++ b/ironic/tests/unit/drivers/modules/test_deploy_utils.py @@ -54,27 +54,6 @@ kernel deploy_kernel append initrd=deploy_ramdisk ipappend 3 -label boot_partition -kernel kernel -append initrd=ramdisk root={{ ROOT }} - -label boot_whole_disk -COM32 chain.c32 -append mbr:{{ DISK_IDENTIFIER }} -""" - -_PXECONF_BOOT_PARTITION = """ -default boot_partition - -label deploy -kernel deploy_kernel -append initrd=deploy_ramdisk -ipappend 3 - -label boot_partition -kernel kernel -append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef - label boot_whole_disk COM32 chain.c32 append mbr:{{ DISK_IDENTIFIER }} @@ -88,10 +67,6 @@ kernel deploy_kernel append initrd=deploy_ramdisk ipappend 3 -label boot_partition -kernel kernel -append initrd=ramdisk root={{ ROOT }} - label boot_whole_disk COM32 chain.c32 append mbr:0x12345678 @@ -109,34 +84,6 @@ kernel deploy_kernel initrd deploy_ramdisk boot -:boot_partition -kernel kernel -append initrd=ramdisk root={{ ROOT }} -boot - -:boot_whole_disk -kernel chain.c32 -append mbr:{{ DISK_IDENTIFIER }} -boot -""" - -_IPXECONF_BOOT_PARTITION = """ -#!ipxe - -dhcp - -goto boot_partition - -:deploy -kernel deploy_kernel -initrd deploy_ramdisk -boot - -:boot_partition -kernel kernel -append initrd=ramdisk root=UUID=12345678-1234-1234-1234-1234567890abcdef -boot - :boot_whole_disk kernel chain.c32 append mbr:{{ DISK_IDENTIFIER }} @@ -155,11 +102,6 @@ kernel deploy_kernel initrd deploy_ramdisk boot -:boot_partition -kernel kernel -append initrd=ramdisk root={{ ROOT }} -boot - :boot_whole_disk kernel chain.c32 append mbr:0x12345678 @@ -178,11 +120,6 @@ kernel deploy_kernel initrd deploy_ramdisk boot -:boot_partition -kernel kernel -append initrd=ramdisk root=UUID=0x12345678 -boot - :boot_whole_disk kernel chain.c32 append mbr:{{ DISK_IDENTIFIER }} @@ -197,29 +134,6 @@ image=deploy_kernel initrd=deploy_ramdisk append="ro text" -image=kernel - label=boot_partition - initrd=ramdisk - append="root={{ ROOT }}" - -image=chain.c32 - label=boot_whole_disk - append="mbr:{{ DISK_IDENTIFIER }}" -""" - -_UEFI_PXECONF_BOOT_PARTITION = """ -default=boot_partition - -image=deploy_kernel - label=deploy - initrd=deploy_ramdisk - append="ro text" - -image=kernel - label=boot_partition - initrd=ramdisk - append="root=UUID=12345678-1234-1234-1234-1234567890abcdef" - image=chain.c32 label=boot_whole_disk append="mbr:{{ DISK_IDENTIFIER }}" @@ -233,11 +147,6 @@ image=deploy_kernel initrd=deploy_ramdisk append="ro text" -image=kernel - label=boot_partition - initrd=ramdisk - append="root={{ ROOT }}" - image=chain.c32 label=boot_whole_disk append="mbr:0x12345678" @@ -253,31 +162,6 @@ menuentry "deploy" { initrdefi deploy_ramdisk } -menuentry "boot_partition" { - linuxefi kernel "root=(( ROOT ))" - initrdefi ramdisk -} - -menuentry "boot_whole_disk" { - linuxefi chain.c32 mbr:(( DISK_IDENTIFIER )) -} -""" - -_UEFI_PXECONF_BOOT_PARTITION_GRUB = """ -set default=boot_partition -set timeout=5 -set hidden_timeout_quiet=false - -menuentry "deploy" { - linuxefi deploy_kernel "ro text" - initrdefi deploy_ramdisk -} - -menuentry "boot_partition" { - linuxefi kernel "root=UUID=12345678-1234-1234-1234-1234567890abcdef" - initrdefi ramdisk -} - menuentry "boot_whole_disk" { linuxefi chain.c32 mbr:(( DISK_IDENTIFIER )) } @@ -293,11 +177,6 @@ menuentry "deploy" { initrdefi deploy_ramdisk } -menuentry "boot_partition" { - linuxefi kernel "root=(( ROOT ))" - initrdefi ramdisk -} - menuentry "boot_whole_disk" { linuxefi chain.c32 mbr:0x12345678 } @@ -322,17 +201,6 @@ class SwitchPxeConfigTestCase(tests_base.TestCase): self.addCleanup(os.unlink, fname) return fname - def test_switch_pxe_config_partition_image(self): - boot_mode = 'bios' - fname = self._create_config() - utils.switch_pxe_config(fname, - '12345678-1234-1234-1234-1234567890abcdef', - boot_mode, - False) - with open(fname, 'r') as f: - pxeconf = f.read() - self.assertEqual(_PXECONF_BOOT_PARTITION, pxeconf) - def test_switch_pxe_config_whole_disk_image(self): boot_mode = 'bios' fname = self._create_config() @@ -344,18 +212,6 @@ class SwitchPxeConfigTestCase(tests_base.TestCase): pxeconf = f.read() self.assertEqual(_PXECONF_BOOT_WHOLE_DISK, pxeconf) - def test_switch_ipxe_config_partition_image(self): - boot_mode = 'bios' - fname = self._create_config(ipxe=True) - utils.switch_pxe_config(fname, - '12345678-1234-1234-1234-1234567890abcdef', - boot_mode, - False, - ipxe_enabled=True) - with open(fname, 'r') as f: - pxeconf = f.read() - self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf) - def test_switch_ipxe_config_whole_disk_image(self): boot_mode = 'bios' fname = self._create_config(ipxe=True) @@ -370,19 +226,6 @@ class SwitchPxeConfigTestCase(tests_base.TestCase): # NOTE(TheJulia): Remove elilo support after the deprecation period, # in the Queens release. - def test_switch_uefi_elilo_pxe_config_partition_image(self): - boot_mode = 'uefi' - fname = self._create_config(boot_mode=boot_mode) - utils.switch_pxe_config(fname, - '12345678-1234-1234-1234-1234567890abcdef', - boot_mode, - False) - with open(fname, 'r') as f: - pxeconf = f.read() - self.assertEqual(_UEFI_PXECONF_BOOT_PARTITION, pxeconf) - - # NOTE(TheJulia): Remove elilo support after the deprecation period, - # in the Queens release. def test_switch_uefi_elilo_config_whole_disk_image(self): boot_mode = 'uefi' fname = self._create_config(boot_mode=boot_mode) @@ -394,17 +237,6 @@ class SwitchPxeConfigTestCase(tests_base.TestCase): pxeconf = f.read() self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK, pxeconf) - def test_switch_uefi_grub_pxe_config_partition_image(self): - boot_mode = 'uefi' - fname = self._create_config(boot_mode=boot_mode, boot_loader='grub') - utils.switch_pxe_config(fname, - '12345678-1234-1234-1234-1234567890abcdef', - boot_mode, - False) - with open(fname, 'r') as f: - pxeconf = f.read() - self.assertEqual(_UEFI_PXECONF_BOOT_PARTITION_GRUB, pxeconf) - def test_switch_uefi_grub_config_whole_disk_image(self): boot_mode = 'uefi' fname = self._create_config(boot_mode=boot_mode, boot_loader='grub') @@ -416,18 +248,6 @@ class SwitchPxeConfigTestCase(tests_base.TestCase): pxeconf = f.read() self.assertEqual(_UEFI_PXECONF_BOOT_WHOLE_DISK_GRUB, pxeconf) - def test_switch_uefi_ipxe_config_partition_image(self): - boot_mode = 'uefi' - fname = self._create_config(boot_mode=boot_mode, ipxe=True) - utils.switch_pxe_config(fname, - '12345678-1234-1234-1234-1234567890abcdef', - boot_mode, - False, - ipxe_enabled=True) - with open(fname, 'r') as f: - pxeconf = f.read() - self.assertEqual(_IPXECONF_BOOT_PARTITION, pxeconf) - def test_switch_uefi_ipxe_config_whole_disk_image(self): boot_mode = 'uefi' fname = self._create_config(boot_mode=boot_mode, ipxe=True) @@ -738,36 +558,11 @@ class OtherFunctionTestCase(db_base.DbTestCase): self._test_set_failed_state(collect_logs=False) self.assertFalse(mock_collect.called) - def test_get_boot_option(self): - self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} - result = utils.get_boot_option(self.node) - self.assertEqual("local", result) - def test_get_boot_option_default_value(self): self.node.instance_info = {} result = utils.get_boot_option(self.node) self.assertEqual("local", result) - def test_get_boot_option_overridden_default_value(self): - cfg.CONF.set_override('default_boot_option', 'local', 'deploy') - self.node.instance_info = {} - result = utils.get_boot_option(self.node) - self.assertEqual("local", result) - - def test_get_boot_option_instance_info_priority(self): - cfg.CONF.set_override('default_boot_option', 'local', 'deploy') - self.node.instance_info = {'capabilities': - '{"boot_option": "netboot"}'} - result = utils.get_boot_option(self.node) - self.assertEqual("netboot", result) - - @mock.patch.object(utils, 'is_software_raid', autospec=True) - def test_get_boot_option_software_raid(self, mock_is_software_raid): - mock_is_software_raid.return_value = True - cfg.CONF.set_override('default_boot_option', 'netboot', 'deploy') - result = utils.get_boot_option(self.node) - self.assertEqual("local", result) - @mock.patch.object(utils, 'is_anaconda_deploy', autospec=True) def test_get_boot_option_anaconda_deploy(self, mock_is_anaconda_deploy): mock_is_anaconda_deploy.return_value = True @@ -972,8 +767,6 @@ class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase): utils.validate_capabilities, self.node) def test_all_supported_capabilities(self): - self.assertEqual(('local', 'netboot', 'ramdisk', 'kickstart'), - utils.SUPPORTED_CAPABILITIES['boot_option']) self.assertEqual(('bios', 'uefi'), utils.SUPPORTED_CAPABILITIES['boot_mode']) self.assertEqual(('true', 'false'), @@ -1302,38 +1095,6 @@ class ValidateImagePropertiesTestCase(db_base.DbTestCase): utils.validate_image_properties(self.task, inst_info) @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') - @mock.patch.object(image_service, 'get_image_service', autospec=True) - def test_validate_image_properties_glance_image(self, image_service_mock, - boot_options_mock): - inst_info = utils.get_image_instance_info(self.node) - image_service_mock.return_value.show.return_value = { - 'properties': {'kernel_id': '1111', 'ramdisk_id': '2222'}, - } - - utils.validate_image_properties(self.task, inst_info) - image_service_mock.assert_called_once_with( - self.node.instance_info['image_source'], context=self.context - ) - - @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') - @mock.patch.object(image_service, 'get_image_service', autospec=True) - def test_validate_image_properties_glance_image_missing_prop( - self, image_service_mock, boot_options_mock): - inst_info = utils.get_image_instance_info(self.node) - image_service_mock.return_value.show.return_value = { - 'properties': {'kernel_id': '1111'}, - } - - self.assertRaises(exception.MissingParameterValue, - utils.validate_image_properties, - self.task, inst_info) - image_service_mock.assert_called_once_with( - self.node.instance_info['image_source'], context=self.context - ) - - @mock.patch.object(utils, 'get_boot_option', autospec=True, return_value='kickstart') @mock.patch.object(image_service, 'get_image_service', autospec=True) def test_validate_image_properties_glance_image_missing_stage2_id( @@ -1351,7 +1112,7 @@ class ValidateImagePropertiesTestCase(db_base.DbTestCase): ) @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') + return_value='kickstart') @mock.patch.object(image_service, 'get_image_service', autospec=True) def test_validate_image_properties_glance_image_not_authorized( self, image_service_mock, boot_options_mock): @@ -1363,7 +1124,7 @@ class ValidateImagePropertiesTestCase(db_base.DbTestCase): inst_info) @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') + return_value='kickstart') @mock.patch.object(image_service, 'get_image_service', autospec=True) def test_validate_image_properties_glance_image_not_found( self, image_service_mock, boot_options_mock): @@ -1381,7 +1142,7 @@ class ValidateImagePropertiesTestCase(db_base.DbTestCase): inst_info) @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') + return_value='kickstart') def test_validate_image_properties_nonglance_image( self, boot_options_mock): instance_info = { @@ -1473,8 +1234,8 @@ class ValidateParametersTestCase(db_base.DbTestCase): self.assertNotIn('ramdisk', info) @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') - def test__get_img_instance_info_good_non_glance_image_netboot( + return_value='kickstart') + def test__get_img_instance_info_good_non_glance_image_anaconda( self, mock_boot_opt): instance_info = INST_INFO_DICT.copy() instance_info['image_source'] = 'http://image' @@ -1488,7 +1249,7 @@ class ValidateParametersTestCase(db_base.DbTestCase): self.assertIsNotNone(info['kernel']) @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') + return_value='kickstart') def test__get_img_instance_info_non_glance_image_missing_kernel( self, mock_boot_opt): instance_info = INST_INFO_DICT.copy() @@ -1501,7 +1262,7 @@ class ValidateParametersTestCase(db_base.DbTestCase): instance_info=instance_info) @mock.patch.object(utils, 'get_boot_option', autospec=True, - return_value='netboot') + return_value='kickstart') def test__get_img_instance_info_non_glance_image_missing_ramdisk( self, mock_boot_opt): instance_info = INST_INFO_DICT.copy() @@ -1768,23 +1529,26 @@ class InstanceInfoTestCase(db_base.DbTestCase): ) utils.parse_instance_info(node) - def test_parse_instance_info_nonglance_image_netboot(self): + @mock.patch.object(utils, 'get_boot_option', autospec=True, + return_value='kickstart') + def test_parse_instance_info_nonglance_image_anaconda(self, mock_boot_opt): info = INST_INFO_DICT.copy() info['image_source'] = 'file:///image.qcow2' info['kernel'] = 'file:///image.vmlinuz' info['ramdisk'] = 'file:///image.initrd' - info['capabilities'] = {'boot_option': 'netboot'} node = obj_utils.create_test_node( self.context, instance_info=info, driver_internal_info=DRV_INTERNAL_INFO_DICT, ) utils.parse_instance_info(node) - def test_parse_instance_info_nonglance_image_no_kernel(self): + @mock.patch.object(utils, 'get_boot_option', autospec=True, + return_value='kickstart') + def test_parse_instance_info_nonglance_image_no_kernel(self, + mock_boot_opt): info = INST_INFO_DICT.copy() info['image_source'] = 'file:///image.qcow2' info['ramdisk'] = 'file:///image.initrd' - info['capabilities'] = {'boot_option': 'netboot'} node = obj_utils.create_test_node( self.context, instance_info=info, driver_internal_info=DRV_INTERNAL_INFO_DICT, @@ -1947,12 +1711,15 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase): self.assertEqual(expected_i_info, info) parse_instance_info_mock.assert_called_once_with(task.node) + @mock.patch.object(utils, 'get_boot_option', autospec=True, + return_value='kickstart') @mock.patch.object(image_service.HttpImageService, 'validate_href', autospec=True) @mock.patch.object(utils, 'parse_instance_info', autospec=True) @mock.patch.object(image_service, 'GlanceImageService', autospec=True) - def test_build_instance_info_for_deploy_glance_partition_image_netboot( - self, glance_mock, parse_instance_info_mock, validate_mock): + def test_build_instance_info_for_deploy_glance_partition_image_anaconda( + self, glance_mock, parse_instance_info_mock, validate_mock, + boot_opt_mock): i_info = {} i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810' i_info['kernel'] = '13ce5a56-1de3-4916-b8b2-be778645d003' @@ -1962,7 +1729,6 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase): i_info['ephemeral_gb'] = 0 i_info['ephemeral_format'] = None i_info['configdrive'] = 'configdrive' - i_info['capabilities'] = {'boot_option': 'netboot'} driver_internal_info = self.node.driver_internal_info driver_internal_info['is_whole_disk_image'] = False self.node.driver_internal_info = driver_internal_info @@ -1980,8 +1746,7 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase): glance_obj_mock.swift_temp_url.return_value = 'http://temp-url' parse_instance_info_mock.return_value = {'swap_mb': 4} image_source = '733d1c44-a2ea-414b-aca7-69decf20d810' - expected_i_info = {'capabilities': {'boot_option': 'netboot'}, - 'root_gb': 5, + expected_i_info = {'root_gb': 5, 'swap_mb': 4, 'ephemeral_gb': 0, 'ephemeral_format': None, diff --git a/ironic/tests/unit/drivers/modules/test_image_utils.py b/ironic/tests/unit/drivers/modules/test_image_utils.py index 6d79629d9..753452f5d 100644 --- a/ironic/tests/unit/drivers/modules/test_image_utils.py +++ b/ironic/tests/unit/drivers/modules/test_image_utils.py @@ -151,6 +151,32 @@ class RedfishImageHandlerTestCase(db_base.DbTestCase): @mock.patch.object(image_utils, 'shutil', autospec=True) @mock.patch.object(os, 'link', autospec=True) @mock.patch.object(os, 'mkdir', autospec=True) + def test_publish_image_external_ip_node_override( + self, mock_mkdir, mock_link, mock_shutil, mock_chmod): + self.config(use_swift=False, group='redfish') + self.config(http_url='http://localhost', + external_http_url='http://non-local.host', + group='deploy') + img_handler_obj = image_utils.ImageHandler(self.node.driver) + self.node.driver_info["external_http_url"] = "http://node.override.url" + + override_url = self.node.driver_info.get("external_http_url") + + url = img_handler_obj.publish_image('file.iso', 'boot.iso', + override_url) + + self.assertEqual( + 'http://node.override.url/redfish/boot.iso', url) + + mock_mkdir.assert_called_once_with('/httpboot/redfish', 0o755) + mock_link.assert_called_once_with( + 'file.iso', '/httpboot/redfish/boot.iso') + mock_chmod.assert_called_once_with('file.iso', 0o644) + + @mock.patch.object(os, 'chmod', autospec=True) + @mock.patch.object(image_utils, 'shutil', autospec=True) + @mock.patch.object(os, 'link', autospec=True) + @mock.patch.object(os, 'mkdir', autospec=True) def test_publish_image_local_copy(self, mock_mkdir, mock_link, mock_shutil, mock_chmod): self.config(use_swift=False, group='redfish') @@ -271,8 +297,8 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): object_name = 'image-%s' % task.node.uuid - mock_publish_image.assert_called_once_with(mock.ANY, - mock.ANY, object_name) + mock_publish_image.assert_called_once_with(mock.ANY, mock.ANY, + object_name, None) mock_create_vfat_image.assert_called_once_with( mock.ANY, parameters=None) @@ -295,8 +321,63 @@ class RedfishImageUtilsTestCase(db_base.DbTestCase): object_name = 'image-%s' % task.node.uuid - mock_publish_image.assert_called_once_with(mock.ANY, - mock.ANY, object_name) + mock_publish_image.assert_called_once_with(mock.ANY, mock.ANY, + object_name, None) + + mock_create_vfat_image.assert_called_once_with( + mock.ANY, parameters={"ipa-api-url": "http://callback"}) + + self.assertEqual(expected_url, url) + + @mock.patch.object(image_utils.ImageHandler, 'publish_image', + autospec=True) + @mock.patch.object(images, 'create_vfat_image', autospec=True) + def test_prepare_floppy_image_publish_with_config_external_http_url( + self, mock_create_vfat_image, mock_publish_image): + self.config(external_callback_url='http://callback/', + external_http_url='http://config.external.url', + group='deploy') + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + expected_url = 'https://config.external.url/c.f?e=f' + + mock_publish_image.return_value = expected_url + + url = image_utils.prepare_floppy_image(task) + + object_name = 'image-%s' % task.node.uuid + + mock_publish_image.assert_called_once_with(mock.ANY, mock.ANY, + object_name, None) + + mock_create_vfat_image.assert_called_once_with( + mock.ANY, parameters={"ipa-api-url": "http://callback"}) + + self.assertEqual(expected_url, url) + + @mock.patch.object(image_utils.ImageHandler, 'publish_image', + autospec=True) + @mock.patch.object(images, 'create_vfat_image', autospec=True) + def test_prepare_floppy_image_publish_with_node_external_http_url( + self, mock_create_vfat_image, mock_publish_image): + self.config(external_callback_url='http://callback/', + external_http_url='http://config.external.url', + group='deploy') + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.driver_info["external_http_url"] = \ + "https://node.external" + override_url = task.node.driver_info.get("external_http_url") + expected_url = '"https://node.external/c.f?e=f' + + mock_publish_image.return_value = expected_url + + url = image_utils.prepare_floppy_image(task) + + object_name = 'image-%s' % task.node.uuid + + mock_publish_image.assert_called_once_with( + mock.ANY, mock.ANY, object_name, override_url) mock_create_vfat_image.assert_called_once_with( mock.ANY, parameters={"ipa-api-url": "http://callback"}) diff --git a/ironic/tests/unit/drivers/modules/test_ipxe.py b/ironic/tests/unit/drivers/modules/test_ipxe.py index d9dd126b3..ef37e3fc4 100644 --- a/ironic/tests/unit/drivers/modules/test_ipxe.py +++ b/ironic/tests/unit/drivers/modules/test_ipxe.py @@ -169,16 +169,6 @@ class iPXEBootTestCase(db_base.DbTestCase): task.driver.boot.validate(task) mock_boot_option.assert_called_with(task.node) - @mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option', - return_value='netboot', autospec=True) - def test_validate_fail_missing_image_source(self, mock_boot_option): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - del task.node['instance_info']['image_source'] - self.assertRaises(exception.MissingParameterValue, - task.driver.boot.validate, task) - mock_boot_option.assert_called_with(task.node) - def test_validate_fail_no_port(self): new_node = obj_utils.create_test_node( self.context, @@ -190,48 +180,6 @@ class iPXEBootTestCase(db_base.DbTestCase): self.assertRaises(exception.MissingParameterValue, task.driver.boot.validate, task) - @mock.patch.object(image_service.GlanceImageService, 'show', - autospec=True) - def test_validate_fail_no_image_kernel_ramdisk_props(self, mock_glance): - instance_info = {"boot_option": "netboot"} - mock_glance.return_value = {'properties': {}} - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.instance_info['capabilities'] = instance_info - self.assertRaises(exception.MissingParameterValue, - task.driver.boot.validate, - task) - - @mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option', - return_value='netboot', autospec=True) - @mock.patch.object(image_service.GlanceImageService, 'show', - autospec=True) - def test_validate_fail_glance_image_doesnt_exists(self, mock_glance, - mock_boot_option): - mock_glance.side_effect = exception.ImageNotFound('not found') - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - task.driver.boot.validate, task) - mock_boot_option.assert_called_with(task.node) - - @mock.patch('ironic.drivers.modules.deploy_utils.get_boot_option', - return_value='netboot', autospec=True) - @mock.patch.object(image_service.GlanceImageService, 'show', - autospec=True) - def test_validate_fail_glance_conn_problem(self, mock_glance, - mock_boot_option): - exceptions = (exception.GlanceConnectionFailed('connection fail'), - exception.ImageNotAuthorized('not authorized'), - exception.Invalid('invalid')) - mock_glance.side_effect = exceptions - for exc in exceptions: - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - task.driver.boot.validate, task) - mock_boot_option.assert_called_with(task.node) - def test_validate_inspection(self): with task_manager.acquire(self.context, self.node.uuid) as task: task.driver.boot.validate_inspection(task) @@ -563,94 +511,6 @@ class iPXEBootTestCase(db_base.DbTestCase): self.node.save() self._test_clean_up_ramdisk(mode='rescue') - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - instance_info = {"boot_option": "netboot"} - get_image_info_mock.return_value = image_info - with task_manager.acquire(self.context, self.node.uuid) as task: - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=6) - pxe_config_path = pxe_utils.get_pxe_config_file_path( - task.node.uuid, ipxe_enabled=True) - task.node.properties['capabilities'] = 'boot_mode:uefi' - task.node.instance_info['capabilities'] = instance_info - task.node.driver_internal_info['root_uuid_or_disk_id'] = ( - "30212642-09d3-467f-8e09-21685826ab50") - task.node.driver_internal_info['is_whole_disk_image'] = False - - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=True) - cache_mock.assert_called_once_with(task, image_info, - ipxe_enabled=True) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - switch_pxe_config_mock.assert_called_once_with( - pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", - 'uefi', False, iscsi_boot=False, ramdisk_boot=False, - ipxe_enabled=True, anaconda_boot=False) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.PXE, - persistent=True) - - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_bios( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - instance_info = {"boot_option": "netboot", - "boot_mode": "bios"} - get_image_info_mock.return_value = image_info - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.instance_info['capabilities'] = instance_info - task.node.driver_internal_info['root_uuid_or_disk_id'] = ( - "30212642-09d3-467f-8e09-21685826ab50") - task.node.driver_internal_info['is_whole_disk_image'] = False - - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=6) - pxe_config_path = pxe_utils.get_pxe_config_file_path( - task.node.uuid, ipxe_enabled=True) - - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=True) - cache_mock.assert_called_once_with(task, image_info, - ipxe_enabled=True) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - switch_pxe_config_mock.assert_called_once_with( - pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", - 'bios', False, iscsi_boot=False, ramdisk_boot=False, - ipxe_enabled=True, anaconda_boot=False) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.PXE, - persistent=True) - @mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True) @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) @@ -665,8 +525,7 @@ class iPXEBootTestCase(db_base.DbTestCase): dhcp_factory_mock.return_value = provider_mock image_info = {'kernel': ('', '/path/to/kernel'), 'ramdisk': ('', '/path/to/ramdisk')} - i_info_caps = {"boot_option": "ramdisk", - "boot_mode": "bios"} + i_info_caps = {"boot_mode": "bios"} kernel_arg = "meow" get_image_info_mock.return_value = image_info @@ -676,6 +535,7 @@ class iPXEBootTestCase(db_base.DbTestCase): i_info['capabilities'] = i_info_caps i_info['kernel_append_params'] = kernel_arg task.node.instance_info = i_info + task.node.deploy_interface = 'ramdisk' task.node.save() dhcp_opts = pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=True) @@ -721,15 +581,14 @@ class iPXEBootTestCase(db_base.DbTestCase): dhcp_factory_mock.return_value = provider_mock image_info = {'kernel': ('', '/path/to/kernel'), 'ramdisk': ('', '/path/to/ramdisk')} - i_info_caps = {"boot_option": "ramdisk"} kernel_arg = "meow" get_image_info_mock.return_value = image_info with task_manager.acquire(self.context, self.node.uuid) as task: i_info = task.node.instance_info - i_info['capabilities'] = i_info_caps i_info['kernel_append_params'] = kernel_arg task.node.instance_info = i_info + task.node.deploy_interface = 'ramdisk' task.node.save() dhcp_opts = pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=True) @@ -761,160 +620,6 @@ class iPXEBootTestCase(db_base.DbTestCase): mock_create_pxe_config.assert_called_once_with( task, expected_params, mock.ANY, ipxe_enabled=True) - @mock.patch('os.path.isfile', return_value=False, autospec=True) - @mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True) - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_active( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock, create_pxe_config_mock, isfile_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - instance_info = {"boot_option": "netboot"} - get_image_info_mock.return_value = image_info - self.node.provision_state = states.ACTIVE - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.instance_info['capabilities'] = instance_info - task.node.driver_internal_info['root_uuid_or_disk_id'] = ( - "30212642-09d3-467f-8e09-21685826ab50") - task.node.driver_internal_info['is_whole_disk_image'] = False - - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=6) - pxe_config_path = pxe_utils.get_pxe_config_file_path( - task.node.uuid, ipxe_enabled=True) - - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=True) - cache_mock.assert_called_once_with(task, image_info, - ipxe_enabled=True) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - create_pxe_config_mock.assert_called_once_with( - task, mock.ANY, CONF.pxe.ipxe_config_template, - ipxe_enabled=True) - switch_pxe_config_mock.assert_called_once_with( - pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", - 'bios', False, iscsi_boot=False, ramdisk_boot=False, - ipxe_enabled=True, anaconda_boot=False) - self.assertFalse(set_boot_device_mock.called) - - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_missing_root_uuid( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - get_image_info_mock.return_value = image_info - instance_info = {"boot_option": "netboot"} - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.instance_info['capabilities'] = instance_info - task.node.driver_internal_info['is_whole_disk_image'] = False - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=4) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=6) - - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=True) - cache_mock.assert_called_once_with(task, image_info, - ipxe_enabled=True) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - self.assertFalse(switch_pxe_config_mock.called) - self.assertFalse(set_boot_device_mock.called) - - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_missing_root_uuid_default( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - get_image_info_mock.return_value = image_info - instance_info = self.node.instance_info - instance_info['capabilities'] = {"boot_option": "netboot"} - self.node.instance_info = instance_info - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_internal_info['is_whole_disk_image'] = False - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=4) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=6) - - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=True) - cache_mock.assert_called_once_with(task, image_info, - ipxe_enabled=True) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - self.assertFalse(switch_pxe_config_mock.called) - self.assertFalse(set_boot_device_mock.called) - - # NOTE(TheJulia): The log mock below is attached to the iPXE interface - # which directly logs the warning that is being checked for. - @mock.patch.object(pxe_base.LOG, 'warning', autospec=True) - @mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True) - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_whole_disk_image_missing_root_uuid( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, set_boot_device_mock, - clean_up_pxe_mock, log_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - get_image_info_mock.return_value = {} - instance_info = {"boot_option": "netboot", - "boot_mode": "bios"} - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.instance_info['capabilities'] = instance_info - task.node.driver_internal_info['is_whole_disk_image'] = True - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=True, ip_version=6) - - task.driver.boot.prepare_instance(task) - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=True) - cache_mock.assert_called_once_with(task, {}, ipxe_enabled=True) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - self.assertTrue(log_mock.called) - clean_up_pxe_mock.assert_called_once_with(task, ipxe_enabled=True) - set_boot_device_mock.assert_called_once_with( - task, boot_devices.DISK, persistent=True) - @mock.patch('os.path.isfile', lambda filename: False) @mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True) @mock.patch.object(deploy_utils, 'is_iscsi_boot', lambda task: True) @@ -1037,8 +742,8 @@ class iPXEBootTestCase(db_base.DbTestCase): self.config(http_url=http_url, group='deploy') provider_mock = mock.MagicMock() dhcp_factory_mock.return_value = provider_mock - self.node.instance_info = {'boot_iso': 'http://1.2.3.4:1234/boot.iso', - 'capabilities': {'boot_option': 'ramdisk'}} + self.node.deploy_interface = 'ramdisk' + self.node.instance_info = {'boot_iso': 'http://1.2.3.4:1234/boot.iso'} image_info = {'kernel': ('', '/path/to/kernel'), 'deploy_kernel': ('', '/path/to/kernel'), 'ramdisk': ('', '/path/to/ramdisk'), @@ -1075,7 +780,7 @@ class iPXEBootTestCase(db_base.DbTestCase): @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_ramdisk_with_kernel_arg( + def test_prepare_instance_ramdisk_with_kernel_arg( self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, set_boot_device_mock, create_pxe_config_mock): @@ -1130,14 +835,9 @@ class iPXEBootTestCase(db_base.DbTestCase): autospec=True) @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) @mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True) - def test_prepare_instance_localboot(self, clean_up_pxe_config_mock, - set_boot_device_mock, - secure_boot_mock): + def test_prepare_instance(self, clean_up_pxe_config_mock, + set_boot_device_mock, secure_boot_mock): with task_manager.acquire(self.context, self.node.uuid) as task: - instance_info = task.node.instance_info - instance_info['capabilities'] = {'boot_option': 'local'} - task.node.instance_info = instance_info - task.node.save() task.driver.boot.prepare_instance(task) clean_up_pxe_config_mock.assert_called_once_with( task, ipxe_enabled=True) @@ -1148,15 +848,11 @@ class iPXEBootTestCase(db_base.DbTestCase): @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) @mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True) - def test_prepare_instance_localboot_active(self, clean_up_pxe_config_mock, - set_boot_device_mock): + def test_prepare_instance_active(self, clean_up_pxe_config_mock, + set_boot_device_mock): self.node.provision_state = states.ACTIVE self.node.save() with task_manager.acquire(self.context, self.node.uuid) as task: - instance_info = task.node.instance_info - instance_info['capabilities'] = {'boot_option': 'local'} - task.node.instance_info = instance_info - task.node.save() task.driver.boot.prepare_instance(task) clean_up_pxe_config_mock.assert_called_once_with( task, ipxe_enabled=True) @@ -1168,14 +864,12 @@ class iPXEBootTestCase(db_base.DbTestCase): @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_localboot_with_fallback( + def test_prepare_instance_with_fallback( self, get_image_info_mock, cache_mock, dhcp_factory_mock, switch_pxe_config_mock, clean_up_pxe_config_mock, set_boot_device_mock): self.config(enable_netboot_fallback=True, group='pxe') with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.instance_info = task.node.instance_info - task.node.instance_info['capabilities'] = {'boot_option': 'local'} task.node.driver_internal_info['root_uuid_or_disk_id'] = ( "30212642-09d3-467f-8e09-21685826ab50") task.node.driver_internal_info['is_whole_disk_image'] = False diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py index 779606229..e7d444104 100644 --- a/ironic/tests/unit/drivers/modules/test_pxe.py +++ b/ironic/tests/unit/drivers/modules/test_pxe.py @@ -77,7 +77,8 @@ class PXEBootTestCase(db_base.DbTestCase): self.config(enabled_boot_interfaces=[self.boot_interface, 'ipxe', 'fake']) - self.config(enabled_deploy_interfaces=['fake', 'direct', 'anaconda']) + self.config(enabled_deploy_interfaces=['fake', 'direct', 'anaconda', + 'ramdisk']) self.node = obj_utils.create_test_node( self.context, driver=self.driver, @@ -144,15 +145,6 @@ class PXEBootTestCase(db_base.DbTestCase): del task.node['instance_info']['image_source'] task.driver.boot.validate(task) - def test_validate_fail_missing_image_source(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node['instance_info']['capabilities'] = { - 'boot_option': 'netboot'} - del task.node['instance_info']['image_source'] - self.assertRaises(exception.MissingParameterValue, - task.driver.boot.validate, task) - def test_validate_fail_no_port(self): new_node = obj_utils.create_test_node( self.context, @@ -164,40 +156,18 @@ class PXEBootTestCase(db_base.DbTestCase): self.assertRaises(exception.MissingParameterValue, task.driver.boot.validate, task) - @mock.patch.object(image_service.GlanceImageService, 'show', autospec=True) - def test_validate_fail_no_image_kernel_ramdisk_props(self, mock_glance): - instance_info = {"boot_option": "netboot"} - mock_glance.return_value = {'properties': {}} - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.instance_info['capabilities'] = instance_info - self.assertRaises(exception.MissingParameterValue, - task.driver.boot.validate, - task) - - @mock.patch.object(image_service.GlanceImageService, 'show', autospec=True) - def test_validate_fail_glance_image_doesnt_exists(self, mock_glance): - mock_glance.side_effect = exception.ImageNotFound('not found') + @mock.patch.object(deploy_utils, 'get_boot_option', + return_value='ramdisk', autospec=True) + @mock.patch.object(deploy_utils, 'validate_image_properties', + autospec=True) + @mock.patch.object(deploy_utils, 'get_image_instance_info', autospec=True) + def test_validate_non_local(self, mock_get_iinfo, mock_validate, + mock_boot_opt): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: - task.node.instance_info['capabilities'] = { - 'boot_option': 'netboot'} - self.assertRaises(exception.InvalidParameterValue, - task.driver.boot.validate, task) - - @mock.patch.object(image_service.GlanceImageService, 'show', autospec=True) - def test_validate_fail_glance_conn_problem(self, mock_glance): - exceptions = (exception.GlanceConnectionFailed('connection fail'), - exception.ImageNotAuthorized('not authorized'), - exception.Invalid('invalid')) - mock_glance.side_effect = exceptions - for exc in exceptions: - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.instance_info['capabilities'] = { - 'boot_option': 'netboot'} - self.assertRaises(exception.InvalidParameterValue, - task.driver.boot.validate, task) + task.driver.boot.validate(task) + mock_validate.assert_called_once_with( + task, mock_get_iinfo.return_value) def test_validate_inspection(self): with task_manager.acquire(self.context, self.node.uuid) as task: @@ -486,217 +456,13 @@ class PXEBootTestCase(db_base.DbTestCase): self.node.save() self._test_clean_up_ramdisk(mode='rescue') - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_bios( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - get_image_info_mock.return_value = image_info - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.driver_internal_info['root_uuid_or_disk_id'] = ( - "30212642-09d3-467f-8e09-21685826ab50") - task.node.driver_internal_info['is_whole_disk_image'] = False - task.node.instance_info = { - 'capabilities': {'boot_option': 'netboot', - 'boot_mode': 'bios'}} - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False, ip_version=4) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False, ip_version=6) - pxe_config_path = pxe_utils.get_pxe_config_file_path( - task.node.uuid) - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=False) - cache_mock.assert_called_once_with( - task, image_info, ipxe_enabled=False) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - switch_pxe_config_mock.assert_called_once_with( - pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", - 'bios', False, iscsi_boot=False, ramdisk_boot=False, - ipxe_enabled=False, anaconda_boot=False) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.PXE, - persistent=True) - - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_uefi( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - get_image_info_mock.return_value = image_info - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.driver_internal_info['root_uuid_or_disk_id'] = ( - "30212642-09d3-467f-8e09-21685826ab50") - task.node.driver_internal_info['is_whole_disk_image'] = False - task.node.instance_info = { - 'capabilities': {'boot_option': 'netboot'}} - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False, ip_version=4) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False, ip_version=6) - pxe_config_path = pxe_utils.get_pxe_config_file_path( - task.node.uuid) - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=False) - cache_mock.assert_called_once_with( - task, image_info, ipxe_enabled=False) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - switch_pxe_config_mock.assert_called_once_with( - pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", - 'uefi', False, iscsi_boot=False, ramdisk_boot=False, - ipxe_enabled=False, anaconda_boot=False) - set_boot_device_mock.assert_called_once_with(task, - boot_devices.PXE, - persistent=True) - - @mock.patch('os.path.isfile', return_value=False, autospec=True) - @mock.patch.object(pxe_utils, 'create_pxe_config', autospec=True) - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_active( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock, create_pxe_config_mock, isfile_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - instance_info = {"boot_option": "netboot"} - get_image_info_mock.return_value = image_info - self.node.provision_state = states.ACTIVE - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.driver_internal_info['root_uuid_or_disk_id'] = ( - "30212642-09d3-467f-8e09-21685826ab50") - task.node.driver_internal_info['is_whole_disk_image'] = False - task.node.instance_info['capabilities'] = instance_info - task.driver.boot.prepare_instance(task) - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False, ip_version=6) - pxe_config_path = pxe_utils.get_pxe_config_file_path( - task.node.uuid) - - get_image_info_mock.assert_called_once_with( - task, ipxe_enabled=False) - cache_mock.assert_called_once_with( - task, image_info, ipxe_enabled=False) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - create_pxe_config_mock.assert_called_once_with( - task, mock.ANY, CONF.pxe.pxe_config_template, - ipxe_enabled=False) - switch_pxe_config_mock.assert_called_once_with( - pxe_config_path, "30212642-09d3-467f-8e09-21685826ab50", - 'bios', False, iscsi_boot=False, ramdisk_boot=False, - ipxe_enabled=False, anaconda_boot=False) - self.assertFalse(set_boot_device_mock.called) - - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_netboot_missing_root_uuid( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, switch_pxe_config_mock, - set_boot_device_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - image_info = {'kernel': ('', '/path/to/kernel'), - 'ramdisk': ('', '/path/to/ramdisk')} - instance_info = {"boot_option": "netboot"} - get_image_info_mock.return_value = image_info - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.instance_info['capabilities'] = instance_info - task.node.driver_internal_info['is_whole_disk_image'] = False - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False, ip_version=6) - - task.driver.boot.prepare_instance(task) - - get_image_info_mock.assert_called_once_with(task, - ipxe_enabled=False) - cache_mock.assert_called_once_with( - task, image_info, ipxe_enabled=False) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - self.assertFalse(switch_pxe_config_mock.called) - self.assertFalse(set_boot_device_mock.called) - - @mock.patch.object(pxe_base.LOG, 'warning', autospec=True) - @mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True) - @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) - @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) - @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - def test_prepare_instance_whole_disk_image_missing_root_uuid( - self, get_image_info_mock, cache_mock, - dhcp_factory_mock, set_boot_device_mock, - clean_up_pxe_mock, log_mock): - provider_mock = mock.MagicMock() - dhcp_factory_mock.return_value = provider_mock - get_image_info_mock.return_value = {} - instance_info = {"boot_option": "netboot"} - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.properties['capabilities'] = 'boot_mode:bios' - task.node.instance_info['capabilities'] = instance_info - task.node.driver_internal_info['is_whole_disk_image'] = True - dhcp_opts = pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False) - dhcp_opts += pxe_utils.dhcp_options_for_instance( - task, ipxe_enabled=False, ip_version=6) - task.driver.boot.prepare_instance(task) - get_image_info_mock.assert_called_once_with(task, - ipxe_enabled=False) - cache_mock.assert_called_once_with( - task, {}, ipxe_enabled=False) - provider_mock.update_dhcp.assert_called_once_with(task, dhcp_opts) - self.assertTrue(log_mock.called) - clean_up_pxe_mock.assert_called_once_with( - task, ipxe_enabled=False) - set_boot_device_mock.assert_called_once_with( - task, boot_devices.DISK, persistent=True) - @mock.patch.object(boot_mode_utils, 'configure_secure_boot_if_needed', autospec=True) @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) @mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True) - def test_prepare_instance_localboot(self, clean_up_pxe_config_mock, - set_boot_device_mock, - secure_boot_mock): + def test_prepare_instance(self, clean_up_pxe_config_mock, + set_boot_device_mock, secure_boot_mock): with task_manager.acquire(self.context, self.node.uuid) as task: - instance_info = task.node.instance_info - instance_info['capabilities'] = {'boot_option': 'local'} - task.node.instance_info = instance_info - task.node.save() task.driver.boot.prepare_instance(task) clean_up_pxe_config_mock.assert_called_once_with( task, ipxe_enabled=False) @@ -707,15 +473,11 @@ class PXEBootTestCase(db_base.DbTestCase): @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) @mock.patch.object(pxe_utils, 'clean_up_pxe_config', autospec=True) - def test_prepare_instance_localboot_active(self, clean_up_pxe_config_mock, - set_boot_device_mock): + def test_prepare_instance_active(self, clean_up_pxe_config_mock, + set_boot_device_mock): self.node.provision_state = states.ACTIVE self.node.save() with task_manager.acquire(self.context, self.node.uuid) as task: - instance_info = task.node.instance_info - instance_info['capabilities'] = {'boot_option': 'local'} - task.node.instance_info = instance_info - task.node.save() task.driver.boot.prepare_instance(task) clean_up_pxe_config_mock.assert_called_once_with( task, ipxe_enabled=False) @@ -741,9 +503,7 @@ class PXEBootTestCase(db_base.DbTestCase): self.node.provision_state = states.DEPLOYING get_image_info_mock.return_value = image_info with task_manager.acquire(self.context, self.node.uuid) as task: - instance_info = task.node.instance_info - instance_info['capabilities'] = {'boot_option': 'ramdisk'} - task.node.instance_info = instance_info + task.node.deploy_interface = 'ramdisk' task.node.save() dhcp_opts = pxe_utils.dhcp_options_for_instance( task, ipxe_enabled=False) diff --git a/ironic/tests/unit/drivers/modules/test_ramdisk.py b/ironic/tests/unit/drivers/modules/test_ramdisk.py index 4deedbfc2..66d11aa18 100644 --- a/ironic/tests/unit/drivers/modules/test_ramdisk.py +++ b/ironic/tests/unit/drivers/modules/test_ramdisk.py @@ -47,8 +47,6 @@ class RamdiskDeployTestCase(db_base.DbTestCase): self.config(tftp_root=self.temp_dir, group='pxe') self.temp_dir = tempfile.mkdtemp() self.config(images_path=self.temp_dir, group='pxe') - self.config(enabled_deploy_interfaces=['ramdisk']) - self.config(enabled_boot_interfaces=['pxe']) for iface in drivers_base.ALL_INTERFACES: impl = 'fake' if iface == 'network': @@ -94,7 +92,6 @@ class RamdiskDeployTestCase(db_base.DbTestCase): task, ipxe_enabled=False, ip_version=6) pxe_config_path = pxe_utils.get_pxe_config_file_path( task.node.uuid) - task.node.properties['capabilities'] = 'boot_option:netboot' task.node.driver_internal_info['is_whole_disk_image'] = False task.driver.deploy.prepare(task) task.driver.deploy.deploy(task) @@ -122,20 +119,26 @@ class RamdiskDeployTestCase(db_base.DbTestCase): image_info = {'kernel': ('', '/path/to/kernel'), 'ramdisk': ('', '/path/to/ramdisk')} mock_image_info.return_value = image_info - i_info = self.node.instance_info - i_info.update({'capabilities': {'boot_option': 'ramdisk'}}) - self.node.instance_info = i_info - self.node.save() with task_manager.acquire(self.context, self.node.uuid) as task: self.assertIsNone(task.driver.deploy.deploy(task)) mock_image_info.assert_called_once_with(task, ipxe_enabled=False) mock_cache.assert_called_once_with( task, image_info, ipxe_enabled=False) self.assertFalse(mock_warning.called) - i_info['configdrive'] = 'meow' - self.node.instance_info = i_info + + @mock.patch.object(ramdisk.LOG, 'warning', autospec=True) + @mock.patch.object(deploy_utils, 'switch_pxe_config', autospec=True) + @mock.patch.object(dhcp_factory, 'DHCPFactory', autospec=True) + @mock.patch.object(pxe_utils, 'cache_ramdisk_kernel', autospec=True) + @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) + def test_deploy_with_configdrive(self, mock_image_info, mock_cache, + mock_dhcp_factory, mock_switch_config, + mock_warning): + image_info = {'kernel': ('', '/path/to/kernel'), + 'ramdisk': ('', '/path/to/ramdisk')} + mock_image_info.return_value = image_info + self.node.set_instance_info('configdrive', 'meow') self.node.save() - mock_warning.reset_mock() with task_manager.acquire(self.context, self.node.uuid) as task: self.assertIsNone(task.driver.deploy.deploy(task)) self.assertTrue(mock_warning.called) diff --git a/ironic/tests/unit/drivers/modules/test_snmp.py b/ironic/tests/unit/drivers/modules/test_snmp.py index 36a59a396..6bdd2da5a 100644 --- a/ironic/tests/unit/drivers/modules/test_snmp.py +++ b/ironic/tests/unit/drivers/modules/test_snmp.py @@ -875,7 +875,7 @@ class SNMPDeviceDriverTestCase(db_base.DbTestCase): def test_power_off_timeout(self, mock_sleep, mock_get_client): # Ensure that a power off consistency poll timeout causes an error mock_client = mock_get_client.return_value - CONF.snmp.power_timeout = 5 + CONF.set_override('power_timeout', 5, 'snmp') driver = snmp._get_driver(self.node) mock_client.get.return_value = driver.value_power_on pstate = driver.power_off() diff --git a/ironic/tests/unit/drivers/pxe_config.template b/ironic/tests/unit/drivers/pxe_config.template index 7cf91e369..238dde4ce 100644 --- a/ironic/tests/unit/drivers/pxe_config.template +++ b/ironic/tests/unit/drivers/pxe_config.template @@ -5,12 +5,6 @@ kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk selinux=0 troubleshoot=0 text test_param ipappend 2 - -label boot_partition -kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel -append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk root={{ ROOT }} ro text test_param - - label boot_whole_disk COM32 chain.c32 append mbr:{{ DISK_IDENTIFIER }} diff --git a/ironic/tests/unit/drivers/pxe_grub_config.template b/ironic/tests/unit/drivers/pxe_grub_config.template index 568018671..c4410b489 100644 --- a/ironic/tests/unit/drivers/pxe_grub_config.template +++ b/ironic/tests/unit/drivers/pxe_grub_config.template @@ -7,11 +7,6 @@ menuentry "deploy" { initrdefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk } -menuentry "boot_partition" { - linuxefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel root=(( ROOT )) ro text test_param boot_server=192.0.2.1 - initrdefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk -} - menuentry "boot_ramdisk" { linuxefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel root=/dev/ram0 text test_param ramdisk_param initrdefi /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk diff --git a/releasenotes/notes/ValueDisplayName-13837c653277ff08.yaml b/releasenotes/notes/ValueDisplayName-13837c653277ff08.yaml new file mode 100644 index 000000000..6abac74c6 --- /dev/null +++ b/releasenotes/notes/ValueDisplayName-13837c653277ff08.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixes detecting of allowable values for a BIOS settings enumeration in + the ``redfish`` BIOS interface when only ``ValueDisplayName`` is provided. diff --git a/releasenotes/notes/adds-kickstart-auto-url-in-template-9f716c244adff159.yaml b/releasenotes/notes/adds-kickstart-auto-url-in-template-9f716c244adff159.yaml new file mode 100644 index 000000000..df7e0bdcf --- /dev/null +++ b/releasenotes/notes/adds-kickstart-auto-url-in-template-9f716c244adff159.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds an automatic switch to ``url`` for the kickstart template when + the source is a URL path as opposed to a ``stage2`` ramdisk. diff --git a/releasenotes/notes/deprecate-syslinux-support-98d327c67607fc8e.yaml b/releasenotes/notes/deprecate-syslinux-support-98d327c67607fc8e.yaml index 670047a82..9543f9d84 100644 --- a/releasenotes/notes/deprecate-syslinux-support-98d327c67607fc8e.yaml +++ b/releasenotes/notes/deprecate-syslinux-support-98d327c67607fc8e.yaml @@ -23,4 +23,4 @@ deprecations: - Deprecation of ``pxelinux``, as a result of the deprecation of ``syslinux``, does ultimately mean the default for the ``pxe`` boot_interface to carry defaults for the use of grub based network booting, specifically for - for operators who are unable to use iPXE. + operators who are unable to use iPXE. diff --git a/releasenotes/notes/fast-track-bios-fa9ae685c151dd24.yaml b/releasenotes/notes/fast-track-bios-fa9ae685c151dd24.yaml new file mode 100644 index 000000000..b4a8004f7 --- /dev/null +++ b/releasenotes/notes/fast-track-bios-fa9ae685c151dd24.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes rebooting into the agent after changing BIOS settings in fast-track + mode with the ``redfish-virtual-media`` boot interface. Previously, the ISO + would not be configured. diff --git a/releasenotes/notes/fix-ilo-boot-interface-order-238a2da9933cf28c.yaml b/releasenotes/notes/fix-ilo-boot-interface-order-238a2da9933cf28c.yaml new file mode 100644 index 000000000..586ea6b82 --- /dev/null +++ b/releasenotes/notes/fix-ilo-boot-interface-order-238a2da9933cf28c.yaml @@ -0,0 +1,26 @@ +--- +fixes: + - | + Fixes the default boot interface order for the ``ilo`` hardware type + where previously it would prefer ``pxe`` over ``ipxe``. This created + inconsistencies for operators using multiple hardware types, where + both interfaces were enabled in the deployment. +upgrade: + - | + Operators who are upgrading should be aware that a bug was discovered + with the automatic selection of ``boot_interface`` for users of the + ``ilo`` and ``ilo5`` hardware types. This was an inconsistency, + resulting in ``pxe`` being selected instead of ``ipxe`` if both + boot interfaces were enabled. Depending on the local configuration, + this may, or may not have happened and will remain static on + preexisting baremetal nodes. Some users may have been relying + upon this incorrect behavior by having misalligned defaults by trying + to use the ``pxe`` interface for ``ipxe``. Users wishing to continue + this usage as it was previously will need to explicitly set a + ``boot_interface`` value to either ``pxe`` or ``ilo-ipxe`` by default, + depending on the local configuration. Most operators have leveraged + the default examples, and thus did not explicitly encounter this + condition. Operators explicitly wishing to use ``pxe`` boot interfaces + with the ``ipxe`` templates and defaults set to override the defaults + for ``ironic.conf`` will need to either continue to leverage default + override configurations in their ``ironic.conf`` file. diff --git a/releasenotes/notes/fix-pxe-glance-lookup-anaconda-86fe616c6286ec08.yaml b/releasenotes/notes/fix-pxe-glance-lookup-anaconda-86fe616c6286ec08.yaml new file mode 100644 index 000000000..961b49cb0 --- /dev/null +++ b/releasenotes/notes/fix-pxe-glance-lookup-anaconda-86fe616c6286ec08.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes an issue in the ``anaconda`` deployment interface where PXE argument + processing and preparation was erroneously directly connecting to Glance, + potentially leading to an exception in the standalone use case. diff --git a/releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml b/releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml new file mode 100644 index 000000000..14e20864b --- /dev/null +++ b/releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Adds driver_info/irmc_verify_ca option to specify certification file. + Default value of driver_info/irmc_verify_ca is True. +security: + - | + Modifies the ``irmc`` hardware type to include a capability to control + enforcement of HTTPS certificate verification. By default this is enforced. + python-scciclient >= 0.12.0 is required. diff --git a/releasenotes/notes/jsonschema-4.8-1146d103b877cffd.yaml b/releasenotes/notes/jsonschema-4.8-1146d103b877cffd.yaml new file mode 100644 index 000000000..75c0a6c50 --- /dev/null +++ b/releasenotes/notes/jsonschema-4.8-1146d103b877cffd.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixes API error messages with jsonschema>=4.8. A possible root cause is + now detected for generic schema errors. diff --git a/releasenotes/notes/no-netboot-d08f46c12edabd35.yaml b/releasenotes/notes/no-netboot-d08f46c12edabd35.yaml new file mode 100644 index 000000000..2427192c9 --- /dev/null +++ b/releasenotes/notes/no-netboot-d08f46c12edabd35.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The deprecated support for instance network booting (not to be confused + with the ``ramdisk`` deploy, iSCSI boot or Anaconda deploy) has been + removed. The ``boot_option`` capability is no longer supported. diff --git a/releasenotes/notes/node-creation-no-longer-scope-restricted-b455f66a751f10ec.yaml b/releasenotes/notes/node-creation-no-longer-scope-restricted-b455f66a751f10ec.yaml new file mode 100644 index 000000000..b405dddb3 --- /dev/null +++ b/releasenotes/notes/node-creation-no-longer-scope-restricted-b455f66a751f10ec.yaml @@ -0,0 +1,27 @@ +--- +features: + - | + Adds the capability for a project scoped ``admin`` user to be able to + create nodes in Ironic, which are then manageable by the project scoped + ``admin`` user. Effectively, this is self service Bare Metal as a Service, + however more advanced fields such as drivers, chassies, are not available + to these users. This is controlled through an auto-population of the + Node ``owner`` field, and can be controlled through the + ``[api]project_admin_can_manage_own_nodes`` setting, which defaults to + ``True``, and the new policy ``baremetal:node:create:self_owned_node``. + - | + Adds the capability for a project scoped ``admin`` user to be able to + delete nodes from Ironic which their `project` owns. This can be + contolled through the ``[api]project_admin_can_manage_own_nodes`` + setting, which defaults to ``True``, as well as the + ``baremetal:node:delete:self_owned_node`` policy. +security: + - | + This release contains an improvement which, by default, allows users to + create and delete baremetal nodes inside their own project. This can be + disabled using the ``[api]project_admin_can_manage_own_nodes`` setting. +upgrades: + - | + The API version has been increased to ``1.80`` in order to signify + the addition of additoinal Role Based Access Controls capabilities + around node creation and deletion.
\ No newline at end of file diff --git a/releasenotes/notes/override-external_http_url-per-node-f5423b00b373e528.yaml b/releasenotes/notes/override-external_http_url-per-node-f5423b00b373e528.yaml new file mode 100644 index 000000000..93e661ba7 --- /dev/null +++ b/releasenotes/notes/override-external_http_url-per-node-f5423b00b373e528.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Nodes using virtual media can now specify their own external URL. + This setting can be leveraged via the ``driver_info\external_http_url`` + node setting. + When used, this setting overrides the ``[deploy]http_url`` and + ``[deploy]external_http_url`` settings in the configuration file. diff --git a/releasenotes/notes/prevent-pxe-retry-when-token-exists-a4f38f7da56c1397.yaml b/releasenotes/notes/prevent-pxe-retry-when-token-exists-a4f38f7da56c1397.yaml index 5db6db6ec..62e7ae9ca 100644 --- a/releasenotes/notes/prevent-pxe-retry-when-token-exists-a4f38f7da56c1397.yaml +++ b/releasenotes/notes/prevent-pxe-retry-when-token-exists-a4f38f7da56c1397.yaml @@ -1,7 +1,7 @@ --- fixes: - | - Fixes a race condition in PXE initialization where logic to retry + Fixes a race condition in PXE initialization where the logic to retry what we suspect as potentially failed PXE boot operations was not consulting if an ``agent token`` had been established, which is the very first step in agent initialization. diff --git a/releasenotes/notes/ramdisk-deploy-384a38c3c96059dd.yaml b/releasenotes/notes/ramdisk-deploy-384a38c3c96059dd.yaml new file mode 100644 index 000000000..0fb33937e --- /dev/null +++ b/releasenotes/notes/ramdisk-deploy-384a38c3c96059dd.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``ramdisk`` deploy interface is now enabled by default. When the + default ``direct`` deploy is also enabled, the ``ramdisk`` deploy must be + explicitly requested on the node level. diff --git a/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml b/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml index df9bef955..a829cbd97 100644 --- a/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml +++ b/releasenotes/notes/skip-clear-job-queue-idrac-reset-if-attr-missing-b2a2b609c906c6c4.yaml @@ -1,8 +1,8 @@ --- fixes: - | - Resolved clear_job_queue and reset_idrac verify step failures which occur - when the functionality is not supported by the iDRAC. When this condition - is detected, the code in the step handles the exception and logs a warning - and completes successfully in case of verification steps but fails in case - of cleaning steps. + Resolved ``clear_job_queue`` and ``reset_idrac`` verify step failures which + occur when the functionality is not supported by the iDRAC. When this + condition is detected, the code in the step handles the exception and logs + a warning and completes successfully in case of verification steps but + fails in case of cleaning steps. diff --git a/releasenotes/notes/version-foo-2eb39b768112547f.yaml b/releasenotes/notes/version-foo-2eb39b768112547f.yaml new file mode 100644 index 000000000..bd0485863 --- /dev/null +++ b/releasenotes/notes/version-foo-2eb39b768112547f.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes ``enable_netboot_fallback`` to cause iPXE config to exit 0 + when "sanboot --no-describe" fails. Allowing the firmware to + move onto the next device in the boot order. diff --git a/zuul.d/ironic-jobs.yaml b/zuul.d/ironic-jobs.yaml index 9fd0e601d..c9b969d4f 100644 --- a/zuul.d/ironic-jobs.yaml +++ b/zuul.d/ironic-jobs.yaml @@ -240,17 +240,6 @@ s-object: False s-proxy: False -# TODO(dtantsur): remove when sushy-tools no longer uses it. -- job: - name: ironic-tempest-bios-redfish-netboot - description: "Deploy ironic node over PXE using BIOS boot mode" - parent: ironic-tempest-bios-redfish-pxe - vars: - devstack_localrc: - IRONIC_DEFAULT_BOOT_OPTION: netboot - IRONIC_TEMPEST_WHOLE_DISK_IMAGE: False - IRONIC_VM_EPHEMERAL_DISK: 1 - - job: name: ironic-tempest-uefi-redfish-vmedia description: "Deploy ironic node over Redfish virtual media using UEFI boot mode" @@ -519,7 +508,6 @@ IRONIC_BAREMETAL_BASIC_OPS: True IRONIC_BUILD_DEPLOY_RAMDISK: False IRONIC_CALLBACK_TIMEOUT: 600 - IRONIC_DEFAULT_BOOT_OPTION: local IRONIC_DEPLOY_DRIVER: ipmi IRONIC_ENABLED_NETWORK_INTERFACES: flat,neutron IRONIC_INSPECTOR_BUILD_RAMDISK: False @@ -609,7 +597,6 @@ IRONIC_AUTOMATED_CLEAN_ENABLED: False IRONIC_BAREMETAL_BASIC_OPS: True IRONIC_DEPLOY_DRIVER: ipmi - IRONIC_DEFAULT_BOOT_OPTION: local IRONIC_ENABLED_NETWORK_INTERFACES: flat,neutron IRONIC_NETWORK_INTERFACE: neutron IRONIC_PROVISION_NETWORK_NAME: ironic-provision |