From 929907d68473ae8a433ebb8c4dcb110473d42676 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Mon, 3 May 2021 17:03:29 +0200 Subject: [PATCH] Bye-bye iSCSI deploy, you served us well The iSCSI deploy was very easy to start with, but it has since become apparently that it suffers from scalability and maintenance issues. It was deprecated in the Victoria cycle and can now be removed. Hide the guide to upgrade to hardware types since it's very outdated. I had to remove the iBMC diagram since my SVG-fu is not enough to fix it. Change-Id: I2cd6bf7b27fe0be2c08104b0cc37654b506b2e62 --- .../source/samples/driver-get-response.json | 4 +- .../samples/drivers-list-detail-response.json | 4 +- .../samples/nodes-list-details-response.json | 2 +- bindep.txt | 1 - doc/source/admin/drivers.rst | 2 +- doc/source/admin/drivers/ibmc.rst | 9 - doc/source/admin/drivers/idrac.rst | 2 +- doc/source/admin/drivers/ilo.rst | 20 +- doc/source/admin/drivers/ipa.rst | 25 +- doc/source/admin/drivers/irmc.rst | 2 +- doc/source/admin/interfaces/boot.rst | 4 +- doc/source/admin/interfaces/deploy.rst | 25 +- doc/source/admin/node-deployment.rst | 8 +- doc/source/admin/notifications.rst | 12 +- doc/source/admin/ramdisk-boot.rst | 2 +- doc/source/admin/troubleshooting.rst | 30 +- doc/source/admin/tuning.rst | 2 +- doc/source/contributor/dev-quickstart.rst | 13 +- .../contributor/ironic-boot-from-volume.rst | 1 - .../ironic-multitenant-networking.rst | 1 - .../ironic_standalone_with_ibmc_driver.svg | 1309 ------------ doc/source/index.rst | 1 - doc/source/install/configure-iscsi.rst | 5 - .../install/configure-tenant-networks.rst | 4 +- doc/source/install/enabling-drivers.rst | 19 +- doc/source/install/enrollment.rst | 10 +- doc/source/install/refarch/common.rst | 17 - doc/source/install/setup-drivers.rst | 1 - doc/source/user/index.rst | 60 +- etc/ironic/rootwrap.d/ironic-utils.filters | 3 - ironic/cmd/dbsync.py | 2 - ironic/conf/__init__.py | 4 +- ironic/conf/iscsi.py | 44 - ironic/conf/opts.py | 1 - ironic/db/api.py | 12 - ironic/db/sqlalchemy/api.py | 53 - ironic/drivers/generic.py | 7 +- ironic/drivers/modules/agent_client.py | 28 - ironic/drivers/modules/iscsi_deploy.py | 813 -------- ironic/objects/conductor.py | 2 +- ironic/tests/base.py | 2 +- .../unit/api/controllers/v1/test_driver.py | 8 +- ironic/tests/unit/common/test_hash_ring.py | 6 +- .../tests/unit/conductor/test_base_manager.py | 14 +- ironic/tests/unit/conductor/test_manager.py | 4 +- ironic/tests/unit/conductor/test_rpcapi.py | 14 +- ironic/tests/unit/db/test_api.py | 70 - ironic/tests/unit/db/test_conductor.py | 14 +- .../unit/drivers/modules/ilo/test_boot.py | 2 +- .../drivers/modules/ilo/test_management.py | 2 +- .../unit/drivers/modules/ilo/test_raid.py | 2 +- .../unit/drivers/modules/test_agent_client.py | 40 - .../unit/drivers/modules/test_iscsi_deploy.py | 1816 ----------------- ironic/tests/unit/drivers/modules/test_pxe.py | 3 +- ironic/tests/unit/objects/test_conductor.py | 4 +- .../notes/bye-bye-iscsi-658920cf126db0b8.yaml | 5 + setup.cfg | 1 - 57 files changed, 116 insertions(+), 4455 deletions(-) delete mode 100644 doc/source/images/ironic_standalone_with_ibmc_driver.svg delete mode 100644 doc/source/install/configure-iscsi.rst delete mode 100644 ironic/conf/iscsi.py delete mode 100644 ironic/drivers/modules/iscsi_deploy.py delete mode 100644 ironic/tests/unit/drivers/modules/test_iscsi_deploy.py create mode 100644 releasenotes/notes/bye-bye-iscsi-658920cf126db0b8.yaml 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 =