diff options
author | Faizan Barmawer <faizan.barmawer@gmail.com> | 2015-05-20 01:16:37 -0700 |
---|---|---|
committer | Faizan Barmawer <faizan.barmawer@gmail.com> | 2015-12-09 22:03:35 -0800 |
commit | 7c138f01a0085e49aab240eb2f7fc63601c348ff (patch) | |
tree | 5694cefda3466e793fb77e77bfe48dcff8066505 | |
parent | 7b13f3b0a6dce57de61d23a5130835570da6658b (diff) | |
download | ironic-7c138f01a0085e49aab240eb2f7fc63601c348ff.tar.gz |
Refactor disk partitioner code from ironic and use ironic-lib.
The disk partitioner related code from ironic/common/disk_partitioner.py
and ironic/drivers/modules/deploy_utils.py is being moved to ironic-lib.
The code in ironic needs to be removed and use ironic-lib to perform
disk related activities.
Change-Id: I7b1b1d8b45b947a7b5715f3c6ab49d84e84b6b90
-rw-r--r-- | etc/ironic/ironic.conf.sample | 45 | ||||
-rw-r--r-- | etc/ironic/rootwrap.d/ironic-lib.filters | 19 | ||||
-rw-r--r-- | etc/ironic/rootwrap.d/ironic-utils.filters | 7 | ||||
-rw-r--r-- | ironic/common/disk_partitioner.py | 226 | ||||
-rw-r--r-- | ironic/drivers/modules/deploy_utils.py | 478 | ||||
-rw-r--r-- | ironic/drivers/modules/iscsi_deploy.py | 3 | ||||
-rw-r--r-- | ironic/tests/unit/common/test_disk_partitioner.py | 198 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/test_deploy_utils.py | 1053 | ||||
-rw-r--r-- | ironic/tests/unit/drivers/modules/test_iscsi_deploy.py | 5 | ||||
-rw-r--r-- | releasenotes/notes/refactor-ironic-lib-22939896d8d46a77.yaml | 20 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | tools/config/oslo.config.generator.rc | 2 |
12 files changed, 423 insertions, 1634 deletions
diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index f55f67e6a..4c7a3abbb 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -908,18 +908,6 @@ # Options defined in ironic.drivers.modules.deploy_utils # -# Size of EFI system partition in MiB when configuring UEFI -# systems for local boot. (integer value) -#efi_system_partition_size=200 - -# Block size to use when writing to the nodes disk. (string -# value) -#dd_block_size=1M - -# Maximum attempts to verify an iSCSI connection is active, -# sleeping 1 second between attempts. (integer value) -#iscsi_verify_attempts=3 - # ironic-conductor node's HTTP server URL. Example: # http://192.1.2.3:8080 (string value) # Deprecated group/name - [pxe]/http_url @@ -956,7 +944,7 @@ [disk_partitioner] # -# Options defined in ironic.common.disk_partitioner +# Options defined in ironic_lib.disk_partitioner # # After Ironic has completed creating the partition table, it @@ -972,6 +960,25 @@ #check_device_max_retries=20 +[disk_utils] + +# +# Options defined in ironic_lib.disk_utils +# + +# Size of EFI system partition in MiB when configuring UEFI +# systems for local boot. (integer value) +#efi_system_partition_size=200 + +# Block size to use when writing to the nodes disk. (string +# value) +#dd_block_size=1M + +# Maximum attempts to verify an iSCSI connection is active, +# sleeping 1 second between attempts. (integer value) +#iscsi_verify_attempts=3 + + [drac] # @@ -1281,6 +1288,18 @@ #sensor_method=ipmitool +[ironic_lib] + +# +# Options defined in ironic_lib.utils +# + +# Command that is prefixed to commands that are run as root. +# If not specified, no commands are run as root. (string +# value) +#root_helper=sudo ironic-rootwrap /etc/ironic/rootwrap.conf + + [keystone] # diff --git a/etc/ironic/rootwrap.d/ironic-lib.filters b/etc/ironic/rootwrap.d/ironic-lib.filters new file mode 100644 index 000000000..acce7a7b6 --- /dev/null +++ b/etc/ironic/rootwrap.d/ironic-lib.filters @@ -0,0 +1,19 @@ +# An ironic-lib.filters to be used with rootwrap command. +# The following commands should be used in filters for disk manipulation. +# This file should be owned by (and only-writeable by) the root user. + +[Filters] +# ironic_lib/disk_utils.py +blkid: CommandFilter, blkid, root +blockdev: CommandFilter, blockdev, root +hexdump: CommandFilter, hexdump, root +qemu-img: CommandFilter, qemu-img, root + +# ironic_lib/utils.py +mkswap: CommandFilter, mkswap, root +mkfs: CommandFilter, mkfs, root +dd: CommandFilter, dd, root + +# ironic_lib/disk_partitioner.py +fuser: CommandFilter, fuser, root +parted: CommandFilter, parted, root diff --git a/etc/ironic/rootwrap.d/ironic-utils.filters b/etc/ironic/rootwrap.d/ironic-utils.filters index 972ddf5cf..1c7eb858e 100644 --- a/etc/ironic/rootwrap.d/ironic-utils.filters +++ b/etc/ironic/rootwrap.d/ironic-utils.filters @@ -4,9 +4,6 @@ [Filters] # ironic/drivers/modules/deploy_utils.py iscsiadm: CommandFilter, iscsiadm, root -blkid: CommandFilter, blkid, root -blockdev: CommandFilter, blockdev, root -hexdump: CommandFilter, hexdump, root # ironic/common/utils.py mkswap: CommandFilter, mkswap, root @@ -14,7 +11,3 @@ mkfs: CommandFilter, mkfs, root mount: CommandFilter, mount, root umount: CommandFilter, umount, root dd: CommandFilter, dd, root - -# ironic/common/disk_partitioner.py -fuser: CommandFilter, fuser, root -parted: CommandFilter, parted, root diff --git a/ironic/common/disk_partitioner.py b/ironic/common/disk_partitioner.py deleted file mode 100644 index f80c8c257..000000000 --- a/ironic/common/disk_partitioner.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import re - -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_log import log as logging -from oslo_service import loopingcall - -from ironic.common import exception -from ironic.common.i18n import _ -from ironic.common.i18n import _LW -from ironic.common import utils - -opts = [ - cfg.IntOpt('check_device_interval', - default=1, - help=_('After Ironic has completed creating the partition ' - 'table, it continues to check for activity on the ' - 'attached iSCSI device status at this interval prior ' - 'to copying the image to the node, in seconds')), - cfg.IntOpt('check_device_max_retries', - default=20, - help=_('The maximum number of times to check that the device ' - 'is not accessed by another process. If the device is ' - 'still busy after that, the disk partitioning will be ' - 'treated as having failed.')), -] - -CONF = cfg.CONF -opt_group = cfg.OptGroup(name='disk_partitioner', - title='Options for the disk partitioner') -CONF.register_group(opt_group) -CONF.register_opts(opts, opt_group) - -LOG = logging.getLogger(__name__) - - -class DiskPartitioner(object): - - def __init__(self, device, disk_label='msdos', alignment='optimal'): - """A convenient wrapper around the parted tool. - - :param device: The device path. - :param disk_label: The type of the partition table. Valid types are: - "bsd", "dvh", "gpt", "loop", "mac", "msdos", - "pc98", or "sun". - :param alignment: Set alignment for newly created partitions. - Valid types are: none, cylinder, minimal and - optimal. - - """ - self._device = device - self._disk_label = disk_label - self._alignment = alignment - self._partitions = [] - self._fuser_pids_re = re.compile(r'((\d)+\s*)+') - - def _exec(self, *args): - # NOTE(lucasagomes): utils.execute() is already a wrapper on top - # of processutils.execute() which raises specific - # exceptions. It also logs any failure so we don't - # need to log it again here. - utils.execute('parted', '-a', self._alignment, '-s', self._device, - '--', 'unit', 'MiB', *args, check_exit_code=[0], - use_standard_locale=True, run_as_root=True) - - def add_partition(self, size, part_type='primary', fs_type='', - bootable=False): - """Add a partition. - - :param size: The size of the partition in MiB. - :param part_type: The type of the partition. Valid values are: - primary, logical, or extended. - :param fs_type: The filesystem type. Valid types are: ext2, fat32, - fat16, HFS, linux-swap, NTFS, reiserfs, ufs. - If blank (''), it will create a Linux native - partition (83). - :param bootable: Boolean value; whether the partition is bootable - or not. - :returns: The partition number. - - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please - # also do the same modification in ironic-lib - self._partitions.append({'size': size, - 'type': part_type, - 'fs_type': fs_type, - 'bootable': bootable}) - return len(self._partitions) - - def get_partitions(self): - """Get the partitioning layout. - - :returns: An iterator with the partition number and the - partition layout. - - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please - # also do the same modification in ironic-lib - return enumerate(self._partitions, 1) - - def _wait_for_disk_to_become_available(self, retries, max_retries, pids, - stderr): - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please - # also do the same modification in ironic-lib - retries[0] += 1 - if retries[0] > max_retries: - raise loopingcall.LoopingCallDone() - - try: - # NOTE(ifarkas): fuser returns a non-zero return code if none of - # the specified files is accessed - out, err = utils.execute('fuser', self._device, - check_exit_code=[0, 1], run_as_root=True) - - if not out and not err: - raise loopingcall.LoopingCallDone() - else: - if err: - stderr[0] = err - if out: - pids_match = re.search(self._fuser_pids_re, out) - pids[0] = pids_match.group() - except processutils.ProcessExecutionError as exc: - LOG.warning(_LW('Failed to check the device %(device)s with fuser:' - ' %(err)s'), {'device': self._device, 'err': exc}) - - def commit(self): - """Write to the disk.""" - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please - # also do the same modification in ironic-lib - LOG.debug("Committing partitions to disk.") - cmd_args = ['mklabel', self._disk_label] - # NOTE(lucasagomes): Lead in with 1MiB to allow room for the - # partition table itself. - start = 1 - for num, part in self.get_partitions(): - end = start + part['size'] - cmd_args.extend(['mkpart', part['type'], part['fs_type'], - str(start), str(end)]) - if part['bootable']: - cmd_args.extend(['set', str(num), 'boot', 'on']) - start = end - - self._exec(*cmd_args) - - retries = [0] - pids = [''] - fuser_err = [''] - interval = CONF.disk_partitioner.check_device_interval - max_retries = CONF.disk_partitioner.check_device_max_retries - - timer = loopingcall.FixedIntervalLoopingCall( - self._wait_for_disk_to_become_available, - retries, max_retries, pids, fuser_err) - timer.start(interval=interval).wait() - - if retries[0] > max_retries: - if pids[0]: - raise exception.InstanceDeployFailure( - _('Disk partitioning failed on device %(device)s. ' - 'Processes with the following PIDs are holding it: ' - '%(pids)s. Time out waiting for completion.') - % {'device': self._device, 'pids': pids[0]}) - else: - raise exception.InstanceDeployFailure( - _('Disk partitioning failed on device %(device)s. Fuser ' - 'exited with "%(fuser_err)s". Time out waiting for ' - 'completion.') - % {'device': self._device, 'fuser_err': fuser_err[0]}) - - -_PARTED_PRINT_RE = re.compile(r"^(\d+):([\d\.]+)MiB:" - "([\d\.]+)MiB:([\d\.]+)MiB:(\w*)::(\w*)") - - -def list_partitions(device): - """Get partitions information from given device. - - :param device: The device path. - :returns: list of dictionaries (one per partition) with keys: - number, start, end, size (in MiB), filesystem, flags - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - output = utils.execute( - 'parted', '-s', '-m', device, 'unit', 'MiB', 'print', - use_standard_locale=True, run_as_root=True)[0] - if isinstance(output, bytes): - output = output.decode("utf-8") - lines = [line for line in output.split('\n') if line.strip()][2:] - # Example of line: 1:1.00MiB:501MiB:500MiB:ext4::boot - fields = ('number', 'start', 'end', 'size', 'filesystem', 'flags') - result = [] - for line in lines: - match = _PARTED_PRINT_RE.match(line) - if match is None: - LOG.warning(_LW("Partition information from parted for device " - "%(device)s does not match " - "expected format: %(line)s"), - dict(device=device, line=line)) - continue - # Cast int fields to ints (some are floats and we round them down) - groups = [int(float(x)) if i < 4 else x - for i, x in enumerate(match.groups())] - result.append(dict(zip(fields, groups))) - return result diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py index 432095979..3b9d93b30 100644 --- a/ironic/drivers/modules/deploy_utils.py +++ b/ironic/drivers/modules/deploy_utils.py @@ -14,38 +14,28 @@ # under the License. -import base64 import contextlib -import gzip -import math import os import re -import shutil import socket -import stat -import tempfile import time +from ironic_lib import disk_utils from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils -from oslo_utils import units -import requests import six from six.moves.urllib import parse from ironic.common import dhcp_factory -from ironic.common import disk_partitioner from ironic.common import exception from ironic.common.glance_service import service_utils from ironic.common.i18n import _ from ironic.common.i18n import _LE -from ironic.common.i18n import _LI from ironic.common.i18n import _LW from ironic.common import image_service -from ironic.common import images from ironic.common import keystone from ironic.common import states from ironic.common import utils @@ -57,17 +47,6 @@ from ironic import objects deploy_opts = [ - cfg.IntOpt('efi_system_partition_size', - default=200, - help=_('Size of EFI system partition in MiB when configuring ' - 'UEFI systems for local boot.')), - cfg.StrOpt('dd_block_size', - default='1M', - help=_('Block size to use when writing to the nodes disk.')), - cfg.IntOpt('iscsi_verify_attempts', - default=3, - help=_('Maximum attempts to verify an iSCSI connection is ' - 'active, sleeping 1 second between attempts.')), cfg.StrOpt('http_url', help='ironic-conductor node\'s HTTP server URL. ' 'Example: http://192.1.2.3:8080', @@ -95,6 +74,14 @@ deploy_opts = [ CONF = cfg.CONF CONF.register_opts(deploy_opts, group='deploy') +# TODO(Faizan): Move this logic to common/utils.py and deprecate +# rootwrap_config. +# This is required to set the default value of ironic_lib option +# only if rootwrap_config does not contain the default value. +if CONF.rootwrap_config != '/etc/ironic/rootwrap.conf': + root_helper = 'sudo ironic-rootwrap %s' % CONF.rootwrap_config + CONF.set_default('root_helper', root_helper, 'ironic_lib') + LOG = logging.getLogger(__name__) VALID_ROOT_DEVICE_HINTS = set(('size', 'model', 'wwn', 'serial', 'vendor', @@ -151,7 +138,7 @@ def check_file_system_for_iscsi_device(portal_address, check_dir = "/dev/disk/by-path/ip-%s:%s-iscsi-%s-lun-1" % (portal_address, portal_port, target_iqn) - total_checks = CONF.deploy.iscsi_verify_attempts + total_checks = CONF.disk_utils.iscsi_verify_attempts for attempt in range(total_checks): if os.path.exists(check_dir): break @@ -171,7 +158,7 @@ def verify_iscsi_connection(target_iqn): """Verify iscsi connection.""" LOG.debug("Checking for iSCSI target to become active.") - for attempt in range(CONF.deploy.iscsi_verify_attempts): + for attempt in range(CONF.disk_utils.iscsi_verify_attempts): out, _err = utils.execute('iscsiadm', '-m', 'node', '-S', @@ -183,10 +170,10 @@ def verify_iscsi_connection(target_iqn): LOG.debug("iSCSI connection not active. Rechecking. Attempt " "%(attempt)d out of %(total)d", {"attempt": attempt + 1, - "total": CONF.deploy.iscsi_verify_attempts}) + "total": CONF.disk_utils.iscsi_verify_attempts}) else: msg = _("iSCSI connection did not become active after attempting to " - "verify %d times.") % CONF.deploy.iscsi_verify_attempts + "verify %d times.") % CONF.disk_utils.iscsi_verify_attempts LOG.error(msg) raise exception.InstanceDeployFailure(msg) @@ -231,163 +218,6 @@ def delete_iscsi(portal_address, portal_port, target_iqn): delay_on_retry=True) -def get_disk_identifier(dev): - """Get the disk identifier from the disk being exposed by the ramdisk. - - This disk identifier is appended to the pxe config which will then be - used by chain.c32 to detect the correct disk to chainload. This is helpful - in deployments to nodes with multiple disks. - - http://www.syslinux.org/wiki/index.php/Comboot/chain.c32#mbr: - - :param dev: Path for the already populated disk device. - :returns The Disk Identifier. - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - disk_identifier = utils.execute('hexdump', '-s', '440', '-n', '4', - '-e', '''\"0x%08x\"''', - dev, - run_as_root=True, - check_exit_code=[0], - attempts=5, - delay_on_retry=True) - return disk_identifier[0] - - -def make_partitions(dev, root_mb, swap_mb, ephemeral_mb, - configdrive_mb, node_uuid, commit=True, - boot_option="netboot", boot_mode="bios"): - """Partition the disk device. - - Create partitions for root, swap, ephemeral and configdrive on a - disk device. - - :param root_mb: Size of the root partition in mebibytes (MiB). - :param swap_mb: Size of the swap partition in mebibytes (MiB). If 0, - no partition will be created. - :param ephemeral_mb: Size of the ephemeral partition in mebibytes (MiB). - If 0, no partition will be created. - :param configdrive_mb: Size of the configdrive partition in - mebibytes (MiB). If 0, no partition will be created. - :param commit: True/False. Default for this setting is True. If False - partitions will not be written to disk. - :param boot_option: Can be "local" or "netboot". "netboot" by default. - :param boot_mode: Can be "bios" or "uefi". "bios" by default. - :param node_uuid: Node's uuid. Used for logging. - :returns: A dictionary containing the partition type as Key and partition - path as Value for the partitions created by this method. - - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - LOG.debug("Starting to partition the disk device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'node': node_uuid}) - part_template = dev + '-part%d' - part_dict = {} - - # For uefi localboot, switch partition table to gpt and create the efi - # system partition as the first partition. - if boot_mode == "uefi" and boot_option == "local": - dp = disk_partitioner.DiskPartitioner(dev, disk_label="gpt") - part_num = dp.add_partition(CONF.deploy.efi_system_partition_size, - fs_type='fat32', - bootable=True) - part_dict['efi system partition'] = part_template % part_num - else: - dp = disk_partitioner.DiskPartitioner(dev) - - if ephemeral_mb: - LOG.debug("Add ephemeral partition (%(size)d MB) to device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'size': ephemeral_mb, 'node': node_uuid}) - part_num = dp.add_partition(ephemeral_mb) - part_dict['ephemeral'] = part_template % part_num - if swap_mb: - LOG.debug("Add Swap partition (%(size)d MB) to device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'size': swap_mb, 'node': node_uuid}) - part_num = dp.add_partition(swap_mb, fs_type='linux-swap') - part_dict['swap'] = part_template % part_num - if configdrive_mb: - LOG.debug("Add config drive partition (%(size)d MB) to device: " - "%(dev)s for node %(node)s", - {'dev': dev, 'size': configdrive_mb, 'node': node_uuid}) - part_num = dp.add_partition(configdrive_mb) - part_dict['configdrive'] = part_template % part_num - - # NOTE(lucasagomes): Make the root partition the last partition. This - # enables tools like cloud-init's growroot utility to expand the root - # partition until the end of the disk. - LOG.debug("Add root partition (%(size)d MB) to device: %(dev)s " - "for node %(node)s", - {'dev': dev, 'size': root_mb, 'node': node_uuid}) - part_num = dp.add_partition(root_mb, bootable=(boot_option == "local" and - boot_mode == "bios")) - part_dict['root'] = part_template % part_num - - if commit: - # write to the disk - dp.commit() - return part_dict - - -def is_block_device(dev): - """Check whether a device is block or not.""" - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - attempts = CONF.deploy.iscsi_verify_attempts - for attempt in range(attempts): - try: - s = os.stat(dev) - except OSError as e: - LOG.debug("Unable to stat device %(dev)s. Attempt %(attempt)d " - "out of %(total)d. Error: %(err)s", - {"dev": dev, "attempt": attempt + 1, - "total": attempts, "err": e}) - time.sleep(1) - else: - return stat.S_ISBLK(s.st_mode) - msg = _("Unable to stat device %(dev)s after attempting to verify " - "%(attempts)d times.") % {'dev': dev, 'attempts': attempts} - LOG.error(msg) - raise exception.InstanceDeployFailure(msg) - - -def dd(src, dst): - """Execute dd from src to dst.""" - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - utils.dd(src, dst, 'bs=%s' % CONF.deploy.dd_block_size, 'oflag=direct') - - -def populate_image(src, dst): - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - data = images.qemu_img_info(src) - if data.file_format == 'raw': - dd(src, dst) - else: - images.convert_image(src, dst, 'raw', True) - - -def block_uuid(dev): - """Get UUID of a block device.""" - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - out, _err = utils.execute('blkid', '-s', 'UUID', '-o', 'value', dev, - run_as_root=True, - check_exit_code=[0]) - return out.strip() - - def _replace_lines_in_file(path, regex_pattern, replacement): with open(path) as f: lines = f.readlines() @@ -468,278 +298,6 @@ def get_dev(address, port, iqn, lun): return dev -def get_image_mb(image_path, virtual_size=True): - """Get size of an image in Megabyte.""" - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - mb = 1024 * 1024 - if not virtual_size: - image_byte = os.path.getsize(image_path) - else: - image_byte = images.converted_size(image_path) - # round up size to MB - image_mb = int((image_byte + mb - 1) / mb) - return image_mb - - -def get_dev_block_size(dev): - """Get the device size in 512 byte sectors.""" - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - block_sz, cmderr = utils.execute('blockdev', '--getsz', dev, - run_as_root=True, check_exit_code=[0]) - return int(block_sz) - - -def destroy_disk_metadata(dev, node_uuid): - """Destroy metadata structures on node's disk. - - Ensure that node's disk appears to be blank without zeroing the entire - drive. To do this we will zero: - - the first 18KiB to clear MBR / GPT data - - the last 18KiB to clear GPT and other metadata like: LVM, veritas, - MDADM, DMRAID, ... - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - - # NOTE(NobodyCam): This is needed to work around bug: - # https://bugs.launchpad.net/ironic/+bug/1317647 - LOG.debug("Start destroy disk metadata for node %(node)s.", - {'node': node_uuid}) - try: - utils.dd('/dev/zero', dev, 'bs=512', 'count=36') - except processutils.ProcessExecutionError as err: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to erase beginning of disk for node " - "%(node)s. Command: %(command)s. Error: %(error)s."), - {'node': node_uuid, - 'command': err.cmd, - 'error': err.stderr}) - - # now wipe the end of the disk. - # get end of disk seek value - try: - block_sz = get_dev_block_size(dev) - except processutils.ProcessExecutionError as err: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to get disk block count for node %(node)s. " - "Command: %(command)s. Error: %(error)s."), - {'node': node_uuid, - 'command': err.cmd, - 'error': err.stderr}) - else: - seek_value = block_sz - 36 - try: - utils.dd('/dev/zero', dev, 'bs=512', 'count=36', - 'seek=%d' % seek_value) - except processutils.ProcessExecutionError as err: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to erase the end of the disk on node " - "%(node)s. Command: %(command)s. " - "Error: %(error)s."), - {'node': node_uuid, - 'command': err.cmd, - 'error': err.stderr}) - LOG.info(_LI("Disk metadata on %(dev)s successfully destroyed for node " - "%(node)s"), {'dev': dev, 'node': node_uuid}) - - -def _get_configdrive(configdrive, node_uuid): - """Get the information about size and location of the configdrive. - - :param configdrive: Base64 encoded Gzipped configdrive content or - configdrive HTTP URL. - :param node_uuid: Node's uuid. Used for logging. - :raises: InstanceDeployFailure if it can't download or decode the - config drive. - :returns: A tuple with the size in MiB and path to the uncompressed - configdrive file. - - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - # Check if the configdrive option is a HTTP URL or the content directly - is_url = utils.is_http_url(configdrive) - if is_url: - try: - data = requests.get(configdrive).content - except requests.exceptions.RequestException as e: - raise exception.InstanceDeployFailure( - _("Can't download the configdrive content for node %(node)s " - "from '%(url)s'. Reason: %(reason)s") % - {'node': node_uuid, 'url': configdrive, 'reason': e}) - else: - data = configdrive - - try: - data = six.BytesIO(base64.b64decode(data)) - except TypeError: - error_msg = (_('Config drive for node %s is not base64 encoded ' - 'or the content is malformed.') % node_uuid) - if is_url: - error_msg += _(' Downloaded from "%s".') % configdrive - raise exception.InstanceDeployFailure(error_msg) - - configdrive_file = tempfile.NamedTemporaryFile(delete=False, - prefix='configdrive', - dir=CONF.tempdir) - configdrive_mb = 0 - with gzip.GzipFile('configdrive', 'rb', fileobj=data) as gunzipped: - try: - shutil.copyfileobj(gunzipped, configdrive_file) - except EnvironmentError as e: - # Delete the created file - utils.unlink_without_raise(configdrive_file.name) - raise exception.InstanceDeployFailure( - _('Encountered error while decompressing and writing ' - 'config drive for node %(node)s. Error: %(exc)s') % - {'node': node_uuid, 'exc': e}) - else: - # Get the file size and convert to MiB - configdrive_file.seek(0, os.SEEK_END) - bytes_ = configdrive_file.tell() - configdrive_mb = int(math.ceil(float(bytes_) / units.Mi)) - finally: - configdrive_file.close() - - return (configdrive_mb, configdrive_file.name) - - -def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format, - image_path, node_uuid, preserve_ephemeral=False, - configdrive=None, boot_option="netboot", - boot_mode="bios"): - """Create partitions and copy an image to the root partition. - - :param dev: Path for the device to work on. - :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 image_path: Path for the instance's disk image. - :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. - :returns: a dictionary containing the following keys: - 'root uuid': UUID of root partition - 'efi system partition uuid': UUID of the uefi system partition - (if boot mode is uefi). - NOTE: If key exists but value is None, it means partition doesn't - exist. - """ - # NOTE(jlvillal): This function has been moved to ironic-lib. And is - # planned to be deleted here. If need to modify this function, please also - # do the same modification in ironic-lib - - # the only way for preserve_ephemeral to be set to true is if we are - # rebuilding an instance with --preserve_ephemeral. - commit = not preserve_ephemeral - # now if we are committing the changes to disk clean first. - if commit: - destroy_disk_metadata(dev, node_uuid) - - try: - # If requested, get the configdrive file and determine the size - # of the configdrive partition - configdrive_mb = 0 - configdrive_file = None - if configdrive: - configdrive_mb, configdrive_file = _get_configdrive(configdrive, - node_uuid) - - part_dict = make_partitions(dev, root_mb, swap_mb, ephemeral_mb, - configdrive_mb, node_uuid, - commit=commit, - boot_option=boot_option, - boot_mode=boot_mode) - LOG.info(_LI("Successfully completed the disk device" - " %(dev)s partitioning for node %(node)s"), - {'dev': dev, "node": node_uuid}) - - ephemeral_part = part_dict.get('ephemeral') - swap_part = part_dict.get('swap') - configdrive_part = part_dict.get('configdrive') - root_part = part_dict.get('root') - - if not is_block_device(root_part): - raise exception.InstanceDeployFailure( - _("Root device '%s' not found") % root_part) - - for part in ('swap', 'ephemeral', 'configdrive', - 'efi system partition'): - part_device = part_dict.get(part) - LOG.debug("Checking for %(part)s device (%(dev)s) on node " - "%(node)s.", - {'part': part, 'dev': part_device, 'node': node_uuid}) - if part_device and not is_block_device(part_device): - raise exception.InstanceDeployFailure( - _("'%(partition)s' device '%(part_device)s' not found") % - {'partition': part, 'part_device': part_device}) - - # If it's a uefi localboot, then we have created the efi system - # partition. Create a fat filesystem on it. - if boot_mode == "uefi" and boot_option == "local": - efi_system_part = part_dict.get('efi system partition') - utils.mkfs('vfat', efi_system_part, 'efi-part') - - if configdrive_part: - # Copy the configdrive content to the configdrive partition - dd(configdrive_file, configdrive_part) - LOG.info(_LI("Configdrive for node %(node)s successfully copied " - "onto partition %(partition)s"), - {'node': node_uuid, 'partition': configdrive_part}) - - finally: - # If the configdrive was requested make sure we delete the file - # after copying the content to the partition - if configdrive_file: - utils.unlink_without_raise(configdrive_file) - - populate_image(image_path, root_part) - LOG.info(_LI("Image for %(node)s successfully populated"), - {'node': node_uuid}) - - if swap_part: - utils.mkfs('swap', swap_part, 'swap1') - LOG.info(_LI("Swap partition %(swap)s successfully formatted " - "for node %(node)s"), - {'swap': swap_part, 'node': node_uuid}) - - if ephemeral_part and not preserve_ephemeral: - utils.mkfs(ephemeral_format, ephemeral_part, "ephemeral0") - LOG.info(_LI("Ephemeral partition %(ephemeral)s successfully " - "formatted for node %(node)s"), - {'ephemeral': ephemeral_part, 'node': node_uuid}) - - uuids_to_return = { - 'root uuid': root_part, - 'efi system partition uuid': part_dict.get('efi system partition') - } - - try: - for part, part_dev in uuids_to_return.items(): - if part_dev: - uuids_to_return[part] = block_uuid(part_dev) - - except processutils.ProcessExecutionError: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to detect %s"), part) - - return uuids_to_return - - def deploy_partition_image( address, port, iqn, lun, image_path, root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, @@ -775,7 +333,7 @@ def deploy_partition_image( NOTE: If key exists but value is None, it means partition doesn't exist. """ - image_mb = get_image_mb(image_path) + 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') @@ -783,7 +341,7 @@ def deploy_partition_image( raise exception.InstanceDeployFailure(msg) with _iscsi_setup_and_handle_errors(address, port, iqn, lun) as dev: - uuid_dict_returned = work_on_disk( + 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, @@ -808,8 +366,8 @@ def deploy_disk_image(address, port, iqn, lun, """ with _iscsi_setup_and_handle_errors(address, port, iqn, lun) as dev: - populate_image(image_path, dev) - disk_identifier = get_disk_identifier(dev) + disk_utils.populate_image(image_path, dev) + disk_identifier = disk_utils.get_disk_identifier(dev) return {'disk identifier': disk_identifier} @@ -826,7 +384,7 @@ def _iscsi_setup_and_handle_errors(address, port, iqn, lun): dev = get_dev(address, port, iqn, lun) discovery(address, port) login_iscsi(address, port, iqn) - if not is_block_device(dev): + if not disk_utils.is_block_device(dev): raise exception.InstanceDeployFailure(_("Parent device '%s' not found") % dev) try: diff --git a/ironic/drivers/modules/iscsi_deploy.py b/ironic/drivers/modules/iscsi_deploy.py index b2637467d..443fe0091 100644 --- a/ironic/drivers/modules/iscsi_deploy.py +++ b/ironic/drivers/modules/iscsi_deploy.py @@ -15,6 +15,7 @@ import os +from ironic_lib import disk_utils from oslo_config import cfg from oslo_log import log as logging from oslo_utils import fileutils @@ -241,7 +242,7 @@ def check_image_size(task): """ i_info = parse_instance_info(task.node) image_path = _get_image_file_path(task.node.uuid) - image_mb = deploy_utils.get_image_mb(image_path) + image_mb = disk_utils.get_image_mb(image_path) root_mb = 1024 * int(i_info['root_gb']) if image_mb > root_mb: msg = (_('Root partition is too small for requested image. Image ' diff --git a/ironic/tests/unit/common/test_disk_partitioner.py b/ironic/tests/unit/common/test_disk_partitioner.py deleted file mode 100644 index b32abbc40..000000000 --- a/ironic/tests/unit/common/test_disk_partitioner.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright 2014 Red Hat, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import eventlet -import mock -from testtools.matchers import HasLength - -from ironic.common import disk_partitioner -from ironic.common import exception -from ironic.common import utils -from ironic.tests import base - - -@mock.patch.object(eventlet.greenthread, 'sleep', lambda seconds: None) -class DiskPartitionerTestCase(base.TestCase): - - def test_add_partition(self): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - dp.add_partition(1024) - dp.add_partition(512, fs_type='linux-swap') - dp.add_partition(2048, bootable=True) - expected = [(1, {'bootable': False, - 'fs_type': '', - 'type': 'primary', - 'size': 1024}), - (2, {'bootable': False, - 'fs_type': 'linux-swap', - 'type': 'primary', - 'size': 512}), - (3, {'bootable': True, - 'fs_type': '', - 'type': 'primary', - 'size': 2048})] - partitions = [(n, p) for n, p in dp.get_partitions()] - self.assertThat(partitions, HasLength(3)) - self.assertEqual(expected, partitions) - - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit(self, mock_utils_exc, mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'bootable': False, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'bootable': True, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.return_value = (None, None) - dp.commit() - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on') - mock_utils_exc.assert_called_once_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit_with_device_is_busy_once(self, mock_utils_exc, - mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'bootable': False, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'bootable': True, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - fuser_outputs = iter([("/dev/fake: 10000 10001", None), (None, None)]) - - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.side_effect = fuser_outputs - dp.commit() - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on') - mock_utils_exc.assert_called_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - self.assertEqual(2, mock_utils_exc.call_count) - - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit_with_device_is_always_busy(self, mock_utils_exc, - mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'bootable': False, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'bootable': True, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.return_value = ("/dev/fake: 10000 10001", None) - self.assertRaises(exception.InstanceDeployFailure, dp.commit) - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on') - mock_utils_exc.assert_called_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - self.assertEqual(20, mock_utils_exc.call_count) - - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', - autospec=True) - @mock.patch.object(utils, 'execute', autospec=True) - def test_commit_with_device_disconnected(self, mock_utils_exc, - mock_disk_partitioner_exec): - dp = disk_partitioner.DiskPartitioner('/dev/fake') - fake_parts = [(1, {'bootable': False, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1}), - (2, {'bootable': True, - 'fs_type': 'fake-fs-type', - 'type': 'fake-type', - 'size': 1})] - - with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: - mock_gp.return_value = fake_parts - mock_utils_exc.return_value = (None, "Specified filename /dev/fake" - " does not exist.") - self.assertRaises(exception.InstanceDeployFailure, dp.commit) - - mock_disk_partitioner_exec.assert_called_once_with( - mock.ANY, 'mklabel', 'msdos', - 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', - 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', - 'set', '2', 'boot', 'on') - mock_utils_exc.assert_called_with( - 'fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - self.assertEqual(20, mock_utils_exc.call_count) - - -@mock.patch.object(utils, 'execute', autospec=True) -class ListPartitionsTestCase(base.TestCase): - - def test_correct(self, execute_mock): - output = """ -BYT; -/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:; -1:1.00MiB:501MiB:500MiB:ext4::boot; -2:501MiB:476940MiB:476439MiB:::; -""" - expected = [ - {'number': 1, 'start': 1, 'end': 501, 'size': 500, - 'filesystem': 'ext4', 'flags': 'boot'}, - {'number': 2, 'start': 501, 'end': 476940, 'size': 476439, - 'filesystem': '', 'flags': ''}, - ] - execute_mock.return_value = (output, '') - result = disk_partitioner.list_partitions('/dev/fake') - self.assertEqual(expected, result) - execute_mock.assert_called_once_with( - 'parted', '-s', '-m', '/dev/fake', 'unit', 'MiB', 'print', - use_standard_locale=True, run_as_root=True) - - @mock.patch.object(disk_partitioner.LOG, 'warning', autospec=True) - def test_incorrect(self, log_mock, execute_mock): - output = """ -BYT; -/dev/sda:500107862016B:scsi:512:4096:msdos:ATA HGST HTS725050A7:; -1:XX1076MiB:---:524MiB:ext4::boot; -""" - execute_mock.return_value = (output, '') - self.assertEqual([], disk_partitioner.list_partitions('/dev/fake')) - self.assertEqual(1, log_mock.call_count) diff --git a/ironic/tests/unit/drivers/modules/test_deploy_utils.py b/ironic/tests/unit/drivers/modules/test_deploy_utils.py index 41ffd0dba..4f8c3ca34 100644 --- a/ironic/tests/unit/drivers/modules/test_deploy_utils.py +++ b/ironic/tests/unit/drivers/modules/test_deploy_utils.py @@ -14,28 +14,22 @@ # License for the specific language governing permissions and limitations # under the License. -import base64 -import gzip import os -import shutil -import stat import tempfile import time import types +from ironic_lib import disk_utils +from ironic_lib import utils as lib_utils import mock -from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import uuidutils -import requests import testtools from testtools import matchers from ironic.common import boot_devices -from ironic.common import disk_partitioner from ironic.common import exception from ironic.common import image_service -from ironic.common import images from ironic.common import keystone from ironic.common import states from ironic.common import utils as common_utils @@ -329,8 +323,8 @@ menuentry "boot_whole_disk" { @mock.patch.object(time, 'sleep', lambda seconds: None) class PhysicalWorkTestCase(tests_base.TestCase): - def _mock_calls(self, name_list): - patch_list = [mock.patch.object(utils, name, + 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] @@ -342,8 +336,7 @@ class PhysicalWorkTestCase(tests_base.TestCase): parent_mock.attach_mock(mocker, name) return parent_mock - @mock.patch.object(common_utils, 'mkfs', autospec=True) - def _test_deploy_partition_image(self, mock_mkfs, boot_option=None, + def _test_deploy_partition_image(self, boot_option=None, boot_mode=None): """Check loosely all functions are called with right args.""" address = '127.0.0.1' @@ -363,17 +356,22 @@ class PhysicalWorkTestCase(tests_base.TestCase): root_part = '/dev/fake-part2' root_uuid = '12345678-1234-1234-12345678-12345678abcdef' - name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'block_uuid', - 'notify', 'destroy_disk_metadata'] - parent_mock = self._mock_calls(name_list) - parent_mock.get_dev.return_value = dev - parent_mock.get_image_mb.return_value = 1 - parent_mock.is_block_device.return_value = True - parent_mock.block_uuid.return_value = root_uuid - parent_mock.make_partitions.return_value = {'root': root_part, - 'swap': swap_part} + utils_name_list = ['get_dev', 'discovery', 'login_iscsi', + 'logout_iscsi', 'delete_iscsi', 'notify'] + + disk_utils_name_list = ['is_block_device', 'get_image_mb', + 'make_partitions', 'populate_image', 'mkfs', + 'block_uuid', 'destroy_disk_metadata'] + + 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.is_block_device.return_value = True + disk_utils_mock.block_uuid.return_value = root_uuid + disk_utils_mock.make_partitions.return_value = {'root': root_part, + 'swap': swap_part} make_partitions_expected_args = [dev, root_mb, swap_mb, ephemeral_mb, configdrive_mb, node_uuid] @@ -393,32 +391,37 @@ class PhysicalWorkTestCase(tests_base.TestCase): make_partitions_expected_kwargs['boot_mode'] = 'bios' # If no boot_option, then it should default to netboot. - calls_expected = [mock.call.get_image_mb(image_path), - mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.is_block_device(dev), - mock.call.destroy_disk_metadata(dev, node_uuid), - mock.call.make_partitions( - *make_partitions_expected_args, - **make_partitions_expected_kwargs), - mock.call.is_block_device(root_part), - mock.call.is_block_device(swap_part), - mock.call.populate_image(image_path, root_part), - mock.call.block_uuid(root_part), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + 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.is_block_device(dev), + mock.call.destroy_disk_metadata( + dev, node_uuid), + mock.call.make_partitions( + *make_partitions_expected_args, + **make_partitions_expected_kwargs), + mock.call.is_block_device(root_part), + mock.call.is_block_device(swap_part), + mock.call.populate_image( + image_path, root_part), + mock.call.mkfs( + dev=swap_part, fs='swap', + label='swap1'), + mock.call.block_uuid(root_part)] uuids_dict_returned = utils.deploy_partition_image( address, port, iqn, lun, image_path, root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, **deploy_kwargs) - self.assertEqual(calls_expected, parent_mock.mock_calls) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) expected_uuid_dict = { 'root uuid': root_uuid, 'efi system partition uuid': None} self.assertEqual(expected_uuid_dict, uuids_dict_returned) - mock_mkfs.assert_called_once_with('swap', swap_part, 'swap1') def test_deploy_partition_image_without_boot_option(self): self._test_deploy_partition_image() @@ -444,7 +447,8 @@ class PhysicalWorkTestCase(tests_base.TestCase): self._test_deploy_partition_image(boot_option="netboot", boot_mode="uefi") - @mock.patch.object(utils, 'get_image_mb', return_value=129, autospec=True) + @mock.patch.object(disk_utils, 'get_image_mb', return_value=129, + autospec=True) def test_deploy_partition_image_image_exceeds_root_partition(self, gim_mock): address = '127.0.0.1' @@ -467,10 +471,8 @@ class PhysicalWorkTestCase(tests_base.TestCase): # We mock utils.block_uuid separately here because we can't predict # the order in which it will be called. - @mock.patch.object(utils, 'block_uuid', autospec=True) - @mock.patch.object(common_utils, 'mkfs', autospec=True) - def test_deploy_partition_image_localboot_uefi(self, mock_mkfs, - block_uuid_mock): + @mock.patch.object(disk_utils, 'block_uuid', autospec=True) + def test_deploy_partition_image_localboot_uefi(self, block_uuid_mock): """Check loosely all functions are called with right args.""" address = '127.0.0.1' port = 3306 @@ -491,14 +493,19 @@ class PhysicalWorkTestCase(tests_base.TestCase): root_uuid = '12345678-1234-1234-12345678-12345678abcdef' efi_system_part_uuid = '9036-482' - name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'notify', - 'destroy_disk_metadata'] - parent_mock = self._mock_calls(name_list) - parent_mock.get_dev.return_value = dev - parent_mock.get_image_mb.return_value = 1 - parent_mock.is_block_device.return_value = True + utils_name_list = ['get_dev', 'discovery', 'login_iscsi', + 'logout_iscsi', 'delete_iscsi', 'notify'] + + disk_utils_name_list = ['get_image_mb', 'make_partitions', + 'is_block_device', 'populate_image', 'mkfs', + 'destroy_disk_metadata'] + + 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.is_block_device.return_value = True def block_uuid_side_effect(device): if device == root_part: @@ -507,46 +514,56 @@ class PhysicalWorkTestCase(tests_base.TestCase): return efi_system_part_uuid block_uuid_mock.side_effect = block_uuid_side_effect - parent_mock.make_partitions.return_value = { + disk_utils_mock.make_partitions.return_value = { 'root': root_part, 'swap': swap_part, 'efi system partition': efi_system_part} # If no boot_option, then it should default to netboot. - calls_expected = [mock.call.get_image_mb(image_path), - mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.is_block_device(dev), - mock.call.destroy_disk_metadata(dev, node_uuid), - mock.call.make_partitions(dev, root_mb, swap_mb, - ephemeral_mb, - configdrive_mb, - node_uuid, - commit=True, - boot_option="local", - boot_mode="uefi"), - mock.call.is_block_device(root_part), - mock.call.is_block_device(swap_part), - mock.call.is_block_device(efi_system_part), - mock.call.populate_image(image_path, root_part), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + 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.is_block_device(dev), + mock.call.destroy_disk_metadata( + dev, node_uuid), + mock.call.make_partitions( + dev, root_mb, swap_mb, + ephemeral_mb, + configdrive_mb, + node_uuid, + commit=True, + boot_option="local", + boot_mode="uefi"), + mock.call.is_block_device(root_part), + mock.call.is_block_device(swap_part), + mock.call.is_block_device( + efi_system_part), + mock.call.mkfs( + dev=efi_system_part, fs='vfat', + label='efi-part'), + mock.call.populate_image( + image_path, root_part), + mock.call.mkfs( + dev=swap_part, fs='swap', + label='swap1')] uuid_dict_returned = utils.deploy_partition_image( address, port, iqn, lun, image_path, root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, boot_option="local", boot_mode="uefi") - self.assertEqual(calls_expected, parent_mock.mock_calls) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) + block_uuid_mock.assert_any_call('/dev/fake-part1') block_uuid_mock.assert_any_call('/dev/fake-part3') expected_uuid_dict = { 'root uuid': root_uuid, 'efi system partition uuid': efi_system_part_uuid} self.assertEqual(expected_uuid_dict, uuid_dict_returned) - expected_calls = [mock.call('vfat', efi_system_part, 'efi-part'), - mock.call('swap', swap_part, 'swap1')] - mock_mkfs.assert_has_calls(expected_calls) def test_deploy_partition_image_without_swap(self): """Check loosely all functions are called with right args.""" @@ -566,34 +583,42 @@ class PhysicalWorkTestCase(tests_base.TestCase): root_part = '/dev/fake-part1' root_uuid = '12345678-1234-1234-12345678-12345678abcdef' - name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'block_uuid', - 'notify', 'destroy_disk_metadata'] - parent_mock = self._mock_calls(name_list) - parent_mock.get_dev.return_value = dev - parent_mock.get_image_mb.return_value = 1 - parent_mock.is_block_device.return_value = True - parent_mock.block_uuid.return_value = root_uuid - parent_mock.make_partitions.return_value = {'root': root_part} - calls_expected = [mock.call.get_image_mb(image_path), - mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.is_block_device(dev), - mock.call.destroy_disk_metadata(dev, node_uuid), - mock.call.make_partitions(dev, root_mb, swap_mb, - ephemeral_mb, - configdrive_mb, - node_uuid, - commit=True, - boot_option="netboot", - boot_mode="bios"), - mock.call.is_block_device(root_part), - mock.call.populate_image(image_path, root_part), - mock.call.block_uuid(root_part), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + utils_name_list = ['get_dev', 'discovery', 'login_iscsi', + 'notify', 'logout_iscsi', 'delete_iscsi'] + + disk_utils_name_list = ['make_partitions', 'get_image_mb', + 'is_block_device', 'populate_image', + 'block_uuid', 'destroy_disk_metadata'] + + 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.is_block_device.return_value = True + disk_utils_mock.block_uuid.return_value = root_uuid + disk_utils_mock.make_partitions.return_value = {'root': root_part} + 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.is_block_device(dev), + mock.call.destroy_disk_metadata( + dev, node_uuid), + mock.call.make_partitions( + dev, root_mb, swap_mb, + ephemeral_mb, + configdrive_mb, + node_uuid, + commit=True, + boot_option="netboot", + boot_mode="bios"), + mock.call.is_block_device(root_part), + mock.call.populate_image( + image_path, root_part), + mock.call.block_uuid(root_part)] uuid_dict_returned = utils.deploy_partition_image(address, port, iqn, lun, image_path, @@ -602,11 +627,11 @@ class PhysicalWorkTestCase(tests_base.TestCase): ephemeral_format, node_uuid) - self.assertEqual(calls_expected, parent_mock.mock_calls) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) self.assertEqual(root_uuid, uuid_dict_returned['root uuid']) - @mock.patch.object(common_utils, 'mkfs', autospec=True) - def test_deploy_partition_image_with_ephemeral(self, mock_mkfs): + def test_deploy_partition_image_with_ephemeral(self): """Check loosely all functions are called with right args.""" address = '127.0.0.1' port = 3306 @@ -626,37 +651,54 @@ class PhysicalWorkTestCase(tests_base.TestCase): root_part = '/dev/fake-part3' root_uuid = '12345678-1234-1234-12345678-12345678abcdef' - name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'block_uuid', - 'notify', 'destroy_disk_metadata'] - parent_mock = self._mock_calls(name_list) - parent_mock.get_dev.return_value = dev - parent_mock.get_image_mb.return_value = 1 - parent_mock.is_block_device.return_value = True - parent_mock.block_uuid.return_value = root_uuid - parent_mock.make_partitions.return_value = { - 'swap': swap_part, 'ephemeral': ephemeral_part, 'root': root_part} - calls_expected = [mock.call.get_image_mb(image_path), - mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.is_block_device(dev), - mock.call.destroy_disk_metadata(dev, node_uuid), - mock.call.make_partitions(dev, root_mb, swap_mb, - ephemeral_mb, - configdrive_mb, - node_uuid, - commit=True, - boot_option="netboot", - boot_mode="bios"), - mock.call.is_block_device(root_part), - mock.call.is_block_device(swap_part), - mock.call.is_block_device(ephemeral_part), - mock.call.populate_image(image_path, root_part), - mock.call.block_uuid(root_part), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + utils_name_list = ['get_dev', 'discovery', 'login_iscsi', + 'logout_iscsi', 'delete_iscsi', 'notify'] + + disk_utils_name_list = ['get_image_mb', 'make_partitions', + 'is_block_device', 'populate_image', 'mkfs', + 'block_uuid', 'destroy_disk_metadata'] + + 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.is_block_device.return_value = True + disk_utils_mock.block_uuid.return_value = root_uuid + disk_utils_mock.make_partitions.return_value = { + 'swap': swap_part, + 'ephemeral': ephemeral_part, + 'root': root_part} + 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.is_block_device(dev), + mock.call.destroy_disk_metadata( + dev, node_uuid), + mock.call.make_partitions( + dev, root_mb, swap_mb, + ephemeral_mb, + configdrive_mb, + node_uuid, + commit=True, + boot_option="netboot", + boot_mode="bios"), + mock.call.is_block_device(root_part), + mock.call.is_block_device(swap_part), + mock.call.is_block_device(ephemeral_part), + mock.call.populate_image( + image_path, root_part), + mock.call.mkfs( + dev=swap_part, fs='swap', + label='swap1'), + mock.call.mkfs( + dev=ephemeral_part, + fs=ephemeral_format, + label='ephemeral0'), + mock.call.block_uuid(root_part)] uuid_dict_returned = utils.deploy_partition_image(address, port, iqn, lun, image_path, @@ -665,15 +707,11 @@ class PhysicalWorkTestCase(tests_base.TestCase): ephemeral_format, node_uuid) - self.assertEqual(calls_expected, parent_mock.mock_calls) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) self.assertEqual(root_uuid, uuid_dict_returned['root uuid']) - expected_calls = [mock.call('swap', swap_part, 'swap1'), - mock.call(ephemeral_format, ephemeral_part, - 'ephemeral0')] - mock_mkfs.assert_has_calls(expected_calls) - @mock.patch.object(common_utils, 'mkfs', autospec=True) - def test_deploy_partition_image_preserve_ephemeral(self, mock_mkfs): + def test_deploy_partition_image_preserve_ephemeral(self): """Check if all functions are called with right args.""" address = '127.0.0.1' port = 3306 @@ -693,48 +731,60 @@ class PhysicalWorkTestCase(tests_base.TestCase): root_part = '/dev/fake-part3' root_uuid = '12345678-1234-1234-12345678-12345678abcdef' - name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'block_uuid', - 'notify', 'get_dev_block_size'] - parent_mock = self._mock_calls(name_list) - parent_mock.get_dev.return_value = dev - parent_mock.get_image_mb.return_value = 1 - parent_mock.is_block_device.return_value = True - parent_mock.block_uuid.return_value = root_uuid - parent_mock.make_partitions.return_value = { - 'swap': swap_part, 'ephemeral': ephemeral_part, 'root': root_part} - parent_mock.block_uuid.return_value = root_uuid - calls_expected = [mock.call.get_image_mb(image_path), - mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.is_block_device(dev), - mock.call.make_partitions(dev, root_mb, swap_mb, - ephemeral_mb, - configdrive_mb, - node_uuid, - commit=False, - boot_option="netboot", - boot_mode="bios"), - mock.call.is_block_device(root_part), - mock.call.is_block_device(swap_part), - mock.call.is_block_device(ephemeral_part), - mock.call.populate_image(image_path, root_part), - mock.call.block_uuid(root_part), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + utils_name_list = ['get_dev', 'discovery', 'login_iscsi', + 'delete_iscsi', 'logout_iscsi', 'notify'] + disk_utils_name_list = ['make_partitions', 'get_image_mb', + 'is_block_device', 'populate_image', 'mkfs', + 'block_uuid', 'get_dev_block_size'] + + 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.is_block_device.return_value = True + disk_utils_mock.block_uuid.return_value = root_uuid + disk_utils_mock.make_partitions.return_value = { + 'swap': swap_part, + 'ephemeral': ephemeral_part, + 'root': root_part} + disk_utils_mock.block_uuid.return_value = root_uuid + + 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.is_block_device(dev), + mock.call.make_partitions( + dev, root_mb, swap_mb, + ephemeral_mb, + configdrive_mb, + node_uuid, + commit=False, + boot_option="netboot", + boot_mode="bios"), + mock.call.is_block_device(root_part), + mock.call.is_block_device(swap_part), + mock.call.is_block_device(ephemeral_part), + mock.call.populate_image( + image_path, root_part), + mock.call.mkfs( + dev=swap_part, fs='swap', + label='swap1'), + mock.call.block_uuid(root_part)] uuid_dict_returned = utils.deploy_partition_image( address, port, iqn, lun, image_path, root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, preserve_ephemeral=True, boot_option="netboot") - self.assertEqual(calls_expected, parent_mock.mock_calls) - self.assertFalse(parent_mock.get_dev_block_size.called) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) + self.assertFalse(disk_utils_mock.get_dev_block_size.called) self.assertEqual(root_uuid, uuid_dict_returned['root uuid']) - mock_mkfs.assert_called_once_with('swap', swap_part, 'swap1') - @mock.patch.object(common_utils, 'unlink_without_raise', autospec=True) + @mock.patch.object(lib_utils, 'unlink_without_raise', autospec=True) def test_deploy_partition_image_with_configdrive(self, mock_unlink): """Check loosely all functions are called with right args.""" address = '127.0.0.1' @@ -755,53 +805,64 @@ class PhysicalWorkTestCase(tests_base.TestCase): root_part = '/dev/fake-part2' root_uuid = '12345678-1234-1234-12345678-12345678abcdef' - name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi', 'make_partitions', - 'is_block_device', 'populate_image', 'block_uuid', - 'notify', 'destroy_disk_metadata', 'dd', - '_get_configdrive'] - parent_mock = self._mock_calls(name_list) - parent_mock.get_dev.return_value = dev - parent_mock.get_image_mb.return_value = 1 - parent_mock.is_block_device.return_value = True - parent_mock.block_uuid.return_value = root_uuid - parent_mock.make_partitions.return_value = {'root': root_part, - 'configdrive': - configdrive_part} - parent_mock._get_configdrive.return_value = (10, 'configdrive-path') - calls_expected = [mock.call.get_image_mb(image_path), - mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.is_block_device(dev), - mock.call.destroy_disk_metadata(dev, node_uuid), - mock.call._get_configdrive(configdrive_url, - node_uuid), - mock.call.make_partitions(dev, root_mb, swap_mb, - ephemeral_mb, - configdrive_mb, - node_uuid, - commit=True, - boot_option="netboot", - boot_mode="bios"), - mock.call.is_block_device(root_part), - mock.call.is_block_device(configdrive_part), - mock.call.dd(mock.ANY, configdrive_part), - mock.call.populate_image(image_path, root_part), - mock.call.block_uuid(root_part), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + utils_name_list = ['get_dev', 'discovery', 'login_iscsi', + 'logout_iscsi', 'delete_iscsi', 'notify'] + disk_utils_name_list = ['is_block_device', 'populate_image', + 'get_image_mb', 'destroy_disk_metadata', 'dd', + 'block_uuid', 'make_partitions', + '_get_configdrive'] + + 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.is_block_device.return_value = True + disk_utils_mock.block_uuid.return_value = root_uuid + disk_utils_mock.make_partitions.return_value = { + 'root': root_part, + 'configdrive': configdrive_part} + disk_utils_mock._get_configdrive.return_value = (10, + 'configdrive-path') + 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.is_block_device(dev), + mock.call.destroy_disk_metadata( + dev, node_uuid), + mock.call._get_configdrive( + configdrive_url, node_uuid, + tempdir=None), + mock.call.make_partitions( + dev, root_mb, swap_mb, + ephemeral_mb, + configdrive_mb, + node_uuid, + commit=True, + boot_option="netboot", + boot_mode="bios"), + mock.call.is_block_device(root_part), + mock.call.is_block_device( + configdrive_part), + mock.call.dd(mock.ANY, configdrive_part), + mock.call.populate_image( + image_path, root_part), + mock.call.block_uuid(root_part)] uuid_dict_returned = utils.deploy_partition_image( address, port, iqn, lun, image_path, root_mb, swap_mb, ephemeral_mb, ephemeral_format, node_uuid, configdrive=configdrive_url) - self.assertEqual(calls_expected, parent_mock.mock_calls) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) self.assertEqual(root_uuid, uuid_dict_returned['root uuid']) mock_unlink.assert_called_once_with('configdrive-path') - @mock.patch.object(utils, 'get_disk_identifier', autospec=True) + @mock.patch.object(disk_utils, 'get_disk_identifier', autospec=True) def test_deploy_whole_disk_image(self, mock_gdi): """Check loosely all functions are called with right args.""" address = '127.0.0.1' @@ -812,25 +873,29 @@ class PhysicalWorkTestCase(tests_base.TestCase): node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" dev = '/dev/fake' - name_list = ['get_dev', 'discovery', 'login_iscsi', 'logout_iscsi', - 'delete_iscsi', 'is_block_device', 'populate_image', - 'notify'] - parent_mock = self._mock_calls(name_list) - parent_mock.get_dev.return_value = dev - parent_mock.is_block_device.return_value = True + utils_name_list = ['get_dev', 'discovery', 'login_iscsi', + 'logout_iscsi', 'delete_iscsi', 'notify'] + 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' - calls_expected = [mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - mock.call.is_block_device(dev), - mock.call.populate_image(image_path, dev), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + 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)] uuid_dict_returned = utils.deploy_disk_image(address, port, iqn, lun, image_path, node_uuid) - self.assertEqual(calls_expected, parent_mock.mock_calls) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) self.assertEqual('0x12345678', uuid_dict_returned['disk identifier']) @mock.patch.object(common_utils, 'execute', autospec=True) @@ -921,7 +986,7 @@ class PhysicalWorkTestCase(tests_base.TestCase): mock_check_dev.assert_called_once_with(address, port, iqn) - @mock.patch.object(utils, 'is_block_device', lambda d: True) + @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. @@ -945,42 +1010,39 @@ class PhysicalWorkTestCase(tests_base.TestCase): class TestException(Exception): pass - name_list = ['get_dev', 'get_image_mb', 'discovery', 'login_iscsi', - 'logout_iscsi', 'delete_iscsi', 'work_on_disk'] - patch_list = [mock.patch.object(utils, 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) - - parent_mock.get_dev.return_value = dev - parent_mock.get_image_mb.return_value = 1 - parent_mock.work_on_disk.side_effect = TestException - calls_expected = [mock.call.get_image_mb(image_path), - mock.call.get_dev(address, port, iqn, lun), - mock.call.discovery(address, port), - mock.call.login_iscsi(address, port, iqn), - 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"), - mock.call.logout_iscsi(address, port, iqn), - mock.call.delete_iscsi(address, port, iqn)] + 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")] 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(calls_expected, parent_mock.mock_calls) + self.assertEqual(utils_calls_expected, utils_mock.mock_calls) + self.assertEqual(disk_utils_calls_expected, disk_utils_mock.mock_calls) class SwitchPxeConfigTestCase(tests_base.TestCase): @@ -1114,45 +1176,6 @@ class OtherFunctionTestCase(db_base.DbTestCase): actual = utils.get_dev('1.2.3.4', 5678, 'iqn.fake', 9) self.assertEqual(expected, actual) - @mock.patch.object(os, 'stat', autospec=True) - @mock.patch.object(stat, 'S_ISBLK', autospec=True) - def test_is_block_device_works(self, mock_is_blk, mock_os): - device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9' - mock_is_blk.return_value = True - mock_os().st_mode = 10000 - self.assertTrue(utils.is_block_device(device)) - mock_is_blk.assert_called_once_with(mock_os().st_mode) - - @mock.patch.object(os, 'stat', autospec=True) - def test_is_block_device_raises(self, mock_os): - device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9' - mock_os.side_effect = OSError - self.assertRaises(exception.InstanceDeployFailure, - utils.is_block_device, device) - mock_os.assert_has_calls([mock.call(device)] * 3) - - @mock.patch.object(os.path, 'getsize', autospec=True) - @mock.patch.object(images, 'converted_size', autospec=True) - def test_get_image_mb(self, mock_csize, mock_getsize): - mb = 1024 * 1024 - - mock_getsize.return_value = 0 - mock_csize.return_value = 0 - self.assertEqual(0, utils.get_image_mb('x', False)) - self.assertEqual(0, utils.get_image_mb('x', True)) - mock_getsize.return_value = 1 - mock_csize.return_value = 1 - self.assertEqual(1, utils.get_image_mb('x', False)) - self.assertEqual(1, utils.get_image_mb('x', True)) - mock_getsize.return_value = mb - mock_csize.return_value = mb - self.assertEqual(1, utils.get_image_mb('x', False)) - self.assertEqual(1, utils.get_image_mb('x', True)) - mock_getsize.return_value = mb + 1 - mock_csize.return_value = mb + 1 - self.assertEqual(2, utils.get_image_mb('x', False)) - self.assertEqual(2, utils.get_image_mb('x', True)) - def test_parse_root_device_hints(self): self.node.properties['root_device'] = { 'wwn': 123456, 'model': 'foo-model', 'size': 123, @@ -1233,351 +1256,6 @@ class OtherFunctionTestCase(db_base.DbTestCase): result = utils.get_boot_option(self.node) self.assertEqual("netboot", result) - -@mock.patch.object(disk_partitioner.DiskPartitioner, 'commit', lambda _: None) -class WorkOnDiskTestCase(tests_base.TestCase): - - def setUp(self): - super(WorkOnDiskTestCase, self).setUp() - self.image_path = '/tmp/xyz/image' - self.root_mb = 128 - self.swap_mb = 64 - self.ephemeral_mb = 0 - self.ephemeral_format = None - self.configdrive_mb = 0 - self.dev = '/dev/fake' - self.swap_part = '/dev/fake-part1' - self.root_part = '/dev/fake-part2' - - self.mock_ibd_obj = mock.patch.object( - utils, 'is_block_device', autospec=True) - self.mock_ibd = self.mock_ibd_obj.start() - self.addCleanup(self.mock_ibd_obj.stop) - self.mock_mp_obj = mock.patch.object( - utils, 'make_partitions', autospec=True) - self.mock_mp = self.mock_mp_obj.start() - self.addCleanup(self.mock_mp_obj.stop) - self.mock_remlbl_obj = mock.patch.object( - utils, 'destroy_disk_metadata', autospec=True) - self.mock_remlbl = self.mock_remlbl_obj.start() - self.addCleanup(self.mock_remlbl_obj.stop) - self.mock_mp.return_value = {'swap': self.swap_part, - 'root': self.root_part} - - def test_no_root_partition(self): - self.mock_ibd.return_value = False - self.assertRaises(exception.InstanceDeployFailure, - utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.ephemeral_format, self.image_path, 'fake-uuid') - self.mock_ibd.assert_called_once_with(self.root_part) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.configdrive_mb, - 'fake-uuid', - commit=True, - boot_option="netboot", - boot_mode="bios") - - def test_no_swap_partition(self): - self.mock_ibd.side_effect = iter([True, False]) - calls = [mock.call(self.root_part), - mock.call(self.swap_part)] - self.assertRaises(exception.InstanceDeployFailure, - utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.ephemeral_format, self.image_path, 'fake-uuid') - self.assertEqual(self.mock_ibd.call_args_list, calls) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.configdrive_mb, - 'fake-uuid', - commit=True, - boot_option="netboot", - boot_mode="bios") - - def test_no_ephemeral_partition(self): - ephemeral_part = '/dev/fake-part1' - swap_part = '/dev/fake-part2' - root_part = '/dev/fake-part3' - ephemeral_mb = 256 - ephemeral_format = 'exttest' - - self.mock_mp.return_value = {'ephemeral': ephemeral_part, - 'swap': swap_part, - 'root': root_part} - self.mock_ibd.side_effect = iter([True, True, False]) - calls = [mock.call(root_part), - mock.call(swap_part), - mock.call(ephemeral_part)] - self.assertRaises(exception.InstanceDeployFailure, - utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, ephemeral_mb, ephemeral_format, - self.image_path, 'fake-uuid') - self.assertEqual(self.mock_ibd.call_args_list, calls) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, ephemeral_mb, - self.configdrive_mb, - 'fake-uuid', - commit=True, - boot_option="netboot", - boot_mode="bios") - - @mock.patch.object(common_utils, 'unlink_without_raise', autospec=True) - @mock.patch.object(utils, '_get_configdrive', autospec=True) - def test_no_configdrive_partition(self, mock_configdrive, mock_unlink): - mock_configdrive.return_value = (10, 'fake-path') - swap_part = '/dev/fake-part1' - configdrive_part = '/dev/fake-part2' - root_part = '/dev/fake-part3' - configdrive_url = 'http://1.2.3.4/cd' - configdrive_mb = 10 - - self.mock_mp.return_value = {'swap': swap_part, - 'configdrive': configdrive_part, - 'root': root_part} - self.mock_ibd.side_effect = iter([True, True, False]) - calls = [mock.call(root_part), - mock.call(swap_part), - mock.call(configdrive_part)] - self.assertRaises(exception.InstanceDeployFailure, - utils.work_on_disk, self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - self.ephemeral_format, self.image_path, 'fake-uuid', - preserve_ephemeral=False, - configdrive=configdrive_url, - boot_option="netboot") - self.assertEqual(self.mock_ibd.call_args_list, calls) - self.mock_mp.assert_called_once_with(self.dev, self.root_mb, - self.swap_mb, self.ephemeral_mb, - configdrive_mb, - 'fake-uuid', - commit=True, - boot_option="netboot", - boot_mode="bios") - mock_unlink.assert_called_once_with('fake-path') - - -@mock.patch.object(common_utils, 'execute', autospec=True) -class MakePartitionsTestCase(tests_base.TestCase): - - def setUp(self): - super(MakePartitionsTestCase, self).setUp() - self.dev = 'fake-dev' - self.root_mb = 1024 - self.swap_mb = 512 - self.ephemeral_mb = 0 - self.configdrive_mb = 0 - self.parted_static_cmd = ['parted', '-a', 'optimal', '-s', self.dev, - '--', 'unit', 'MiB', 'mklabel', 'msdos'] - - def _test_make_partitions(self, mock_exc, boot_option): - mock_exc.return_value = (None, None) - utils.make_partitions(self.dev, self.root_mb, self.swap_mb, - self.ephemeral_mb, self.configdrive_mb, - '12345678-1234-1234-1234-1234567890abcxyz', - boot_option=boot_option) - - expected_mkpart = ['mkpart', 'primary', 'linux-swap', '1', '513', - 'mkpart', 'primary', '', '513', '1537'] - if boot_option == "local": - expected_mkpart.extend(['set', '2', 'boot', 'on']) - parted_cmd = self.parted_static_cmd + expected_mkpart - parted_call = mock.call(*parted_cmd, use_standard_locale=True, - run_as_root=True, check_exit_code=[0]) - fuser_cmd = ['fuser', 'fake-dev'] - fuser_call = mock.call(*fuser_cmd, run_as_root=True, - check_exit_code=[0, 1]) - mock_exc.assert_has_calls([parted_call, fuser_call]) - - def test_make_partitions(self, mock_exc): - self._test_make_partitions(mock_exc, boot_option="netboot") - - def test_make_partitions_local_boot(self, mock_exc): - self._test_make_partitions(mock_exc, boot_option="local") - - def test_make_partitions_with_ephemeral(self, mock_exc): - self.ephemeral_mb = 2048 - expected_mkpart = ['mkpart', 'primary', '', '1', '2049', - 'mkpart', 'primary', 'linux-swap', '2049', '2561', - 'mkpart', 'primary', '', '2561', '3585'] - cmd = self.parted_static_cmd + expected_mkpart - mock_exc.return_value = (None, None) - utils.make_partitions(self.dev, self.root_mb, self.swap_mb, - self.ephemeral_mb, self.configdrive_mb, - '12345678-1234-1234-1234-1234567890abcxyz') - - parted_call = mock.call(*cmd, use_standard_locale=True, - run_as_root=True, check_exit_code=[0]) - mock_exc.assert_has_calls([parted_call]) - - -@mock.patch.object(utils, 'get_dev_block_size', autospec=True) -@mock.patch.object(common_utils, 'execute', autospec=True) -class DestroyMetaDataTestCase(tests_base.TestCase): - - def setUp(self): - super(DestroyMetaDataTestCase, self).setUp() - self.dev = 'fake-dev' - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - - def test_destroy_disk_metadata(self, mock_exec, mock_gz): - mock_gz.return_value = 64 - expected_calls = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev', - 'bs=512', 'count=36', run_as_root=True, - check_exit_code=[0], - use_standard_locale=True), - mock.call('dd', 'if=/dev/zero', 'of=fake-dev', - 'bs=512', 'count=36', 'seek=28', - run_as_root=True, - check_exit_code=[0], - use_standard_locale=True)] - utils.destroy_disk_metadata(self.dev, self.node_uuid) - mock_exec.assert_has_calls(expected_calls) - self.assertTrue(mock_gz.called) - - def test_destroy_disk_metadata_get_dev_size_fail(self, mock_exec, mock_gz): - mock_gz.side_effect = processutils.ProcessExecutionError - - expected_call = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev', - 'bs=512', 'count=36', run_as_root=True, - check_exit_code=[0], - use_standard_locale=True)] - self.assertRaises(processutils.ProcessExecutionError, - utils.destroy_disk_metadata, - self.dev, - self.node_uuid) - mock_exec.assert_has_calls(expected_call) - - def test_destroy_disk_metadata_dd_fail(self, mock_exec, mock_gz): - mock_exec.side_effect = processutils.ProcessExecutionError - - expected_call = [mock.call('dd', 'if=/dev/zero', 'of=fake-dev', - 'bs=512', 'count=36', run_as_root=True, - check_exit_code=[0], - use_standard_locale=True)] - self.assertRaises(processutils.ProcessExecutionError, - utils.destroy_disk_metadata, - self.dev, - self.node_uuid) - mock_exec.assert_has_calls(expected_call) - self.assertFalse(mock_gz.called) - - -@mock.patch.object(common_utils, 'execute', autospec=True) -class GetDeviceBlockSizeTestCase(tests_base.TestCase): - - def setUp(self): - super(GetDeviceBlockSizeTestCase, self).setUp() - self.dev = 'fake-dev' - self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" - - def test_get_dev_block_size(self, mock_exec): - mock_exec.return_value = ("64", "") - expected_call = [mock.call('blockdev', '--getsz', self.dev, - run_as_root=True, check_exit_code=[0])] - utils.get_dev_block_size(self.dev) - mock_exec.assert_has_calls(expected_call) - - -@mock.patch.object(utils, 'dd', autospec=True) -@mock.patch.object(images, 'qemu_img_info', autospec=True) -@mock.patch.object(images, 'convert_image', autospec=True) -class PopulateImageTestCase(tests_base.TestCase): - - def setUp(self): - super(PopulateImageTestCase, self).setUp() - - def test_populate_raw_image(self, mock_cg, mock_qinfo, mock_dd): - type(mock_qinfo.return_value).file_format = mock.PropertyMock( - return_value='raw') - utils.populate_image('src', 'dst') - mock_dd.assert_called_once_with('src', 'dst') - self.assertFalse(mock_cg.called) - - def test_populate_qcow2_image(self, mock_cg, mock_qinfo, mock_dd): - type(mock_qinfo.return_value).file_format = mock.PropertyMock( - return_value='qcow2') - utils.populate_image('src', 'dst') - mock_cg.assert_called_once_with('src', 'dst', 'raw', True) - self.assertFalse(mock_dd.called) - - -@mock.patch.object(utils, 'is_block_device', lambda d: True) -@mock.patch.object(utils, 'block_uuid', lambda p: 'uuid') -@mock.patch.object(utils, 'dd', lambda *_: None) -@mock.patch.object(images, 'convert_image', lambda *_: None) -@mock.patch.object(common_utils, 'mkfs', lambda *_: None) -# NOTE(dtantsur): destroy_disk_metadata resets file size, disabling it -@mock.patch.object(utils, 'destroy_disk_metadata', lambda *_: None) -class RealFilePartitioningTestCase(tests_base.TestCase): - """This test applies some real-world partitioning scenario to a file. - - This test covers the whole partitioning, mocking everything not possible - on a file. That helps us assure, that we do all partitioning math properly - and also conducts integration testing of DiskPartitioner. - """ - - def setUp(self): - super(RealFilePartitioningTestCase, self).setUp() - # NOTE(dtantsur): no parted utility on gate-ironic-python26 - try: - common_utils.execute('parted', '--version') - except OSError as exc: - self.skipTest('parted utility was not found: %s' % exc) - self.file = tempfile.NamedTemporaryFile(delete=False) - # NOTE(ifarkas): the file needs to be closed, so fuser won't report - # any usage - self.file.close() - # NOTE(dtantsur): 20 MiB file with zeros - common_utils.execute('dd', 'if=/dev/zero', 'of=%s' % self.file.name, - 'bs=1', 'count=0', 'seek=20MiB') - - @staticmethod - def _run_without_root(func, *args, **kwargs): - """Make sure root is not required when using utils.execute.""" - real_execute = common_utils.execute - - def fake_execute(*cmd, **kwargs): - kwargs['run_as_root'] = False - return real_execute(*cmd, **kwargs) - - with mock.patch.object(common_utils, 'execute', fake_execute): - return func(*args, **kwargs) - - def test_different_sizes(self): - # NOTE(dtantsur): Keep this list in order with expected partitioning - fields = ['ephemeral_mb', 'swap_mb', 'root_mb'] - variants = ((0, 0, 12), (4, 2, 8), (0, 4, 10), (5, 0, 10)) - for variant in variants: - kwargs = dict(zip(fields, variant)) - self._run_without_root(utils.work_on_disk, self.file.name, - ephemeral_format='ext4', node_uuid='', - image_path='path', **kwargs) - part_table = self._run_without_root( - disk_partitioner.list_partitions, self.file.name) - for part, expected_size in zip(part_table, filter(None, variant)): - self.assertEqual(expected_size, part['size'], - "comparison failed for %s" % list(variant)) - - def test_whole_disk(self): - # 6 MiB ephemeral + 3 MiB swap + 9 MiB root + 1 MiB for MBR - # + 1 MiB MAGIC == 20 MiB whole disk - # TODO(dtantsur): figure out why we need 'magic' 1 more MiB - # and why the is different on Ubuntu and Fedora (see below) - self._run_without_root(utils.work_on_disk, self.file.name, - root_mb=9, ephemeral_mb=6, swap_mb=3, - ephemeral_format='ext4', node_uuid='', - image_path='path') - part_table = self._run_without_root( - disk_partitioner.list_partitions, self.file.name) - sizes = [part['size'] for part in part_table] - # NOTE(dtantsur): parted in Ubuntu 12.04 will occupy the last MiB, - # parted in Fedora 20 won't - thus two possible variants for last part - self.assertEqual([6, 3], sizes[:2], - "unexpected partitioning %s" % part_table) - self.assertIn(sizes[2], (9, 10)) - @mock.patch.object(image_cache, 'clean_up_caches', autospec=True) def test_fetch_images(self, mock_clean_up_caches): @@ -1609,83 +1287,6 @@ class RealFilePartitioningTestCase(tests_base.TestCase): [('uuid', 'path')]) -@mock.patch.object(tempfile, 'NamedTemporaryFile', autospec=True) -@mock.patch.object(shutil, 'copyfileobj', autospec=True) -@mock.patch.object(requests, 'get', autospec=True) -class GetConfigdriveTestCase(tests_base.TestCase): - - def setUp(self): - super(GetConfigdriveTestCase, self).setUp() - # NOTE(lucasagomes): "name" can't be passed to Mock() when - # instantiating the object because it's an expected parameter. - # https://docs.python.org/3/library/unittest.mock.html - self.fake_configdrive_file = mock.Mock(tell=lambda *_: 123) - self.fake_configdrive_file.name = '/tmp/foo' - - @mock.patch.object(gzip, 'GzipFile', autospec=True) - def test_get_configdrive(self, mock_gzip, mock_requests, mock_copy, - mock_file): - mock_file.return_value = self.fake_configdrive_file - mock_requests.return_value = mock.MagicMock( - spec_set=['content'], content='Zm9vYmFy') - utils._get_configdrive('http://1.2.3.4/cd', 'fake-node-uuid') - mock_requests.assert_called_once_with('http://1.2.3.4/cd') - mock_gzip.assert_called_once_with('configdrive', 'rb', - fileobj=mock.ANY) - mock_copy.assert_called_once_with(mock.ANY, mock.ANY) - mock_file.assert_called_once_with(prefix='configdrive', - dir=cfg.CONF.tempdir, delete=False) - - @mock.patch.object(gzip, 'GzipFile', autospec=True) - def test_get_configdrive_base64_string(self, mock_gzip, mock_requests, - mock_copy, mock_file): - mock_file.return_value = self.fake_configdrive_file - utils._get_configdrive('Zm9vYmFy', 'fake-node-uuid') - self.assertFalse(mock_requests.called) - mock_gzip.assert_called_once_with('configdrive', 'rb', - fileobj=mock.ANY) - mock_copy.assert_called_once_with(mock.ANY, mock.ANY) - mock_file.assert_called_once_with(prefix='configdrive', - dir=cfg.CONF.tempdir, delete=False) - - def test_get_configdrive_bad_url(self, mock_requests, mock_copy, - mock_file): - mock_requests.side_effect = requests.exceptions.RequestException - self.assertRaises(exception.InstanceDeployFailure, - utils._get_configdrive, 'http://1.2.3.4/cd', - 'fake-node-uuid') - self.assertFalse(mock_copy.called) - self.assertFalse(mock_file.called) - - @mock.patch.object(base64, 'b64decode', autospec=True) - def test_get_configdrive_base64_error(self, mock_b64, mock_requests, - mock_copy, mock_file): - mock_b64.side_effect = TypeError - self.assertRaises(exception.InstanceDeployFailure, - utils._get_configdrive, - 'malformed', 'fake-node-uuid') - mock_b64.assert_called_once_with('malformed') - self.assertFalse(mock_copy.called) - self.assertFalse(mock_file.called) - - @mock.patch.object(gzip, 'GzipFile', autospec=True) - def test_get_configdrive_gzip_error(self, mock_gzip, mock_requests, - mock_copy, mock_file): - mock_file.return_value = self.fake_configdrive_file - mock_requests.return_value = mock.MagicMock( - spec_set=['content'], content='Zm9vYmFy') - mock_copy.side_effect = IOError - self.assertRaises(exception.InstanceDeployFailure, - utils._get_configdrive, 'http://1.2.3.4/cd', - 'fake-node-uuid') - mock_requests.assert_called_once_with('http://1.2.3.4/cd') - mock_gzip.assert_called_once_with('configdrive', 'rb', - fileobj=mock.ANY) - mock_copy.assert_called_once_with(mock.ANY, mock.ANY) - mock_file.assert_called_once_with(prefix='configdrive', - dir=cfg.CONF.tempdir, delete=False) - - class VirtualMediaDeployUtilsTestCase(db_base.DbTestCase): def setUp(self): @@ -2217,7 +1818,7 @@ class AgentMethodsTestCase(db_base.DbTestCase): self.assertEqual('model=fake_model', options['root_device']) -@mock.patch.object(utils, 'is_block_device', autospec=True) +@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) diff --git a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py index 5fea6de75..01f396b65 100644 --- a/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py +++ b/ironic/tests/unit/drivers/modules/test_iscsi_deploy.py @@ -18,6 +18,7 @@ import os import tempfile +from ironic_lib import disk_utils import mock from oslo_config import cfg from oslo_utils import fileutils @@ -344,7 +345,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): mgr_utils.mock_the_extension_manager(driver="fake_pxe") self.node = obj_utils.create_test_node(self.context, **n) - @mock.patch.object(deploy_utils, 'get_image_mb', autospec=True) + @mock.patch.object(disk_utils, 'get_image_mb', autospec=True) def test_check_image_size(self, get_image_mb_mock): get_image_mb_mock.return_value = 1000 with task_manager.acquire(self.context, self.node.uuid, @@ -354,7 +355,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): get_image_mb_mock.assert_called_once_with( iscsi_deploy._get_image_file_path(task.node.uuid)) - @mock.patch.object(deploy_utils, 'get_image_mb', autospec=True) + @mock.patch.object(disk_utils, 'get_image_mb', autospec=True) def test_check_image_size_fails(self, get_image_mb_mock): get_image_mb_mock.return_value = 1025 with task_manager.acquire(self.context, self.node.uuid, diff --git a/releasenotes/notes/refactor-ironic-lib-22939896d8d46a77.yaml b/releasenotes/notes/refactor-ironic-lib-22939896d8d46a77.yaml new file mode 100644 index 000000000..19d76938f --- /dev/null +++ b/releasenotes/notes/refactor-ironic-lib-22939896d8d46a77.yaml @@ -0,0 +1,20 @@ +--- +upgrade: + - | + Adds new configuration [ironic_lib]root_helper, to specify + the command that is prefixed to commands that are run as root. + Defaults to using the rootwrap config file at + /etc/ironic/rootwrap.conf. + - | + Moves these configuration options from [deploy] group to the + new [disk_utils] group: efi_system_partition_size, dd_block_size + and iscsi_verify_attempts. +deprecations: + - | + The following configuration options have been moved to + the [disk_utils] group; they are deprecated from the + [deploy] group: efi_system_partition_size, dd_block_size and + iscsi_verify_attempts. +other: + - Code related to disk partitioning was moved to + ironic-lib. diff --git a/requirements.txt b/requirements.txt index 98a236c47..65890407a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ paramiko>=1.13.0 python-neutronclient>=2.6.0 python-glanceclient>=0.18.0 python-keystoneclient!=1.8.0,>=1.6.0 +ironic-lib>=0.5.0 python-swiftclient>=2.2.0 pytz>=2013.6 stevedore>=1.5.0 # Apache-2.0 diff --git a/tools/config/oslo.config.generator.rc b/tools/config/oslo.config.generator.rc index e66acd5ac..447d8144d 100644 --- a/tools/config/oslo.config.generator.rc +++ b/tools/config/oslo.config.generator.rc @@ -1,2 +1,2 @@ export IRONIC_CONFIG_GENERATOR_EXTRA_LIBRARIES='oslo.db oslo.messaging oslo.middleware.cors keystonemiddleware.auth_token oslo.concurrency oslo.policy oslo.log oslo.service.service oslo.service.periodic_task oslo.service.sslutils' -export IRONIC_CONFIG_GENERATOR_EXTRA_MODULES= +export IRONIC_CONFIG_GENERATOR_EXTRA_MODULES='ironic_lib.disk_utils ironic_lib.disk_partitioner ironic_lib.utils' |