From d8dccc8d06017e79f2b49afec069a18b3c7413fc Mon Sep 17 00:00:00 2001
From: Dmitry Tantsur <dtantsur@protonmail.com>
Date: Mon, 7 Sep 2020 18:50:53 +0200
Subject: [PATCH] Deprecate the iscsi deploy interface

This change marks the iscsi deploy interface as deprecated and
stops enabling it by default.

An online data migration is provided for iscsi->direct, provided that:
1) the direct deploy is enabled,
2) image_download_source!=swift.

The CI coverage for iscsi deploy is left only on standalone jobs.

Story: #2008114
Task: #40830
Change-Id: I4a66401b24c49c705861e0745867b7fc706a7509
---
 devstack/lib/ironic                           | 46 ++++++++------
 ironic/cmd/dbsync.py                          |  2 +
 ironic/conf/default.py                        |  2 +-
 ironic/db/api.py                              | 12 ++++
 ironic/db/sqlalchemy/api.py                   | 47 ++++++++++++++
 ironic/drivers/generic.py                     |  2 +-
 ironic/drivers/modules/iscsi_deploy.py        |  2 +
 .../tests/unit/common/test_driver_factory.py  | 12 ++--
 ironic/tests/unit/conductor/test_manager.py   | 63 ++++++++-----------
 ironic/tests/unit/db/test_api.py              | 62 ++++++++++++++++++
 .../modules/intel_ipmi/test_intel_ipmi.py     |  3 +-
 ironic/tests/unit/drivers/test_drac.py        | 12 +---
 ironic/tests/unit/drivers/test_generic.py     |  7 ++-
 ironic/tests/unit/drivers/test_ibmc.py        |  4 +-
 ironic/tests/unit/drivers/test_ilo.py         | 14 ++---
 ironic/tests/unit/drivers/test_ipmi.py        |  5 +-
 ironic/tests/unit/drivers/test_irmc.py        | 11 +---
 ironic/tests/unit/drivers/test_redfish.py     |  4 +-
 ironic/tests/unit/drivers/test_snmp.py        |  4 +-
 ironic/tests/unit/drivers/test_xclarity.py    |  2 -
 .../iscsi-deprecation-eb184141f88e7182.yaml   | 16 +++++
 zuul.d/ironic-jobs.yaml                       | 13 +---
 22 files changed, 226 insertions(+), 119 deletions(-)
 create mode 100644 releasenotes/notes/iscsi-deprecation-eb184141f88e7182.yaml

diff --git a/devstack/lib/ironic b/devstack/lib/ironic
index 76d36fff24..5fea88d2db 100644
--- a/devstack/lib/ironic
+++ b/devstack/lib/ironic
@@ -194,7 +194,7 @@ IRONIC_DRIVER_INTERFACE_TYPES="bios boot power management deploy console inspect
 IRONIC_ENABLED_BIOS_INTERFACES=${IRONIC_ENABLED_BIOS_INTERFACES:-"fake,no-bios"}
 IRONIC_ENABLED_BOOT_INTERFACES=${IRONIC_ENABLED_BOOT_INTERFACES:-"fake,ipxe"}
 IRONIC_ENABLED_CONSOLE_INTERFACES=${IRONIC_ENABLED_CONSOLE_INTERFACES:-"fake,no-console"}
-IRONIC_ENABLED_DEPLOY_INTERFACES=${IRONIC_ENABLED_DEPLOY_INTERFACES:-"fake,iscsi,direct,ramdisk"}
+IRONIC_ENABLED_DEPLOY_INTERFACES=${IRONIC_ENABLED_DEPLOY_INTERFACES:-"fake,direct,ramdisk"}
 IRONIC_ENABLED_INSPECT_INTERFACES=${IRONIC_ENABLED_INSPECT_INTERFACES:-"fake,no-inspect"}
 IRONIC_ENABLED_MANAGEMENT_INTERFACES=${IRONIC_ENABLED_MANAGEMENT_INTERFACES:-"fake,ipmitool,noop"}
 IRONIC_ENABLED_NETWORK_INTERFACES=${IRONIC_ENABLED_NETWORK_INTERFACES:-"flat,noop"}
@@ -754,7 +754,8 @@ function is_ironic_enabled {
 }
 
 function is_deployed_by_agent {
-    [[ -z "${IRONIC_DEPLOY_DRIVER%%agent*}" || "$IRONIC_DEFAULT_DEPLOY_INTERFACE" == "direct" ]] && return 0
+    [[ "$IRONIC_DEFAULT_DEPLOY_INTERFACE" == "direct"
+        || "$IRONIC_DEFAULT_DEPLOY_INTERFACE" == "" ]] && return 0
     return 1
 }
 
@@ -815,6 +816,12 @@ function is_ansible_with_tinyipa {
     return 1
 }
 
