summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2020-03-17 22:25:46 +0000
committerGerrit Code Review <review@openstack.org>2020-03-17 22:25:46 +0000
commit70f3cd2aa4b461aaad23e6badf7446eb9c23f501 (patch)
treea937533c4b4629454c345081926e9d5fe5b051cb
parent3eb0c164011a744126330de44413ab704b55ea83 (diff)
parentfb79ea2c4018ea190ee5202cb45221868ee8e31c (diff)
downloadironic-70f3cd2aa4b461aaad23e6badf7446eb9c23f501.tar.gz
Merge "Refactoring: move iSCSI deploy code to iscsi_deploy.py"
-rw-r--r--ironic/drivers/modules/deploy_utils.py281
-rw-r--r--ironic/drivers/modules/iscsi_deploy.py281
-rw-r--r--ironic/tests/unit/drivers/modules/test_deploy_utils.py573
-rw-r--r--ironic/tests/unit/drivers/modules/test_iscsi_deploy.py565
4 files changed, 838 insertions, 862 deletions
diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index 301f84ed7..582c33423 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -14,20 +14,15 @@
# under the License.
-import contextlib
-import glob
import os
import re
import time
-from ironic_lib import disk_utils
from ironic_lib import metrics_utils
from ironic_lib import utils as il_utils
-from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import fileutils
-from oslo_utils import netutils
from oslo_utils import strutils
from ironic.common import exception
@@ -86,12 +81,6 @@ def _get_ironic_session():
return _IRONIC_SESSION
-def _wrap_ipv6(ip):
- if netutils.is_valid_ipv6(ip):
- return "[%s]" % ip
- return ip
-
-
def get_ironic_api_url():
"""Resolve Ironic API endpoint
@@ -127,145 +116,6 @@ def rescue_or_deploy_mode(node):
else 'deploy')
-def discovery(portal_address, portal_port):
- """Do iSCSI discovery on portal."""
- utils.execute('iscsiadm',
- '-m', 'discovery',
- '-t', 'st',
- '-p', '%s:%s' % (_wrap_ipv6(portal_address), portal_port),
- run_as_root=True,
- check_exit_code=[0],
- 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' % (_wrap_ipv6(portal_address), portal_port),
- '-T', target_iqn,
- '--login',
- run_as_root=True,
- check_exit_code=[0],
- 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,
- check_exit_code=[0])
- 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,
- check_exit_code=[0])
-
-
-def logout_iscsi(portal_address, portal_port, target_iqn):
- """Logout from an iSCSI target."""
- utils.execute('iscsiadm',
- '-m', 'node',
- '-p', '%s:%s' % (_wrap_ipv6(portal_address), portal_port),
- '-T', target_iqn,
- '--logout',
- run_as_root=True,
- check_exit_code=[0],
- 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' % (_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)
-
-
def _replace_lines_in_file(path, regex_pattern, replacement):
with open(path) as f:
lines = f.readlines()
@@ -343,137 +193,6 @@ def switch_pxe_config(path, root_uuid_or_disk_id, boot_mode,
iscsi_boot, ramdisk_boot, ipxe_enabled)
-def get_dev(address, port, iqn, lun):
- """Returns a device path for given parameters."""
- dev = ("/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-%s"
- % (address, port, iqn, lun))
- return dev
-
-
-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.
- """
- boot_option = boot_option or 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}
-
-
-@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 = get_dev(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 check_for_missing_params(info_dict, error_msg, param_prefix=''):
"""Check for empty params in the provided dictionary.
diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py
index cb885ae51..f47b8a956 100644
--- a/ironic/drivers/modules/iscsi_deploy.py
+++ b/ironic/drivers/modules/iscsi_deploy.py
@@ -13,17 +13,24 @@
# 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 oslo_utils import netutils
from ironic.common import dhcp_factory
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
@@ -58,6 +65,276 @@ def _save_disk_layout(node, i_info):
node.save()
+def _wrap_ipv6(ip):
+ if netutils.is_valid_ipv6(ip):
+ return "[%s]" % ip
+ return ip
+
+
+def discovery(portal_address, portal_port):
+ """Do iSCSI discovery on portal."""
+ utils.execute('iscsiadm',
+ '-m', 'discovery',
+ '-t', 'st',
+ '-p', '%s:%s' % (_wrap_ipv6(portal_address), portal_port),
+ run_as_root=True,
+ check_exit_code=[0],
+ 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' % (_wrap_ipv6(portal_address), portal_port),
+ '-T', target_iqn,
+ '--login',
+ run_as_root=True,
+ check_exit_code=[0],
+ 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,
+ check_exit_code=[0])
+ 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,
+ check_exit_code=[0])
+
+
+def logout_iscsi(portal_address, portal_port, target_iqn):
+ """Logout from an iSCSI target."""
+ utils.execute('iscsiadm',
+ '-m', 'node',
+ '-p', '%s:%s' % (_wrap_ipv6(portal_address), portal_port),
+ '-T', target_iqn,
+ '--logout',
+ run_as_root=True,
+ check_exit_code=[0],
+ 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' % (_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.
+ """
+ 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.
@@ -203,9 +480,9 @@ def continue_deploy(task, **kwargs):
uuid_dict_returned = {}
try:
if node.driver_internal_info['is_whole_disk_image']:
- uuid_dict_returned = deploy_utils.deploy_disk_image(**params)
+ uuid_dict_returned = deploy_disk_image(**params)
else:
- uuid_dict_returned = deploy_utils.deploy_partition_image(**params)
+ 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 '
diff --git a/ironic/tests/unit/drivers/modules/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
index 3d5ee4e1c..be2472c89 100644
--- a/ironic/tests/unit/drivers/modules/test_deploy_utils.py
+++ b/ironic/tests/unit/drivers/modules/test_deploy_utils.py
@@ -16,17 +16,12 @@
import os
import tempfile
-import time
-import types
import fixtures
-from ironic_lib import disk_utils
import mock
-from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_utils import fileutils
from oslo_utils import uuidutils
-import testtools
from ironic.common import boot_devices
from ironic.common import exception
@@ -345,533 +340,6 @@ menuentry "boot_whole_disk" {
"""
-@mock.patch.object(time, 'sleep', lambda seconds: None)
-class PhysicalWorkTestCase(tests_base.TestCase):
-
- 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(utils, 'logout_iscsi', autospec=True)
- @mock.patch.object(utils, 'login_iscsi', autospec=True)
- @mock.patch.object(utils, 'get_dev', autospec=True)
- @mock.patch.object(utils, 'discovery', autospec=True)
- @mock.patch.object(utils, 'delete_iscsi', autospec=True)
- def _test_deploy_partition_image(self,
- mock_delete_iscsi,
- mock_discovery,
- mock_get_dev,
- 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)
-
- address = '127.0.0.1'
- port = 3306
- iqn = 'iqn.xyz'
- lun = 1
- image_path = '/tmp/xyz/image'
- node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
- dev = '/dev/fake'
- root_uuid = '12345678-1234-1234-12345678-12345678abcdef'
-
- mock_get_dev.return_value = dev
- 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']
- }
- utils.deploy_partition_image(
- address, port, iqn, lun, image_path, deploy_args['root_mb'],
- deploy_args['swap_mb'], deploy_args['ephemeral_mb'],
- deploy_args['ephemeral_format'], node_uuid, **deploy_kwargs)
-
- mock_get_dev.assert_called_once_with(address, port, iqn, lun)
- mock_discovery.assert_called_once_with(address, port)
- mock_login_iscsi.assert_called_once_with(address, port, iqn)
- mock_logout_iscsi.assert_called_once_with(address, port, iqn)
- mock_delete_iscsi.assert_called_once_with(address, port, iqn)
- mock_get_image_mb.assert_called_once_with(image_path)
- mock_is_block_device.assert_called_once_with(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 'netboot',
- '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(
- dev, deploy_args['root_mb'], deploy_args['swap_mb'],
- deploy_args['ephemeral_mb'], deploy_args['ephemeral_format'],
- image_path, 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."""
- address = '127.0.0.1'
- port = 3306
- iqn = 'iqn.xyz'
- lun = 1
- image_path = '/tmp/xyz/image'
- node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
-
- dev = '/dev/fake'
- utils_name_list = ['get_dev', 'discovery', 'login_iscsi',
- 'logout_iscsi', 'delete_iscsi']
- disk_utils_name_list = ['is_block_device', 'populate_image']
-
- utils_mock = self._mock_calls(utils_name_list, utils)
- utils_mock.get_dev.return_value = dev
-
- 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.get_dev(address, port, iqn, lun),
- 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.is_block_device(dev),
- mock.call.populate_image(image_path, dev,
- conv_flags=None)]
- uuid_dict_returned = utils.deploy_disk_image(address, port, iqn, lun,
- image_path, node_uuid)
-
- self.assertEqual(utils_calls_expected, utils_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."""
- address = '127.0.0.1'
- port = 3306
- iqn = 'iqn.xyz'
- lun = 1
- image_path = '/tmp/xyz/image'
- node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
- config_url = 'http://1.2.3.4/cd'
-
- dev = '/dev/fake'
- utils_list = ['get_dev', 'discovery', 'login_iscsi', 'logout_iscsi',
- 'delete_iscsi']
-
- disk_utils_list = ['is_block_device', 'populate_image']
- utils_mock = self._mock_calls(utils_list, utils)
- disk_utils_mock = self._mock_calls(disk_utils_list, disk_utils)
- utils_mock.get_dev.return_value = dev
- disk_utils_mock.is_block_device.return_value = True
- mock_gdi.return_value = '0x12345678'
- utils_calls_expected = [mock.call.get_dev(address, port, iqn, lun),
- 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.is_block_device(dev),
- mock.call.populate_image(image_path, dev,
- conv_flags=None)]
-
- uuid_dict_returned = utils.deploy_disk_image(address, port, iqn, lun,
- image_path, node_uuid,
- configdrive=config_url)
-
- utils_mock.assert_has_calls(utils_calls_expected)
- disk_utils_mock.assert_has_calls(disk_utils_calls_expected)
- create_partition_mock.assert_called_once_with(node_uuid, 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."""
- address = '127.0.0.1'
- port = 3306
- iqn = 'iqn.xyz'
- lun = 1
- image_path = '/tmp/xyz/image'
- node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
-
- dev = '/dev/fake'
- utils_name_list = ['get_dev', 'discovery', 'login_iscsi',
- 'logout_iscsi', 'delete_iscsi']
- disk_utils_name_list = ['is_block_device', 'populate_image']
-
- utils_mock = self._mock_calls(utils_name_list, utils)
- utils_mock.get_dev.return_value = dev
-
- 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.get_dev(address, port, iqn, lun),
- 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.is_block_device(dev),
- mock.call.populate_image(
- image_path, dev, conv_flags='sparse')]
-
- uuid_dict_returned = utils.deploy_disk_image(address, port, iqn, lun,
- image_path, node_uuid,
- configdrive=None,
- conv_flags='sparse')
-
- self.assertEqual(utils_calls_expected, utils_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(common_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,
- utils.verify_iscsi_connection, iqn)
- self.assertEqual(3, mock_exec.call_count)
-
- @mock.patch.object(common_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,
- utils.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,
- utils.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
- utils.check_file_system_for_iscsi_device(ip, port, iqn)
- mock_os.assert_called_once_with(check_dir)
-
- @mock.patch.object(common_utils, 'execute', autospec=True)
- def test_verify_iscsi_connection(self, mock_exec):
- iqn = 'iqn.xyz'
- mock_exec.return_value = ['iqn.xyz', '']
- utils.verify_iscsi_connection(iqn)
- mock_exec.assert_called_once_with(
- 'iscsiadm',
- '-m', 'node',
- '-S',
- run_as_root=True,
- check_exit_code=[0])
-
- @mock.patch.object(common_utils, 'execute', autospec=True)
- def test_force_iscsi_lun_update(self, mock_exec):
- iqn = 'iqn.xyz'
- utils.force_iscsi_lun_update(iqn)
- mock_exec.assert_called_once_with(
- 'iscsiadm',
- '-m', 'node',
- '-T', iqn,
- '-R',
- run_as_root=True,
- check_exit_code=[0])
-
- @mock.patch.object(common_utils, 'execute', autospec=True)
- @mock.patch.object(utils, 'verify_iscsi_connection', autospec=True)
- @mock.patch.object(utils, 'force_iscsi_lun_update', autospec=True)
- @mock.patch.object(utils, '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', '']
- utils.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,
- check_exit_code=[0],
- 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(utils, 'LOG', autospec=True)
- @mock.patch.object(common_utils, 'execute', autospec=True)
- @mock.patch.object(utils, 'verify_iscsi_connection', autospec=True)
- @mock.patch.object(utils, 'force_iscsi_lun_update', autospec=True)
- @mock.patch.object(utils, 'check_file_system_for_iscsi_device',
- autospec=True)
- @mock.patch.object(utils, 'delete_iscsi', autospec=True)
- @mock.patch.object(utils, '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,
- utils.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(utils, 'LOG', autospec=True)
- @mock.patch.object(common_utils, 'execute', autospec=True)
- @mock.patch.object(utils, 'verify_iscsi_connection', autospec=True)
- @mock.patch.object(utils, 'force_iscsi_lun_update', autospec=True)
- @mock.patch.object(utils, 'check_file_system_for_iscsi_device',
- autospec=True)
- @mock.patch.object(utils, 'delete_iscsi', autospec=True)
- @mock.patch.object(utils, '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,
- utils.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"
-
- dev = '/dev/fake'
-
- class TestException(Exception):
- pass
-
- utils_name_list = ['get_dev', 'discovery', 'login_iscsi',
- 'logout_iscsi', 'delete_iscsi']
-
- disk_utils_name_list = ['get_image_mb', 'work_on_disk']
-
- utils_mock = self._mock_calls(utils_name_list, utils)
- utils_mock.get_dev.return_value = dev
-
- 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.get_dev(address, port, iqn, lun),
- 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(
- dev, root_mb, swap_mb,
- ephemeral_mb,
- ephemeral_format, image_path,
- node_uuid, configdrive=None,
- preserve_ephemeral=False,
- boot_option="netboot",
- boot_mode="bios",
- disk_label=None,
- cpu_arch="")]
-
- self.assertRaises(TestException, utils.deploy_partition_image,
- address, port, iqn, lun, image_path,
- root_mb, swap_mb, ephemeral_mb, ephemeral_format,
- node_uuid)
-
- self.assertEqual(utils_calls_expected, utils_mock.mock_calls)
- self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls)
-
- @mock.patch.object(common_utils, 'execute', autospec=True)
- @mock.patch.object(utils, 'verify_iscsi_connection', autospec=True)
- @mock.patch.object(utils, 'force_iscsi_lun_update', autospec=True)
- @mock.patch.object(utils, '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', '']
- utils.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,
- check_exit_code=[0],
- attempts=5,
- delay_on_retry=True)
-
-
class SwitchPxeConfigTestCase(tests_base.TestCase):
# NOTE(TheJulia): Remove elilo support after the deprecation period,
@@ -1138,11 +606,6 @@ class OtherFunctionTestCase(db_base.DbTestCase):
self.node = obj_utils.create_test_node(self.context,
boot_interface='pxe')
- def test_get_dev(self):
- expected = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
- actual = utils.get_dev('1.2.3.4', 5678, 'iqn.fake', 9)
- self.assertEqual(expected, actual)
-
@mock.patch.object(utils, 'LOG', autospec=True)
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
@mock.patch.object(manager_utils, 'deploying_error_handler', autospec=True)
@@ -1779,42 +1242,6 @@ class AgentMethodsTestCase(db_base.DbTestCase):
utils.direct_deploy_should_convert_raw_image(self.node))
-@mock.patch.object(disk_utils, 'is_block_device', autospec=True)
-@mock.patch.object(utils, 'login_iscsi', lambda *_: None)
-@mock.patch.object(utils, 'discovery', lambda *_: None)
-@mock.patch.object(utils, 'logout_iscsi', lambda *_: None)
-@mock.patch.object(utils, 'delete_iscsi', lambda *_: None)
-@mock.patch.object(utils, 'get_dev', lambda *_: '/dev/fake')
-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/fake'
- with testtools.ExpectedException(exception.InstanceDeployFailure):
- with utils._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/fake'
- mock_ibd.return_value = True
- with utils._iscsi_setup_and_handle_errors(
- address, port, iqn, lun) as dev:
- self.assertEqual(expected_dev, dev)
-
- mock_ibd.assert_called_once_with(expected_dev)
-
-
class ValidateImagePropertiesTestCase(db_base.DbTestCase):
@mock.patch.object(image_service, 'get_image_service', autospec=True)
diff --git a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
index adf4fc247..3c382f792 100644
--- a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
+++ b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py
@@ -17,12 +17,16 @@
import os
import tempfile
+import time
+import types
from ironic_lib import disk_utils
from ironic_lib import utils as ironic_utils
import mock
+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
@@ -41,6 +45,7 @@ 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
@@ -187,7 +192,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@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(deploy_utils, 'deploy_partition_image', 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):
@@ -220,7 +225,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@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(deploy_utils, 'deploy_partition_image', 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):
@@ -251,7 +256,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@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(deploy_utils, 'deploy_partition_image', 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):
@@ -281,7 +286,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@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(deploy_utils, 'deploy_partition_image', 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):
@@ -312,7 +317,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@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(deploy_utils, 'deploy_partition_image', 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'}
@@ -364,7 +369,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase):
@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(deploy_utils, 'deploy_disk_image', 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):
@@ -1284,3 +1289,551 @@ class CleanUpFullFlowTestCase(db_base.DbTestCase):
+ 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 'netboot',
+ '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,
+ check_exit_code=[0])
+
+ @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,
+ check_exit_code=[0])
+
+ @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,
+ check_exit_code=[0],
+ 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="netboot",
+ 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,
+ check_exit_code=[0],
+ 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)