Merge "Refactoring: move iSCSI deploy code to iscsi_deploy.py"
This commit is contained in:
commit
70f3cd2aa4
@ -14,20 +14,15 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import glob
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ironic_lib import disk_utils
|
|
||||||
from ironic_lib import metrics_utils
|
from ironic_lib import metrics_utils
|
||||||
from ironic_lib import utils as il_utils
|
from ironic_lib import utils as il_utils
|
||||||
from oslo_concurrency import processutils
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
from oslo_utils import netutils
|
|
||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
@ -86,12 +81,6 @@ def _get_ironic_session():
|
|||||||
return _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():
|
def get_ironic_api_url():
|
||||||
"""Resolve Ironic API endpoint
|
"""Resolve Ironic API endpoint
|
||||||
|
|
||||||
@ -127,145 +116,6 @@ def rescue_or_deploy_mode(node):
|
|||||||
else 'deploy')
|
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):
|
def _replace_lines_in_file(path, regex_pattern, replacement):
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
lines = f.readlines()
|
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)
|
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=''):
|
def check_for_missing_params(info_dict, error_msg, param_prefix=''):
|
||||||
"""Check for empty params in the provided dictionary.
|
"""Check for empty params in the provided dictionary.
|
||||||
|
|
||||||
|
@ -13,17 +13,24 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import time
|
||||||
from urllib import parse as urlparse
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
from ironic_lib import disk_utils
|
from ironic_lib import disk_utils
|
||||||
from ironic_lib import metrics_utils
|
from ironic_lib import metrics_utils
|
||||||
|
from oslo_concurrency import processutils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
|
from oslo_utils import netutils
|
||||||
|
|
||||||
from ironic.common import dhcp_factory
|
from ironic.common import dhcp_factory
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
|
from ironic.common import utils
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.conductor import utils as manager_utils
|
from ironic.conductor import utils as manager_utils
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
@ -58,6 +65,276 @@ def _save_disk_layout(node, i_info):
|
|||||||
node.save()
|
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')
|
@METRICS.timer('check_image_size')
|
||||||
def check_image_size(task):
|
def check_image_size(task):
|
||||||
"""Check if the requested image is larger than the root partition size.
|
"""Check if the requested image is larger than the root partition size.
|
||||||
@ -203,9 +480,9 @@ def continue_deploy(task, **kwargs):
|
|||||||
uuid_dict_returned = {}
|
uuid_dict_returned = {}
|
||||||
try:
|
try:
|
||||||
if node.driver_internal_info['is_whole_disk_image']:
|
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:
|
else:
|
||||||
uuid_dict_returned = deploy_utils.deploy_partition_image(**params)
|
uuid_dict_returned = deploy_partition_image(**params)
|
||||||
except exception.IronicException as e:
|
except exception.IronicException as e:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.error('Deploy of instance %(instance)s on node %(node)s '
|
LOG.error('Deploy of instance %(instance)s on node %(node)s '
|
||||||
|
@ -16,17 +16,12 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
|
||||||
import types
|
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
from ironic_lib import disk_utils
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_concurrency import processutils
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import testtools
|
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
from ironic.common import exception
|
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):
|
class SwitchPxeConfigTestCase(tests_base.TestCase):
|
||||||
|
|
||||||
# NOTE(TheJulia): Remove elilo support after the deprecation period,
|
# 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,
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
boot_interface='pxe')
|
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(utils, 'LOG', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'deploying_error_handler', 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))
|
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):
|
class ValidateImagePropertiesTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
@mock.patch.object(image_service, 'get_image_service', autospec=True)
|
||||||
|
@ -17,12 +17,16 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import time
|
||||||
|
import types
|
||||||
|
|
||||||
from ironic_lib import disk_utils
|
from ironic_lib import disk_utils
|
||||||
from ironic_lib import utils as ironic_utils
|
from ironic_lib import utils as ironic_utils
|
||||||
import mock
|
import mock
|
||||||
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
|
import testtools
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
from ironic.common import dhcp_factory
|
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 import pxe
|
||||||
from ironic.drivers.modules.storage import noop as noop_storage
|
from ironic.drivers.modules.storage import noop as noop_storage
|
||||||
from ironic.drivers import utils as driver_utils
|
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 base as db_base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
from ironic.tests.unit.objects import utils as obj_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(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', 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(
|
def test_continue_deploy_fail(
|
||||||
self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout,
|
self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout,
|
||||||
mock_collect_logs):
|
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(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', 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(
|
def test_continue_deploy_unexpected_fail(
|
||||||
self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout,
|
self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout,
|
||||||
mock_collect_logs):
|
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(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', 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(
|
def test_continue_deploy_fail_no_root_uuid_or_disk_id(
|
||||||
self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout,
|
self, deploy_mock, power_mock, mock_image_cache, mock_disk_layout,
|
||||||
mock_collect_logs):
|
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(iscsi_deploy, '_save_disk_layout', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', 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(
|
def test_continue_deploy_fail_empty_root_uuid(
|
||||||
self, deploy_mock, power_mock, mock_image_cache,
|
self, deploy_mock, power_mock, mock_image_cache,
|
||||||
mock_disk_layout, mock_collect_logs):
|
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(iscsi_deploy, 'get_deploy_info', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', 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,
|
def test_continue_deploy(self, deploy_mock, power_mock, mock_image_cache,
|
||||||
mock_deploy_info, mock_log, mock_disk_layout):
|
mock_deploy_info, mock_log, mock_disk_layout):
|
||||||
kwargs = {'address': '123456', 'iqn': 'aaa-bbb'}
|
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(iscsi_deploy, 'get_deploy_info', autospec=True)
|
||||||
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
@mock.patch.object(deploy_utils, 'InstanceImageCache', autospec=True)
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', 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(
|
def test_continue_deploy_whole_disk_image(
|
||||||
self, deploy_mock, power_mock, mock_image_cache, mock_deploy_info,
|
self, deploy_mock, power_mock, mock_image_cache, mock_deploy_info,
|
||||||
mock_log):
|
mock_log):
|
||||||
@ -1284,3 +1289,551 @@ class CleanUpFullFlowTestCase(db_base.DbTestCase):
|
|||||||
+ self.files):
|
+ self.files):
|
||||||
self.assertFalse(os.path.exists(path),
|
self.assertFalse(os.path.exists(path),
|
||||||
'%s is not expected to exist' % 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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user