+function is_http_server_required {
+    [[ "$IRONIC_IPXE_ENABLED" == "True" ]] && return 0
+    is_deployed_by_agent && [[ "$IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE" != "swift" ]] && return 0
+    return 1
+}
+
 function is_glance_configuration_required {
     # Always configure if we're asked to
     [[ "$IRONIC_CONFIGURE_GLANCE_WITH_SWIFT" == "True" ]] && return 0
@@ -1085,7 +1092,7 @@ function install_ironic {
 
     setup_develop $IRONIC_DIR
 
-    if [[ "$IRONIC_USE_WSGI" == "True" || "$IRONIC_IPXE_ENABLED" == "True" ]]; then
+    if [[ "$IRONIC_USE_WSGI" == "True" ]] || is_http_server_required; then
         install_apache_wsgi
     fi
 
@@ -1148,9 +1155,9 @@ function install_ironicclient {
 
 # _cleanup_ironic_apache_additions() - Remove uwsgi files, disable and remove apache vhost file
 function _cleanup_ironic_apache_additions {
-
-    if [[ "$IRONIC_IPXE_ENABLED" == "True" ]]; then
+    if is_http_server_required; then
         sudo rm -rf $IRONIC_HTTP_DIR
+        # TODO(dtantsur): rename the site, it's also used for direct deploy
         disable_apache_site ipxe-ironic
         sudo rm -f $(apache_site_config_for ipxe-ironic)
     fi
@@ -1160,8 +1167,8 @@ function _cleanup_ironic_apache_additions {
     restart_apache_server
 }
 
-# _config_ironic_apache_ipxe() - Configure ironic IPXE site
-function _config_ironic_apache_ipxe {
+# _config_ironic_apache_additions() - Configure ironic IPXE site
+function _config_ironic_apache_additions {
     local ipxe_apache_conf
     ipxe_apache_conf=$(apache_site_config_for ipxe-ironic)
     sudo cp $IRONIC_DEVSTACK_FILES_DIR/apache-ipxe-ironic.template $ipxe_apache_conf
@@ -1185,7 +1192,7 @@ function cleanup_ironic {
     cleanup_ironic_config_files
 
     # Cleanup additions made to Apache
-    if [[ "$IRONIC_USE_WSGI" == "True" || "$IRONIC_IPXE_ENABLED" == "True" ]]; then
+    if [[ "$IRONIC_USE_WSGI" == "True" ]] || is_http_server_required; then
         _cleanup_ironic_apache_additions
     fi
 
@@ -1204,7 +1211,7 @@ function configure_ironic_dirs {
         $IRONIC_STATE_PATH $IRONIC_TFTPBOOT_DIR $IRONIC_TFTPBOOT_DIR/pxelinux.cfg
     sudo chown -R $STACK_USER:$STACK_USER $IRONIC_TFTPBOOT_DIR
 
-    if [[ "$IRONIC_IPXE_ENABLED" == "True" ]]; then
+    if is_http_server_required; then
         sudo install -d -o $STACK_USER -g $STACK_USER $IRONIC_HTTP_DIR
     fi
 
@@ -1456,9 +1463,9 @@ function configure_ironic {
     # Format logging
     setup_logging $IRONIC_CONF_FILE
 
-    # Adds ironic site for IPXE
-    if [[ "$IRONIC_IPXE_ENABLED" == "True" ]]; then
-        _config_ironic_apache_ipxe
+    # Adds ironic site for IPXE and direct deploy
+    if is_http_server_required; then
+        _config_ironic_apache_additions
     fi
 
     # Adds uWSGI for Ironic API
@@ -1728,14 +1735,17 @@ function configure_ironic_conductor {
     # step  because it is too slow to run in the gate.
     iniset $IRONIC_CONF_FILE deploy erase_devices_priority 0
 
+    if is_http_server_required; then
+        iniset $IRONIC_CONF_FILE deploy http_root $IRONIC_HTTP_DIR
+        iniset $IRONIC_CONF_FILE deploy http_url "http://$([[ $IRONIC_HTTP_SERVER =~ : ]] && echo "[$IRONIC_HTTP_SERVER]" || echo $IRONIC_HTTP_SERVER):$IRONIC_HTTP_PORT"
+    fi
+
     if [[ "$IRONIC_IPXE_ENABLED" == "True" ]] ; then
         local pxebin
         pxebin=`basename $IRONIC_PXE_BOOT_IMAGE`
         uefipxebin=`basename $(get_uefi_ipxe_boot_file)`
         iniset $IRONIC_CONF_FILE pxe ipxe_bootfile_name $pxebin
         iniset $IRONIC_CONF_FILE pxe uefi_ipxe_bootfile_name $uefipxebin
-        iniset $IRONIC_CONF_FILE deploy http_root $IRONIC_HTTP_DIR
-        iniset $IRONIC_CONF_FILE deploy http_url "http://$([[ $IRONIC_HTTP_SERVER =~ : ]] && echo "[$IRONIC_HTTP_SERVER]" || echo $IRONIC_HTTP_SERVER):$IRONIC_HTTP_PORT"
         if [[ "$IRONIC_IPXE_USE_SWIFT" == "True" ]]; then
             iniset $IRONIC_CONF_FILE pxe ipxe_use_swift True
         fi
@@ -1836,8 +1846,8 @@ function start_ironic {
         start_ironic_conductor
     fi
 
-    # Start Apache if iPXE is enabled
-    if [[ "$IRONIC_IPXE_ENABLED" == "True" ]] ; then
+    # Start Apache if iPXE or agent+http is enabled
+    if is_http_server_required; then
         restart_apache_server
     fi
 }
@@ -2589,7 +2599,7 @@ function configure_iptables {
         sudo iptables -I INPUT -d $HOST_IP -p tcp --dport $GLANCE_SERVICE_PORT -j ACCEPT || true
     fi
 
-    if [[ "$IRONIC_IPXE_ENABLED" == "True" ]] ; then
+    if is_http_server_required; then
         sudo iptables -I INPUT -d $IRONIC_HTTP_SERVER -p tcp --dport $IRONIC_HTTP_PORT -j ACCEPT || true
         sudo ip6tables -I INPUT -d $IRONIC_HOST_IPV6 -p tcp --dport $IRONIC_HTTP_PORT -j ACCEPT || true
     fi
@@ -2918,7 +2928,7 @@ function prepare_baremetal_basic_ops {
         return 0
     fi
 
-    if ! is_service_enabled nova && [[ "$IRONIC_IPXE_ENABLED" == "True" ]] ; then
+    if ! is_service_enabled nova && is_http_server_required; then
         local image_file_path
         if [[ ${IRONIC_WHOLEDISK_IMAGE_NAME} =~ \.img$ ]]; then
             image_file_path=$FILES/${IRONIC_WHOLEDISK_IMAGE_NAME}
diff --git a/ironic/cmd/dbsync.py b/ironic/cmd/dbsync.py
index 81524aa969..071d06a540 100644
--- a/ironic/cmd/dbsync.py
+++ b/ironic/cmd/dbsync.py
@@ -64,6 +64,8 @@ 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/default.py b/ironic/conf/default.py
index 0e8a957c83..5cee6be310 100644
--- a/ironic/conf/default.py
+++ b/ironic/conf/default.py
@@ -111,7 +111,7 @@ driver_opts = [
     cfg.StrOpt('default_console_interface',
                help=_DEFAULT_IFACE_HELP.format('console')),
     cfg.ListOpt('enabled_deploy_interfaces',
-                default=['iscsi', 'direct'],
+                default=['direct'],
                 help=_ENABLED_IFACE_HELP.format('deploy')),
     cfg.StrOpt('default_deploy_interface',
                help=_DEFAULT_IFACE_HELP.format('deploy')),
diff --git a/ironic/db/api.py b/ironic/db/api.py
index 33561296e1..48228773fd 100644
--- a/ironic/db/api.py
+++ b/ironic/db/api.py
@@ -950,6 +950,18 @@ 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 374898bdb0..3e859226cb 100644
--- a/ironic/db/sqlalchemy/api.py
+++ b/ironic/db/sqlalchemy/api.py
@@ -1527,6 +1527,53 @@ 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.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 599e1139cf..956119df8f 100644
--- a/ironic/drivers/generic.py
+++ b/ironic/drivers/generic.py
@@ -49,7 +49,7 @@ class GenericHardware(hardware_type.AbstractHardwareType):
     @property
     def supported_deploy_interfaces(self):
         """List of supported deploy interfaces."""
-        return [iscsi_deploy.ISCSIDeploy, agent.AgentDeploy,
+        return [agent.AgentDeploy, iscsi_deploy.ISCSIDeploy,
                 ansible_deploy.AnsibleDeploy, pxe.PXERamdiskDeploy]
 
     @property
diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py
index c1f15d990b..7e36873065 100644
--- a/ironic/drivers/modules/iscsi_deploy.py
+++ b/ironic/drivers/modules/iscsi_deploy.py
@@ -604,6 +604,8 @@ class ISCSIDeploy(agent_base.AgentDeployMixin, agent_base.AgentBaseMixin,
 
     has_decomposed_deploy_steps = True
 
+    supported = False
+
     def get_properties(self):
         return agent_base.VENDOR_PROPERTIES
 
diff --git a/ironic/tests/unit/common/test_driver_factory.py b/ironic/tests/unit/common/test_driver_factory.py
index 682622eecd..f63c0ee6a1 100644
--- a/ironic/tests/unit/common/test_driver_factory.py
+++ b/ironic/tests/unit/common/test_driver_factory.py
@@ -282,22 +282,22 @@ class DefaultInterfaceTestCase(db_base.DbTestCase):
 
     def test_calculated_with_one(self):
         self.config(default_deploy_interface=None)
-        self.config(enabled_deploy_interfaces=['direct'])
+        self.config(enabled_deploy_interfaces=['ansible'])
         iface = driver_factory.default_interface(self.driver, 'deploy')
-        self.assertEqual('direct', iface)
+        self.assertEqual('ansible', iface)
 
     def test_calculated_with_two(self):
         self.config(default_deploy_interface=None)
-        self.config(enabled_deploy_interfaces=['iscsi', 'direct'])
+        self.config(enabled_deploy_interfaces=['ansible', 'direct'])
         iface = driver_factory.default_interface(self.driver, 'deploy')
-        self.assertEqual('iscsi', iface)
+        self.assertEqual('direct', iface)
 
     def test_calculated_with_unsupported(self):
         self.config(default_deploy_interface=None)
         # manual-management doesn't support fake deploy
-        self.config(enabled_deploy_interfaces=['fake', 'direct'])
+        self.config(enabled_deploy_interfaces=['fake', 'ansible'])
         iface = driver_factory.default_interface(self.driver, 'deploy')
-        self.assertEqual('direct', iface)
+        self.assertEqual('ansible', iface)
 
     def test_calculated_no_answer(self):
         # manual-management supports no power interfaces
diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py
index 93e6585685..a45fb82197 100644
--- a/ironic/tests/unit/conductor/test_manager.py
+++ b/ironic/tests/unit/conductor/test_manager.py
@@ -5777,29 +5777,38 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
         super(ManagerTestProperties, self).setUp()
         self.service = manager.ConductorManager('test-host', 'test-topic')
 
-    def _check_driver_properties(self, hw_type, expected):
+    def _check_driver_properties(self, hw_type, expected, agent_common=True,
+                                 pxe_common=True):
         self._start_service()
         properties = self.service.get_driver_properties(self.context, hw_type)
-        self.assertEqual(sorted(expected), sorted(properties))
+        if agent_common:
+            expected.extend(['agent_verify_ca',
+                             'deploy_kernel', 'deploy_ramdisk',
+                             'deploy_forces_oob_reboot',
+                             'image_download_source',
+                             'image_http_proxy', 'image_https_proxy',
+                             'image_no_proxy'])
+        if pxe_common:
+            expected.extend(['force_persistent_boot_device',
+                             'rescue_kernel', 'rescue_ramdisk'])
+        self.assertCountEqual(expected, properties)
 
     def test_driver_properties_fake(self):
         expected = ['B1', 'B2']
-        self._check_driver_properties("fake-hardware", expected)
+        self._check_driver_properties("fake-hardware", expected,
+                                      agent_common=False, pxe_common=False)
 
     def test_driver_properties_ipmi(self):
         self.config(enabled_hardware_types='ipmi',
                     enabled_power_interfaces=['ipmitool'],
                     enabled_management_interfaces=['ipmitool'],
                     enabled_console_interfaces=['ipmitool-socat'])
-        expected = ['agent_verify_ca', 'ipmi_address', 'ipmi_terminal_port',
+        expected = ['ipmi_address', 'ipmi_terminal_port',
                     'ipmi_password', 'ipmi_port', 'ipmi_priv_level',
                     'ipmi_username', 'ipmi_bridging', 'ipmi_transit_channel',
                     'ipmi_transit_address', 'ipmi_target_channel',
                     'ipmi_target_address', 'ipmi_local_address',
-                    'deploy_kernel', 'deploy_ramdisk',
-                    'force_persistent_boot_device', 'ipmi_protocol_version',
-                    'ipmi_force_boot_device', 'deploy_forces_oob_reboot',
-                    'rescue_kernel', 'rescue_ramdisk',
+                    'ipmi_protocol_version', 'ipmi_force_boot_device',
                     'ipmi_disable_boot_timeout', 'ipmi_hex_kg_key',
                     'ipmi_cipher_suite']
         self._check_driver_properties("ipmi", expected)
@@ -5807,18 +5816,14 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
     def test_driver_properties_snmp(self):
         self.config(enabled_hardware_types='snmp',
                     enabled_power_interfaces=['snmp'])
-        expected = ['agent_verify_ca', 'deploy_kernel', 'deploy_ramdisk',
-                    'force_persistent_boot_device',
-                    'rescue_kernel', 'rescue_ramdisk',
-                    'snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
+        expected = ['snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
                     'snmp_community',
                     'snmp_community_read', 'snmp_community_write',
                     'snmp_security', 'snmp_outlet',
                     'snmp_user',
                     'snmp_context_engine_id', 'snmp_context_name',
                     'snmp_auth_key', 'snmp_auth_protocol',
-                    'snmp_priv_key', 'snmp_priv_protocol',
-                    'deploy_forces_oob_reboot']
+                    'snmp_priv_key', 'snmp_priv_protocol']
         self._check_driver_properties("snmp", expected)
 
     def test_driver_properties_ilo(self):
@@ -5828,14 +5833,17 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
                     enabled_boot_interfaces=['ilo-virtual-media'],
                     enabled_inspect_interfaces=['ilo'],
                     enabled_console_interfaces=['ilo'])
-        expected = ['agent_verify_ca', 'ilo_address', 'ilo_username',
+        expected = ['ilo_address', 'ilo_username',
                     'ilo_password', 'client_port', 'client_timeout',
                     'ilo_deploy_iso', 'console_port', 'ilo_change_password',
                     'ca_file', 'snmp_auth_user', 'snmp_auth_prot_password',
                     'snmp_auth_priv_password', 'snmp_auth_protocol',
-                    'snmp_auth_priv_protocol', 'deploy_forces_oob_reboot',
-                    'ilo_verify_ca']
-        self._check_driver_properties("ilo", expected)
+                    'snmp_auth_priv_protocol', 'ilo_verify_ca']
+        self._check_driver_properties("ilo", expected, pxe_common=False)
+
+    def test_driver_properties_manual_management(self):
+        self.config(enabled_hardware_types=['manual-management'])
+        self._check_driver_properties('manual-management', [])
 
     def test_driver_properties_fail(self):
         self.service.init_host()
@@ -5846,25 +5854,6 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
         self.assertEqual(exception.DriverNotFound, exc.exc_info[0])
 
 
-@mgr_utils.mock_record_keepalive
-class ManagerTestHardwareTypeProperties(mgr_utils.ServiceSetUpMixin,
-                                        db_base.DbTestCase):
-
-    def _check_hardware_type_properties(self, hardware_type, expected):
-        self.config(enabled_hardware_types=[hardware_type])
-        self.hardware_type = driver_factory.get_hardware_type(hardware_type)
-        self._start_service()
-        properties = self.service.get_driver_properties(self.context,
-                                                        hardware_type)
-        self.assertEqual(sorted(expected), sorted(properties))
-
-    def test_hardware_type_properties_manual_management(self):
-        expected = ['agent_verify_ca', 'deploy_kernel', 'deploy_ramdisk',
-                    'force_persistent_boot_device', 'deploy_forces_oob_reboot',
-                    'rescue_kernel', 'rescue_ramdisk']
-        self._check_hardware_type_properties('manual-management', expected)
-
-
 @mock.patch.object(waiters, 'wait_for_all', autospec=True)
 @mock.patch.object(manager.ConductorManager, '_spawn_worker', autospec=True)
 @mock.patch.object(manager.ConductorManager, '_sync_power_state_nodes_task',
diff --git a/ironic/tests/unit/db/test_api.py b/ironic/tests/unit/db/test_api.py
index f855e9c91b..9252e1d59b 100644
--- a/ironic/tests/unit/db/test_api.py
+++ b/ironic/tests/unit/db/test_api.py
@@ -237,3 +237,65 @@ 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_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/drivers/modules/intel_ipmi/test_intel_ipmi.py b/ironic/tests/unit/drivers/modules/intel_ipmi/test_intel_ipmi.py
index 199bc7a397..2668839738 100644
--- a/ironic/tests/unit/drivers/modules/intel_ipmi/test_intel_ipmi.py
+++ b/ironic/tests/unit/drivers/modules/intel_ipmi/test_intel_ipmi.py
@@ -14,7 +14,6 @@ from ironic.conductor import task_manager
 from ironic.drivers.modules import agent
 from ironic.drivers.modules.intel_ipmi import management as intel_management
 from ironic.drivers.modules import ipmitool
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
 from ironic.drivers.modules import pxe
 from ironic.drivers.modules.storage import cinder
@@ -47,7 +46,7 @@ class IntelIPMIHardwareTestCase(db_base.DbTestCase):
             kwargs.get('boot', pxe.PXEBoot))
         self.assertIsInstance(
             task.driver.deploy,
-            kwargs.get('deploy', iscsi_deploy.ISCSIDeploy))
+            kwargs.get('deploy', agent.AgentDeploy))
         self.assertIsInstance(
             task.driver.console,
             kwargs.get('console', noop.NoConsole))
diff --git a/ironic/tests/unit/drivers/test_drac.py b/ironic/tests/unit/drivers/test_drac.py
index 8a551070b9..a53bb76cac 100644
--- a/ironic/tests/unit/drivers/test_drac.py
+++ b/ironic/tests/unit/drivers/test_drac.py
@@ -19,7 +19,6 @@ from ironic.drivers.modules import agent
 from ironic.drivers.modules import drac
 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 import noop
 from ironic.drivers.modules.storage import noop as noop_storage
@@ -56,7 +55,7 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
             kwargs.get('boot', ipxe.iPXEBoot))
         self.assertIsInstance(
             driver.deploy,
-            kwargs.get('deploy', iscsi_deploy.ISCSIDeploy))
+            kwargs.get('deploy', agent.AgentDeploy))
         self.assertIsInstance(
             driver.management,
             kwargs.get('management', drac.management.DracWSManManagement))
@@ -100,15 +99,6 @@ class IDRACHardwareTestCase(db_base.DbTestCase):
             self._validate_interfaces(task.driver,
                                       inspect=inspector.Inspector)
 
-    def test_override_with_agent(self):
-        node = obj_utils.create_test_node(self.context, driver='idrac',
-                                          deploy_interface='direct',
-                                          inspect_interface='inspector')
-        with task_manager.acquire(self.context, node.id) as task:
-            self._validate_interfaces(task.driver,
-                                      deploy=agent.AgentDeploy,
-                                      inspect=inspector.Inspector)
-
     def test_override_with_raid(self):
         for iface, impl in [('agent', agent.AgentRAID),
                             ('no-raid', noop.NoRAID)]:
diff --git a/ironic/tests/unit/drivers/test_generic.py b/ironic/tests/unit/drivers/test_generic.py
index d1acb59b88..f2bc2623fc 100644
--- a/ironic/tests/unit/drivers/test_generic.py
+++ b/ironic/tests/unit/drivers/test_generic.py
@@ -21,7 +21,6 @@ from ironic.drivers import base as driver_base
 from ironic.drivers.modules import agent
 from ironic.drivers.modules import fake
 from ironic.drivers.modules import inspector
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
 from ironic.drivers.modules import noop_mgmt
 from ironic.drivers.modules import pxe
@@ -45,7 +44,7 @@ class ManualManagementHardwareTestCase(db_base.DbTestCase):
                                   noop_mgmt.NoopManagement)
             self.assertIsInstance(task.driver.power, fake.FakePower)
             self.assertIsInstance(task.driver.boot, pxe.PXEBoot)
-            self.assertIsInstance(task.driver.deploy, iscsi_deploy.ISCSIDeploy)
+            self.assertIsInstance(task.driver.deploy, agent.AgentDeploy)
             self.assertIsInstance(task.driver.inspect, noop.NoInspect)
             self.assertIsInstance(task.driver.raid, noop.NoRAID)
 
@@ -70,10 +69,12 @@ class ManualManagementHardwareTestCase(db_base.DbTestCase):
         expected_prop_keys = [
             'agent_verify_ca', 'deploy_forces_oob_reboot',
             'deploy_kernel', 'deploy_ramdisk',
+            'image_download_source', 'image_http_proxy',
+            'image_https_proxy', 'image_no_proxy',
             'force_persistent_boot_device', 'rescue_kernel', 'rescue_ramdisk']
         hardware_type = driver_factory.get_hardware_type("manual-management")
         properties = hardware_type.get_properties()
-        self.assertEqual(sorted(expected_prop_keys), sorted(properties))
+        self.assertCountEqual(expected_prop_keys, properties)
 
     @mock.patch.object(driver_factory, 'default_interface', autospec=True)
     def test_get_properties_none(self, mock_def_iface):
diff --git a/ironic/tests/unit/drivers/test_ibmc.py b/ironic/tests/unit/drivers/test_ibmc.py
index 7e1a9fe30e..019b50be6f 100644
--- a/ironic/tests/unit/drivers/test_ibmc.py
+++ b/ironic/tests/unit/drivers/test_ibmc.py
@@ -14,12 +14,12 @@
 # Version 1.0.0
 
 from ironic.conductor import task_manager
+from ironic.drivers.modules import agent
 from ironic.drivers.modules.ibmc import management as ibmc_mgmt
 from ironic.drivers.modules.ibmc import power as ibmc_power
 from ironic.drivers.modules.ibmc import raid as ibmc_raid
 from ironic.drivers.modules.ibmc import vendor as ibmc_vendor
 from ironic.drivers.modules import inspector
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
 from ironic.drivers.modules import pxe
 from ironic.tests.unit.db import base as db_base
@@ -46,7 +46,7 @@ class IBMCHardwareTestCase(db_base.DbTestCase):
                                   ibmc_power.IBMCPower)
             self.assertIsInstance(task.driver.boot, pxe.PXEBoot)
             self.assertIsInstance(task.driver.console, noop.NoConsole)
-            self.assertIsInstance(task.driver.deploy, iscsi_deploy.ISCSIDeploy)
+            self.assertIsInstance(task.driver.deploy, agent.AgentDeploy)
             self.assertIsInstance(task.driver.raid, ibmc_raid.IbmcRAID)
             self.assertIsInstance(task.driver.vendor, ibmc_vendor.IBMCVendor)
             self.assertIsInstance(task.driver.inspect, inspector.Inspector)
diff --git a/ironic/tests/unit/drivers/test_ilo.py b/ironic/tests/unit/drivers/test_ilo.py
index 3e85264363..1b2e10dae5 100644
--- a/ironic/tests/unit/drivers/test_ilo.py
+++ b/ironic/tests/unit/drivers/test_ilo.py
@@ -23,7 +23,6 @@ from ironic.drivers import ilo
 from ironic.drivers.modules import agent
 from ironic.drivers.modules.ilo import raid
 from ironic.drivers.modules import inspector
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
 from ironic.tests.unit.db import base as db_base
 from ironic.tests.unit.objects import utils as obj_utils
@@ -37,7 +36,7 @@ class IloHardwareTestCase(db_base.DbTestCase):
                     enabled_boot_interfaces=['ilo-virtual-media', 'ilo-pxe'],
                     enabled_bios_interfaces=['no-bios', 'ilo'],
                     enabled_console_interfaces=['ilo'],
-                    enabled_deploy_interfaces=['iscsi', 'direct'],
+                    enabled_deploy_interfaces=['ansible', 'direct'],
                     enabled_inspect_interfaces=['ilo'],
                     enabled_management_interfaces=['ilo'],
                     enabled_power_interfaces=['ilo'],
@@ -56,7 +55,7 @@ class IloHardwareTestCase(db_base.DbTestCase):
             self.assertIsInstance(task.driver.console,
                                   ilo.console.IloConsoleInterface)
             self.assertIsInstance(task.driver.deploy,
-                                  iscsi_deploy.ISCSIDeploy)
+                                  agent.AgentDeploy)
             self.assertIsInstance(task.driver.inspect,
                                   ilo.inspect.IloInspect)
             self.assertIsInstance(task.driver.management,
@@ -74,7 +73,6 @@ class IloHardwareTestCase(db_base.DbTestCase):
         self.config(enabled_inspect_interfaces=['inspector', 'ilo'])
         node = obj_utils.create_test_node(
             self.context, driver='ilo',
-            deploy_interface='direct',
             inspect_interface='inspector',
             raid_interface='agent',
             vendor_interface='no-vendor')
@@ -109,7 +107,7 @@ class IloHardwareTestCase(db_base.DbTestCase):
             self.assertIsInstance(task.driver.console,
                                   ilo.console.IloConsoleInterface)
             self.assertIsInstance(task.driver.deploy,
-                                  iscsi_deploy.ISCSIDeploy)
+                                  agent.AgentDeploy)
             self.assertIsInstance(task.driver.inspect,
                                   ilo.inspect.IloInspect)
             self.assertIsInstance(task.driver.management,
@@ -127,7 +125,6 @@ class IloHardwareTestCase(db_base.DbTestCase):
         self.config(enabled_inspect_interfaces=['inspector', 'ilo'])
         node = obj_utils.create_test_node(
             self.context, driver='ilo',
-            deploy_interface='direct',
             rescue_interface='agent',
             raid_interface='agent')
         with task_manager.acquire(self.context, node.id) as task:
@@ -155,7 +152,6 @@ class IloHardwareTestCase(db_base.DbTestCase):
             self.context, driver='ilo',
             boot_interface='ilo-pxe',
             bios_interface='no-bios',
-            deploy_interface='direct',
             raid_interface='agent')
         with task_manager.acquire(self.context, node.id) as task:
             self.assertIsInstance(task.driver.boot,
@@ -177,7 +173,7 @@ class Ilo5HardwareTestCase(db_base.DbTestCase):
         self.config(enabled_hardware_types=['ilo5'],
                     enabled_boot_interfaces=['ilo-virtual-media', 'ilo-pxe'],
                     enabled_console_interfaces=['ilo'],
-                    enabled_deploy_interfaces=['iscsi', 'direct'],
+                    enabled_deploy_interfaces=['ansible', 'direct'],
                     enabled_inspect_interfaces=['ilo'],
                     enabled_management_interfaces=['ilo5'],
                     enabled_power_interfaces=['ilo'],
@@ -193,7 +189,7 @@ class Ilo5HardwareTestCase(db_base.DbTestCase):
             self.assertIsInstance(task.driver.console,
                                   ilo.console.IloConsoleInterface)
             self.assertIsInstance(task.driver.deploy,
-                                  iscsi_deploy.ISCSIDeploy)
+                                  agent.AgentDeploy)
             self.assertIsInstance(task.driver.inspect,
                                   ilo.inspect.IloInspect)
             self.assertIsInstance(task.driver.management,
diff --git a/ironic/tests/unit/drivers/test_ipmi.py b/ironic/tests/unit/drivers/test_ipmi.py
index 9676949717..88cab808fd 100644
--- a/ironic/tests/unit/drivers/test_ipmi.py
+++ b/ironic/tests/unit/drivers/test_ipmi.py
@@ -13,7 +13,6 @@
 from ironic.conductor import task_manager
 from ironic.drivers.modules import agent
 from ironic.drivers.modules import ipmitool
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
 from ironic.drivers.modules import noop_mgmt
 from ironic.drivers.modules import pxe
@@ -46,7 +45,7 @@ class IPMIHardwareTestCase(db_base.DbTestCase):
             kwargs.get('boot', pxe.PXEBoot))
         self.assertIsInstance(
             task.driver.deploy,
-            kwargs.get('deploy', iscsi_deploy.ISCSIDeploy))
+            kwargs.get('deploy', agent.AgentDeploy))
         self.assertIsInstance(
             task.driver.console,
             kwargs.get('console', noop.NoConsole))
@@ -73,14 +72,12 @@ class IPMIHardwareTestCase(db_base.DbTestCase):
                                                 'ipmitool-socat'])
         node = obj_utils.create_test_node(
             self.context, driver='ipmi',
-            deploy_interface='direct',
             raid_interface='agent',
             console_interface='ipmitool-shellinabox',
             vendor_interface='no-vendor')
         with task_manager.acquire(self.context, node.id) as task:
             self._validate_interfaces(
                 task,
-                deploy=agent.AgentDeploy,
                 console=ipmitool.IPMIShellinaboxConsole,
                 raid=agent.AgentRAID,
                 vendor=noop.NoVendor)
diff --git a/ironic/tests/unit/drivers/test_irmc.py b/ironic/tests/unit/drivers/test_irmc.py
index b5f15246fd..64cd5b9640 100644
--- a/ironic/tests/unit/drivers/test_irmc.py
+++ b/ironic/tests/unit/drivers/test_irmc.py
@@ -27,7 +27,6 @@ from ironic.drivers.modules import ipxe
 from ironic.drivers.modules.irmc import bios as irmc_bios
 from ironic.drivers.modules.irmc import boot as irmc_boot
 from ironic.drivers.modules.irmc import raid
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
 from ironic.tests.unit.db import base as db_base
 from ironic.tests.unit.objects import utils as obj_utils
@@ -43,7 +42,7 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
         self.config(enabled_hardware_types=['irmc'],
                     enabled_boot_interfaces=['irmc-virtual-media', 'ipxe'],
                     enabled_console_interfaces=['ipmitool-socat'],
-                    enabled_deploy_interfaces=['iscsi', 'direct'],
+                    enabled_deploy_interfaces=['ansible', 'direct'],
                     enabled_inspect_interfaces=['irmc'],
                     enabled_management_interfaces=['irmc'],
                     enabled_power_interfaces=['irmc', 'ipmitool'],
@@ -59,7 +58,7 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
             self.assertIsInstance(task.driver.console,
                                   ipmitool.IPMISocatConsole)
             self.assertIsInstance(task.driver.deploy,
-                                  iscsi_deploy.ISCSIDeploy)
+                                  agent.AgentDeploy)
             self.assertIsInstance(task.driver.inspect,
                                   irmc.inspect.IRMCInspect)
             self.assertIsInstance(task.driver.management,
@@ -77,7 +76,6 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
         self.config(enabled_inspect_interfaces=['inspector', 'irmc'])
         node = obj_utils.create_test_node(
             self.context, driver='irmc',
-            deploy_interface='direct',
             inspect_interface='inspector',
             raid_interface='agent')
         with task_manager.acquire(self.context, node.id) as task:
@@ -101,7 +99,6 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
     def test_override_with_agent_rescue(self, check_share_fs_mounted_mock):
         node = obj_utils.create_test_node(
             self.context, driver='irmc',
-            deploy_interface='direct',
             rescue_interface='agent',
             raid_interface='agent')
         with task_manager.acquire(self.context, node.id) as task:
@@ -131,7 +128,7 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
             self.assertIsInstance(task.driver.console,
                                   ipmitool.IPMISocatConsole)
             self.assertIsInstance(task.driver.deploy,
-                                  iscsi_deploy.ISCSIDeploy)
+                                  agent.AgentDeploy)
             self.assertIsInstance(task.driver.inspect,
                                   irmc.inspect.IRMCInspect)
             self.assertIsInstance(task.driver.management,
@@ -147,7 +144,6 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
                                               check_share_fs_mounted_mock):
         node = obj_utils.create_test_node(
             self.context, driver='irmc',
-            deploy_interface='direct',
             rescue_interface='agent',
             raid_interface='irmc')
         with task_manager.acquire(self.context, node.id) as task:
@@ -172,7 +168,6 @@ class IRMCHardwareTestCase(db_base.DbTestCase):
                                               check_share_fs_mounted_mock):
         node = obj_utils.create_test_node(
             self.context, driver='irmc',
-            deploy_interface='direct',
             rescue_interface='agent',
             bios_interface='no-bios')
         with task_manager.acquire(self.context, node.id) as task:
diff --git a/ironic/tests/unit/drivers/test_redfish.py b/ironic/tests/unit/drivers/test_redfish.py
index f2675b5c6d..b692c6167c 100644
--- a/ironic/tests/unit/drivers/test_redfish.py
+++ b/ironic/tests/unit/drivers/test_redfish.py
@@ -14,7 +14,7 @@
 #    under the License.
 
 from ironic.conductor import task_manager
-from ironic.drivers.modules import iscsi_deploy
+from ironic.drivers.modules import agent
 from ironic.drivers.modules import noop
 from ironic.drivers.modules.redfish import boot as redfish_boot
 from ironic.drivers.modules.redfish import inspect as redfish_inspect
@@ -46,6 +46,6 @@ class RedfishHardwareTestCase(db_base.DbTestCase):
                                   redfish_power.RedfishPower)
             self.assertIsInstance(task.driver.boot,
                                   redfish_boot.RedfishVirtualMediaBoot)
-            self.assertIsInstance(task.driver.deploy, iscsi_deploy.ISCSIDeploy)
+            self.assertIsInstance(task.driver.deploy, agent.AgentDeploy)
             self.assertIsInstance(task.driver.console, noop.NoConsole)
             self.assertIsInstance(task.driver.raid, noop.NoRAID)
diff --git a/ironic/tests/unit/drivers/test_snmp.py b/ironic/tests/unit/drivers/test_snmp.py
index 10692383cd..385714b33a 100644
--- a/ironic/tests/unit/drivers/test_snmp.py
+++ b/ironic/tests/unit/drivers/test_snmp.py
@@ -16,8 +16,8 @@
 from unittest import mock
 
 from ironic.conductor import task_manager
+from ironic.drivers.modules import agent
 from ironic.drivers.modules import fake
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import noop
 from ironic.drivers.modules import noop_mgmt
 from ironic.drivers.modules import pxe
@@ -39,7 +39,7 @@ class SNMPHardwareTestCase(db_base.DbTestCase):
         with task_manager.acquire(self.context, node.id) as task:
             self.assertIsInstance(task.driver.power, snmp.SNMPPower)
             self.assertIsInstance(task.driver.boot, pxe.PXEBoot)
-            self.assertIsInstance(task.driver.deploy, iscsi_deploy.ISCSIDeploy)
+            self.assertIsInstance(task.driver.deploy, agent.AgentDeploy)
             self.assertIsInstance(task.driver.management,
                                   noop_mgmt.NoopManagement)
             self.assertIsInstance(task.driver.console, noop.NoConsole)
diff --git a/ironic/tests/unit/drivers/test_xclarity.py b/ironic/tests/unit/drivers/test_xclarity.py
index e43eeec7b9..9d7f9e207f 100644
--- a/ironic/tests/unit/drivers/test_xclarity.py
+++ b/ironic/tests/unit/drivers/test_xclarity.py
@@ -18,7 +18,6 @@ Test class for XClarity Driver
 
 from ironic.conductor import task_manager
 from ironic.drivers.modules import agent
-from ironic.drivers.modules import iscsi_deploy
 from ironic.drivers.modules import pxe
 from ironic.drivers.xclarity import management as xc_management
 from ironic.drivers.xclarity import power as xc_power
@@ -40,7 +39,6 @@ class XClarityHardwareTestCase(db_base.DbTestCase):
             self.assertIsInstance(task.driver.boot,
                                   pxe.PXEBoot)
             self.assertIsInstance(task.driver.deploy,
-                                  iscsi_deploy.ISCSIDeploy,
                                   agent.AgentDeploy)
             self.assertIsInstance(task.driver.management,
                                   xc_management.XClarityManagement)
diff --git a/releasenotes/notes/iscsi-deprecation-eb184141f88e7182.yaml b/releasenotes/notes/iscsi-deprecation-eb184141f88e7182.yaml
new file mode 100644
index 0000000000..b2d8924534
--- /dev/null
+++ b/releasenotes/notes/iscsi-deprecation-eb184141f88e7182.yaml
@@ -0,0 +1,16 @@
+---
+upgrade:
+  - |
+    The deprecated ``iscsi`` deploy interface is no longer enabled by default,
+    set ``enabled_deploy_interfaces`` to override. It is also no longer
+    the first in the list of deploy interface priorities, so it has to be
+    requested explicitly if the ``direct`` deploy is also enabled.
+  - |
+    Since the ``direct`` deploy interface is now used by default, you need to
+    configure ``[deploy]http_url`` and ``[deploy]http_root`` to point at a
+    local HTTP server or configure access to Swift.
+deprecations:
+  - |
+    The ``iscsi`` deploy interface is now deprecated, ``direct`` or ``ansible``
+    deploy should be used instead. We expected the complete removal of the
+    ``iscsi`` deploy code to happen in the "X" release.
diff --git a/zuul.d/ironic-jobs.yaml b/zuul.d/ironic-jobs.yaml
index a120aaf6d5..5d520e3bf8 100644
--- a/zuul.d/ironic-jobs.yaml
+++ b/zuul.d/ironic-jobs.yaml
@@ -53,7 +53,6 @@
         # each of them. For don't need all 10 GiB for CirrOS anyway.
         IRONIC_VM_SPECS_DISK: 4
         IRONIC_VM_SPECS_CPU: 2
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: iscsi
 
         Q_AGENT: openvswitch
         Q_ML2_TENANT_NETWORK_TYPE: vxlan
@@ -114,10 +113,10 @@
         FORCE_CONFIG_DRIVE: False
         IRONIC_AGENT_IMAGE_DOWNLOAD_SOURCE: http
         IRONIC_AUTOMATED_CLEAN_ENABLED: False
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_DEFAULT_RESCUE_INTERFACE: agent
+        IRONIC_ENABLED_HARDWARE_TYPES: ipmi
         IRONIC_ENABLED_DEPLOY_INTERFACES: "iscsi,direct,ramdisk"
-        IRONIC_ENABLED_RESCUE_INTERFACES: "fake,agent,no-rescue"
+        IRONIC_ENABLED_RESCUE_INTERFACES: "agent,no-rescue"
         IRONIC_JSON_RPC_AUTH_STRATEGY: 'http_basic'
         IRONIC_RAMDISK_TYPE: tinyipa
         IRONIC_RPC_TRANSPORT: json-rpc
@@ -177,7 +176,6 @@
         IRONIC_ENABLED_HARDWARE_TYPES: redfish
         IRONIC_ENABLED_POWER_INTERFACES: redfish
         IRONIC_ENABLED_MANAGEMENT_INTERFACES: redfish
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_ENABLED_DEPLOY_INTERFACES: "iscsi,direct,ansible,ramdisk"
         IRONIC_RPC_TRANSPORT: json-rpc
         IRONIC_RAMDISK_TYPE: tinyipa
@@ -284,7 +282,6 @@
     parent: ironic-base
     vars:
       devstack_localrc:
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_DEFAULT_RESCUE_INTERFACE: agent
         IRONIC_ENABLED_RESCUE_INTERFACES: "fake,agent,no-rescue"
         IRONIC_TEMPEST_WHOLE_DISK_IMAGE: True
@@ -343,7 +340,6 @@
         IRONIC_STORAGE_INTERFACE: cinder
         IRONIC_ENABLED_BOOT_INTERFACES: ipxe,pxe,fake
         IRONIC_DEFAULT_BOOT_INTERFACE: ipxe
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_TEMPEST_WHOLE_DISK_IMAGE: True
         IRONIC_VM_EPHEMERAL_DISK: 0
         IRONIC_VM_COUNT: 3
@@ -366,7 +362,6 @@
     vars:
       tempest_test_regex: InspectorBasicTest
       devstack_localrc:
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_INSPECTOR_MANAGE_FIREWALL: True
         IRONIC_TEMPEST_WHOLE_DISK_IMAGE: True
         IRONIC_VM_EPHEMERAL_DISK: 0
@@ -463,7 +458,6 @@
         IRONIC_BAREMETAL_BASIC_OPS: True
         IRONIC_BUILD_DEPLOY_RAMDISK: False
         IRONIC_CALLBACK_TIMEOUT: 600
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_DEFAULT_BOOT_OPTION: local
         IRONIC_DEPLOY_DRIVER: ipmi
         IRONIC_ENABLED_NETWORK_INTERFACES: flat,neutron
@@ -654,7 +648,6 @@
         s-object: True
         s-proxy: True
       devstack_localrc:
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_DIB_RAMDISK_OS: centos8
         IRONIC_TEMPEST_WHOLE_DISK_IMAGE: True
         IRONIC_VM_EPHEMERAL_DISK: 0
@@ -702,7 +695,6 @@
         TENANT_VLAN_RANGE: 100:150
         IRONIC_ENABLED_NETWORK_INTERFACES: flat,neutron
         IRONIC_NETWORK_INTERFACE: neutron
-        IRONIC_DEFAILT_DEPLOY_INTERFACE: direct
         IRONIC_DEFAILT_RESCUE_INTERFACE: no-rescue
         IRONIC_USE_LINK_LOCAL: True
         IRONIC_TEMPEST_WHOLE_DISK_IMAGE: True
@@ -732,7 +724,6 @@
     parent: ironic-base
     vars:
       devstack_localrc:
-        IRONIC_DEFAULT_DEPLOY_INTERFACE: direct
         IRONIC_DEFAULT_RESCUE_INTERFACE: agent
         IRONIC_ENABLED_RESCUE_INTERFACES: "fake,agent,no-rescue"
         IRONIC_RAMDISK_TYPE: tinyipa