diff --git a/api-ref/source/samples/driver-get-response.json b/api-ref/source/samples/driver-get-response.json index 87cbcb1d34..14175beabc 100644 --- a/api-ref/source/samples/driver-get-response.json +++ b/api-ref/source/samples/driver-get-response.json @@ -2,7 +2,7 @@ "default_bios_interface": "no-bios", "default_boot_interface": "pxe", "default_console_interface": "no-console", - "default_deploy_interface": "iscsi", + "default_deploy_interface": "direct", "default_inspect_interface": "no-inspect", "default_management_interface": "ipmitool", "default_network_interface": "flat", @@ -21,7 +21,7 @@ "no-console" ], "enabled_deploy_interfaces": [ - "iscsi", + "ansible", "direct" ], "enabled_inspect_interfaces": [ diff --git a/api-ref/source/samples/drivers-list-detail-response.json b/api-ref/source/samples/drivers-list-detail-response.json index d3d93c8cef..3ba03200ba 100644 --- a/api-ref/source/samples/drivers-list-detail-response.json +++ b/api-ref/source/samples/drivers-list-detail-response.json @@ -106,7 +106,7 @@ "default_bios_interface": "no-bios", "default_boot_interface": "pxe", "default_console_interface": "no-console", - "default_deploy_interface": "iscsi", + "default_deploy_interface": "direct", "default_inspect_interface": "no-inspect", "default_management_interface": "ipmitool", "default_network_interface": "flat", @@ -125,7 +125,7 @@ "no-console" ], "enabled_deploy_interfaces": [ - "iscsi", + "ansible", "direct" ], "enabled_inspect_interfaces": [ diff --git a/api-ref/source/samples/nodes-list-details-response.json b/api-ref/source/samples/nodes-list-details-response.json index 98c22aa9a4..70451eee27 100644 --- a/api-ref/source/samples/nodes-list-details-response.json +++ b/api-ref/source/samples/nodes-list-details-response.json @@ -119,7 +119,7 @@ "console_enabled": false, "console_interface": "no-console", "created_at": "2016-08-18T22:28:48.643434+11:11", - "deploy_interface": "iscsi", + "deploy_interface": "direct", "deploy_step": {}, "driver": "ipmi", "driver_info": { diff --git a/bindep.txt b/bindep.txt index 2538b79c8f..f88618df9a 100644 --- a/bindep.txt +++ b/bindep.txt @@ -2,7 +2,6 @@ ipmitool [default] ipxe [platform:dpkg default] ipxe-bootimgs [platform:rpm default] -open-iscsi [platform:dpkg default] socat [default] xinetd [default] tftpd-hpa [platform:dpkg default] diff --git a/doc/source/admin/drivers.rst b/doc/source/admin/drivers.rst index 7df4dc6ad4..c3d8eb377d 100644 --- a/doc/source/admin/drivers.rst +++ b/doc/source/admin/drivers.rst @@ -76,7 +76,7 @@ not compatible with them. There are three ways to deal with this situation: baremetal node set test --driver ipmi \ --boot-interface pxe \ - --deploy-interface iscsi \ + --deploy-interface direct \ --management-interface ipmitool \ --power-interface ipmitool diff --git a/doc/source/admin/drivers/ibmc.rst b/doc/source/admin/drivers/ibmc.rst index 84786df02e..1bf9a3ba27 100644 --- a/doc/source/admin/drivers/ibmc.rst +++ b/doc/source/admin/drivers/ibmc.rst @@ -312,15 +312,6 @@ boot_up_seq GET Query boot up sequence get_raid_controller_list GET Query RAID controller summary info ======================== ============ ====================================== - -PXE Boot and iSCSI Deploy Process with Ironic Standalone Environment -==================================================================== - -.. figure:: ../../images/ironic_standalone_with_ibmc_driver.svg - :width: 960px - :align: left - :alt: Ironic standalone with iBMC driver node - .. _Huawei iBMC: https://e.huawei.com/en/products/cloud-computing-dc/servers/accessories/ibmc .. _TLS: https://en.wikipedia.org/wiki/Transport_Layer_Security .. _HUAWEI iBMC Client library: https://pypi.org/project/python-ibmcclient/ diff --git a/doc/source/admin/drivers/idrac.rst b/doc/source/admin/drivers/idrac.rst index d1a1cb59f6..8f37514c3d 100644 --- a/doc/source/admin/drivers/idrac.rst +++ b/doc/source/admin/drivers/idrac.rst @@ -96,7 +96,7 @@ Interface Supported Implementations ``bios`` ``idrac-wsman``, ``idrac-redfish``, ``no-bios`` ``boot`` ``ipxe``, ``pxe``, ``idrac-redfish-virtual-media`` ``console`` ``no-console`` -``deploy`` ``iscsi``, ``direct``, ``ansible``, ``ramdisk`` +``deploy`` ``direct``, ``ansible``, ``ramdisk`` ``inspect`` ``idrac-wsman``, ``idrac``, ``idrac-redfish``, ``inspector``, ``no-inspect`` ``management`` ``idrac-wsman``, ``idrac``, ``idrac-redfish`` diff --git a/doc/source/admin/drivers/ilo.rst b/doc/source/admin/drivers/ilo.rst index f82ed3ec5f..838a3cb976 100644 --- a/doc/source/admin/drivers/ilo.rst +++ b/doc/source/admin/drivers/ilo.rst @@ -1097,8 +1097,9 @@ Netboot with glance and swift IPA -> Conductor [label = "Lookup node"]; Conductor -> IPA [label = "Provides node UUID"]; IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Exposes the disk over iSCSI"]; - Conductor -> Conductor [label = "Connects to bare metal's disk over iSCSI and writes image"]; + 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"]; @@ -1222,8 +1223,9 @@ Netboot in swiftless deploy for intermediate images IPA -> Conductor [label = "Lookup node"]; Conductor -> IPA [label = "Provides node UUID"]; IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Exposes the disk over iSCSI"]; - Conductor -> Conductor [label = "Connects to bare metal's disk over iSCSI and writes image"]; + 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"]; @@ -1303,8 +1305,9 @@ Netboot with HTTP(S) based deploy IPA -> Conductor [label = "Lookup node"]; Conductor -> IPA [label = "Provides node UUID"]; IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Exposes the disk over iSCSI"]; - Conductor -> Conductor [label = "Connects to bare metal's disk over iSCSI and writes image"]; + 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"]; @@ -1381,8 +1384,9 @@ Netboot in standalone ironic IPA -> Conductor [label = "Lookup node"]; Conductor -> IPA [label = "Provides node UUID"]; IPA -> Conductor [label = "Heartbeat"]; - Conductor -> IPA [label = "Exposes the disk over iSCSI"]; - Conductor -> Conductor [label = "Connects to bare metal's disk over iSCSI and writes image"]; + 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"]; diff --git a/doc/source/admin/drivers/ipa.rst b/doc/source/admin/drivers/ipa.rst index 2fea3f9c84..ecafa081f6 100644 --- a/doc/source/admin/drivers/ipa.rst +++ b/doc/source/admin/drivers/ipa.rst @@ -17,26 +17,11 @@ For more information see the Drivers ======= -Starting with the Kilo release all deploy interfaces (except for fake ones) -are using IPA. There are two types of them: - -* For nodes using the :ref:`iscsi-deploy` interface, IPA exposes the root hard - drive as an iSCSI share and calls back to the ironic conductor. The - conductor mounts the share and copies an image there. It then signals back - to IPA for post-installation actions like setting up a bootloader for local - boot support. - -* For nodes using the :ref:`direct-deploy` interface, the conductor prepares - a swift temporary URL for an image. IPA then handles the whole deployment - process: downloading an image from swift, putting it on the machine and doing - any post-deploy actions. - -Which one to choose depends on your environment. :ref:`iscsi-deploy` puts -higher load on conductors, :ref:`direct-deploy` currently requires the whole -image to fit in the node's memory, except when using raw images. It also -requires :doc:`/install/configure-glance-swift`. - -.. todo: other differences? +Starting with the Kilo release all deploy interfaces (except for fake ones) are +using IPA. For nodes using the :ref:`direct-deploy` interface, the conductor +prepares a swift temporary URL or a local HTTP URL for the image. IPA then +handles the whole deployment process: downloading an image from swift, putting +it on the machine and doing any post-deploy actions. Requirements ------------ diff --git a/doc/source/admin/drivers/irmc.rst b/doc/source/admin/drivers/irmc.rst index 460a59b757..3b388c3388 100644 --- a/doc/source/admin/drivers/irmc.rst +++ b/doc/source/admin/drivers/irmc.rst @@ -88,7 +88,7 @@ interfaces enabled for ``irmc`` hardware type. enabled_bios_interfaces = irmc enabled_boot_interfaces = irmc-virtual-media,irmc-pxe enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console - enabled_deploy_interfaces = iscsi,direct + enabled_deploy_interfaces = direct enabled_inspect_interfaces = irmc,inspector,no-inspect enabled_management_interfaces = irmc enabled_network_interfaces = flat,neutron diff --git a/doc/source/admin/interfaces/boot.rst b/doc/source/admin/interfaces/boot.rst index 2b74b17de4..aa87dae1ff 100644 --- a/doc/source/admin/interfaces/boot.rst +++ b/doc/source/admin/interfaces/boot.rst @@ -24,8 +24,8 @@ common, and usually requires bootstrapping using PXE first. The ``pxe`` boot interface works by preparing a PXE/iPXE environment for a node on the file system, then instructing the DHCP provider (for example, the Networking service) to boot the node from it. See -:ref:`iscsi-deploy-example` and :ref:`direct-deploy-example` for a better -understanding of the whole deployment process. +ref:`direct-deploy-example` for a better understanding of the whole deployment +process. .. note:: Both PXE and iPXE are configured differently, when UEFI boot is used diff --git a/doc/source/admin/interfaces/deploy.rst b/doc/source/admin/interfaces/deploy.rst index aaac9245ea..f2aceb3bdd 100644 --- a/doc/source/admin/interfaces/deploy.rst +++ b/doc/source/admin/interfaces/deploy.rst @@ -105,7 +105,7 @@ section of ironic's configuration file: [DEFAULT] ... - enabled_deploy_interfaces = iscsi,direct,ansible + enabled_deploy_interfaces = direct,ansible ... Once enabled, you can specify this deploy interface when creating or updating @@ -133,26 +133,3 @@ Ramdisk deploy The ramdisk interface is intended to provide a mechanism to "deploy" an instance where the item to be deployed is in reality a ramdisk. It is documented separately, see :doc:`/admin/ramdisk-boot`. - -.. _iscsi-deploy: - -iSCSI deploy -============ - -.. warning:: - This deploy interface is deprecated and will be removed in the Xena release - cycle. Please use `direct deploy`_ instead. - -With ``iscsi`` deploy interface, the deploy ramdisk publishes the node's hard -drive as an iSCSI_ share. The ironic-conductor then copies the image to this -share. See :ref:`iSCSI deploy diagram ` for a detailed -explanation of how this deploy interface works. - -This interface is used by default, if enabled (see -:ref:`enable-hardware-interfaces`). You can specify it explicitly -when creating or updating a node:: - - baremetal node create --driver ipmi --deploy-interface iscsi - baremetal node set --deploy-interface iscsi - -.. _iSCSI: https://en.wikipedia.org/wiki/ISCSI diff --git a/doc/source/admin/node-deployment.rst b/doc/source/admin/node-deployment.rst index 421edc1797..5ca95567bd 100644 --- a/doc/source/admin/node-deployment.rst +++ b/doc/source/admin/node-deployment.rst @@ -41,13 +41,13 @@ BIOS, and RAID interfaces. Agent steps ----------- -All deploy interfaces based on ironic-python-agent (i.e. ``direct``, ``iscsi`` -and ``ansible`` and any derivatives) expose the following deploy steps: +All deploy interfaces based on ironic-python-agent (i.e. ``direct``, +``ansible`` and any derivatives) expose the following deploy steps: ``deploy.deploy`` (priority 100) In this step the node is booted using a provisioning image. ``deploy.write_image`` (priority 80) - An out-of-band (``iscsi``, ``ansible``) or in-band (``direct``) step that + An out-of-band (``ansible``) or in-band (``direct``) step that downloads and writes the image to the node. ``deploy.tear_down_agent`` (priority 40) In this step the provisioning image is shut down. @@ -57,7 +57,7 @@ and ``ansible`` and any derivatives) expose the following deploy steps: ``deploy.boot_instance`` (priority 20) In this step the node is booted into the user image. -Additionally, the ``iscsi`` and ``direct`` deploy interfaces have: +Additionally, the ``direct`` deploy interfaces has: ``deploy.prepare_instance_boot`` (priority 60) In this step the boot device is configured and the bootloader is installed. diff --git a/doc/source/admin/notifications.rst b/doc/source/admin/notifications.rst index 2f83e64830..bf299d66fc 100644 --- a/doc/source/admin/notifications.rst +++ b/doc/source/admin/notifications.rst @@ -210,7 +210,7 @@ Example of node CRUD notification:: "bios_interface": "no-bios", "boot_interface": "pxe", "console_interface": "no-console", - "deploy_interface": "iscsi", + "deploy_interface": "direct", "inspect_interface": "no-inspect", "management_interface": "ipmitool", "network_interface": "flat", @@ -444,7 +444,7 @@ node maintenance notification:: "bios_interface": "no-bios", "boot_interface": "pxe", "console_interface": "no-console", - "deploy_interface": "iscsi", + "deploy_interface": "direct", "inspect_interface": "no-inspect", "management_interface": "ipmitool", "network_interface": "flat", @@ -534,7 +534,7 @@ level, "error" has ERROR. Example of node console notification:: "bios_interface": "no-bios", "boot_interface": "pxe", "console_interface": "no-console", - "deploy_interface": "iscsi", + "deploy_interface": "direct", "inspect_interface": "no-inspect", "management_interface": "ipmitool", "network_interface": "flat", @@ -617,7 +617,7 @@ ironic-conductor is attempting to change the node:: "bios_interface": "no-bios", "boot_interface": "pxe", "console_interface": "no-console", - "deploy_interface": "iscsi", + "deploy_interface": "direct", "inspect_interface": "no-inspect", "management_interface": "ipmitool", "network_interface": "flat", @@ -695,7 +695,7 @@ prior to the correction:: "bios_interface": "no-bios", "boot_interface": "pxe", "console_interface": "no-console", - "deploy_interface": "iscsi", + "deploy_interface": "direct", "inspect_interface": "no-inspect", "management_interface": "ipmitool", "network_interface": "flat", @@ -787,7 +787,7 @@ indicate a node's provision states before state change, "event" is the FSM "bios_interface": "no-bios", "boot_interface": "pxe", "console_interface": "no-console", - "deploy_interface": "iscsi", + "deploy_interface": "direct", "inspect_interface": "no-inspect", "management_interface": "ipmitool", "network_interface": "flat", diff --git a/doc/source/admin/ramdisk-boot.rst b/doc/source/admin/ramdisk-boot.rst index b24f868815..d961b21abb 100644 --- a/doc/source/admin/ramdisk-boot.rst +++ b/doc/source/admin/ramdisk-boot.rst @@ -18,7 +18,7 @@ non-default interfaces, it must be enabled and set for a node to be utilized: [DEFAULT] ... - enabled_deploy_interfaces = iscsi,direct,ramdisk + enabled_deploy_interfaces = direct,ramdisk ... Once enabled and the conductor(s) have been restarted, the interface can diff --git a/doc/source/admin/troubleshooting.rst b/doc/source/admin/troubleshooting.rst index acbcdea767..c7347cbd8f 100644 --- a/doc/source/admin/troubleshooting.rst +++ b/doc/source/admin/troubleshooting.rst @@ -420,13 +420,10 @@ Overall: timers to help ensure a deployment does not fail due to a short-lived transitory network connectivity failure in the form of a switch port having moved to a temporary blocking state. Where applicable and possible, - many of these patches have been backported to supported releases, - however users of the iSCSI deployment interface will see the least - capability for these sorts of situations to be handled - automatically. These patches also require that the switchport has an - eventual fallback to a non-bonded mode. If the port remains in a blocking - state, then traffic will be unable to flow and the deloyment is likely to - time out. + many of these patches have been backported to supported releases. + These patches also require that the switchport has an eventual fallback to a + non-bonded mode. If the port remains in a blocking state, then traffic will + be unable to flow and the deployment is likely to time out. * If you must use LACP, consider ``passive`` LACP negotiation settings in the network switch as opposed to ``active``. The difference being with passive the connected workload is likely a server where it should likely @@ -543,16 +540,10 @@ Again, these sorts of cases will depend upon the exact configuration of the deployment, but hopefully these are areas where these actions can occur. * Conversion to raw image files upon download to the conductor, from the - ``[DEFAULT]force_raw_images`` option, in particular with the ``iscsi`` - deployment interface. Users using glance and the ``direct`` deployment - interface may also experience issues here as the conductor will cache - the image to be written which takes place when the - ``[agent]image_download_source`` is set to ``http`` instead of ``swift``. - -* Write of a QCOW2 file over the ``iscsi`` deployment interface from the - conductor to the node being deployed can result in large amounts of - "white space" to be written to be transmitted over the wire and written - to the end device. + ``[DEFAULT]force_raw_images`` option. Users using Glance may also experience + issues here as the conductor will cache the image to be written which takes + place when the ``[agent]image_download_source`` is set to ``http`` instead of + ``swift``. .. note:: The QCOW2 image conversion utility does consume quite a bit of memory @@ -560,9 +551,8 @@ deployment, but hopefully these are areas where these actions can occur. is because the files are not sequential in nature, and must be re-assembled from an internal block mapping. Internally Ironic limits this to 1GB of RAM. Operators performing large numbers of deployments may wish to - explore the ``direct`` deployment interface in these sorts of cases in - order to minimize the conductor becoming a limiting factor due to memory - and network IO. + disable raw images in these sorts of cases in order to minimize the + conductor becoming a limiting factor due to memory and network IO. Why are my nodes stuck in a "wait" state? ========================================= diff --git a/doc/source/admin/tuning.rst b/doc/source/admin/tuning.rst index 02636e6083..b554d76ea3 100644 --- a/doc/source/admin/tuning.rst +++ b/doc/source/admin/tuning.rst @@ -10,7 +10,7 @@ be asked by API consumers to perform work for which the underlying tools require large amounts of memory. The biggest example of this is image conversion. Images not in a raw format -need to be written out to disk (local files or remote in iscsi deploy) which +need to be written out to disk for conversion (when requested) which requires the conversion process to generate an in-memory map to re-assemble the image contents into a coherent stream of data. This entire process also stresses the kernel buffers and cache. diff --git a/doc/source/contributor/dev-quickstart.rst b/doc/source/contributor/dev-quickstart.rst index 84638c61bc..639999115d 100644 --- a/doc/source/contributor/dev-quickstart.rst +++ b/doc/source/contributor/dev-quickstart.rst @@ -420,8 +420,8 @@ Ironic ------ Create devstack/local.conf with minimal settings required to enable Ironic. -An example local.conf that enables both ``direct`` and ``iscsi`` -:doc:`deploy interfaces ` and uses the ``ipmi`` +An example local.conf that enables the ``direct`` +:doc:`deploy interface ` and uses the ``ipmi`` hardware type by default:: cd devstack @@ -468,8 +468,6 @@ hardware type by default:: # interfaces, most often power and management: #IRONIC_ENABLED_MANAGEMENT_INTERFACES=ipmitool,fake #IRONIC_ENABLED_POWER_INTERFACES=ipmitool,fake - # The 'ipmi' hardware type's default deploy interface is 'iscsi'. - # This would change the default to 'direct': #IRONIC_DEFAULT_DEPLOY_INTERFACE=direct # Change this to alter the default driver for nodes created by devstack. @@ -516,9 +514,8 @@ directory you cloned DevStack:: An example local.conf that enables the ironic tempest plugin and Ironic can be found below. The ``TEMPEST_PLUGINS`` variable needs to have the absolute path to the ironic-tempest-plugin folder, otherwise the plugin won't be installed. -Ironic will have enabled both ``direct`` and -``iscsi`` :doc:`deploy interfaces ` and uses the -``ipmi`` hardware type by default:: +Ironic will have enabled the ``direct`` :doc:`deploy interface +` and uses the ``ipmi`` hardware type by default:: cd devstack cat >local.conf < - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - API - - - - - API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Conductor - - - - - Conductor - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - do node deploy - - - - - - - - - - User - - - - - User - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create ibmc driver node - - - - - - - - Set driver_info (ibmc_address, ibmc_username, ibmc_password, etc) - - - - - - - - Set instance_info(image_source, root_gb, etc.) - - - - - - - - Validate power, management and vendor interfaces - - - - - - - - Create bare metal node network port - - - - - - - - Set provision_state, optionally pass configdrive - - - - - - - - - - DHCP - - - - - DHCP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TFTP - - - - - TFTP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Validate power, management and vendor interfaces - - - - - - - - - - Node - - - - - Node - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Set PXE boot devicethrough iBMC - - - - - - - - REBOOT through iBMC - - - - - - - - - - - - - Prepare PXEenvironment fordeployment - - - - - - - Run agent ramdisk - - - - - - - - - - - - - - Send PXE DHCP request - - - - - - - - Offer IP to node - - - - - - - - Send PXE image and agent image - - - - - - - - - - - - - - - - - - - - - - - - - - - Send IPA a command to expose disks via iSCSI - - - - - - - - iSCSI attach - - - - - - - - Copies user image and configdrive, if presend - - - - - - - - iSCSI detach - - - - - - - - Install boot loader if requested - - - - - - - - Set boot device either to PXE or to disk - - - - - - - - Collect ramdisk logs - - - - - - - - POWER OFF - - - - - - - - POWER ON - - - - - - - Mark node as ACTIVE - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - 2 - - - - - - - - - - 1 - - - - - - - - - - 2 - - - - - - - - - - 2 - - - - - - - - - - 2 - - - - - - - - - - 1 - - - - - - - IBMC management interface - - - - - - - IBMC power interface - - - - - - - \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst index 7cc671c86a..601d6b16c0 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -40,7 +40,6 @@ Upgrade Guide :maxdepth: 2 admin/upgrade-guide - admin/upgrade-to-hardware-types User Guide ========== diff --git a/doc/source/install/configure-iscsi.rst b/doc/source/install/configure-iscsi.rst deleted file mode 100644 index ce90376f58..0000000000 --- a/doc/source/install/configure-iscsi.rst +++ /dev/null @@ -1,5 +0,0 @@ -Configuring iSCSI-based drivers -------------------------------- - -Ensure that the ``qemu-img`` and ``iscsiadm`` tools are installed on the -**ironic-conductor** host(s). diff --git a/doc/source/install/configure-tenant-networks.rst b/doc/source/install/configure-tenant-networks.rst index 37656483b3..6fca4cd50d 100644 --- a/doc/source/install/configure-tenant-networks.rst +++ b/doc/source/install/configure-tenant-networks.rst @@ -93,10 +93,8 @@ provisioning will happen in a multi-tenant environment (which means using the * TFTP * egress port used for the Bare Metal service (6385 by default) * ingress port used for ironic-python-agent (9999 by default) - * if using :ref:`iscsi-deploy`, the ingress port used for iSCSI - (3260 by default) * if using :ref:`direct-deploy`, the egress port used for the Object - Storage service (typically 80 or 443) + Storage service or the local HTTP server (typically 80 or 443) * if using iPXE, the egress port used for the HTTP server running on the ironic-conductor nodes (typically 80). diff --git a/doc/source/install/enabling-drivers.rst b/doc/source/install/enabling-drivers.rst index 344b371316..81c1b91788 100644 --- a/doc/source/install/enabling-drivers.rst +++ b/doc/source/install/enabling-drivers.rst @@ -78,7 +78,7 @@ console deploy defines how the image gets transferred to the target disk. See :doc:`/admin/interfaces/deploy` for an explanation of the difference - between supported deploy interfaces ``direct`` and ``iscsi``. + between supported deploy interfaces. The deploy interfaces can be enabled as follows: @@ -86,13 +86,10 @@ deploy [DEFAULT] enabled_hardware_types = ipmi,redfish - enabled_deploy_interfaces = iscsi,direct + enabled_deploy_interfaces = direct,ramdisk - Additionally, - - * the ``iscsi`` deploy interface requires :doc:`configure-iscsi` - - * the ``direct`` deploy interface requires the Object Storage service + .. note:: + The ``direct`` deploy interface requires the Object Storage service or an HTTP service inspect implements fetching hardware information from nodes. Can be implemented @@ -186,7 +183,7 @@ IPMI and Redfish, with a few additional features: enabled_hardware_types = ipmi,redfish enabled_boot_interfaces = pxe enabled_console_interfaces = ipmitool-socat,no-console - enabled_deploy_interfaces = iscsi,direct + enabled_deploy_interfaces = direct enabled_inspect_interfaces = inspector enabled_management_interfaces = ipmitool,redfish enabled_network_interfaces = flat,neutron @@ -222,7 +219,7 @@ respectively: [DEFAULT] enabled_hardware_types = redfish - enabled_deploy_interfaces = iscsi + enabled_deploy_interfaces = ansible enabled_power_interfaces = redfish enabled_management_interfaces = redfish @@ -241,13 +238,13 @@ respectively: [DEFAULT] enabled_hardware_types = redfish - enabled_deploy_interfaces = iscsi + enabled_deploy_interfaces = ansible enabled_power_interfaces = redfish enabled_management_interfaces = redfish This is because the ``redfish`` hardware type will have different enabled *deploy* interfaces on these conductors. It would have been fine, if the second -conductor had ``enabled_deploy_interfaces = direct`` instead of ``iscsi``. +conductor had ``enabled_deploy_interfaces = direct`` instead of ``ansible``. This situation is not detected by the Bare Metal service, but it can cause inconsistent behavior in the API, when node functionality will depend on diff --git a/doc/source/install/enrollment.rst b/doc/source/install/enrollment.rst index ce20f5aad0..f52ca08ab2 100644 --- a/doc/source/install/enrollment.rst +++ b/doc/source/install/enrollment.rst @@ -572,7 +572,7 @@ interfaces for a hardware type (for your deployment): +-------------------------------+----------------+ | default_boot_interface | pxe | | default_console_interface | no-console | - | default_deploy_interface | iscsi | + | default_deploy_interface | direct | | default_inspect_interface | no-inspect | | default_management_interface | ipmitool | | default_network_interface | flat | @@ -581,7 +581,7 @@ interfaces for a hardware type (for your deployment): | default_vendor_interface | no-vendor | | enabled_boot_interfaces | pxe | | enabled_console_interfaces | no-console | - | enabled_deploy_interfaces | iscsi, direct | + | enabled_deploy_interfaces | direct | | enabled_inspect_interfaces | no-inspect | | enabled_management_interfaces | ipmitool | | enabled_network_interfaces | flat, noop | @@ -627,10 +627,10 @@ Consider the following configuration (shortened for simplicity): [DEFAULT] enabled_hardware_types = ipmi,redfish enabled_console_interfaces = no-console,ipmitool-shellinabox - enabled_deploy_interfaces = iscsi,direct + enabled_deploy_interfaces = direct enabled_management_interfaces = ipmitool,redfish enabled_power_interfaces = ipmitool,redfish - default_deploy_interface = direct + default_deploy_interface = ansible A new node is created with the ``ipmi`` driver and no interfaces specified: @@ -654,7 +654,7 @@ Then the defaults for the interfaces that will be used by the node in this example are calculated as follows: deploy - An explicit value of ``direct`` is provided for + An explicit value of ``ansible`` is provided for ``default_deploy_interface``, so it is used. power No default is configured. The ``ipmi`` hardware type supports only diff --git a/doc/source/install/refarch/common.rst b/doc/source/install/refarch/common.rst index f4fdac2b81..ec2a0dbbbe 100644 --- a/doc/source/install/refarch/common.rst +++ b/doc/source/install/refarch/common.rst @@ -99,18 +99,6 @@ implementation is available for the hardware, it is recommended using it for better scalability and security. Otherwise, it is recommended to use iPXE, when it is supported by target hardware. -Deploy interface -~~~~~~~~~~~~~~~~ - -There are two deploy interfaces in-tree, ``iscsi`` and ``direct``. See -:doc:`../../admin/interfaces/deploy` for explanation of the difference. -With the ``iscsi`` deploy method, most of the deployment operations happen on -the conductor. If the Object Storage service (swift) or RadosGW is present in -the environment, it is recommended to use the ``direct`` deploy method for -better scalability and reliability. - -.. TODO(dtantsur): say something about the ansible deploy, when it's in - Hardware specifications ~~~~~~~~~~~~~~~~~~~~~~~ @@ -328,11 +316,6 @@ the space requirements are different: ``image_download_source`` can also be provided in the node's ``driver_info`` or ``instance_info``. See :ref:`image_download_source`. -* The ``iscsi`` deploy method always requires caching of the whole instance - image locally during the deployment. The image has to be converted to the raw - format, which may increase the required amount of disk space, as well as the - CPU load. - * When network boot is used, the instance image kernel and ramdisk are cached locally while the instance is active. diff --git a/doc/source/install/setup-drivers.rst b/doc/source/install/setup-drivers.rst index 41abd1f496..8e69b52d6b 100644 --- a/doc/source/install/setup-drivers.rst +++ b/doc/source/install/setup-drivers.rst @@ -7,4 +7,3 @@ Set up the drivers for the Bare Metal service enabling-drivers configure-pxe configure-ipmi - configure-iscsi diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst index c6884558cf..41eba082a6 100644 --- a/doc/source/user/index.rst +++ b/doc/source/user/index.rst @@ -260,7 +260,7 @@ options. .. _direct-deploy-example: -Example 1: PXE Boot and Direct Deploy Process +Example: PXE Boot and Direct Deploy Process --------------------------------------------- This process is how :ref:`direct-deploy` works. @@ -318,63 +318,5 @@ This process is how :ref:`direct-deploy` works. (From a `talk`_ and `slides`_) -.. _iscsi-deploy-example: - -Example 2: PXE Boot and iSCSI Deploy Process --------------------------------------------- - -This process is how the currently deprecated :ref:`iscsi-deploy` works. - -.. seqdiag:: - :scale: 75 - - diagram { - Nova; API; Conductor; Neutron; HTTPStore; "TFTP/HTTPd"; Node; - activation = none; - span_height = 1; - edge_length = 250; - default_note_color = white; - default_fontsize = 14; - - Nova -> API [label = "Set instance_info\n(image_source,\nroot_gb, etc.)"]; - Nova -> API [label = "Validate power and deploy\ninterfaces"]; - Nova -> API [label = "Plug VIFs to the node"]; - Nova -> API [label = "Set provision_state,\noptionally pass configdrive"]; - API -> Conductor [label = "do_node_deploy()"]; - Conductor -> Conductor [label = "Validate power and deploy interfaces"]; - Conductor -> HTTPStore [label = "Store configdrive if configdrive_use_swift \noption is set"]; - Conductor -> Node [label = "POWER OFF"]; - Conductor -> Neutron [label = "Attach provisioning network to port(s)"]; - Conductor -> Neutron [label = "Update DHCP boot options"]; - Conductor -> Conductor [label = "Prepare PXE\nenvironment for\ndeployment"]; - Conductor -> Node [label = "Set PXE boot device \nthrough the BMC"]; - Conductor -> Conductor [label = "Cache deploy\nkernel, ramdisk,\ninstance images"]; - Conductor -> Node [label = "REBOOT"]; - Node -> Neutron [label = "DHCP request"]; - Neutron -> Node [label = "next-server = Conductor"]; - Node -> Node [label = "Runs agent\nramdisk"]; - Node -> API [label = "lookup()"]; - API -> Node [label = "Pass UUID"]; - Node -> API [label = "Heartbeat (UUID)"]; - API -> Conductor [label = "Heartbeat"]; - Conductor -> Node [label = "Send IPA a command to expose disks via iSCSI"]; - Conductor -> Node [label = "iSCSI attach"]; - Conductor -> Node [label = "Copies user image and configdrive, if present"]; - Conductor -> Node [label = "iSCSI detach"]; - Conductor -> Conductor [label = "Delete instance\nimage from cache"]; - Conductor -> Node [label = "Install boot loader, if requested"]; - Conductor -> Neutron [label = "Update DHCP boot options"]; - Conductor -> Conductor [label = "Prepare PXE\nenvironment for\ninstance image"]; - Conductor -> Node [label = "Set boot device either to PXE or to disk"]; - Conductor -> Node [label = "Collect ramdisk logs"]; - Conductor -> Node [label = "POWER OFF"]; - Conductor -> Neutron [label = "Detach provisioning network\nfrom port(s)"]; - Conductor -> Neutron [label = "Bind tenant port"]; - Conductor -> Node [label = "POWER ON"]; - Conductor -> Conductor [label = "Mark node as\nACTIVE"]; - } - -(From a `talk`_ and `slides`_) - .. _talk: https://www.openstack.org/summit/vancouver-2015/summit-videos/presentation/isn-and-039t-it-ironic-the-bare-metal-cloud .. _slides: http://www.slideshare.net/devananda1/isnt-it-ironic-managing-a-bare-metal-cloud-osl-tes-2015 diff --git a/etc/ironic/rootwrap.d/ironic-utils.filters b/etc/ironic/rootwrap.d/ironic-utils.filters index ad7de6f0de..e1ac730d8b 100644 --- a/etc/ironic/rootwrap.d/ironic-utils.filters +++ b/etc/ironic/rootwrap.d/ironic-utils.filters @@ -2,9 +2,6 @@ # This file should be owned by (and only-writable by) the root user [Filters] -# ironic/drivers/modules/deploy_utils.py -iscsiadm: CommandFilter, iscsiadm, root - # ironic/common/utils.py mount: CommandFilter, mount, root umount: CommandFilter, umount, root diff --git a/ironic/cmd/dbsync.py b/ironic/cmd/dbsync.py index 071d06a540..81524aa969 100644 --- a/ironic/cmd/dbsync.py +++ b/ironic/cmd/dbsync.py @@ -64,8 +64,6 @@ dbapi = db_api.get_instance() # object, in case it is lazy loaded. The attribute will be accessed when needed # by doing getattr on the object ONLINE_MIGRATIONS = ( - # Added in Victoria, remove when removing iscsi deploy. - (dbapi, 'migrate_from_iscsi_deploy'), # NOTE(rloo): Don't remove this; it should always be last (dbapi, 'update_to_latest_versions'), ) diff --git a/ironic/conf/__init__.py b/ironic/conf/__init__.py index 2d6fab8db0..4e4b7bf7ad 100644 --- a/ironic/conf/__init__.py +++ b/ironic/conf/__init__.py @@ -35,7 +35,6 @@ from ironic.conf import ilo from ironic.conf import inspector from ironic.conf import ipmi from ironic.conf import irmc -from ironic.conf import iscsi from ironic.conf import metrics from ironic.conf import metrics_statsd from ironic.conf import molds @@ -51,6 +50,7 @@ from ironic.conf import xclarity CONF = cfg.CONF agent.register_opts(CONF) +anaconda.register_opts(CONF) ansible.register_opts(CONF) api.register_opts(CONF) audit.register_opts(CONF) @@ -69,8 +69,6 @@ ilo.register_opts(CONF) inspector.register_opts(CONF) ipmi.register_opts(CONF) irmc.register_opts(CONF) -iscsi.register_opts(CONF) -anaconda.register_opts(CONF) metrics.register_opts(CONF) metrics_statsd.register_opts(CONF) molds.register_opts(CONF) diff --git a/ironic/conf/iscsi.py b/ironic/conf/iscsi.py deleted file mode 100644 index 5e977e72c4..0000000000 --- a/ironic/conf/iscsi.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2016 Intel Corporation -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_config import cfg - -from ironic.common.i18n import _ - -opts = [ - cfg.PortOpt('portal_port', - default=3260, - mutable=True, - help=_('The port number on which the iSCSI portal listens ' - 'for incoming connections.')), - cfg.StrOpt('conv_flags', - mutable=True, - help=_('Flags that need to be sent to the dd command, ' - 'to control the conversion of the original file ' - 'when copying to the host. It can contain several ' - 'options separated by commas.')), - cfg.IntOpt('verify_attempts', - default=3, - min=1, - mutable=True, - help=_('Maximum attempts to verify an iSCSI connection is ' - 'active, sleeping 1 second between attempts. Defaults ' - 'to 3.')), -] - - -def register_opts(conf): - conf.register_opts(opts, group='iscsi') diff --git a/ironic/conf/opts.py b/ironic/conf/opts.py index 97b9c9d447..607dd8510c 100644 --- a/ironic/conf/opts.py +++ b/ironic/conf/opts.py @@ -34,7 +34,6 @@ _opts = [ ('inspector', ironic.conf.inspector.list_opts()), ('ipmi', ironic.conf.ipmi.opts), ('irmc', ironic.conf.irmc.opts), - ('iscsi', ironic.conf.iscsi.opts), ('anaconda', ironic.conf.anaconda.opts), ('metrics', ironic.conf.metrics.opts), ('metrics_statsd', ironic.conf.metrics_statsd.opts), diff --git a/ironic/db/api.py b/ironic/db/api.py index d44ea73ef2..da08384785 100644 --- a/ironic/db/api.py +++ b/ironic/db/api.py @@ -973,18 +973,6 @@ class Connection(object, metaclass=abc.ABCMeta): of migrated objects. """ - @abc.abstractmethod - def migrate_from_iscsi_deploy(self, context, max_count): - """Tries to migrate away from the iscsi deploy interface. - - :param context: the admin context - :param max_count: The maximum number of objects to migrate. Must be - >= 0. If zero, all the objects will be migrated. - :returns: A 2-tuple, 1. the total number of objects that need to be - migrated (at the beginning of this call) and 2. the number - of migrated objects. - """ - @abc.abstractmethod def set_node_traits(self, node_id, traits, version): """Replace all of the node traits with specified list of traits. diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py index 2029d89425..702755e972 100644 --- a/ironic/db/sqlalchemy/api.py +++ b/ironic/db/sqlalchemy/api.py @@ -1578,59 +1578,6 @@ class Connection(api.Connection): return total_to_migrate, total_migrated - @oslo_db_api.retry_on_deadlock - def migrate_from_iscsi_deploy(self, context, max_count, force=False): - """Tries to migrate away from the iscsi deploy interface. - - :param context: the admin context - :param max_count: The maximum number of objects to migrate. Must be - >= 0. If zero, all the objects will be migrated. - :returns: A 2-tuple, 1. the total number of objects that need to be - migrated (at the beginning of this call) and 2. the number - of migrated objects. - """ - # TODO(dtantsur): maybe change to force=True by default in W? - if not force: - if 'direct' not in CONF.enabled_deploy_interfaces: - LOG.warning('The direct deploy interface is not enabled, will ' - 'not migrate nodes to it. Run with --option ' - 'force=true to override.') - return 0, 0 - - if CONF.default_deploy_interface == 'iscsi': - LOG.warning('The iscsi deploy interface is the default, will ' - 'not migrate nodes away from it. Run with ' - '--option force=true to override.') - return 0, 0 - - if CONF.agent.image_download_source == 'swift': - LOG.warning('The direct deploy interface is using swift, will ' - 'not migrate nodes to it. Run with --option ' - 'force=true to override.') - return 0, 0 - - total_to_migrate = (model_query(models.Node) - .filter_by(deploy_interface='iscsi') - .count()) - if not total_to_migrate: - return 0, 0 - - max_to_migrate = max_count or total_to_migrate - - with _session_for_write(): - query = (model_query(models.Node.id) - .filter_by(deploy_interface='iscsi') - .slice(0, max_to_migrate)) - ids = [row[0] for row in query] - - num_migrated = (model_query(models.Node) - .filter_by(deploy_interface='iscsi') - .filter(models.Node.id.in_(ids)) - .update({'deploy_interface': 'direct'}, - synchronize_session=False)) - - return total_to_migrate, num_migrated - @staticmethod def _verify_max_traits_per_node(node_id, num_traits): """Verify that an operation would not exceed the per-node trait limit. diff --git a/ironic/drivers/generic.py b/ironic/drivers/generic.py index fddd2242c1..e0cc132357 100644 --- a/ironic/drivers/generic.py +++ b/ironic/drivers/generic.py @@ -23,7 +23,6 @@ from ironic.drivers.modules.ansible import deploy as ansible_deploy from ironic.drivers.modules import fake from ironic.drivers.modules import inspector from ironic.drivers.modules import ipxe -from ironic.drivers.modules import iscsi_deploy from ironic.drivers.modules.network import flat as flat_net from ironic.drivers.modules.network import neutron from ironic.drivers.modules.network import noop as noop_net @@ -49,9 +48,9 @@ class GenericHardware(hardware_type.AbstractHardwareType): @property def supported_deploy_interfaces(self): """List of supported deploy interfaces.""" - return [agent.AgentDeploy, iscsi_deploy.ISCSIDeploy, - ansible_deploy.AnsibleDeploy, pxe.PXERamdiskDeploy, - pxe.PXEAnacondaDeploy, agent.CustomAgentDeploy] + return [agent.AgentDeploy, ansible_deploy.AnsibleDeploy, + pxe.PXERamdiskDeploy, pxe.PXEAnacondaDeploy, + agent.CustomAgentDeploy] @property def supported_inspect_interfaces(self): diff --git a/ironic/drivers/modules/agent_client.py b/ironic/drivers/modules/agent_client.py index 5ca93a3b1d..c348559766 100644 --- a/ironic/drivers/modules/agent_client.py +++ b/ironic/drivers/modules/agent_client.py @@ -342,34 +342,6 @@ class AgentClient(object): {'cmd': method, 'node': node.uuid}) return None - @METRICS.timer('AgentClient.start_iscsi_target') - def start_iscsi_target(self, node, iqn, - portal_port=DEFAULT_IPA_PORTAL_PORT, - wipe_disk_metadata=False): - """Expose the node's disk as an ISCSI target. - - :param node: an Ironic node object - :param iqn: iSCSI target IQN - :param portal_port: iSCSI portal port - :param wipe_disk_metadata: True if the agent should wipe first the - disk magic strings like the partition - table, RAID or filesystem signature. - :raises: IronicException when failed to issue the request or there was - a malformed response from the agent. - :raises: AgentAPIError when agent failed to execute specified command. - :raises: AgentInProgress when the command fails to execute as the agent - is presently executing the prior command. - :returns: A dict containing command response from agent. - See :func:`get_commands_status` for a command result sample. - """ - params = {'iqn': iqn, - 'portal_port': portal_port, - 'wipe_disk_metadata': wipe_disk_metadata} - return self._command(node=node, - method='iscsi.start_iscsi_target', - params=params, - wait=True) - @METRICS.timer('AgentClient.install_bootloader') def install_bootloader(self, node, root_uuid, target_boot_mode, efi_system_part_uuid=None, diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py deleted file mode 100644 index 1f532906b7..0000000000 --- a/ironic/drivers/modules/iscsi_deploy.py +++ /dev/null @@ -1,813 +0,0 @@ -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import contextlib -import glob -import os -import time -from urllib import parse as urlparse - -from ironic_lib import disk_utils -from ironic_lib import metrics_utils -from oslo_concurrency import processutils -from oslo_log import log as logging -from oslo_utils import excutils - -from ironic.common import exception -from ironic.common.i18n import _ -from ironic.common import states -from ironic.common import utils -from ironic.conductor import task_manager -from ironic.conductor import utils as manager_utils -from ironic.conf import CONF -from ironic.drivers import base -from ironic.drivers.modules import agent_base -from ironic.drivers.modules import boot_mode_utils -from ironic.drivers.modules import deploy_utils - -LOG = logging.getLogger(__name__) - -METRICS = metrics_utils.get_metrics_logger(__name__) - -DISK_LAYOUT_PARAMS = ('root_gb', 'swap_mb', 'ephemeral_gb') - - -def _save_disk_layout(node, i_info): - """Saves the disk layout. - - The disk layout used for deployment of the node, is saved. - - :param node: the node of interest - :param i_info: instance information (a dictionary) for the node, containing - disk layout information - """ - driver_internal_info = node.driver_internal_info - driver_internal_info['instance'] = {} - - for param in DISK_LAYOUT_PARAMS: - driver_internal_info['instance'][param] = i_info[param] - - node.driver_internal_info = driver_internal_info - node.save() - - -def discovery(portal_address, portal_port): - """Do iSCSI discovery on portal.""" - utils.execute('iscsiadm', - '-m', 'discovery', - '-t', 'st', - '-p', '%s:%s' % (utils.wrap_ipv6(portal_address), - portal_port), - run_as_root=True, - attempts=5, - delay_on_retry=True) - - -def login_iscsi(portal_address, portal_port, target_iqn): - """Login to an iSCSI target.""" - utils.execute('iscsiadm', - '-m', 'node', - '-p', '%s:%s' % (utils.wrap_ipv6(portal_address), - portal_port), - '-T', target_iqn, - '--login', - run_as_root=True, - attempts=5, - delay_on_retry=True) - - error_occurred = False - try: - # Ensure the login complete - verify_iscsi_connection(target_iqn) - # force iSCSI initiator to re-read luns - force_iscsi_lun_update(target_iqn) - # ensure file system sees the block device - check_file_system_for_iscsi_device(portal_address, - portal_port, - target_iqn) - except (exception.InstanceDeployFailure, - processutils.ProcessExecutionError) as e: - with excutils.save_and_reraise_exception(): - error_occurred = True - LOG.error("Failed to login to an iSCSI target due to %s", e) - finally: - if error_occurred: - try: - logout_iscsi(portal_address, portal_port, target_iqn) - delete_iscsi(portal_address, portal_port, target_iqn) - except processutils.ProcessExecutionError as e: - LOG.warning("An error occurred when trying to cleanup " - "failed ISCSI session error %s", e) - - -def check_file_system_for_iscsi_device(portal_address, - portal_port, - target_iqn): - """Ensure the file system sees the iSCSI block device.""" - check_dir = "/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-1" % (portal_address, - portal_port, - target_iqn) - total_checks = CONF.iscsi.verify_attempts - for attempt in range(total_checks): - if os.path.exists(check_dir): - break - time.sleep(1) - if LOG.isEnabledFor(logging.DEBUG): - existing_devs = ', '.join(glob.iglob('/dev/disk/by-path/*iscsi*')) - LOG.debug("iSCSI connection not seen by file system. Rechecking. " - "Attempt %(attempt)d out of %(total)d. Available iSCSI " - "devices: %(devs)s.", - {"attempt": attempt + 1, - "total": total_checks, - "devs": existing_devs}) - else: - msg = _("iSCSI connection was not seen by the file system after " - "attempting to verify %d times.") % total_checks - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - -def verify_iscsi_connection(target_iqn): - """Verify iscsi connection.""" - LOG.debug("Checking for iSCSI target to become active.") - - total_checks = CONF.iscsi.verify_attempts - for attempt in range(total_checks): - out, _err = utils.execute('iscsiadm', - '-m', 'node', - '-S', - run_as_root=True) - if target_iqn in out: - break - time.sleep(1) - LOG.debug("iSCSI connection not active. Rechecking. Attempt " - "%(attempt)d out of %(total)d", - {"attempt": attempt + 1, "total": total_checks}) - else: - msg = _("iSCSI connection did not become active after attempting to " - "verify %d times.") % total_checks - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - -def force_iscsi_lun_update(target_iqn): - """force iSCSI initiator to re-read luns.""" - LOG.debug("Re-reading iSCSI luns.") - utils.execute('iscsiadm', - '-m', 'node', - '-T', target_iqn, - '-R', - run_as_root=True) - - -def logout_iscsi(portal_address, portal_port, target_iqn): - """Logout from an iSCSI target.""" - utils.execute('iscsiadm', - '-m', 'node', - '-p', '%s:%s' % (utils.wrap_ipv6(portal_address), - portal_port), - '-T', target_iqn, - '--logout', - run_as_root=True, - attempts=5, - delay_on_retry=True) - - -def delete_iscsi(portal_address, portal_port, target_iqn): - """Delete the iSCSI target.""" - # Retry delete until it succeeds (exit code 0) or until there is - # no longer a target to delete (exit code 21). - utils.execute('iscsiadm', - '-m', 'node', - '-p', '%s:%s' % (utils.wrap_ipv6(portal_address), - portal_port), - '-T', target_iqn, - '-o', 'delete', - run_as_root=True, - check_exit_code=[0, 21], - attempts=5, - delay_on_retry=True) - - -@contextlib.contextmanager -def _iscsi_setup_and_handle_errors(address, port, iqn, lun): - """Function that yields an iSCSI target device to work on. - - :param address: The iSCSI IP address. - :param port: The iSCSI port number. - :param iqn: The iSCSI qualified name. - :param lun: The iSCSI logical unit number. - """ - dev = ("/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-%s" - % (address, port, iqn, lun)) - discovery(address, port) - login_iscsi(address, port, iqn) - if not disk_utils.is_block_device(dev): - raise exception.InstanceDeployFailure(_("Parent device '%s' not found") - % dev) - try: - yield dev - except processutils.ProcessExecutionError as err: - with excutils.save_and_reraise_exception(): - LOG.error("Deploy to address %s failed.", address) - LOG.error("Command: %s", err.cmd) - LOG.error("StdOut: %r", err.stdout) - LOG.error("StdErr: %r", err.stderr) - except exception.InstanceDeployFailure as e: - with excutils.save_and_reraise_exception(): - LOG.error("Deploy to address %s failed.", address) - LOG.error(e) - finally: - logout_iscsi(address, port, iqn) - delete_iscsi(address, port, iqn) - - -def deploy_partition_image( - address, port, iqn, lun, image_path, - root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, - preserve_ephemeral=False, configdrive=None, - boot_option=None, boot_mode="bios", disk_label=None, - cpu_arch=""): - """All-in-one function to deploy a partition image to a node. - - :param address: The iSCSI IP address. - :param port: The iSCSI port number. - :param iqn: The iSCSI qualified name. - :param lun: The iSCSI logical unit number. - :param image_path: Path for the instance's disk image. - :param root_mb: Size of the root partition in megabytes. - :param swap_mb: Size of the swap partition in megabytes. - :param ephemeral_mb: Size of the ephemeral partition in megabytes. If 0, - no ephemeral partition will be created. - :param ephemeral_format: The type of file system to format the ephemeral - partition. - :param node_uuid: node's uuid. Used for logging. - :param preserve_ephemeral: If True, no filesystem is written to the - ephemeral block device, preserving whatever - content it had (if the partition table has - not changed). - :param configdrive: Optional. Base64 encoded Gzipped configdrive content - or configdrive HTTP URL. - :param boot_option: Can be "local" or "netboot". - "netboot" by default. - :param boot_mode: Can be "bios" or "uefi". "bios" by default. - :param disk_label: The disk label to be used when creating the - partition table. Valid values are: "msdos", - "gpt" or None; If None ironic will figure it - out according to the boot_mode parameter. - :param cpu_arch: Architecture of the node being deployed to. - :raises: InstanceDeployFailure if image virtual size is bigger than root - partition size. - :returns: a dictionary containing the following keys: - 'root uuid': UUID of root partition - 'efi system partition uuid': UUID of the uefi system partition - (if boot mode is uefi). - NOTE: If key exists but value is None, it means partition doesn't - exist. - """ - # NOTE(dtantsur): CONF.default_boot_option is mutable, don't use it in - # the function signature! - boot_option = boot_option or deploy_utils.get_default_boot_option() - image_mb = disk_utils.get_image_mb(image_path) - if image_mb > root_mb: - msg = (_('Root partition is too small for requested image. Image ' - 'virtual size: %(image_mb)d MB, Root size: %(root_mb)d MB') - % {'image_mb': image_mb, 'root_mb': root_mb}) - raise exception.InstanceDeployFailure(msg) - - with _iscsi_setup_and_handle_errors(address, port, iqn, lun) as dev: - uuid_dict_returned = disk_utils.work_on_disk( - dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, image_path, - node_uuid, preserve_ephemeral=preserve_ephemeral, - configdrive=configdrive, boot_option=boot_option, - boot_mode=boot_mode, disk_label=disk_label, cpu_arch=cpu_arch) - - return uuid_dict_returned - - -def deploy_disk_image(address, port, iqn, lun, - image_path, node_uuid, configdrive=None, - conv_flags=None): - """All-in-one function to deploy a whole disk image to a node. - - :param address: The iSCSI IP address. - :param port: The iSCSI port number. - :param iqn: The iSCSI qualified name. - :param lun: The iSCSI logical unit number. - :param image_path: Path for the instance's disk image. - :param node_uuid: node's uuid. - :param configdrive: Optional. Base64 encoded Gzipped configdrive content - or configdrive HTTP URL. - :param conv_flags: Optional. Add a flag that will modify the behaviour of - the image copy to disk. - :returns: a dictionary containing the key 'disk identifier' to identify - the disk which was used for deployment. - """ - with _iscsi_setup_and_handle_errors(address, port, iqn, - lun) as dev: - disk_utils.populate_image(image_path, dev, conv_flags=conv_flags) - - if configdrive: - disk_utils.create_config_drive_partition(node_uuid, dev, - configdrive) - - disk_identifier = disk_utils.get_disk_identifier(dev) - - return {'disk identifier': disk_identifier} - - -@METRICS.timer('check_image_size') -def check_image_size(task): - """Check if the requested image is larger than the root partition size. - - Does nothing for whole-disk images. - - :param task: a TaskManager instance containing the node to act on. - :raises: InstanceDeployFailure if size of the image is greater than root - partition. - """ - if task.node.driver_internal_info['is_whole_disk_image']: - # The root partition is already created and populated, no use - # validating its size - return - - i_info = deploy_utils.parse_instance_info(task.node) - image_path = deploy_utils._get_image_file_path(task.node.uuid) - image_mb = disk_utils.get_image_mb(image_path) - root_mb = 1024 * int(i_info['root_gb']) - if image_mb > root_mb: - msg = (_('Root partition is too small for requested image. Image ' - 'virtual size: %(image_mb)d MB, Root size: %(root_mb)d MB') - % {'image_mb': image_mb, 'root_mb': root_mb}) - raise exception.InstanceDeployFailure(msg) - - -@METRICS.timer('get_deploy_info') -def get_deploy_info(node, address, iqn, port=None, lun='1', conv_flags=None): - """Returns the information required for doing iSCSI deploy in a dictionary. - - :param node: ironic node object - :param address: iSCSI address - :param iqn: iSCSI iqn for the target disk - :param port: iSCSI port, defaults to one specified in the configuration - :param lun: iSCSI lun, defaults to '1' - :param conv_flags: flag that will modify the behaviour of the image copy - to disk. - :raises: MissingParameterValue, if some required parameters were not - passed. - :raises: InvalidParameterValue, if any of the parameters have invalid - value. - """ - i_info = deploy_utils.parse_instance_info(node) - - params = { - 'address': address, - 'port': port or CONF.iscsi.portal_port, - 'iqn': iqn, - 'lun': lun, - 'image_path': deploy_utils._get_image_file_path(node.uuid), - 'node_uuid': node.uuid} - - is_whole_disk_image = node.driver_internal_info['is_whole_disk_image'] - if not is_whole_disk_image: - params.update({'root_mb': i_info['root_mb'], - 'swap_mb': i_info['swap_mb'], - 'ephemeral_mb': i_info['ephemeral_mb'], - 'preserve_ephemeral': i_info['preserve_ephemeral'], - 'boot_option': deploy_utils.get_boot_option(node), - 'boot_mode': boot_mode_utils.get_boot_mode(node)}) - - cpu_arch = node.properties.get('cpu_arch') - if cpu_arch is not None: - params['cpu_arch'] = cpu_arch - - # Append disk label if specified - disk_label = deploy_utils.get_disk_label(node) - if disk_label is not None: - params['disk_label'] = disk_label - - missing = [key for key in params if params[key] is None] - if missing: - raise exception.MissingParameterValue( - _("Parameters %s were not passed to ironic" - " for deploy.") % missing) - - # configdrive is nullable - params['configdrive'] = i_info.get('configdrive') - if is_whole_disk_image: - return params - - if conv_flags: - params['conv_flags'] = conv_flags - - # ephemeral_format is nullable - params['ephemeral_format'] = i_info.get('ephemeral_format') - - return params - - -@METRICS.timer('continue_deploy') -def continue_deploy(task, **kwargs): - """Resume a deployment upon getting POST data from deploy ramdisk. - - This method raises no exceptions because it is intended to be - invoked asynchronously as a callback from the deploy ramdisk. - - :param task: a TaskManager instance containing the node to act on. - :param kwargs: the kwargs to be passed to deploy. - :raises: InvalidState if the event is not allowed by the associated - state machine. - :returns: a dictionary containing the following keys: - - For partition image: - - * 'root uuid': UUID of root partition - * 'efi system partition uuid': UUID of the uefi system partition - (if boot mode is uefi). - - .. note:: If key exists but value is None, it means partition - doesn't exist. - - For whole disk image: - - * 'disk identifier': ID of the disk to which image was deployed. - """ - node = task.node - - params = get_deploy_info(node, **kwargs) - - def _fail_deploy(task, msg, raise_exception=True): - """Fail the deploy after logging and setting error states.""" - if isinstance(msg, Exception): - msg = (_('Deploy failed for instance %(instance)s. ' - 'Error: %(error)s') % - {'instance': node.instance_uuid, 'error': msg}) - deploy_utils.set_failed_state(task, msg) - deploy_utils.destroy_images(task.node.uuid) - if raise_exception: - raise exception.InstanceDeployFailure(msg) - - # NOTE(lucasagomes): Let's make sure we don't log the full content - # of the config drive here because it can be up to 64MB in size, - # so instead let's log "***" in case config drive is enabled. - if LOG.isEnabledFor(logging.logging.DEBUG): - log_params = { - k: params[k] if k != 'configdrive' else '***' - for k in params - } - LOG.debug('Continuing deployment for node %(node)s, params %(params)s', - {'node': node.uuid, 'params': log_params}) - - uuid_dict_returned = {} - try: - if node.driver_internal_info['is_whole_disk_image']: - uuid_dict_returned = deploy_disk_image(**params) - else: - uuid_dict_returned = deploy_partition_image(**params) - except exception.IronicException as e: - with excutils.save_and_reraise_exception(): - LOG.error('Deploy of instance %(instance)s on node %(node)s ' - 'failed: %(error)s', {'instance': node.instance_uuid, - 'node': node.uuid, 'error': e}) - _fail_deploy(task, e, raise_exception=False) - except Exception as e: - LOG.exception('Deploy of instance %(instance)s on node %(node)s ' - 'failed with exception', - {'instance': node.instance_uuid, 'node': node.uuid}) - _fail_deploy(task, e) - - root_uuid_or_disk_id = uuid_dict_returned.get( - 'root uuid', uuid_dict_returned.get('disk identifier')) - if not root_uuid_or_disk_id: - msg = (_("Couldn't determine the UUID of the root " - "partition or the disk identifier after deploying " - "node %s") % node.uuid) - LOG.error(msg) - _fail_deploy(task, msg) - - if params.get('preserve_ephemeral', False): - # Save disk layout information, to check that they are unchanged - # for any future rebuilds - _save_disk_layout(node, deploy_utils.parse_instance_info(node)) - - deploy_utils.destroy_images(node.uuid) - return uuid_dict_returned - - -@METRICS.timer('do_agent_iscsi_deploy') -def do_agent_iscsi_deploy(task, agent_client): - """Method invoked when deployed with the agent ramdisk. - - This method is invoked by drivers for doing iSCSI deploy - using agent ramdisk. This method assumes that the agent - is booted up on the node and is heartbeating. - - :param task: a TaskManager object containing the node. - :param agent_client: an instance of agent_client.AgentClient - which will be used during iscsi deploy - (for exposing node's target disk via iSCSI, - for install boot loader, etc). - :returns: a dictionary containing the following keys: - - For partition image: - - * 'root uuid': UUID of root partition - * 'efi system partition uuid': UUID of the uefi system partition - (if boot mode is uefi). - - .. note:: If key exists but value is None, it means partition - doesn't exist. - - For whole disk image: - - * 'disk identifier': ID of the disk to which image was deployed. - :raises: InstanceDeployFailure if it encounters some error - during the deploy. - """ - node = task.node - i_info = deploy_utils.parse_instance_info(node) - wipe_disk_metadata = not i_info['preserve_ephemeral'] - - iqn = 'iqn.2008-10.org.openstack:%s' % node.uuid - portal_port = CONF.iscsi.portal_port - conv_flags = CONF.iscsi.conv_flags - result = agent_client.start_iscsi_target( - node, iqn, - portal_port, - wipe_disk_metadata=wipe_disk_metadata) - if result['command_status'] == 'FAILED': - msg = (_("Failed to start the iSCSI target to deploy the " - "node %(node)s. Error: %(error)s") % - {'node': node.uuid, 'error': result['command_error']}) - deploy_utils.set_failed_state(task, msg) - raise exception.InstanceDeployFailure(reason=msg) - - address = urlparse.urlparse(node.driver_internal_info['agent_url']) - address = address.hostname - - uuid_dict_returned = continue_deploy(task, iqn=iqn, address=address, - conv_flags=conv_flags) - root_uuid_or_disk_id = uuid_dict_returned.get( - 'root uuid', uuid_dict_returned.get('disk identifier')) - - # TODO(lucasagomes): Move this bit saving the root_uuid to - # continue_deploy() - driver_internal_info = node.driver_internal_info - driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id - node.driver_internal_info = driver_internal_info - node.save() - - return uuid_dict_returned - - -@METRICS.timer('validate') -def validate(task): - """Validates the pre-requisites for iSCSI deploy. - - Validates whether node in the task provided has some ports enrolled. - This method validates whether conductor url is available either from CONF - file or from keystone. - - :param task: a TaskManager instance containing the node to act on. - :raises: InvalidParameterValue if the URL of the Ironic API service is not - configured in config file and is not accessible via Keystone - catalog. - :raises: MissingParameterValue if no ports are enrolled for the given node. - """ - # TODO(lucasagomes): Validate the format of the URL - deploy_utils.get_ironic_api_url() - # Validate the root device hints - deploy_utils.get_root_device_for_deploy(task.node) - deploy_utils.parse_instance_info(task.node) - - -class ISCSIDeploy(agent_base.AgentDeployMixin, agent_base.AgentBaseMixin, - base.DeployInterface): - """iSCSI Deploy Interface for deploy-related actions.""" - - has_decomposed_deploy_steps = True - - supported = False - - def get_properties(self): - return agent_base.VENDOR_PROPERTIES - - @METRICS.timer('ISCSIDeploy.validate') - def validate(self, task): - """Validate the deployment information for the task's node. - - :param task: a TaskManager instance containing the node to act on. - :raises: InvalidParameterValue. - :raises: MissingParameterValue - """ - task.driver.boot.validate(task) - node = task.node - - # Check the boot_mode, boot_option and disk_label capabilities values. - deploy_utils.validate_capabilities(node) - - # Edit early if we are not writing a volume as the validate - # tasks evaluate root device hints. - if not task.driver.storage.should_write_image(task): - LOG.debug('Skipping complete deployment interface validation ' - 'for node %s as it is set to boot from a remote ' - 'volume.', node.uuid) - return - - # TODO(rameshg87): iscsi_ilo driver used to call this function. Remove - # and copy-paste it's contents here. - validate(task) - - @METRICS.timer('ISCSIDeploy.deploy') - @base.deploy_step(priority=100) - @task_manager.require_exclusive_lock - def deploy(self, task): - """Start deployment of the task's node. - - Fetches instance image, updates the DHCP port options for next boot, - and issues a reboot request to the power driver. - This causes the node to boot into the deployment ramdisk and triggers - the next phase of PXE-based deployment via agent heartbeats. - - :param task: a TaskManager instance containing the node to act on. - :returns: deploy state DEPLOYWAIT. - """ - node = task.node - if manager_utils.is_fast_track(task): - # NOTE(mgoddard): For fast track we can mostly skip this step and - # proceed to the next step (i.e. write_image). - LOG.debug('Performing a fast track deployment for %(node)s.', - {'node': task.node.uuid}) - deploy_utils.cache_instance_image(task.context, node) - check_image_size(task) - # NOTE(dtantsur): while the node is up and heartbeating, we don't - # necessary have the deploy steps cached. Force a refresh here. - self.refresh_steps(task, 'deploy') - elif task.driver.storage.should_write_image(task): - # Standard deploy process - deploy_utils.cache_instance_image(task.context, node) - check_image_size(task) - # Check if the driver has already performed a reboot in a previous - # deploy step. - if not task.node.driver_internal_info.get('deployment_reboot', - False): - manager_utils.node_power_action(task, states.REBOOT) - info = task.node.driver_internal_info - info.pop('deployment_reboot', None) - info.pop('deployment_uuids', None) - task.node.driver_internal_info = info - task.node.save() - - return states.DEPLOYWAIT - - @METRICS.timer('ISCSIDeploy.write_image') - @base.deploy_step(priority=80) - @task_manager.require_exclusive_lock - def write_image(self, task): - """Method invoked when deployed using iSCSI. - - This method is invoked during a heartbeat from an agent when - the node is in wait-call-back state. This deploys the image on - the node and then configures the node to boot according to the - desired boot option (netboot or localboot). - - :param task: a TaskManager object containing the node. - :param kwargs: the kwargs passed from the heartbeat method. - :raises: InstanceDeployFailure, if it encounters some error during - the deploy. - """ - if not task.driver.storage.should_write_image(task): - LOG.debug('Skipping write_image for node %s', task.node.uuid) - return - - node = task.node - LOG.debug('Continuing the deployment on node %s', node.uuid) - if utils.is_memory_insufficent(): - # Insufficent memory, but we can just let the agent heartbeat - # again in order to initiate deployment when the situation has - # changed. - LOG.warning('Insufficent memory to write image for node ' - '%(node)s. Skipping until next heartbeat.', - {'node': node.uuid}) - info = node.driver_internal_info - info['skip_current_deploy_step'] = False - node.driver_internal_info = info - node.last_error = "Deploy delayed due to insufficent memory" - node.save() - return states.DEPLOYWAIT - uuid_dict_returned = do_agent_iscsi_deploy(task, self._client) - utils.set_node_nested_field(node, 'driver_internal_info', - 'deployment_uuids', uuid_dict_returned) - node.save() - - @METRICS.timer('ISCSIDeploy.prepare_instance_boot') - @base.deploy_step(priority=60) - def prepare_instance_boot(self, task): - if not task.driver.storage.should_write_image(task): - task.driver.boot.prepare_instance(task) - return - - node = task.node - try: - uuid_dict_returned = node.driver_internal_info['deployment_uuids'] - except KeyError: - raise exception.InstanceDeployFailure( - _('Invalid internal state: the write_image deploy step has ' - 'not been called before prepare_instance_boot')) - root_uuid = uuid_dict_returned.get('root uuid') - efi_sys_uuid = uuid_dict_returned.get('efi system partition uuid') - prep_boot_part_uuid = uuid_dict_returned.get( - 'PrEP Boot partition uuid') - - self.prepare_instance_to_boot(task, root_uuid, efi_sys_uuid, - prep_boot_part_uuid=prep_boot_part_uuid) - - @METRICS.timer('ISCSIDeploy.prepare') - @task_manager.require_exclusive_lock - def prepare(self, task): - """Prepare the deployment environment for this task's node. - - Generates the TFTP configuration for PXE-booting both the deployment - and user images, fetches the TFTP image from Glance and add it to the - local cache. - - :param task: a TaskManager instance containing the node to act on. - :raises: NetworkError: if the previous cleaning ports cannot be removed - or if new cleaning ports cannot be created. - :raises: InvalidParameterValue when the wrong power state is specified - or the wrong driver info is specified for power management. - :raises: StorageError If the storage driver is unable to attach the - configured volumes. - :raises: other exceptions by the node's power driver if something - wrong occurred during the power action. - :raises: any boot interface's prepare_ramdisk exceptions. - """ - node = task.node - deploy_utils.populate_storage_driver_internal_info(task) - if node.provision_state in [states.ACTIVE, states.ADOPTING]: - task.driver.boot.prepare_instance(task) - else: - if node.provision_state == states.DEPLOYING: - fast_track_deploy = manager_utils.is_fast_track(task) - if fast_track_deploy: - # The agent has already recently checked in and we are - # configured to take that as an indicator that we can - # skip ahead. - LOG.debug('The agent for node %(node)s has recently ' - 'checked in, and the node power will remain ' - 'unmodified.', - {'node': task.node.uuid}) - else: - # Adding the node to provisioning network so that the dhcp - # options get added for the provisioning port. - manager_utils.node_power_action(task, states.POWER_OFF) - # NOTE(vdrok): in case of rebuild, we have tenant network - # already configured, unbind tenant ports if present - if task.driver.storage.should_write_image(task): - if not fast_track_deploy: - power_state_to_restore = ( - manager_utils.power_on_node_if_needed(task)) - task.driver.network.unconfigure_tenant_networks(task) - task.driver.network.add_provisioning_network(task) - if not fast_track_deploy: - manager_utils.restore_power_state_if_needed( - task, power_state_to_restore) - task.driver.storage.attach_volumes(task) - if (not task.driver.storage.should_write_image(task) - or fast_track_deploy): - # We have nothing else to do as this is handled in the - # backend storage system, and we can return to the caller - # as we do not need to boot the agent to deploy. - # Alternatively, we are in a fast track deployment - # and have nothing else to do. - return - - deploy_opts = deploy_utils.build_agent_options(node) - task.driver.boot.prepare_ramdisk(task, deploy_opts) - - @METRICS.timer('ISCSIDeploy.clean_up') - def clean_up(self, task): - """Clean up the deployment environment for the task's node. - - Unlinks TFTP and instance images and triggers image cache cleanup. - Removes the TFTP configuration files for this node. - - :param task: a TaskManager instance containing the node to act on. - """ - deploy_utils.destroy_images(task.node.uuid) - super(ISCSIDeploy, self).clean_up(task) - if utils.pop_node_nested_field(task.node, 'driver_internal_info', - 'deployment_uuids'): - task.node.save() diff --git a/ironic/objects/conductor.py b/ironic/objects/conductor.py index ad381359b7..307e218c5f 100644 --- a/ironic/objects/conductor.py +++ b/ironic/objects/conductor.py @@ -163,7 +163,7 @@ class Conductor(base.IronicObject, object_base.VersionedObjectDictCompat): be a dictionary conaining "hardware_type", "interface_type", "interface_name" and "default", e.g. {'hardware_type': 'hardware-type', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True} + 'interface_name': 'direct', 'default': True} """ self.dbapi.register_conductor_hardware_interfaces(self.id, interfaces) diff --git a/ironic/tests/base.py b/ironic/tests/base.py index f5000d0be8..9952ec18ea 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(['iscsi', 'direct', 'anaconda']) + values.extend(['direct', 'anaconda']) elif iface == 'boot': values.append('pxe') elif iface == 'storage': diff --git a/ironic/tests/unit/api/controllers/v1/test_driver.py b/ironic/tests/unit/api/controllers/v1/test_driver.py index 781b22bf05..b991ac7af9 100644 --- a/ironic/tests/unit/api/controllers/v1/test_driver.py +++ b/ironic/tests/unit/api/controllers/v1/test_driver.py @@ -46,14 +46,14 @@ class TestListDrivers(base.BaseApiTest): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': self.hw1, 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': False}, + 'interface_name': 'ansible', 'default': False}, {'hardware_type': self.hw1, 'interface_type': 'deploy', 'interface_name': 'direct', 'default': True}] ) self.dbapi.register_conductor_hardware_interfaces( c1.id, [{'hardware_type': self.hw2, 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': False}, + 'interface_name': 'ansible', 'default': False}, {'hardware_type': self.hw2, 'interface_type': 'deploy', 'interface_name': 'direct', 'default': True}] ) @@ -124,7 +124,7 @@ class TestListDrivers(base.BaseApiTest): { 'hardware_type': self.hw1, 'interface_type': 'deploy', - 'interface_name': 'iscsi', + 'interface_name': 'ansible', 'default': False, }, { @@ -238,7 +238,7 @@ class TestListDrivers(base.BaseApiTest): { 'hardware_type': self.hw1, 'interface_type': 'deploy', - 'interface_name': 'iscsi', + 'interface_name': 'ansible', 'default': False, }, { diff --git a/ironic/tests/unit/common/test_hash_ring.py b/ironic/tests/unit/common/test_hash_ring.py index 7e05aabff4..9ecf34562d 100644 --- a/ironic/tests/unit/common/test_hash_ring.py +++ b/ironic/tests/unit/common/test_hash_ring.py @@ -61,7 +61,7 @@ class HashRingManagerTestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'hardware-type', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'hardware-type', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -107,7 +107,7 @@ class HashRingManagerTestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c1.id, [{'hardware_type': 'hardware-type', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'hardware-type', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -118,7 +118,7 @@ class HashRingManagerTestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c2.id, [{'hardware_type': 'hardware-type', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'hardware-type', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) diff --git a/ironic/tests/unit/conductor/test_base_manager.py b/ironic/tests/unit/conductor/test_base_manager.py index 0ee6e91600..886ab07eda 100644 --- a/ironic/tests/unit/conductor/test_base_manager.py +++ b/ironic/tests/unit/conductor/test_base_manager.py @@ -411,14 +411,14 @@ class RegisterInterfacesTestCase(mgr_utils.ServiceSetUpMixin, esi_mock.side_effect = [ collections.OrderedDict(( ('management', ['fake', 'noop']), - ('deploy', ['agent', 'iscsi']), + ('deploy', ['direct', 'ansible']), )), collections.OrderedDict(( ('management', ['fake']), - ('deploy', ['agent', 'fake']), + ('deploy', ['direct', 'fake']), )), ] - default_mock.side_effect = ('fake', 'agent', 'fake', 'agent') + default_mock.side_effect = ('fake', 'direct', 'fake', 'direct') expected_calls = [ mock.call( mock.ANY, @@ -432,11 +432,11 @@ class RegisterInterfacesTestCase(mgr_utils.ServiceSetUpMixin, 'default': False}, {'hardware_type': 'fake-hardware', 'interface_type': 'deploy', - 'interface_name': 'agent', + 'interface_name': 'direct', 'default': True}, {'hardware_type': 'fake-hardware', 'interface_type': 'deploy', - 'interface_name': 'iscsi', + 'interface_name': 'ansible', 'default': False}, {'hardware_type': 'manual-management', 'interface_type': 'management', @@ -444,7 +444,7 @@ class RegisterInterfacesTestCase(mgr_utils.ServiceSetUpMixin, 'default': True}, {'hardware_type': 'manual-management', 'interface_type': 'deploy', - 'interface_name': 'agent', + 'interface_name': 'direct', 'default': True}, {'hardware_type': 'manual-management', 'interface_type': 'deploy', @@ -471,7 +471,7 @@ class RegisterInterfacesTestCase(mgr_utils.ServiceSetUpMixin, esi_mock.side_effect = [ collections.OrderedDict(( ('management', ['fake', 'noop']), - ('deploy', ['agent', 'iscsi']), + ('deploy', ['direct', 'ansible']), )), ] default_mock.side_effect = exception.NoValidDefaultForInterface("boo") diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 48c62d6137..8b60e7c640 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -713,7 +713,7 @@ class UpdateNodeTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): IFACE_UPDATE_DICT = { 'boot_interface': UpdateInterfaces('pxe', 'fake'), 'console_interface': UpdateInterfaces('no-console', 'fake'), - 'deploy_interface': UpdateInterfaces('iscsi', 'fake'), + 'deploy_interface': UpdateInterfaces('direct', 'fake'), 'inspect_interface': UpdateInterfaces('no-inspect', 'fake'), 'management_interface': UpdateInterfaces(None, 'fake'), 'network_interface': UpdateInterfaces('noop', 'flat'), @@ -984,7 +984,7 @@ class UpdateNodeTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): deploy_interface='fake', extra={'test': 'one'}) - node.deploy_interface = 'iscsi' + node.deploy_interface = 'direct' exc = self.assertRaises(messaging.rpc.ExpectedException, self.service.update_node, self.context, node) diff --git a/ironic/tests/unit/conductor/test_rpcapi.py b/ironic/tests/unit/conductor/test_rpcapi.py index 5624c6439f..0c7763e837 100644 --- a/ironic/tests/unit/conductor/test_rpcapi.py +++ b/ironic/tests/unit/conductor/test_rpcapi.py @@ -84,7 +84,7 @@ class RPCAPITestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'fake-driver', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'fake-driver', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -101,7 +101,7 @@ class RPCAPITestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'other-driver', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'other-driver', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -124,7 +124,7 @@ class RPCAPITestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'fake-driver', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'fake-driver', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -143,7 +143,7 @@ class RPCAPITestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'fake-driver', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'fake-driver', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -160,7 +160,7 @@ class RPCAPITestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'fake-driver', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'fake-driver', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -183,7 +183,7 @@ class RPCAPITestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'fake-driver', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'fake-driver', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) @@ -198,7 +198,7 @@ class RPCAPITestCase(db_base.DbTestCase): self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'fake-driver', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True}, + 'interface_name': 'ansible', 'default': True}, {'hardware_type': 'fake-driver', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}] ) diff --git a/ironic/tests/unit/db/test_api.py b/ironic/tests/unit/db/test_api.py index 701d1afc48..f855e9c91b 100644 --- a/ironic/tests/unit/db/test_api.py +++ b/ironic/tests/unit/db/test_api.py @@ -237,73 +237,3 @@ class UpdateToLatestVersionsTestCase(base.DbTestCase): for uuid in nodes: node = self.dbapi.get_node_by_uuid(uuid) self.assertEqual(self.node_ver, node.version) - - -class MigrateFromIscsiTestCase(base.DbTestCase): - - def setUp(self): - super(MigrateFromIscsiTestCase, self).setUp() - self.context = context.get_admin_context() - self.dbapi = db_api.get_instance() - self.config(enabled_deploy_interfaces='direct') - - def test_empty_db(self): - self.assertEqual( - (0, 0), self.dbapi.migrate_from_iscsi_deploy(self.context, 10)) - - def test_already_direct_exists(self): - utils.create_test_node(deploy_interface='direct') - self.assertEqual( - (0, 0), self.dbapi.update_to_latest_versions(self.context, 10)) - - def test_migrate_by_2(self): - utils.create_test_node(deploy_interface='direct') - for _i in range(3): - uuid = uuidutils.generate_uuid() - utils.create_test_node(uuid=uuid, deploy_interface='iscsi') - self.assertEqual( - (3, 2), self.dbapi.migrate_from_iscsi_deploy(self.context, 2)) - self.assertEqual( - (1, 1), self.dbapi.migrate_from_iscsi_deploy(self.context, 2)) - - def test_migrate_all(self): - utils.create_test_node(deploy_interface='direct') - for _i in range(3): - uuid = uuidutils.generate_uuid() - utils.create_test_node(uuid=uuid, deploy_interface='iscsi') - self.assertEqual( - (3, 3), self.dbapi.migrate_from_iscsi_deploy(self.context, 0)) - - def test_migration_impossible(self): - self.config(enabled_deploy_interfaces='iscsi') - for _i in range(3): - uuid = uuidutils.generate_uuid() - utils.create_test_node(uuid=uuid, deploy_interface='iscsi') - self.assertEqual( - (0, 0), self.dbapi.migrate_from_iscsi_deploy(self.context, 0)) - - def test_migration_impossible2(self): - self.config(image_download_source='swift', group='agent') - for _i in range(3): - uuid = uuidutils.generate_uuid() - utils.create_test_node(uuid=uuid, deploy_interface='iscsi') - self.assertEqual( - (0, 0), self.dbapi.migrate_from_iscsi_deploy(self.context, 0)) - - def test_migration_impossible3(self): - self.config(default_deploy_interface='iscsi') - for _i in range(3): - uuid = uuidutils.generate_uuid() - utils.create_test_node(uuid=uuid, deploy_interface='iscsi') - self.assertEqual( - (0, 0), self.dbapi.migrate_from_iscsi_deploy(self.context, 0)) - - def test_force_migration(self): - self.config(enabled_deploy_interfaces='iscsi') - utils.create_test_node(deploy_interface='direct') - for _i in range(3): - uuid = uuidutils.generate_uuid() - utils.create_test_node(uuid=uuid, deploy_interface='iscsi') - self.assertEqual( - (3, 3), self.dbapi.migrate_from_iscsi_deploy(self.context, 0, - force=True)) diff --git a/ironic/tests/unit/db/test_conductor.py b/ironic/tests/unit/db/test_conductor.py index 1b390af234..fe4e93ed99 100644 --- a/ironic/tests/unit/db/test_conductor.py +++ b/ironic/tests/unit/db/test_conductor.py @@ -59,7 +59,7 @@ class DbConductorTestCase(base.DbTestCase): def test_register_conductor_hardware_interfaces(self): c = self._create_test_cdr() - interfaces = ['direct', 'iscsi'] + interfaces = ['direct', 'ansible'] self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'generic', 'interface_type': 'deploy', @@ -74,10 +74,10 @@ class DbConductorTestCase(base.DbTestCase): self.assertEqual('generic', ci2.hardware_type) self.assertEqual('deploy', ci1.interface_type) self.assertEqual('deploy', ci2.interface_type) - self.assertEqual('direct', ci1.interface_name) - self.assertEqual('iscsi', ci2.interface_name) - self.assertFalse(ci1.default) - self.assertTrue(ci2.default) + self.assertEqual('ansible', ci1.interface_name) + self.assertEqual('direct', ci2.interface_name) + self.assertTrue(ci1.default) + self.assertFalse(ci2.default) def test_register_conductor_hardware_interfaces_duplicate(self): c = self._create_test_cdr() @@ -85,7 +85,7 @@ class DbConductorTestCase(base.DbTestCase): {'hardware_type': 'generic', 'interface_type': 'deploy', 'interface_name': 'direct', 'default': False}, {'hardware_type': 'generic', 'interface_type': 'deploy', - 'interface_name': 'iscsi', 'default': True} + 'interface_name': 'ansible', 'default': True} ] self.dbapi.register_conductor_hardware_interfaces(c.id, interfaces) ifaces = self.dbapi.list_conductor_hardware_interfaces(c.id) @@ -100,7 +100,7 @@ class DbConductorTestCase(base.DbTestCase): def test_unregister_conductor_hardware_interfaces(self): c = self._create_test_cdr() - interfaces = ['direct', 'iscsi'] + interfaces = ['direct', 'ansible'] self.dbapi.register_conductor_hardware_interfaces( c.id, [{'hardware_type': 'generic', 'interface_type': 'deploy', diff --git a/ironic/tests/unit/drivers/modules/ilo/test_boot.py b/ironic/tests/unit/drivers/modules/ilo/test_boot.py index 0c496772e7..81047956f5 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_boot.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_boot.py @@ -1387,7 +1387,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=['iscsi'], + enabled_deploy_interfaces=['direct'], enabled_inspect_interfaces=['ilo'], enabled_management_interfaces=['ilo5'], enabled_power_interfaces=['ilo'], diff --git a/ironic/tests/unit/drivers/modules/ilo/test_management.py b/ironic/tests/unit/drivers/modules/ilo/test_management.py index 07bb9f98b2..80cb07c9fa 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_management.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_management.py @@ -1535,7 +1535,7 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): self.config(enabled_hardware_types=['ilo5'], enabled_boot_interfaces=['ilo-virtual-media'], enabled_console_interfaces=['ilo'], - enabled_deploy_interfaces=['iscsi'], + enabled_deploy_interfaces=['direct'], enabled_inspect_interfaces=['ilo'], enabled_management_interfaces=['ilo5'], enabled_power_interfaces=['ilo'], diff --git a/ironic/tests/unit/drivers/modules/ilo/test_raid.py b/ironic/tests/unit/drivers/modules/ilo/test_raid.py index ddb6b1d8bd..fcb0314b10 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_raid.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_raid.py @@ -53,7 +53,7 @@ class Ilo5RAIDTestCase(db_base.DbTestCase): self.config(enabled_hardware_types=['ilo5'], enabled_boot_interfaces=['ilo-virtual-media'], enabled_console_interfaces=['ilo'], - enabled_deploy_interfaces=['iscsi'], + enabled_deploy_interfaces=['direct'], enabled_inspect_interfaces=['ilo'], enabled_management_interfaces=['ilo5'], enabled_power_interfaces=['ilo'], diff --git a/ironic/tests/unit/drivers/modules/test_agent_client.py b/ironic/tests/unit/drivers/modules/test_agent_client.py index 76db6d1bda..59f76aa15c 100644 --- a/ironic/tests/unit/drivers/modules/test_agent_client.py +++ b/ironic/tests/unit/drivers/modules/test_agent_client.py @@ -464,46 +464,6 @@ class TestAgentClient(base.TestCase): timeout=CONF.agent.command_timeout, verify='/path/to/agent.crt') - def test_start_iscsi_target(self): - self.client._command = mock.MagicMock(spec_set=[]) - iqn = 'fake-iqn' - port = agent_client.DEFAULT_IPA_PORTAL_PORT - wipe_disk_metadata = False - params = {'iqn': iqn, 'portal_port': port, - 'wipe_disk_metadata': wipe_disk_metadata} - - self.client.start_iscsi_target(self.node, iqn) - self.client._command.assert_called_once_with( - node=self.node, method='iscsi.start_iscsi_target', - params=params, wait=True) - - def test_start_iscsi_target_custom_port(self): - self.client._command = mock.MagicMock(spec_set=[]) - iqn = 'fake-iqn' - port = 3261 - wipe_disk_metadata = False - params = {'iqn': iqn, 'portal_port': port, - 'wipe_disk_metadata': wipe_disk_metadata} - - self.client.start_iscsi_target(self.node, iqn, portal_port=port) - self.client._command.assert_called_once_with( - node=self.node, method='iscsi.start_iscsi_target', - params=params, wait=True) - - def test_start_iscsi_target_wipe_disk_metadata(self): - self.client._command = mock.MagicMock(spec_set=[]) - iqn = 'fake-iqn' - port = agent_client.DEFAULT_IPA_PORTAL_PORT - wipe_disk_metadata = True - params = {'iqn': iqn, 'portal_port': port, - 'wipe_disk_metadata': wipe_disk_metadata} - - self.client.start_iscsi_target(self.node, iqn, - wipe_disk_metadata=wipe_disk_metadata) - self.client._command.assert_called_once_with( - node=self.node, method='iscsi.start_iscsi_target', - params=params, wait=True) - def _test_install_bootloader(self, root_uuid, efi_system_part_uuid=None, prep_boot_part_uuid=None): self.client._command = mock.MagicMock(spec_set=[]) diff --git a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py deleted file mode 100644 index 289e29e493..0000000000 --- a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py +++ /dev/null @@ -1,1816 +0,0 @@ -# Copyright 2013 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Test class for iSCSI deploy mechanism.""" - -import os -import tempfile -import time -import types -from unittest import mock - -from ironic_lib import disk_utils -from ironic_lib import utils as ironic_utils -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_utils import fileutils -import testtools - -from ironic.common import boot_devices -from ironic.common import dhcp_factory -from ironic.common import exception -from ironic.common import pxe_utils -from ironic.common import states -from ironic.common import utils -from ironic.conductor import task_manager -from ironic.conductor import utils as manager_utils -from ironic.drivers.modules import agent_base -from ironic.drivers.modules import agent_client -from ironic.drivers.modules import deploy_utils -from ironic.drivers.modules import fake -from ironic.drivers.modules import iscsi_deploy -from ironic.drivers.modules.network import flat as flat_network -from ironic.drivers.modules import pxe -from ironic.drivers.modules.storage import noop as noop_storage -from ironic.drivers import utils as driver_utils -from ironic.tests import base as tests_base -from ironic.tests.unit.db import base as db_base -from ironic.tests.unit.db import utils as db_utils -from ironic.tests.unit.objects import utils as obj_utils - -CONF = cfg.CONF - -INST_INFO_DICT = db_utils.get_test_pxe_instance_info() -DRV_INFO_DICT = db_utils.get_test_pxe_driver_info() -DRV_INTERNAL_INFO_DICT = db_utils.get_test_pxe_driver_internal_info() - - -class IscsiDeployPrivateMethodsTestCase(db_base.DbTestCase): - - def setUp(self): - super(IscsiDeployPrivateMethodsTestCase, self).setUp() - n = { - 'boot_interface': 'pxe', - 'deploy_interface': 'iscsi', - 'instance_info': INST_INFO_DICT, - 'driver_info': DRV_INFO_DICT, - 'driver_internal_info': DRV_INTERNAL_INFO_DICT, - } - self.node = obj_utils.create_test_node(self.context, **n) - - def test__save_disk_layout(self): - info = dict(INST_INFO_DICT) - info['ephemeral_gb'] = 10 - info['swap_mb'] = 0 - info['root_gb'] = 10 - info['preserve_ephemeral'] = False - self.node.instance_info = info - - iscsi_deploy._save_disk_layout(self.node, info) - self.node.refresh() - for param in ('ephemeral_gb', 'swap_mb', 'root_gb'): - self.assertEqual( - info[param], self.node.driver_internal_info['instance'][param] - ) - - def test__get_image_dir_path(self): - self.assertEqual(os.path.join(CONF.pxe.images_path, - self.node.uuid), - deploy_utils._get_image_dir_path(self.node.uuid)) - - def test__get_image_file_path(self): - self.assertEqual(os.path.join(CONF.pxe.images_path, - self.node.uuid, - 'disk'), - deploy_utils._get_image_file_path(self.node.uuid)) - - -class IscsiDeployMethodsTestCase(db_base.DbTestCase): - - def setUp(self): - super(IscsiDeployMethodsTestCase, self).setUp() - instance_info = dict(INST_INFO_DICT) - instance_info['deploy_key'] = 'fake-56789' - n = { - 'boot_interface': 'pxe', - 'deploy_interface': 'iscsi', - 'instance_info': instance_info, - 'driver_info': DRV_INFO_DICT, - 'driver_internal_info': DRV_INTERNAL_INFO_DICT, - } - self.node = obj_utils.create_test_node(self.context, **n) - - @mock.patch.object(disk_utils, 'get_image_mb', autospec=True) - def test_check_image_size(self, get_image_mb_mock): - get_image_mb_mock.return_value = 1000 - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.instance_info['root_gb'] = 1 - iscsi_deploy.check_image_size(task) - get_image_mb_mock.assert_called_once_with( - deploy_utils._get_image_file_path(task.node.uuid)) - - @mock.patch.object(disk_utils, 'get_image_mb', autospec=True) - def test_check_image_size_whole_disk_image(self, get_image_mb_mock): - get_image_mb_mock.return_value = 1025 - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.instance_info['root_gb'] = 1 - task.node.driver_internal_info['is_whole_disk_image'] = True - # No error for whole disk images - iscsi_deploy.check_image_size(task) - self.assertFalse(get_image_mb_mock.called) - - @mock.patch.object(disk_utils, 'get_image_mb', autospec=True) - def test_check_image_size_whole_disk_image_no_root(self, - get_image_mb_mock): - get_image_mb_mock.return_value = 1025 - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - del task.node.instance_info['root_gb'] - task.node.driver_internal_info['is_whole_disk_image'] = True - # No error for whole disk images - iscsi_deploy.check_image_size(task) - self.assertFalse(get_image_mb_mock.called) - - @mock.patch.object(disk_utils, 'get_image_mb', autospec=True) - def test_check_image_size_fails(self, get_image_mb_mock): - get_image_mb_mock.return_value = 1025 - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.instance_info['root_gb'] = 1 - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.check_image_size, - task) - get_image_mb_mock.assert_called_once_with( - deploy_utils._get_image_file_path(task.node.uuid)) - - @mock.patch.object(deploy_utils, 'fetch_images', autospec=True) - def test_cache_instance_images_master_path(self, mock_fetch_image): - temp_dir = tempfile.mkdtemp() - self.config(images_path=temp_dir, group='pxe') - self.config(instance_master_path=os.path.join(temp_dir, - 'instance_master_path'), - group='pxe') - fileutils.ensure_tree(CONF.pxe.instance_master_path) - - (uuid, image_path) = deploy_utils.cache_instance_image(None, self.node) - mock_fetch_image.assert_called_once_with(None, - mock.ANY, - [(uuid, image_path)], True) - self.assertEqual('glance://image_uuid', uuid) - self.assertEqual(os.path.join(temp_dir, - self.node.uuid, - 'disk'), - image_path) - - @mock.patch.object(ironic_utils, 'unlink_without_raise', autospec=True) - @mock.patch.object(utils, 'rmtree_without_raise', autospec=True) - @mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True) - def test_destroy_images(self, mock_cache, mock_rmtree, mock_unlink): - self.config(images_path='/path', group='pxe') - - deploy_utils.destroy_images('uuid') - - mock_cache.return_value.clean_up.assert_called_once_with() - mock_unlink.assert_called_once_with('/path/uuid/disk') - mock_rmtree.assert_called_once_with('/path/uuid') - - @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True) - @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True) - @mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'deploy_partition_image', autospec=True) - def test_continue_deploy_fail( - self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout, - mock_collect_logs): - kwargs = {'address': '123456', 'iqn': 'aaa-bbb', 'conv_flags': None} - deploy_mock.side_effect = exception.InstanceDeployFailure( - "test deploy error") - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - params = iscsi_deploy.get_deploy_info(task.node, **kwargs) - # Ironic exceptions are preserved as they are - self.assertRaisesRegex(exception.InstanceDeployFailure, - '^test deploy error$', - iscsi_deploy.continue_deploy, - task, **kwargs) - self.assertEqual(states.DEPLOYFAIL, task.node.provision_state) - self.assertEqual(states.ACTIVE, task.node.target_provision_state) - self.assertIsNotNone(task.node.last_error) - deploy_mock.assert_called_once_with(**params) - power_mock.assert_called_once_with(task, states.POWER_OFF) - mock_image_cache.assert_called_once_with() - mock_image_cache.return_value.clean_up.assert_called_once_with() - self.assertFalse(mock_disk_layout.called) - mock_collect_logs.assert_called_once_with(task.node) - - @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True) - @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True) - @mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'deploy_partition_image', autospec=True) - def test_continue_deploy_unexpected_fail( - self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout, - mock_collect_logs): - kwargs = {'address': '123456', 'iqn': 'aaa-bbb'} - deploy_mock.side_effect = KeyError('boom') - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - params = iscsi_deploy.get_deploy_info(task.node, **kwargs) - self.assertRaisesRegex(exception.InstanceDeployFailure, - "Deploy failed.*Error: 'boom'", - iscsi_deploy.continue_deploy, - task, **kwargs) - self.assertEqual(states.DEPLOYFAIL, task.node.provision_state) - self.assertEqual(states.ACTIVE, task.node.target_provision_state) - self.assertIsNotNone(task.node.last_error) - deploy_mock.assert_called_once_with(**params) - power_mock.assert_called_once_with(task, states.POWER_OFF) - mock_image_cache.assert_called_once_with() - mock_image_cache.return_value.clean_up.assert_called_once_with() - self.assertFalse(mock_disk_layout.called) - mock_collect_logs.assert_called_once_with(task.node) - - @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True) - @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True) - @mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'deploy_partition_image', autospec=True) - def test_continue_deploy_fail_no_root_uuid_or_disk_id( - self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout, - mock_collect_logs): - kwargs = {'address': '123456', 'iqn': 'aaa-bbb'} - deploy_mock.return_value = {} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - params = iscsi_deploy.get_deploy_info(task.node, **kwargs) - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.continue_deploy, - task, **kwargs) - self.assertEqual(states.DEPLOYFAIL, task.node.provision_state) - self.assertEqual(states.ACTIVE, task.node.target_provision_state) - self.assertIsNotNone(task.node.last_error) - deploy_mock.assert_called_once_with(**params) - power_mock.assert_called_once_with(task, states.POWER_OFF) - mock_image_cache.assert_called_once_with() - mock_image_cache.return_value.clean_up.assert_called_once_with() - self.assertFalse(mock_disk_layout.called) - mock_collect_logs.assert_called_once_with(task.node) - - @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True) - @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True) - @mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'deploy_partition_image', autospec=True) - def test_continue_deploy_fail_empty_root_uuid( - self, deploy_mock, power_mock, mock_image_cache, - mock_disk_layout, mock_collect_logs): - kwargs = {'address': '123456', 'iqn': 'aaa-bbb'} - deploy_mock.return_value = {'root uuid': ''} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - params = iscsi_deploy.get_deploy_info(task.node, **kwargs) - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.continue_deploy, - task, **kwargs) - self.assertEqual(states.DEPLOYFAIL, task.node.provision_state) - self.assertEqual(states.ACTIVE, task.node.target_provision_state) - self.assertIsNotNone(task.node.last_error) - deploy_mock.assert_called_once_with(**params) - power_mock.assert_called_once_with(task, states.POWER_OFF) - mock_image_cache.assert_called_once_with() - mock_image_cache.return_value.clean_up.assert_called_once_with() - self.assertFalse(mock_disk_layout.called) - mock_collect_logs.assert_called_once_with(task.node) - - @mock.patch.object(iscsi_deploy, '_save_disk_layout', autospec=True) - @mock.patch.object(iscsi_deploy, 'LOG', autospec=True) - @mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True) - @mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'deploy_partition_image', autospec=True) - def test_continue_deploy(self, deploy_mock, power_mock, mock_image_cache, - mock_deploy_info, mock_log, mock_disk_layout): - kwargs = {'address': '123456', 'iqn': 'aaa-bbb'} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - - mock_deploy_info.return_value = { - 'address': '123456', - 'boot_option': 'netboot', - 'configdrive': "I've got the power", - 'ephemeral_format': None, - 'ephemeral_mb': 0, - 'image_path': (u'/var/lib/ironic/images/1be26c0b-03f2-4d2e-ae87-' - u'c02d7f33c123/disk'), - 'iqn': 'aaa-bbb', - 'lun': '1', - 'node_uuid': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123', - 'port': '3260', - 'preserve_ephemeral': True, - 'root_mb': 102400, - 'swap_mb': 0, - } - log_params = mock_deploy_info.return_value.copy() - # Make sure we don't log the full content of the configdrive - log_params['configdrive'] = '***' - expected_dict = { - 'node': self.node.uuid, - 'params': log_params, - } - deployment_uuids = {'root uuid': '12345678-87654321'} - deploy_mock.return_value = deployment_uuids - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - mock_log.isEnabledFor.return_value = True - retval = iscsi_deploy.continue_deploy(task, **kwargs) - mock_log.debug.assert_called_once_with( - mock.ANY, expected_dict) - self.assertEqual(states.DEPLOYWAIT, task.node.provision_state) - self.assertEqual(states.ACTIVE, task.node.target_provision_state) - self.assertIsNone(task.node.last_error) - mock_image_cache.assert_called_once_with() - mock_image_cache.return_value.clean_up.assert_called_once_with() - self.assertEqual(deployment_uuids, retval) - mock_disk_layout.assert_called_once_with(task.node, mock.ANY) - - @mock.patch.object(iscsi_deploy, 'LOG', autospec=True) - @mock.patch.object(iscsi_deploy, 'get_deploy_info', autospec=True) - @mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'deploy_disk_image', autospec=True) - def test_continue_deploy_whole_disk_image( - self, deploy_mock, power_mock, mock_image_cache, mock_deploy_info, - mock_log): - kwargs = {'address': '123456', 'iqn': 'aaa-bbb'} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - - mock_deploy_info.return_value = { - 'address': '123456', - 'image_path': (u'/var/lib/ironic/images/1be26c0b-03f2-4d2e-ae87-' - u'c02d7f33c123/disk'), - 'iqn': 'aaa-bbb', - 'lun': '1', - 'node_uuid': u'1be26c0b-03f2-4d2e-ae87-c02d7f33c123', - 'port': '3260', - } - log_params = mock_deploy_info.return_value.copy() - expected_dict = { - 'node': self.node.uuid, - 'params': log_params, - } - deployment_uuids = {'disk identifier': '87654321'} - deploy_mock.return_value = deployment_uuids - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_internal_info['is_whole_disk_image'] = True - mock_log.isEnabledFor.return_value = True - retval = iscsi_deploy.continue_deploy(task, **kwargs) - mock_log.debug.assert_called_once_with( - mock.ANY, expected_dict) - self.assertEqual(states.DEPLOYWAIT, task.node.provision_state) - self.assertEqual(states.ACTIVE, task.node.target_provision_state) - self.assertIsNone(task.node.last_error) - mock_image_cache.assert_called_once_with() - mock_image_cache.return_value.clean_up.assert_called_once_with() - self.assertEqual(deployment_uuids, retval) - - def _test_get_deploy_info(self, extra_instance_info=None): - if extra_instance_info is None: - extra_instance_info = {} - - instance_info = self.node.instance_info - instance_info.update(extra_instance_info) - self.node.instance_info = instance_info - kwargs = {'address': '1.1.1.1', 'iqn': 'target-iqn'} - ret_val = iscsi_deploy.get_deploy_info(self.node, **kwargs) - self.assertEqual('1.1.1.1', ret_val['address']) - self.assertEqual('target-iqn', ret_val['iqn']) - return ret_val - - def test_get_deploy_info_boot_option_default(self): - ret_val = self._test_get_deploy_info() - self.assertEqual('local', ret_val['boot_option']) - - def test_get_deploy_info_netboot_specified(self): - capabilities = {'capabilities': {'boot_option': 'netboot'}} - ret_val = self._test_get_deploy_info(extra_instance_info=capabilities) - self.assertEqual('netboot', ret_val['boot_option']) - - def test_get_deploy_info_localboot(self): - capabilities = {'capabilities': {'boot_option': 'local'}} - ret_val = self._test_get_deploy_info(extra_instance_info=capabilities) - self.assertEqual('local', ret_val['boot_option']) - - def test_get_deploy_info_cpu_arch(self): - ret_val = self._test_get_deploy_info() - self.assertEqual('x86_64', ret_val['cpu_arch']) - - def test_get_deploy_info_cpu_arch_none(self): - self.node.properties['cpu_arch'] = None - ret_val = self._test_get_deploy_info() - self.assertNotIn('cpu_arch', ret_val) - - def test_get_deploy_info_disk_label(self): - capabilities = {'capabilities': {'disk_label': 'msdos'}} - ret_val = self._test_get_deploy_info(extra_instance_info=capabilities) - self.assertEqual('msdos', ret_val['disk_label']) - - def test_get_deploy_info_not_specified(self): - ret_val = self._test_get_deploy_info() - self.assertNotIn('disk_label', ret_val) - - def test_get_deploy_info_portal_port(self): - self.config(portal_port=3266, group='iscsi') - ret_val = self._test_get_deploy_info() - self.assertEqual(3266, ret_val['port']) - - def test_get_deploy_info_whole_disk_image(self): - instance_info = self.node.instance_info - instance_info['configdrive'] = 'My configdrive' - self.node.instance_info = instance_info - self.node.driver_internal_info['is_whole_disk_image'] = True - kwargs = {'address': '1.1.1.1', 'iqn': 'target-iqn'} - ret_val = iscsi_deploy.get_deploy_info(self.node, **kwargs) - self.assertEqual('1.1.1.1', ret_val['address']) - self.assertEqual('target-iqn', ret_val['iqn']) - self.assertEqual('My configdrive', ret_val['configdrive']) - - def test_get_deploy_info_whole_disk_image_no_root(self): - instance_info = self.node.instance_info - instance_info['configdrive'] = 'My configdrive' - del instance_info['root_gb'] - self.node.instance_info = instance_info - self.node.driver_internal_info['is_whole_disk_image'] = True - kwargs = {'address': '1.1.1.1', 'iqn': 'target-iqn'} - ret_val = iscsi_deploy.get_deploy_info(self.node, **kwargs) - self.assertEqual('1.1.1.1', ret_val['address']) - self.assertEqual('target-iqn', ret_val['iqn']) - self.assertEqual('My configdrive', ret_val['configdrive']) - - @mock.patch.object(iscsi_deploy, 'continue_deploy', autospec=True) - def test_do_agent_iscsi_deploy_okay(self, continue_deploy_mock): - agent_client_mock = mock.MagicMock(spec_set=agent_client.AgentClient) - agent_client_mock.start_iscsi_target.return_value = { - 'command_status': 'SUCCESS', 'command_error': None} - driver_internal_info = {'agent_url': 'http://1.2.3.4:1234'} - self.node.driver_internal_info = driver_internal_info - self.node.save() - deployment_uuids = {'root uuid': 'some-root-uuid'} - continue_deploy_mock.return_value = deployment_uuids - expected_iqn = 'iqn.2008-10.org.openstack:%s' % self.node.uuid - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - ret_val = iscsi_deploy.do_agent_iscsi_deploy( - task, agent_client_mock) - agent_client_mock.start_iscsi_target.assert_called_once_with( - task.node, expected_iqn, 3260, wipe_disk_metadata=True) - continue_deploy_mock.assert_called_once_with( - task, iqn=expected_iqn, address='1.2.3.4', conv_flags=None) - self.assertEqual( - 'some-root-uuid', - task.node.driver_internal_info['root_uuid_or_disk_id']) - self.assertEqual(ret_val, deployment_uuids) - - @mock.patch.object(iscsi_deploy, 'continue_deploy', autospec=True) - def test_do_agent_iscsi_deploy_preserve_ephemeral(self, - continue_deploy_mock): - """Ensure the disk is not wiped if preserve_ephemeral is True.""" - agent_client_mock = mock.MagicMock(spec_set=agent_client.AgentClient) - agent_client_mock.start_iscsi_target.return_value = { - 'command_status': 'SUCCESS', 'command_error': None} - driver_internal_info = { - 'agent_url': 'http://1.2.3.4:1234'} - self.node.driver_internal_info = driver_internal_info - self.node.save() - deployment_uuids = {'root uuid': 'some-root-uuid'} - continue_deploy_mock.return_value = deployment_uuids - expected_iqn = 'iqn.2008-10.org.openstack:%s' % self.node.uuid - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.instance_info['preserve_ephemeral'] = True - iscsi_deploy.do_agent_iscsi_deploy( - task, agent_client_mock) - agent_client_mock.start_iscsi_target.assert_called_once_with( - task.node, expected_iqn, 3260, wipe_disk_metadata=False) - - @mock.patch.object(driver_utils, 'collect_ramdisk_logs', autospec=True) - def test_do_agent_iscsi_deploy_start_iscsi_failure( - self, mock_collect_logs): - agent_client_mock = mock.MagicMock(spec_set=agent_client.AgentClient) - agent_client_mock.start_iscsi_target.return_value = { - 'command_status': 'FAILED', 'command_error': 'booom'} - self.node.provision_state = states.DEPLOYING - self.node.target_provision_state = states.ACTIVE - self.node.save() - expected_iqn = 'iqn.2008-10.org.openstack:%s' % self.node.uuid - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.do_agent_iscsi_deploy, - task, agent_client_mock) - agent_client_mock.start_iscsi_target.assert_called_once_with( - task.node, expected_iqn, 3260, wipe_disk_metadata=True) - self.node.refresh() - self.assertEqual(states.DEPLOYFAIL, self.node.provision_state) - self.assertEqual(states.ACTIVE, self.node.target_provision_state) - self.assertIsNotNone(self.node.last_error) - mock_collect_logs.assert_called_once_with(task.node) - - @mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url', - autospec=True) - def test_validate_good_api_url(self, mock_get_url): - mock_get_url.return_value = 'http://127.0.0.1:1234' - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - iscsi_deploy.validate(task) - mock_get_url.assert_called_once_with() - - @mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url', - autospec=True) - def test_validate_fail_no_api_url(self, mock_get_url): - mock_get_url.side_effect = exception.InvalidParameterValue('Ham!') - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - self.assertRaises(exception.InvalidParameterValue, - iscsi_deploy.validate, task) - mock_get_url.assert_called_once_with() - - @mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url', - autospec=True) - def test_validate_invalid_root_device_hints(self, mock_get_url): - mock_get_url.return_value = 'http://spam.ham/baremetal' - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.properties['root_device'] = {'size': 'not-int'} - self.assertRaises(exception.InvalidParameterValue, - iscsi_deploy.validate, task) - - @mock.patch('ironic.drivers.modules.deploy_utils.get_ironic_api_url', - autospec=True) - def test_validate_invalid_root_device_hints_iinfo(self, mock_get_url): - mock_get_url.return_value = 'http://spam.ham/baremetal' - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - task.node.properties['root_device'] = {'size': 42} - task.node.instance_info['root_device'] = {'size': 'not-int'} - self.assertRaises(exception.InvalidParameterValue, - iscsi_deploy.validate, task) - - -class ISCSIDeployTestCase(db_base.DbTestCase): - - def setUp(self): - super(ISCSIDeployTestCase, self).setUp() - # NOTE(TheJulia): We explicitly set the noop storage interface as the - # default below for deployment tests in order to raise any change - # in the default which could be a breaking behavior change - # as the storage interface is explicitly an "opt-in" interface. - self.node = obj_utils.create_test_node( - self.context, boot_interface='pxe', deploy_interface='iscsi', - instance_info=INST_INFO_DICT, - driver_info=DRV_INFO_DICT, - driver_internal_info=DRV_INTERNAL_INFO_DICT, - storage_interface='noop', - ) - self.node.driver_internal_info['agent_url'] = 'http://1.2.3.4:1234' - dhcp_factory.DHCPFactory._dhcp_provider = None - # Memory checks shoudn't apply here as they will lead to - # false positives for unit testing in CI. - self.config(minimum_memory_warning_only=True) - - def test_get_properties(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - props = task.driver.deploy.get_properties() - self.assertEqual({'agent_verify_ca', 'deploy_forces_oob_reboot'}, - set(props)) - - @mock.patch.object(iscsi_deploy, 'validate', autospec=True) - @mock.patch.object(deploy_utils, 'validate_capabilities', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True) - def test_validate(self, pxe_validate_mock, - validate_capabilities_mock, validate_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - - task.driver.deploy.validate(task) - - pxe_validate_mock.assert_called_once_with(task.driver.boot, task) - validate_capabilities_mock.assert_called_once_with(task.node) - validate_mock.assert_called_once_with(task) - - @mock.patch.object(noop_storage.NoopStorage, 'should_write_image', - autospec=True) - @mock.patch.object(iscsi_deploy, 'validate', autospec=True) - @mock.patch.object(deploy_utils, 'validate_capabilities', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'validate', autospec=True) - def test_validate_storage_should_write_image_false( - self, pxe_validate_mock, - validate_capabilities_mock, validate_mock, - should_write_image_mock): - should_write_image_mock.return_value = False - with task_manager.acquire(self.context, self.node.uuid, - shared=True) as task: - - task.driver.deploy.validate(task) - - pxe_validate_mock.assert_called_once_with(task.driver.boot, task) - validate_capabilities_mock.assert_called_once_with(task.node) - self.assertFalse(validate_mock.called) - should_write_image_mock.assert_called_once_with( - task.driver.storage, task) - - @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(flat_network.FlatNetwork, 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True) - def test_prepare_node_active(self, prepare_instance_mock, - add_provisioning_net_mock, - storage_driver_info_mock, - storage_attach_volumes_mock): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.provision_state = states.ACTIVE - - task.driver.deploy.prepare(task) - - prepare_instance_mock.assert_called_once_with( - task.driver.boot, task) - self.assertEqual(0, add_provisioning_net_mock.call_count) - storage_driver_info_mock.assert_called_once_with(task) - self.assertFalse(storage_attach_volumes_mock.called) - - @mock.patch.object(flat_network.FlatNetwork, 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True) - def test_prepare_node_adopting(self, prepare_instance_mock, - add_provisioning_net_mock): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.provision_state = states.ADOPTING - - task.driver.deploy.prepare(task) - - prepare_instance_mock.assert_called_once_with( - task.driver.boot, task) - self.assertEqual(0, add_provisioning_net_mock.call_count) - - @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(deploy_utils, 'build_agent_options', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True) - @mock.patch.object(flat_network.FlatNetwork, 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - def test_prepare_node_deploying( - self, unconfigure_tenant_net_mock, add_provisioning_net_mock, - mock_prepare_ramdisk, mock_agent_options, - storage_driver_info_mock, storage_attach_volumes_mock): - mock_agent_options.return_value = {'c': 'd'} - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.provision_state = states.DEPLOYING - - task.driver.deploy.prepare(task) - - mock_agent_options.assert_called_once_with(task.node) - mock_prepare_ramdisk.assert_called_once_with( - task.driver.boot, task, {'c': 'd'}) - add_provisioning_net_mock.assert_called_once_with(mock.ANY, task) - unconfigure_tenant_net_mock.assert_called_once_with(mock.ANY, task) - storage_driver_info_mock.assert_called_once_with(task) - storage_attach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - - @mock.patch.object(noop_storage.NoopStorage, 'should_write_image', - 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(deploy_utils, 'build_agent_options', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True) - @mock.patch.object(flat_network.FlatNetwork, 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - def test_prepare_node_deploying_storage_should_write_false( - self, unconfigure_tenant_net_mock, add_provisioning_net_mock, - mock_prepare_ramdisk, mock_agent_options, - storage_driver_info_mock, storage_attach_volumes_mock, - storage_should_write_mock): - storage_should_write_mock.return_value = False - mock_agent_options.return_value = {'c': 'd'} - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.provision_state = states.DEPLOYING - - task.driver.deploy.prepare(task) - - self.assertFalse(mock_agent_options.called) - self.assertFalse(mock_prepare_ramdisk.called) - self.assertFalse(add_provisioning_net_mock.called) - self.assertFalse(unconfigure_tenant_net_mock.called) - storage_driver_info_mock.assert_called_once_with(task) - storage_attach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - self.assertEqual(2, storage_should_write_mock.call_count) - - @mock.patch('ironic.conductor.utils.is_fast_track', 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(deploy_utils, 'build_instance_info_for_deploy', - autospec=True) - @mock.patch.object(flat_network.FlatNetwork, 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - @mock.patch.object(flat_network.FlatNetwork, 'validate', - spec_set=True, autospec=True) - def test_prepare_fast_track( - 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, is_fast_track_mock): - # TODO(TheJulia): We should revisit this test. Smartnic - # support didn't wire in tightly on testing for power in - # these tests, and largely fast_track impacts power operations. - node = self.node - node.network_interface = 'flat' - node.save() - is_fast_track_mock.return_value = True - 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'} - task.driver.deploy.prepare(task) - storage_driver_info_mock.assert_called_once_with(task) - # NOTE: Validate is the primary difference between agent/iscsi - self.assertFalse(validate_net_mock.called) - add_provisioning_net_mock.assert_called_once_with(mock.ANY, task) - unconfigure_tenant_net_mock.assert_called_once_with(mock.ANY, task) - self.assertTrue(storage_attach_volumes_mock.called) - self.assertFalse(build_instance_info_mock.called) - # TODO(TheJulia): We should likely consider executing the - # next two methods at some point in order to facilitate - # continuity. While not explicitly required for this feature - # to work, reboots as part of deployment would need the ramdisk - # present and ready. - self.assertFalse(build_options_mock.called) - self.assertFalse(pxe_prepare_ramdisk_mock.called) - - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True) - @mock.patch.object(deploy_utils, 'cache_instance_image', autospec=True) - def test_deploy(self, mock_cache_instance_image, - mock_check_image_size, mock_node_power_action): - with task_manager.acquire(self.context, - self.node.uuid, shared=False) as task: - state = task.driver.deploy.deploy(task) - self.assertEqual(state, states.DEPLOYWAIT) - mock_cache_instance_image.assert_called_once_with( - self.context, task.node) - mock_check_image_size.assert_called_once_with(task) - mock_node_power_action.assert_called_once_with(task, states.REBOOT) - - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True) - @mock.patch.object(deploy_utils, 'cache_instance_image', autospec=True) - def test_deploy_with_deployment_reboot(self, mock_cache_instance_image, - mock_check_image_size, - mock_node_power_action): - driver_internal_info = self.node.driver_internal_info - driver_internal_info['deployment_reboot'] = True - self.node.driver_internal_info = driver_internal_info - self.node.save() - with task_manager.acquire(self.context, - self.node.uuid, shared=False) as task: - state = task.driver.deploy.deploy(task) - self.assertEqual(state, states.DEPLOYWAIT) - mock_cache_instance_image.assert_called_once_with( - self.context, task.node) - mock_check_image_size.assert_called_once_with(task) - self.assertFalse(mock_node_power_action.called) - self.assertNotIn( - 'deployment_reboot', task.node.driver_internal_info) - - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - @mock.patch.object(noop_storage.NoopStorage, 'should_write_image', - autospec=True) - def test_deploy_storage_should_write_image_false( - self, mock_write, mock_node_power_action): - mock_write.return_value = False - self.node.provision_state = states.DEPLOYING - self.node.deploy_step = { - 'step': 'deploy', 'priority': 50, 'interface': 'deploy'} - self.node.save() - with task_manager.acquire( - self.context, self.node.uuid, shared=False) as task: - ret = task.driver.deploy.deploy(task) - self.assertIsNone(ret) - self.assertFalse(mock_node_power_action.called) - - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'refresh_steps', - autospec=True) - @mock.patch.object(iscsi_deploy, 'check_image_size', autospec=True) - @mock.patch.object(deploy_utils, 'cache_instance_image', autospec=True) - @mock.patch.object(iscsi_deploy.ISCSIDeploy, 'write_image', - autospec=True) - @mock.patch('ironic.conductor.utils.is_fast_track', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True) - @mock.patch('ironic.conductor.utils.node_power_action', autospec=True) - def test_deploy_fast_track(self, power_mock, mock_pxe_instance, - mock_is_fast_track, write_image_mock, - cache_image_mock, check_image_size_mock, - refresh_mock): - mock_is_fast_track.return_value = True - self.node.target_provision_state = states.ACTIVE - self.node.provision_state = states.DEPLOYING - i_info = self.node.driver_internal_info - i_info['agent_url'] = 'http://1.2.3.4:1234' - self.node.driver_internal_info = i_info - self.node.save() - with task_manager.acquire( - self.context, self.node['uuid'], shared=False) as task: - result = task.driver.deploy.deploy(task) - self.assertIsNone(result) - self.assertFalse(power_mock.called) - self.assertFalse(mock_pxe_instance.called) - task.node.refresh() - self.assertEqual(states.DEPLOYING, task.node.provision_state) - self.assertEqual(states.ACTIVE, - task.node.target_provision_state) - cache_image_mock.assert_called_with(mock.ANY, task.node) - check_image_size_mock.assert_called_with(task) - self.assertFalse(write_image_mock.called) - refresh_mock.assert_called_once_with(task.driver.deploy, - task, 'deploy') - - @mock.patch.object(noop_storage.NoopStorage, 'detach_volumes', - autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'remove_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - def test_tear_down(self, node_power_action_mock, - unconfigure_tenant_nets_mock, - remove_provisioning_net_mock, - storage_detach_volumes_mock): - obj_utils.create_test_volume_target( - self.context, node_id=self.node.id) - with task_manager.acquire(self.context, - self.node.uuid, shared=False) as task: - state = task.driver.deploy.tear_down(task) - self.assertEqual(state, states.DELETED) - node_power_action_mock.assert_called_once_with(task, - states.POWER_OFF) - unconfigure_tenant_nets_mock.assert_called_once_with(mock.ANY, - task) - remove_provisioning_net_mock.assert_called_once_with(mock.ANY, - task) - storage_detach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - # Verify no volumes exist for new task instances. - with task_manager.acquire(self.context, - self.node.uuid, shared=False) as task: - self.assertEqual(0, len(task.volume_targets)) - - @mock.patch('ironic.common.dhcp_factory.DHCPFactory._set_dhcp_provider', - autospec=True) - @mock.patch('ironic.common.dhcp_factory.DHCPFactory.clean_dhcp', - autospec=True) - @mock.patch.object(pxe.PXEBoot, 'clean_up_instance', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'clean_up_ramdisk', autospec=True) - @mock.patch.object(deploy_utils, 'destroy_images', autospec=True) - def test_clean_up(self, destroy_images_mock, clean_up_ramdisk_mock, - clean_up_instance_mock, clean_dhcp_mock, - set_dhcp_provider_mock): - with task_manager.acquire(self.context, - self.node.uuid, shared=False) as task: - task.driver.deploy.clean_up(task) - destroy_images_mock.assert_called_once_with(task.node.uuid) - clean_up_ramdisk_mock.assert_called_once_with( - task.driver.boot, task) - clean_up_instance_mock.assert_called_once_with( - task.driver.boot, task) - set_dhcp_provider_mock.assert_called_once_with() - clean_dhcp_mock.assert_called_once_with(mock.ANY, task) - - @mock.patch.object(deploy_utils, 'prepare_inband_cleaning', autospec=True) - def test_prepare_cleaning(self, prepare_inband_cleaning_mock): - prepare_inband_cleaning_mock.return_value = states.CLEANWAIT - with task_manager.acquire(self.context, self.node.uuid) as task: - self.assertEqual( - states.CLEANWAIT, task.driver.deploy.prepare_cleaning(task)) - prepare_inband_cleaning_mock.assert_called_once_with( - task, manage_boot=True) - - @mock.patch.object(deploy_utils, 'tear_down_inband_cleaning', - autospec=True) - def test_tear_down_cleaning(self, tear_down_cleaning_mock): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.deploy.tear_down_cleaning(task) - tear_down_cleaning_mock.assert_called_once_with( - task, manage_boot=True) - - @mock.patch.object(agent_base, 'get_steps', autospec=True) - def test_get_clean_steps(self, mock_get_clean_steps): - # Test getting clean steps - self.config(group='deploy', erase_devices_priority=10) - self.config(group='deploy', erase_devices_metadata_priority=5) - mock_steps = [{'priority': 10, 'interface': 'deploy', - 'step': 'erase_devices'}] - self.node.driver_internal_info = {'agent_url': 'foo'} - self.node.save() - mock_get_clean_steps.return_value = mock_steps - with task_manager.acquire(self.context, self.node.uuid) as task: - steps = task.driver.deploy.get_clean_steps(task) - mock_get_clean_steps.assert_called_once_with( - task, 'clean', interface='deploy', - override_priorities={ - 'erase_devices': 10, - 'erase_devices_metadata': 5}) - self.assertEqual(mock_steps, steps) - - @mock.patch.object(agent_base, 'execute_step', autospec=True) - def test_execute_clean_step(self, agent_execute_clean_step_mock): - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.deploy.execute_clean_step( - task, {'some-step': 'step-info'}) - agent_execute_clean_step_mock.assert_called_once_with( - task, {'some-step': 'step-info'}, 'clean') - - @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', autospec=True) - def test_write_image(self, do_agent_iscsi_deploy_mock): - - self.node.instance_info = { - 'capabilities': {'boot_option': 'netboot'}} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - deployment_uuids = {'root uuid': 'some-root-uuid'} - do_agent_iscsi_deploy_mock.return_value = deployment_uuids - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.deploy.write_image(task) - do_agent_iscsi_deploy_mock.assert_called_once_with( - task, task.driver.deploy._client) - self.assertEqual( - task.node.driver_internal_info['deployment_uuids'], - deployment_uuids) - - @mock.patch.object(noop_storage.NoopStorage, 'should_write_image', - autospec=True) - @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', autospec=True) - def test_write_image_bfv(self, do_agent_iscsi_deploy_mock, - should_write_image_mock): - should_write_image_mock.return_value = False - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.deploy.write_image(task) - self.assertFalse(do_agent_iscsi_deploy_mock.called) - - def test_prepare_instance_boot_netboot(self): - deployment_uuids = {'root uuid': 'some-root-uuid'} - self.node.instance_info = { - 'capabilities': {'boot_option': 'netboot'}} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - info = self.node.driver_internal_info - info['deployment_uuids'] = deployment_uuids - self.node.driver_internal_info = info - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid) as task: - with mock.patch.object(task.driver.boot, - 'prepare_instance', - autospec=True) as m_prep_instance: - task.driver.deploy.prepare_instance_boot(task) - m_prep_instance.assert_called_once_with(task) - - @mock.patch.object(fake.FakeManagement, 'set_boot_device', autospec=True) - @mock.patch.object(agent_base.AgentDeployMixin, - 'configure_local_boot', autospec=True) - def test_prepare_instance_boot_localboot(self, configure_local_boot_mock, - set_boot_device_mock): - - deployment_uuids = {'root uuid': 'some-root-uuid'} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - info = self.node.driver_internal_info - info['deployment_uuids'] = deployment_uuids - self.node.driver_internal_info = info - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.deploy.prepare_instance_boot(task) - configure_local_boot_mock.assert_called_once_with( - task.driver.deploy, task, root_uuid='some-root-uuid', - efi_system_part_uuid=None, prep_boot_part_uuid=None) - set_boot_device_mock.assert_called_once_with( - mock.ANY, task, device=boot_devices.DISK, persistent=True) - - @mock.patch.object(fake.FakeManagement, 'set_boot_device', autospec=True) - @mock.patch.object(agent_base.AgentDeployMixin, - 'configure_local_boot', autospec=True) - def test_prepare_instance_boot_localboot_uefi( - self, configure_local_boot_mock, set_boot_device_mock): - deployment_uuids = {'root uuid': 'some-root-uuid', - 'efi system partition uuid': 'efi-part-uuid'} - self.node.instance_info = { - 'capabilities': {'boot_option': 'local'}} - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - info = self.node.driver_internal_info - info['deployment_uuids'] = deployment_uuids - self.node.driver_internal_info = info - self.node.save() - - with task_manager.acquire(self.context, self.node.uuid) as task: - task.driver.deploy.prepare_instance_boot(task) - configure_local_boot_mock.assert_called_once_with( - task.driver.deploy, task, root_uuid='some-root-uuid', - efi_system_part_uuid='efi-part-uuid', prep_boot_part_uuid=None) - set_boot_device_mock.assert_called_once_with( - mock.ANY, task, device=boot_devices.DISK, persistent=True) - - @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy', autospec=True) - @mock.patch.object(utils, 'is_memory_insufficent', autospec=True) - def test_write_image_out_of_memory(self, mock_memory_check, - mock_do_iscsi_deploy): - mock_memory_check.return_value = True - self.node.provision_state = states.DEPLOYWAIT - self.node.target_provision_state = states.ACTIVE - self.node.save() - with task_manager.acquire(self.context, self.node.uuid) as task: - value = task.driver.deploy.write_image(task) - self.assertEqual(states.DEPLOYWAIT, value) - self.assertEqual(states.DEPLOYWAIT, task.node.provision_state) - self.assertIn('skip_current_deploy_step', - task.node.driver_internal_info) - self.assertTrue(mock_memory_check.called) - self.assertFalse(mock_do_iscsi_deploy.called) - - @mock.patch.object(manager_utils, 'restore_power_state_if_needed', - autospec=True) - @mock.patch.object(manager_utils, 'power_on_node_if_needed', 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(deploy_utils, 'build_agent_options', autospec=True) - @mock.patch.object(pxe.PXEBoot, 'prepare_ramdisk', autospec=True) - @mock.patch.object(flat_network.FlatNetwork, 'add_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - def test_prepare_node_deploying_with_smartnic_port( - self, unconfigure_tenant_net_mock, add_provisioning_net_mock, - mock_prepare_ramdisk, mock_agent_options, - storage_driver_info_mock, storage_attach_volumes_mock, - power_on_node_if_needed_mock, restore_power_state_mock): - mock_agent_options.return_value = {'c': 'd'} - with task_manager.acquire(self.context, self.node.uuid) as task: - task.node.provision_state = states.DEPLOYING - power_on_node_if_needed_mock.return_value = states.POWER_OFF - task.driver.deploy.prepare(task) - mock_agent_options.assert_called_once_with(task.node) - mock_prepare_ramdisk.assert_called_once_with( - task.driver.boot, task, {'c': 'd'}) - add_provisioning_net_mock.assert_called_once_with(mock.ANY, task) - unconfigure_tenant_net_mock.assert_called_once_with(mock.ANY, task) - storage_driver_info_mock.assert_called_once_with(task) - storage_attach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - power_on_node_if_needed_mock.assert_called_once_with(task) - restore_power_state_mock.assert_called_once_with( - task, states.POWER_OFF) - - @mock.patch.object(manager_utils, 'restore_power_state_if_needed', - autospec=True) - @mock.patch.object(manager_utils, 'power_on_node_if_needed', autospec=True) - @mock.patch.object(noop_storage.NoopStorage, 'detach_volumes', - autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'remove_provisioning_network', - spec_set=True, autospec=True) - @mock.patch.object(flat_network.FlatNetwork, - 'unconfigure_tenant_networks', - spec_set=True, autospec=True) - @mock.patch.object(manager_utils, 'node_power_action', autospec=True) - def test_tear_down_with_smartnic_port( - self, node_power_action_mock, unconfigure_tenant_nets_mock, - remove_provisioning_net_mock, storage_detach_volumes_mock, - power_on_node_if_needed_mock, restore_power_state_mock): - obj_utils.create_test_volume_target( - self.context, node_id=self.node.id) - with task_manager.acquire( - self.context, self.node.uuid, shared=False) as task: - power_on_node_if_needed_mock.return_value = states.POWER_OFF - state = task.driver.deploy.tear_down(task) - self.assertEqual(state, states.DELETED) - node_power_action_mock.assert_called_once_with( - task, states.POWER_OFF) - unconfigure_tenant_nets_mock.assert_called_once_with( - mock.ANY, task) - remove_provisioning_net_mock.assert_called_once_with( - mock.ANY, task) - storage_detach_volumes_mock.assert_called_once_with( - task.driver.storage, task) - power_on_node_if_needed_mock.assert_called_once_with(task) - restore_power_state_mock.assert_called_once_with( - task, states.POWER_OFF) - # Verify no volumes exist for new task instances. - with task_manager.acquire(self.context, - self.node.uuid, shared=False) as task: - self.assertEqual(0, len(task.volume_targets)) - - -# Cleanup of iscsi_deploy with pxe boot interface -class CleanUpFullFlowTestCase(db_base.DbTestCase): - def setUp(self): - super(CleanUpFullFlowTestCase, self).setUp() - self.config(image_cache_size=0, group='pxe') - - # Configure node - instance_info = INST_INFO_DICT - instance_info['deploy_key'] = 'fake-56789' - self.node = obj_utils.create_test_node( - self.context, boot_interface='pxe', deploy_interface='iscsi', - instance_info=instance_info, - driver_info=DRV_INFO_DICT, - driver_internal_info=DRV_INTERNAL_INFO_DICT, - ) - self.port = obj_utils.create_test_port(self.context, - node_id=self.node.id) - - # Configure temporary directories - pxe_temp_dir = tempfile.mkdtemp() - self.config(tftp_root=pxe_temp_dir, group='pxe') - tftp_master_dir = os.path.join(CONF.pxe.tftp_root, - 'tftp_master') - self.config(tftp_master_path=tftp_master_dir, group='pxe') - os.makedirs(tftp_master_dir) - - instance_temp_dir = tempfile.mkdtemp() - self.config(images_path=instance_temp_dir, - group='pxe') - instance_master_dir = os.path.join(CONF.pxe.images_path, - 'instance_master') - self.config(instance_master_path=instance_master_dir, - group='pxe') - os.makedirs(instance_master_dir) - self.pxe_config_dir = os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg') - os.makedirs(self.pxe_config_dir) - - # Populate some file names - self.master_kernel_path = os.path.join(CONF.pxe.tftp_master_path, - 'kernel') - self.master_instance_path = os.path.join(CONF.pxe.instance_master_path, - 'image_uuid') - self.node_tftp_dir = os.path.join(CONF.pxe.tftp_root, - self.node.uuid) - os.makedirs(self.node_tftp_dir) - self.kernel_path = os.path.join(self.node_tftp_dir, - 'kernel') - self.node_image_dir = deploy_utils._get_image_dir_path(self.node.uuid) - os.makedirs(self.node_image_dir) - self.image_path = deploy_utils._get_image_file_path(self.node.uuid) - self.config_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) - self.mac_path = pxe_utils._get_pxe_mac_path(self.port.address) - - # Create files - self.files = [self.config_path, self.master_kernel_path, - self.master_instance_path] - for fname in self.files: - # NOTE(dtantsur): files with 0 size won't be cleaned up - with open(fname, 'w') as fp: - fp.write('test') - - os.link(self.config_path, self.mac_path) - os.link(self.master_kernel_path, self.kernel_path) - os.link(self.master_instance_path, self.image_path) - dhcp_factory.DHCPFactory._dhcp_provider = None - - @mock.patch('ironic.common.dhcp_factory.DHCPFactory._set_dhcp_provider', - autospec=True) - @mock.patch('ironic.common.dhcp_factory.DHCPFactory.clean_dhcp', - autospec=True) - @mock.patch.object(pxe_utils, 'get_instance_image_info', autospec=True) - @mock.patch.object(pxe_utils, 'get_image_info', autospec=True) - def test_clean_up_with_master(self, mock_get_deploy_image_info, - mock_get_instance_image_info, - clean_dhcp_mock, set_dhcp_provider_mock): - image_info = {'kernel': ('kernel_uuid', - self.kernel_path)} - mock_get_instance_image_info.return_value = image_info - mock_get_deploy_image_info.return_value = {} - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.driver.deploy.clean_up(task) - mock_get_instance_image_info.assert_called_with(task, - ipxe_enabled=False) - mock_get_deploy_image_info.assert_called_with( - task.node, mode='deploy', ipxe_enabled=False) - set_dhcp_provider_mock.assert_called_once_with() - clean_dhcp_mock.assert_called_once_with(mock.ANY, task) - for path in ([self.kernel_path, self.image_path, self.config_path] - + self.files): - self.assertFalse(os.path.exists(path), - '%s is not expected to exist' % path) - - -@mock.patch.object(time, 'sleep', lambda seconds: None) -class PhysicalWorkTestCase(tests_base.TestCase): - - def setUp(self): - super(PhysicalWorkTestCase, self).setUp() - self.address = '127.0.0.1' - self.port = 3306 - self.iqn = 'iqn.xyz' - self.lun = 1 - self.image_path = '/tmp/xyz/image' - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - self.dev = ("/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-%s" - % (self.address, self.port, self.iqn, self.lun)) - - def _mock_calls(self, name_list, module): - patch_list = [mock.patch.object(module, name, - spec_set=types.FunctionType) - for name in name_list] - mock_list = [patcher.start() for patcher in patch_list] - for patcher in patch_list: - self.addCleanup(patcher.stop) - - parent_mock = mock.MagicMock(spec=[]) - for mocker, name in zip(mock_list, name_list): - parent_mock.attach_mock(mocker, name) - return parent_mock - - @mock.patch.object(disk_utils, 'work_on_disk', autospec=True) - @mock.patch.object(disk_utils, 'is_block_device', autospec=True) - @mock.patch.object(disk_utils, 'get_image_mb', autospec=True) - @mock.patch.object(iscsi_deploy, 'logout_iscsi', autospec=True) - @mock.patch.object(iscsi_deploy, 'login_iscsi', autospec=True) - @mock.patch.object(iscsi_deploy, 'discovery', autospec=True) - @mock.patch.object(iscsi_deploy, 'delete_iscsi', autospec=True) - def _test_deploy_partition_image(self, - mock_delete_iscsi, - mock_discovery, - mock_login_iscsi, - mock_logout_iscsi, - mock_get_image_mb, - mock_is_block_device, - mock_work_on_disk, **kwargs): - # Below are the only values we allow callers to modify for testing. - # Check that values other than this aren't passed in. - deploy_args = { - 'boot_mode': None, - 'boot_option': None, - 'configdrive': None, - 'cpu_arch': None, - 'disk_label': None, - 'ephemeral_format': None, - 'ephemeral_mb': None, - 'image_mb': 1, - 'preserve_ephemeral': False, - 'root_mb': 128, - 'swap_mb': 64 - } - disallowed_values = set(kwargs) - set(deploy_args) - if disallowed_values: - raise ValueError("Only the following kwargs are allowed in " - "_test_deploy_partition_image: %(allowed)s. " - "Disallowed values: %(disallowed)s." - % {"allowed": ", ".join(deploy_args), - "disallowed": ", ".join(disallowed_values)}) - deploy_args.update(kwargs) - - root_uuid = '12345678-1234-1234-12345678-12345678abcdef' - - mock_is_block_device.return_value = True - mock_get_image_mb.return_value = deploy_args['image_mb'] - mock_work_on_disk.return_value = { - 'root uuid': root_uuid, - 'efi system partition uuid': None - } - - deploy_kwargs = { - 'boot_mode': deploy_args['boot_mode'], - 'boot_option': deploy_args['boot_option'], - 'configdrive': deploy_args['configdrive'], - 'disk_label': deploy_args['disk_label'], - 'cpu_arch': deploy_args['cpu_arch'] or '', - 'preserve_ephemeral': deploy_args['preserve_ephemeral'] - } - iscsi_deploy.deploy_partition_image( - self.address, self.port, self.iqn, self.lun, self.image_path, - deploy_args['root_mb'], - deploy_args['swap_mb'], deploy_args['ephemeral_mb'], - deploy_args['ephemeral_format'], self.node_uuid, **deploy_kwargs) - - mock_discovery.assert_called_once_with(self.address, self.port) - mock_login_iscsi.assert_called_once_with(self.address, self.port, - self.iqn) - mock_logout_iscsi.assert_called_once_with(self.address, self.port, - self.iqn) - mock_delete_iscsi.assert_called_once_with(self.address, self.port, - self.iqn) - mock_get_image_mb.assert_called_once_with(self.image_path) - mock_is_block_device.assert_called_once_with(self.dev) - - work_on_disk_kwargs = { - 'preserve_ephemeral': deploy_args['preserve_ephemeral'], - 'configdrive': deploy_args['configdrive'], - # boot_option defaults to 'netboot' if - # not set - 'boot_option': deploy_args['boot_option'] or 'local', - 'boot_mode': deploy_args['boot_mode'], - 'disk_label': deploy_args['disk_label'], - 'cpu_arch': deploy_args['cpu_arch'] or '' - } - mock_work_on_disk.assert_called_once_with( - self.dev, deploy_args['root_mb'], deploy_args['swap_mb'], - deploy_args['ephemeral_mb'], deploy_args['ephemeral_format'], - self.image_path, self.node_uuid, **work_on_disk_kwargs) - - def test_deploy_partition_image_without_boot_option(self): - self._test_deploy_partition_image() - - def test_deploy_partition_image_netboot(self): - self._test_deploy_partition_image(boot_option="netboot") - - def test_deploy_partition_image_localboot(self): - self._test_deploy_partition_image(boot_option="local") - - def test_deploy_partition_image_wo_boot_option_and_wo_boot_mode(self): - self._test_deploy_partition_image() - - def test_deploy_partition_image_netboot_bios(self): - self._test_deploy_partition_image(boot_option="netboot", - boot_mode="bios") - - def test_deploy_partition_image_localboot_bios(self): - self._test_deploy_partition_image(boot_option="local", - boot_mode="bios") - - def test_deploy_partition_image_netboot_uefi(self): - self._test_deploy_partition_image(boot_option="netboot", - boot_mode="uefi") - - def test_deploy_partition_image_disk_label(self): - self._test_deploy_partition_image(disk_label='gpt') - - def test_deploy_partition_image_image_exceeds_root_partition(self): - self.assertRaises(exception.InstanceDeployFailure, - self._test_deploy_partition_image, image_mb=129, - root_mb=128) - - def test_deploy_partition_image_localboot_uefi(self): - self._test_deploy_partition_image(boot_option="local", - boot_mode="uefi") - - def test_deploy_partition_image_without_swap(self): - self._test_deploy_partition_image(swap_mb=0) - - def test_deploy_partition_image_with_ephemeral(self): - self._test_deploy_partition_image(ephemeral_format='exttest', - ephemeral_mb=256) - - def test_deploy_partition_image_preserve_ephemeral(self): - self._test_deploy_partition_image(ephemeral_format='exttest', - ephemeral_mb=256, - preserve_ephemeral=True) - - def test_deploy_partition_image_with_configdrive(self): - self._test_deploy_partition_image(configdrive='http://1.2.3.4/cd') - - def test_deploy_partition_image_with_cpu_arch(self): - self._test_deploy_partition_image(cpu_arch='generic') - - @mock.patch.object(disk_utils, 'create_config_drive_partition', - autospec=True) - @mock.patch.object(disk_utils, 'get_disk_identifier', autospec=True) - def test_deploy_whole_disk_image(self, mock_gdi, create_config_drive_mock): - """Check loosely all functions are called with right args.""" - - name_list = ['discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi'] - disk_utils_name_list = ['is_block_device', 'populate_image'] - - iscsi_mock = self._mock_calls(name_list, iscsi_deploy) - - disk_utils_mock = self._mock_calls(disk_utils_name_list, disk_utils) - disk_utils_mock.is_block_device.return_value = True - mock_gdi.return_value = '0x12345678' - utils_calls_expected = [mock.call.discovery(self.address, self.port), - mock.call.login_iscsi(self.address, self.port, - self.iqn), - mock.call.logout_iscsi(self.address, self.port, - self.iqn), - mock.call.delete_iscsi(self.address, self.port, - self.iqn)] - disk_utils_calls_expected = [mock.call.is_block_device(self.dev), - mock.call.populate_image(self.image_path, - self.dev, - conv_flags=None)] - uuid_dict_returned = iscsi_deploy.deploy_disk_image( - self.address, self.port, self.iqn, self.lun, self.image_path, - self.node_uuid) - - self.assertEqual(utils_calls_expected, iscsi_mock.mock_calls) - self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) - self.assertFalse(create_config_drive_mock.called) - self.assertEqual('0x12345678', uuid_dict_returned['disk identifier']) - - @mock.patch.object(disk_utils, 'create_config_drive_partition', - autospec=True) - @mock.patch.object(disk_utils, 'get_disk_identifier', autospec=True) - def test_deploy_whole_disk_image_with_config_drive(self, mock_gdi, - create_partition_mock): - """Check loosely all functions are called with right args.""" - config_url = 'http://1.2.3.4/cd' - - iscsi_list = ['discovery', 'login_iscsi', 'logout_iscsi', - 'delete_iscsi'] - - disk_utils_list = ['is_block_device', 'populate_image'] - iscsi_mock = self._mock_calls(iscsi_list, iscsi_deploy) - disk_utils_mock = self._mock_calls(disk_utils_list, disk_utils) - disk_utils_mock.is_block_device.return_value = True - mock_gdi.return_value = '0x12345678' - utils_calls_expected = [mock.call.discovery(self.address, self.port), - mock.call.login_iscsi(self.address, self.port, - self.iqn), - mock.call.logout_iscsi(self.address, self.port, - self.iqn), - mock.call.delete_iscsi(self.address, self.port, - self.iqn)] - - disk_utils_calls_expected = [mock.call.is_block_device(self.dev), - mock.call.populate_image(self.image_path, - self.dev, - conv_flags=None)] - - uuid_dict_returned = iscsi_deploy.deploy_disk_image( - self.address, self.port, self.iqn, self.lun, self.image_path, - self.node_uuid, configdrive=config_url) - - iscsi_mock.assert_has_calls(utils_calls_expected) - disk_utils_mock.assert_has_calls(disk_utils_calls_expected) - create_partition_mock.assert_called_once_with(self.node_uuid, self.dev, - config_url) - self.assertEqual('0x12345678', uuid_dict_returned['disk identifier']) - - @mock.patch.object(disk_utils, 'create_config_drive_partition', - autospec=True) - @mock.patch.object(disk_utils, 'get_disk_identifier', autospec=True) - def test_deploy_whole_disk_image_sparse(self, mock_gdi, - create_config_drive_mock): - """Check loosely all functions are called with right args.""" - iscsi_name_list = ['discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi'] - disk_utils_name_list = ['is_block_device', 'populate_image'] - - iscsi_mock = self._mock_calls(iscsi_name_list, iscsi_deploy) - - disk_utils_mock = self._mock_calls(disk_utils_name_list, disk_utils) - disk_utils_mock.is_block_device.return_value = True - mock_gdi.return_value = '0x12345678' - utils_calls_expected = [mock.call.discovery(self.address, self.port), - mock.call.login_iscsi(self.address, self.port, - self.iqn), - mock.call.logout_iscsi(self.address, self.port, - self.iqn), - mock.call.delete_iscsi(self.address, self.port, - self.iqn)] - disk_utils_calls_expected = [mock.call.is_block_device(self.dev), - mock.call.populate_image( - self.image_path, self.dev, - conv_flags='sparse')] - - uuid_dict_returned = iscsi_deploy.deploy_disk_image( - self.address, self.port, self.iqn, self.lun, self.image_path, - self.node_uuid, configdrive=None, conv_flags='sparse') - - self.assertEqual(utils_calls_expected, iscsi_mock.mock_calls) - self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) - self.assertFalse(create_config_drive_mock.called) - self.assertEqual('0x12345678', uuid_dict_returned['disk identifier']) - - @mock.patch.object(utils, 'execute', autospec=True) - def test_verify_iscsi_connection_raises(self, mock_exec): - iqn = 'iqn.xyz' - mock_exec.return_value = ['iqn.abc', ''] - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.verify_iscsi_connection, iqn) - self.assertEqual(3, mock_exec.call_count) - - @mock.patch.object(utils, 'execute', autospec=True) - def test_verify_iscsi_connection_override_attempts(self, mock_exec): - utils.CONF.set_override('verify_attempts', 2, group='iscsi') - iqn = 'iqn.xyz' - mock_exec.return_value = ['iqn.abc', ''] - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.verify_iscsi_connection, iqn) - self.assertEqual(2, mock_exec.call_count) - - @mock.patch.object(os.path, 'exists', autospec=True) - def test_check_file_system_for_iscsi_device_raises(self, mock_os): - iqn = 'iqn.xyz' - ip = "127.0.0.1" - port = "22" - mock_os.return_value = False - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.check_file_system_for_iscsi_device, - ip, port, iqn) - self.assertEqual(3, mock_os.call_count) - - @mock.patch.object(os.path, 'exists', autospec=True) - def test_check_file_system_for_iscsi_device(self, mock_os): - iqn = 'iqn.xyz' - ip = "127.0.0.1" - port = "22" - check_dir = "/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-1" % (ip, - port, - iqn) - - mock_os.return_value = True - iscsi_deploy.check_file_system_for_iscsi_device(ip, port, iqn) - mock_os.assert_called_once_with(check_dir) - - @mock.patch.object(utils, 'execute', autospec=True) - def test_verify_iscsi_connection(self, mock_exec): - iqn = 'iqn.xyz' - mock_exec.return_value = ['iqn.xyz', ''] - iscsi_deploy.verify_iscsi_connection(iqn) - mock_exec.assert_called_once_with( - 'iscsiadm', - '-m', 'node', - '-S', - run_as_root=True) - - @mock.patch.object(utils, 'execute', autospec=True) - def test_force_iscsi_lun_update(self, mock_exec): - iqn = 'iqn.xyz' - iscsi_deploy.force_iscsi_lun_update(iqn) - mock_exec.assert_called_once_with( - 'iscsiadm', - '-m', 'node', - '-T', iqn, - '-R', - run_as_root=True) - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(iscsi_deploy, 'verify_iscsi_connection', autospec=True) - @mock.patch.object(iscsi_deploy, 'force_iscsi_lun_update', autospec=True) - @mock.patch.object(iscsi_deploy, 'check_file_system_for_iscsi_device', - autospec=True) - def test_login_iscsi_calls_verify_and_update(self, - mock_check_dev, - mock_update, - mock_verify, - mock_exec): - address = '127.0.0.1' - port = 3306 - iqn = 'iqn.xyz' - mock_exec.return_value = ['iqn.xyz', ''] - iscsi_deploy.login_iscsi(address, port, iqn) - mock_exec.assert_called_once_with( - 'iscsiadm', - '-m', 'node', - '-p', '%s:%s' % (address, port), - '-T', iqn, - '--login', - run_as_root=True, - attempts=5, - delay_on_retry=True) - - mock_verify.assert_called_once_with(iqn) - mock_update.assert_called_once_with(iqn) - mock_check_dev.assert_called_once_with(address, port, iqn) - - @mock.patch.object(iscsi_deploy, 'LOG', autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(iscsi_deploy, 'verify_iscsi_connection', autospec=True) - @mock.patch.object(iscsi_deploy, 'force_iscsi_lun_update', autospec=True) - @mock.patch.object(iscsi_deploy, 'check_file_system_for_iscsi_device', - autospec=True) - @mock.patch.object(iscsi_deploy, 'delete_iscsi', autospec=True) - @mock.patch.object(iscsi_deploy, 'logout_iscsi', autospec=True) - def test_login_iscsi_calls_raises( - self, mock_loiscsi, mock_discsi, mock_check_dev, mock_update, - mock_verify, mock_exec, mock_log): - address = '127.0.0.1' - port = 3306 - iqn = 'iqn.xyz' - mock_exec.return_value = ['iqn.xyz', ''] - mock_check_dev.side_effect = exception.InstanceDeployFailure('boom') - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.login_iscsi, - address, port, iqn) - mock_verify.assert_called_once_with(iqn) - mock_update.assert_called_once_with(iqn) - mock_loiscsi.assert_called_once_with(address, port, iqn) - mock_discsi.assert_called_once_with(address, port, iqn) - self.assertIsInstance(mock_log.error.call_args[0][1], - exception.InstanceDeployFailure) - - @mock.patch.object(iscsi_deploy, 'LOG', autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(iscsi_deploy, 'verify_iscsi_connection', autospec=True) - @mock.patch.object(iscsi_deploy, 'force_iscsi_lun_update', autospec=True) - @mock.patch.object(iscsi_deploy, 'check_file_system_for_iscsi_device', - autospec=True) - @mock.patch.object(iscsi_deploy, 'delete_iscsi', autospec=True) - @mock.patch.object(iscsi_deploy, 'logout_iscsi', autospec=True) - def test_login_iscsi_calls_raises_during_cleanup( - self, mock_loiscsi, mock_discsi, mock_check_dev, mock_update, - mock_verify, mock_exec, mock_log): - address = '127.0.0.1' - port = 3306 - iqn = 'iqn.xyz' - mock_exec.return_value = ['iqn.xyz', ''] - mock_check_dev.side_effect = exception.InstanceDeployFailure('boom') - mock_discsi.side_effect = processutils.ProcessExecutionError('boom') - self.assertRaises(exception.InstanceDeployFailure, - iscsi_deploy.login_iscsi, - address, port, iqn) - mock_verify.assert_called_once_with(iqn) - mock_update.assert_called_once_with(iqn) - mock_loiscsi.assert_called_once_with(address, port, iqn) - mock_discsi.assert_called_once_with(address, port, iqn) - self.assertIsInstance(mock_log.error.call_args[0][1], - exception.InstanceDeployFailure) - self.assertIsInstance(mock_log.warning.call_args[0][1], - processutils.ProcessExecutionError) - - @mock.patch.object(disk_utils, 'is_block_device', lambda d: True) - def test_always_logout_and_delete_iscsi(self): - """Check if logout_iscsi() and delete_iscsi() are called. - - Make sure that logout_iscsi() and delete_iscsi() are called once - login_iscsi() is invoked. - - """ - address = '127.0.0.1' - port = 3306 - iqn = 'iqn.xyz' - lun = 1 - image_path = '/tmp/xyz/image' - root_mb = 128 - swap_mb = 64 - ephemeral_mb = 256 - ephemeral_format = 'exttest' - node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - - class TestException(Exception): - pass - - iscsi_name_list = ['discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi'] - - disk_utils_name_list = ['get_image_mb', 'work_on_disk'] - - iscsi_mock = self._mock_calls(iscsi_name_list, iscsi_deploy) - - disk_utils_mock = self._mock_calls(disk_utils_name_list, disk_utils) - disk_utils_mock.get_image_mb.return_value = 1 - disk_utils_mock.work_on_disk.side_effect = TestException - utils_calls_expected = [mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] - disk_utils_calls_expected = [mock.call.get_image_mb(image_path), - mock.call.work_on_disk( - self.dev, root_mb, swap_mb, - ephemeral_mb, - ephemeral_format, image_path, - node_uuid, configdrive=None, - preserve_ephemeral=False, - boot_option="local", - boot_mode="bios", - disk_label=None, - cpu_arch="")] - - self.assertRaises(TestException, iscsi_deploy.deploy_partition_image, - address, port, iqn, lun, image_path, - root_mb, swap_mb, ephemeral_mb, ephemeral_format, - node_uuid) - - self.assertEqual(utils_calls_expected, iscsi_mock.mock_calls) - self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) - - @mock.patch.object(utils, 'execute', autospec=True) - @mock.patch.object(iscsi_deploy, 'verify_iscsi_connection', autospec=True) - @mock.patch.object(iscsi_deploy, 'force_iscsi_lun_update', autospec=True) - @mock.patch.object(iscsi_deploy, 'check_file_system_for_iscsi_device', - autospec=True) - def test_ipv6_address_wrapped(self, - mock_check_dev, - mock_update, - mock_verify, - mock_exec): - address = '2001:DB8::1111' - port = 3306 - iqn = 'iqn.xyz' - mock_exec.return_value = ['iqn.xyz', ''] - iscsi_deploy.login_iscsi(address, port, iqn) - mock_exec.assert_called_once_with( - 'iscsiadm', - '-m', 'node', - '-p', '[%s]:%s' % (address, port), - '-T', iqn, - '--login', - run_as_root=True, - attempts=5, - delay_on_retry=True) - - -@mock.patch.object(disk_utils, 'is_block_device', autospec=True) -@mock.patch.object(iscsi_deploy, 'login_iscsi', lambda *_: None) -@mock.patch.object(iscsi_deploy, 'discovery', lambda *_: None) -@mock.patch.object(iscsi_deploy, 'logout_iscsi', lambda *_: None) -@mock.patch.object(iscsi_deploy, 'delete_iscsi', lambda *_: None) -class ISCSISetupAndHandleErrorsTestCase(tests_base.TestCase): - - def test_no_parent_device(self, mock_ibd): - address = '127.0.0.1' - port = 3306 - iqn = 'iqn.xyz' - lun = 1 - mock_ibd.return_value = False - expected_dev = ("/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-%s" - % (address, port, iqn, lun)) - with testtools.ExpectedException(exception.InstanceDeployFailure): - with iscsi_deploy._iscsi_setup_and_handle_errors( - address, port, iqn, lun) as dev: - self.assertEqual(expected_dev, dev) - - mock_ibd.assert_called_once_with(expected_dev) - - def test_parent_device_yield(self, mock_ibd): - address = '127.0.0.1' - port = 3306 - iqn = 'iqn.xyz' - lun = 1 - expected_dev = ("/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-%s" - % (address, port, iqn, lun)) - mock_ibd.return_value = True - with iscsi_deploy._iscsi_setup_and_handle_errors( - address, port, iqn, lun) as dev: - self.assertEqual(expected_dev, dev) - - mock_ibd.assert_called_once_with(expected_dev) diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py index 4b06a495a3..af05fcd6d6 100644 --- a/ironic/tests/unit/drivers/modules/test_pxe.py +++ b/ironic/tests/unit/drivers/modules/test_pxe.py @@ -79,8 +79,7 @@ class PXEBootTestCase(db_base.DbTestCase): self.config(enabled_boot_interfaces=[self.boot_interface, 'ipxe', 'fake']) - self.config(enabled_deploy_interfaces=['fake', 'direct', 'iscsi', - 'anaconda']) + self.config(enabled_deploy_interfaces=['fake', 'direct', 'anaconda']) self.node = obj_utils.create_test_node( self.context, driver=self.driver, diff --git a/ironic/tests/unit/objects/test_conductor.py b/ironic/tests/unit/objects/test_conductor.py index 6df22c838d..4b3c954904 100644 --- a/ironic/tests/unit/objects/test_conductor.py +++ b/ironic/tests/unit/objects/test_conductor.py @@ -166,9 +166,9 @@ class TestConductorObject(db_base.DbTestCase): def test_register_hardware_interfaces(self): host = self.fake_conductor['hostname'] - self.config(default_deploy_interface='iscsi') + self.config(default_deploy_interface='ansible') arg = [{"hardware_type": "hardware-type", "interface_type": "deploy", - "interface_name": "iscsi", "default": True}, + "interface_name": "ansible", "default": True}, {"hardware_type": "hardware-type", "interface_type": "deploy", "interface_name": "direct", "default": False}] with mock.patch.object(self.dbapi, 'get_conductor', diff --git a/releasenotes/notes/bye-bye-iscsi-658920cf126db0b8.yaml b/releasenotes/notes/bye-bye-iscsi-658920cf126db0b8.yaml new file mode 100644 index 0000000000..4965a63c08 --- /dev/null +++ b/releasenotes/notes/bye-bye-iscsi-658920cf126db0b8.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + The deprecated ``iscsi`` deploy interface has been removed. Please update + to a different deploy interface before upgrading. diff --git a/setup.cfg b/setup.cfg index a6719d46a8..727b536ed3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -90,7 +90,6 @@ ironic.hardware.interfaces.deploy = custom-agent = ironic.drivers.modules.agent:CustomAgentDeploy direct = ironic.drivers.modules.agent:AgentDeploy fake = ironic.drivers.modules.fake:FakeDeploy - iscsi = ironic.drivers.modules.iscsi_deploy:ISCSIDeploy ramdisk = ironic.drivers.modules.pxe:PXERamdiskDeploy ironic.hardware.interfaces.inspect =