Support configdrive in iscsi deploy for whole disk images

This will work for UEFI only or BIOS only images. It will not
work for hybrid images; which are capable of boot from BIOS
and UEFI boot mode.

Partial-Bug: #1493328
Change-Id: I4c517f63d620b5a9de31ecb2d7c209776d5ded48
This commit is contained in:
Shivanand Tendulker 2015-10-05 03:11:37 -07:00
parent 7aac631fbc
commit 7e926fd3fb
3 changed files with 795 additions and 0 deletions

View File

@ -13,6 +13,8 @@ blockdev: CommandFilter, blockdev, root
hexdump: CommandFilter, hexdump, root
qemu-img: CommandFilter, qemu-img, root
wipefs: CommandFilter, wipefs, root
sgdisk: CommandFilter, sgdisk, root
partprobe: CommandFilter, partprobe, root
# ironic_lib/utils.py
mkswap: CommandFilter, mkswap, root

View File

@ -67,6 +67,12 @@ LOG = logging.getLogger(__name__)
_PARTED_PRINT_RE = re.compile(r"^(\d+):([\d\.]+)MiB:"
"([\d\.]+)MiB:([\d\.]+)MiB:(\w*)::(\w*)")
CONFIGDRIVE_LABEL = "config-2"
MAX_CONFIG_DRIVE_SIZE_MB = 64
# Maximum disk size supported by MBR is 2TB (2 * 1024 * 1024 MB)
MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR = 2097152
def list_partitions(device):
"""Get partitions information from given device.
@ -539,3 +545,221 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
def list_opts():
"""Entry point for oslo-config-generator."""
return [('disk_utils', opts)]
def _is_disk_larger_than_max_size(device, node_uuid):
"""Check if total disk size exceeds 2TB msdos limit
:param device: device path.
:param node_uuid: node's uuid. Used for logging.
:raises: InstanceDeployFailure, if any disk partitioning related
commands fail.
:returns: True if total disk size exceeds 2TB. Returns False otherwise.
"""
try:
disksize_bytes = utils.execute('blockdev', '--getsize64', device,
use_standard_locale=True,
run_as_root=True)
except (processutils.UnknownArgumentError,
processutils.ProcessExecutionError, OSError) as e:
msg = (_('Failed to get size of disk %(disk)s for node %(node)s. '
'Error: %(error)s') %
{'disk': device, 'node': node_uuid, 'error': e})
LOG.error(msg)
raise exception.InstanceDeployFailure(msg)
disksize_mb = int(disksize_bytes) // 1024 // 1024
return disksize_mb > MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR
def _get_labelled_partition(device, label, node_uuid):
"""Check and return if partition with given label exists
:param device: The device path.
:param label: Partition label
:param node_uuid: UUID of the Node. Used for logging.
:raises: InstanceDeployFailure, if any disk partitioning related
commands fail.
:returns: block device file for partition if it exists; otherwise it
returns None.
"""
try:
utils.execute('partprobe', device, run_as_root=True)
label_arg = 'LABEL=%s' % label
output, err = utils.execute('blkid', '-o', 'device', device,
'-t', label_arg, check_exit_code=[0, 2],
use_standard_locale=True, run_as_root=True)
except (processutils.UnknownArgumentError,
processutils.ProcessExecutionError, OSError) as e:
msg = (_('Failed to retrieve partition labels on disk %(disk)s '
'for node %(node)s. Error: %(error)s') %
{'disk': device, 'node': node_uuid, 'error': e})
LOG.error(msg)
raise exception.InstanceDeployFailure(msg)
if output:
if len(output.split()) > 1:
raise exception.InstanceDeployFailure(
_('More than one config drive exists on device %(device)s '
'for node %(node)s.')
% {'device': device, 'node': node_uuid})
return output.rstrip()
def _is_disk_gpt_partitioned(device, node_uuid):
"""Checks if the disk is GPT partitioned
:param device: The device path.
:param node_uuid: UUID of the Node. Used for logging.
:raises: InstanceDeployFailure, if any disk partitioning related
commands fail.
:param node_uuid: UUID of the Node
:returns: Boolean. Returns True if disk is GPT partitioned
"""
try:
output = utils.execute('blkid', '-p', '-o', 'value', '-s', 'PTTYPE',
device, use_standard_locale=True,
run_as_root=True)
except (processutils.UnknownArgumentError,
processutils.ProcessExecutionError, OSError) as e:
msg = (_('Failed to retrieve partition table type for disk %(disk)s '
'for node %(node)s. Error: %(error)s') %
{'disk': device, 'node': node_uuid, 'error': e})
LOG.error(msg)
raise exception.InstanceDeployFailure(msg)
return 'gpt' in output
def _fix_gpt_structs(device, node_uuid):
"""Checks backup GPT data structures and moves them to end of the device
:param device: The device path.
:param node_uuid: UUID of the Node. Used for logging.
:raises: InstanceDeployFailure, if any disk partitioning related
commands fail.
"""
try:
output, err = utils.execute('partprobe', device,
use_standard_locale=True,
run_as_root=True)
search_str = "fix the GPT to use all of the space"
if search_str in err:
utils.execute('sgdisk', '-e', device, run_as_root=True)
except (processutils.UnknownArgumentError,
processutils.ProcessExecutionError, OSError) as e:
msg = (_('Failed to fix GPT data structures on disk %(disk)s '
'for node %(node)s. Error: %(error)s') %
{'disk': device, 'node': node_uuid, 'error': e})
LOG.error(msg)
raise exception.InstanceDeployFailure(msg)
def create_config_drive_partition(node_uuid, device, configdrive):
"""Create a partition for config drive
Checks if the device is GPT or MBR partitioned and creates config drive
partition accordingly.
:param node_uuid: UUID of the Node.
:param device: The device path.
:param configdrive: Base64 encoded Gzipped configdrive content or
configdrive HTTP URL.
:raises: InstanceDeployFailure if config drive size exceeds maximum limit
or if it fails to create config drive.
"""
confdrive_file = None
try:
config_drive_part = _get_labelled_partition(device,
CONFIGDRIVE_LABEL,
node_uuid)
confdrive_mb, confdrive_file = _get_configdrive(configdrive,
node_uuid)
if confdrive_mb > MAX_CONFIG_DRIVE_SIZE_MB:
raise exception.InstanceDeployFailure(
_('Config drive size exceeds maximum limit of 64MiB. '
'Size of the given config drive is %(size)d MiB for '
'node %(node)s.')
% {'size': confdrive_mb, 'node': node_uuid})
LOG.debug("Adding config drive partition %(size)d MiB to "
"device: %(dev)s for node %(node)s",
{'dev': device, 'size': confdrive_mb, 'node': node_uuid})
if config_drive_part:
LOG.debug("Configdrive for node %(node)s exists at "
"%(part)s",
{'node': node_uuid, 'part': config_drive_part})
else:
cur_parts = set(part['number'] for part in list_partitions(device))
if _is_disk_gpt_partitioned(device, node_uuid):
_fix_gpt_structs(device, node_uuid)
create_option = '0:-%dMB:0' % MAX_CONFIG_DRIVE_SIZE_MB
utils.execute('sgdisk', '-n', create_option, device,
run_as_root=True)
else:
# Check if the disk has 4 partitions. The MBR based disk
# cannot have more than 4 partitions.
# TODO(stendulker): One can use logical partitions to create
# a config drive if there are 4 primary partitions.
# https://bugs.launchpad.net/ironic/+bug/1561283
num_parts = len(list_partitions(device))
if num_parts > 3:
raise exception.InstanceDeployFailure(
_('Config drive cannot be created for node %(node)s. '
'Disk uses MBR partitioning and already has '
'%(parts)d primary partitions.')
% {'node': node_uuid, 'parts': num_parts})
# Check if disk size exceeds 2TB msdos limit
startlimit = '-%dMiB' % MAX_CONFIG_DRIVE_SIZE_MB
endlimit = '-0'
if _is_disk_larger_than_max_size(device, node_uuid):
# Need to create a small partition at 2TB limit
LOG.warning(_LW("Disk size is larger than 2TB for "
"node %(node)s. Creating config drive "
"at the end of the disk %(disk)s."),
{'node': node_uuid, 'disk': device})
startlimit = (MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR -
MAX_CONFIG_DRIVE_SIZE_MB - 1)
endlimit = MAX_DISK_SIZE_MB_SUPPORTED_BY_MBR - 1
utils.execute('parted', '-a', 'optimal', '-s', '--', device,
'mkpart', 'primary', 'ext2', startlimit,
endlimit, run_as_root=True)
upd_parts = set(part['number'] for part in list_partitions(device))
new_part = set(upd_parts) - set(cur_parts)
if len(new_part) != 1:
raise exception.InstanceDeployFailure(
_('Disk partitioning failed on device %(device)s. '
'Unable to retrive config drive partition information.')
% {'device': device})
if is_iscsi_device(device, node_uuid):
config_drive_part = '%s-part%s' % (device, new_part.pop())
else:
config_drive_part = '%s%s' % (device, new_part.pop())
dd(confdrive_file, config_drive_part)
LOG.info(_LI("Configdrive for node %(node)s successfully "
"copied onto partition %(part)s"),
{'node': node_uuid, 'part': config_drive_part})
except (processutils.UnknownArgumentError,
processutils.ProcessExecutionError, OSError) as e:
msg = (_('Failed to create config drive on disk %(disk)s '
'for node %(node)s. Error: %(error)s') %
{'disk': device, 'node': node_uuid, 'error': e})
LOG.error(msg)
raise exception.InstanceDeployFailure(msg)
finally:
# If the configdrive was requested make sure we delete the file
# after copying the content to the partition
if confdrive_file:
utils.unlink_without_raise(confdrive_file)

View File

@ -671,3 +671,572 @@ class OtherFunctionTestCase(test_base.BaseTestCase):
return_value=mb + 1)
self.assertEqual(2, disk_utils.get_image_mb('x', False))
self.assertEqual(2, disk_utils.get_image_mb('x', True))
@mock.patch.object(utils, 'execute')
class WholeDiskPartitionTestCases(test_base.BaseTestCase):
def setUp(self):
super(WholeDiskPartitionTestCases, self).setUp()
self.dev = "/dev/fake"
self.config_part_label = "config-2"
self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
def test_get_partition_present(self, mock_execute):
blkid_output = '/dev/fake12\n'
mock_execute.side_effect = [(None, ''), (blkid_output, '')]
result = disk_utils._get_labelled_partition(self.dev,
self.config_part_label,
self.node_uuid)
self.assertEqual(blkid_output.rstrip(), result)
execute_calls = [
mock.call('partprobe', self.dev, run_as_root=True),
mock.call('blkid', '-o', 'device', self.dev, '-t',
'LABEL=config-2', check_exit_code=[0, 2],
use_standard_locale=True, run_as_root=True)
]
mock_execute.assert_has_calls(execute_calls)
def test_get_partition_absent(self, mock_execute):
mock_execute.side_effect = [(None, ''),
(None, '')]
result = disk_utils._get_labelled_partition(self.dev,
self.config_part_label,
self.node_uuid)
self.assertEqual(None, result)
execute_calls = [
mock.call('partprobe', self.dev, run_as_root=True),
mock.call('blkid', '-o', 'device', self.dev, '-t',
'LABEL=config-2', check_exit_code=[0, 2],
use_standard_locale=True, run_as_root=True)
]
mock_execute.assert_has_calls(execute_calls)
def test_get_partition_DeployFail_exc(self, mock_execute):
blkid_output = '/dev/fake12\n/dev/fake13\n'
mock_execute.side_effect = [(None, ''), (blkid_output, '')]
self.assertRaises(exception.InstanceDeployFailure,
disk_utils._get_labelled_partition, self.dev,
self.config_part_label, self.node_uuid)
execute_calls = [
mock.call('partprobe', self.dev, run_as_root=True),
mock.call('blkid', '-o', 'device', self.dev, '-t',
'LABEL=config-2', check_exit_code=[0, 2],
use_standard_locale=True, run_as_root=True)
]
mock_execute.assert_has_calls(execute_calls)
@mock.patch.object(disk_utils.LOG, 'error')
def test_get_partition_exc(self, mock_log, mock_execute):
mock_execute.side_effect = processutils.ProcessExecutionError
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Failed to retrieve partition labels',
disk_utils._get_labelled_partition, self.dev,
self.config_part_label, self.node_uuid)
mock_execute.assert_called_once_with('partprobe', self.dev,
run_as_root=True)
self.assertEqual(1, mock_log.call_count)
def _test_is_disk_larger_than_max_size(self, mock_execute, blk_out):
mock_execute.return_value = blk_out
result = disk_utils._is_disk_larger_than_max_size(self.dev,
self.node_uuid)
mock_execute.assert_called_once_with('blockdev', '--getsize64',
'/dev/fake', run_as_root=True,
use_standard_locale=True)
return result
def test_is_disk_larger_than_max_size_false(self, mock_execute):
blkid_out = "53687091200"
ret = self._test_is_disk_larger_than_max_size(mock_execute,
blk_out=blkid_out)
self.assertFalse(ret)
def test_is_disk_larger_than_max_size_true(self, mock_execute):
blkid_out = "4398046511104"
ret = self._test_is_disk_larger_than_max_size(mock_execute,
blk_out=blkid_out)
self.assertTrue(ret)
@mock.patch.object(disk_utils.LOG, 'error')
def test_is_disk_larger_than_max_size_exc(self, mock_log, mock_execute):
mock_execute.side_effect = processutils.ProcessExecutionError
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Failed to get size of disk',
disk_utils._is_disk_larger_than_max_size,
self.dev, self.node_uuid)
mock_execute.assert_called_once_with('blockdev', '--getsize64',
'/dev/fake', run_as_root=True,
use_standard_locale=True)
self.assertEqual(1, mock_log.call_count)
def test__is_disk_gpt_partitioned_true(self, mock_execute):
blkid_output = 'gpt'
mock_execute.return_value = (blkid_output, '')
result = disk_utils._is_disk_gpt_partitioned('/dev/fake',
self.node_uuid)
self.assertTrue(result)
mock_execute.assert_called_once_with('blkid', '-p', '-o', 'value',
'-s', 'PTTYPE', '/dev/fake',
use_standard_locale=True,
run_as_root=True)
def test_is_disk_gpt_partitioned_false(self, mock_execute):
blkid_output = 'dos'
mock_execute.return_value = (blkid_output, '')
result = disk_utils._is_disk_gpt_partitioned('/dev/fake',
self.node_uuid)
self.assertFalse(result)
mock_execute.assert_called_once_with('blkid', '-p', '-o', 'value',
'-s', 'PTTYPE', '/dev/fake',
use_standard_locale=True,
run_as_root=True)
@mock.patch.object(disk_utils.LOG, 'error')
def test_is_disk_gpt_partitioned_exc(self, mock_log, mock_execute):
mock_execute.side_effect = processutils.ProcessExecutionError
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Failed to retrieve partition table type',
disk_utils._is_disk_gpt_partitioned,
self.dev, self.node_uuid)
mock_execute.assert_called_once_with('blkid', '-p', '-o', 'value',
'-s', 'PTTYPE', '/dev/fake',
use_standard_locale=True,
run_as_root=True)
self.assertEqual(1, mock_log.call_count)
def test_fix_gpt_structs_fix_required(self, mock_execute):
partprobe_err = """
Error: The backup GPT table is not at the end of the disk, as it should be.
This might mean that another operating system believes the disk is smaller.
Fix, by moving the backup to the end (and removing the old backup)?
Warning: Not all of the space available to /dev/sdb appears to be used,
you can fix the GPT to use all of the space (an extra 581456476 blocks)
or continue with the current setting?
"""
mock_execute.return_value = ('', partprobe_err)
execute_calls = [
mock.call('partprobe', '/dev/fake', use_standard_locale=True,
run_as_root=True),
mock.call('sgdisk', '-e', '/dev/fake', run_as_root=True)
]
disk_utils._fix_gpt_structs('/dev/fake', self.node_uuid)
mock_execute.assert_has_calls(execute_calls)
def test_fix_gpt_structs_fix_not_required(self, mock_execute):
mock_execute.return_value = ('', '')
disk_utils._fix_gpt_structs('/dev/fake', self.node_uuid)
mock_execute.assert_called_once_with('partprobe', '/dev/fake',
use_standard_locale=True,
run_as_root=True)
@mock.patch.object(disk_utils.LOG, 'error')
def test_fix_gpt_structs_exc(self, mock_log, mock_execute):
mock_execute.side_effect = processutils.ProcessExecutionError
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Failed to fix GPT data structures on disk',
disk_utils._fix_gpt_structs,
self.dev, self.node_uuid)
mock_execute.assert_called_once_with('partprobe', '/dev/fake',
use_standard_locale=True,
run_as_root=True)
self.assertEqual(1, mock_log.call_count)
class WholeDiskConfigDriveTestCases(test_base.BaseTestCase):
def setUp(self):
super(WholeDiskConfigDriveTestCases, self).setUp()
self.dev = "/dev/fake"
self.config_part_label = "config-2"
self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(utils, 'unlink_without_raise',
autospec=True)
@mock.patch.object(disk_utils, 'dd',
autospec=True)
@mock.patch.object(disk_utils, '_fix_gpt_structs',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_gpt_partitioned',
autospec=True)
@mock.patch.object(disk_utils, 'list_partitions',
autospec=True)
@mock.patch.object(disk_utils, '_get_labelled_partition',
autospec=True)
@mock.patch.object(disk_utils, '_get_configdrive',
autospec=True)
def test_create_partition_exists(self, mock_get_configdrive,
mock_get_labelled_partition,
mock_list_partitions,
mock_is_disk_gpt, mock_fix_gpt,
mock_dd, mock_unlink, mock_execute):
config_url = 'http://1.2.3.4/cd'
configdrive_part = '/dev/fake-part1'
configdrive_file = '/tmp/xyz'
configdrive_mb = 10
mock_get_labelled_partition.return_value = configdrive_part
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
disk_utils.create_config_drive_partition(self.node_uuid, self.dev,
config_url)
mock_get_configdrive.assert_called_with(config_url, self.node_uuid)
mock_get_labelled_partition.assert_called_with(self.dev,
self.config_part_label,
self.node_uuid)
self.assertFalse(mock_list_partitions.called)
self.assertFalse(mock_is_disk_gpt.called)
self.assertFalse(mock_execute.called)
mock_dd.assert_called_with(configdrive_file, configdrive_part)
mock_unlink.assert_called_with(configdrive_file)
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(utils, 'unlink_without_raise',
autospec=True)
@mock.patch.object(disk_utils, 'dd',
autospec=True)
@mock.patch.object(disk_utils, '_fix_gpt_structs',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_gpt_partitioned',
autospec=True)
@mock.patch.object(disk_utils, 'list_partitions',
autospec=True)
@mock.patch.object(disk_utils, '_get_labelled_partition',
autospec=True)
@mock.patch.object(disk_utils, '_get_configdrive',
autospec=True)
def test_create_partition_gpt(self, mock_get_configdrive,
mock_get_labelled_partition,
mock_list_partitions,
mock_is_disk_gpt, mock_fix_gpt,
mock_dd, mock_unlink, mock_execute):
config_url = 'http://1.2.3.4/cd'
configdrive_file = '/tmp/xyz'
configdrive_mb = 10
initial_partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 5, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
updated_partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 4, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 5, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
mock_get_labelled_partition.return_value = None
mock_is_disk_gpt.return_value = True
mock_list_partitions.side_effect = [initial_partitions,
updated_partitions]
expected_part = '/dev/fake4'
disk_utils.create_config_drive_partition(self.node_uuid, self.dev,
config_url)
mock_execute.assert_called_with('sgdisk', '-n', '0:-64MB:0',
self.dev, run_as_root=True)
self.assertEqual(2, mock_list_partitions.call_count)
mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid)
mock_fix_gpt.assert_called_with(self.dev, self.node_uuid)
mock_dd.assert_called_with(configdrive_file, expected_part)
mock_unlink.assert_called_with(configdrive_file)
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(disk_utils.LOG, 'warning')
@mock.patch.object(utils, 'unlink_without_raise',
autospec=True)
@mock.patch.object(disk_utils, 'dd',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_larger_than_max_size',
autospec=True)
@mock.patch.object(disk_utils, '_fix_gpt_structs',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_gpt_partitioned',
autospec=True)
@mock.patch.object(disk_utils, 'list_partitions',
autospec=True)
@mock.patch.object(disk_utils, '_get_labelled_partition',
autospec=True)
@mock.patch.object(disk_utils, '_get_configdrive',
autospec=True)
def _test_create_partition_mbr(self, mock_get_configdrive,
mock_get_labelled_partition,
mock_list_partitions,
mock_is_disk_gpt, mock_fix_gpt,
mock_disk_exceeds, mock_dd,
mock_unlink, mock_log, mock_execute,
disk_size_exceeds_max=False,
is_iscsi_device=False):
config_url = 'http://1.2.3.4/cd'
configdrive_file = '/tmp/xyz'
configdrive_mb = 10
mock_disk_exceeds.return_value = disk_size_exceeds_max
initial_partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 5, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
updated_partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 4, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 5, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
mock_list_partitions.side_effect = [initial_partitions,
initial_partitions,
updated_partitions]
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
mock_get_labelled_partition.return_value = None
mock_is_disk_gpt.return_value = False
self.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz"
if is_iscsi_device:
self.dev = ('/dev/iqn.2008-10.org.openstack:%s.fake' %
self.node_uuid)
expected_part = '%s-part4' % self.dev
else:
expected_part = '/dev/fake4'
disk_utils.create_config_drive_partition(self.node_uuid, self.dev,
config_url)
mock_get_configdrive.assert_called_with(config_url, self.node_uuid)
if disk_size_exceeds_max:
self.assertEqual(1, mock_log.call_count)
mock_execute.assert_called_with('parted', '-a', 'optimal', '-s',
'--', self.dev, 'mkpart',
'primary', 'ext2', 2097087,
2097151, run_as_root=True)
else:
self.assertEqual(0, mock_log.call_count)
mock_execute.assert_called_with('parted', '-a', 'optimal', '-s',
'--', self.dev, 'mkpart',
'primary', 'ext2', '-64MiB',
'-0', run_as_root=True)
self.assertEqual(3, mock_list_partitions.call_count)
mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid)
mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid)
mock_dd.assert_called_with(configdrive_file, expected_part)
mock_unlink.assert_called_with(configdrive_file)
self.assertFalse(mock_fix_gpt.called)
def test__create_partition_mbr_disk_under_2TB(self):
self._test_create_partition_mbr(disk_size_exceeds_max=False,
is_iscsi_device=True)
def test__create_partition_mbr_disk_exceeds_2TB(self):
self._test_create_partition_mbr(disk_size_exceeds_max=True,
is_iscsi_device=False)
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(utils, 'unlink_without_raise',
autospec=True)
@mock.patch.object(disk_utils, 'dd',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_larger_than_max_size',
autospec=True)
@mock.patch.object(disk_utils, '_fix_gpt_structs',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_gpt_partitioned',
autospec=True)
@mock.patch.object(disk_utils, 'list_partitions',
autospec=True)
@mock.patch.object(disk_utils, '_get_labelled_partition',
autospec=True)
@mock.patch.object(disk_utils, '_get_configdrive',
autospec=True)
def test_create_partition_part_create_fail(self, mock_get_configdrive,
mock_get_labelled_partition,
mock_list_partitions,
mock_is_disk_gpt, mock_fix_gpt,
mock_disk_exceeds, mock_dd,
mock_unlink, mock_execute):
config_url = 'http://1.2.3.4/cd'
configdrive_file = '/tmp/xyz'
configdrive_mb = 10
initial_partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 5, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
updated_partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 5, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
mock_get_labelled_partition.return_value = None
mock_is_disk_gpt.return_value = False
mock_disk_exceeds.return_value = False
mock_list_partitions.side_effect = [initial_partitions,
initial_partitions,
updated_partitions]
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Disk partitioning failed on device',
disk_utils.create_config_drive_partition,
self.node_uuid, self.dev, config_url)
mock_get_configdrive.assert_called_with(config_url, self.node_uuid)
mock_execute.assert_called_with('parted', '-a', 'optimal', '-s', '--',
self.dev, 'mkpart', 'primary',
'ext2', '-64MiB', '-0',
run_as_root=True)
self.assertEqual(3, mock_list_partitions.call_count)
mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid)
mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid)
self.assertFalse(mock_fix_gpt.called)
self.assertFalse(mock_dd.called)
mock_unlink.assert_called_with(configdrive_file)
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(utils, 'unlink_without_raise',
autospec=True)
@mock.patch.object(disk_utils, 'dd',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_larger_than_max_size',
autospec=True)
@mock.patch.object(disk_utils, '_fix_gpt_structs',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_gpt_partitioned',
autospec=True)
@mock.patch.object(disk_utils, 'list_partitions',
autospec=True)
@mock.patch.object(disk_utils, '_get_labelled_partition',
autospec=True)
@mock.patch.object(disk_utils, '_get_configdrive',
autospec=True)
def test_create_partition_part_create_exc(self, mock_get_configdrive,
mock_get_labelled_partition,
mock_list_partitions,
mock_is_disk_gpt, mock_fix_gpt,
mock_disk_exceeds, mock_dd,
mock_unlink, mock_execute):
config_url = 'http://1.2.3.4/cd'
configdrive_file = '/tmp/xyz'
configdrive_mb = 10
initial_partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 5, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
mock_get_labelled_partition.return_value = None
mock_is_disk_gpt.return_value = False
mock_disk_exceeds.return_value = False
mock_list_partitions.side_effect = [initial_partitions,
initial_partitions]
mock_execute.side_effect = processutils.ProcessExecutionError
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Failed to create config drive on disk',
disk_utils.create_config_drive_partition,
self.node_uuid, self.dev, config_url)
mock_get_configdrive.assert_called_with(config_url, self.node_uuid)
mock_execute.assert_called_with('parted', '-a', 'optimal', '-s', '--',
self.dev, 'mkpart', 'primary',
'ext2', '-64MiB', '-0',
run_as_root=True)
self.assertEqual(2, mock_list_partitions.call_count)
mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid)
mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid)
self.assertFalse(mock_fix_gpt.called)
self.assertFalse(mock_dd.called)
mock_unlink.assert_called_with(configdrive_file)
@mock.patch.object(utils, 'unlink_without_raise',
autospec=True)
@mock.patch.object(disk_utils, 'dd',
autospec=True)
@mock.patch.object(disk_utils, '_fix_gpt_structs',
autospec=True)
@mock.patch.object(disk_utils, '_is_disk_gpt_partitioned',
autospec=True)
@mock.patch.object(disk_utils, 'list_partitions',
autospec=True)
@mock.patch.object(disk_utils, '_get_labelled_partition',
autospec=True)
@mock.patch.object(disk_utils, '_get_configdrive',
autospec=True)
def test_create_partition_num_parts_exceed(self, mock_get_configdrive,
mock_get_labelled_partition,
mock_list_partitions,
mock_is_disk_gpt, mock_fix_gpt,
mock_dd, mock_unlink):
config_url = 'http://1.2.3.4/cd'
configdrive_file = '/tmp/xyz'
configdrive_mb = 10
partitions = [{'end': 49152, 'number': 1, 'start': 1,
'flags': 'boot', 'filesystem': 'ext4',
'size': 49151},
{'end': 51099, 'number': 2, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 3, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046},
{'end': 51099, 'number': 4, 'start': 49153,
'flags': '', 'filesystem': '', 'size': 2046}]
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
mock_get_labelled_partition.return_value = None
mock_is_disk_gpt.return_value = False
mock_list_partitions.side_effect = [partitions, partitions]
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Config drive cannot be created for node',
disk_utils.create_config_drive_partition,
self.node_uuid, self.dev, config_url)
mock_get_configdrive.assert_called_with(config_url, self.node_uuid)
self.assertEqual(2, mock_list_partitions.call_count)
mock_is_disk_gpt.assert_called_with(self.dev, self.node_uuid)
self.assertFalse(mock_fix_gpt.called)
self.assertFalse(mock_dd.called)
mock_unlink.assert_called_with(configdrive_file)
@mock.patch.object(utils, 'execute', autospec=True)
@mock.patch.object(utils, 'unlink_without_raise',
autospec=True)
@mock.patch.object(disk_utils, '_get_labelled_partition',
autospec=True)
@mock.patch.object(disk_utils, '_get_configdrive',
autospec=True)
def test_create_partition_conf_drive_sz_exceed(self, mock_get_configdrive,
mock_get_labelled_partition,
mock_unlink, mock_execute):
config_url = 'http://1.2.3.4/cd'
configdrive_file = '/tmp/xyz'
configdrive_mb = 65
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
mock_get_labelled_partition.return_value = None
self.assertRaisesRegex(exception.InstanceDeployFailure,
'Config drive size exceeds maximum limit',
disk_utils.create_config_drive_partition,
self.node_uuid, self.dev, config_url)
mock_get_configdrive.assert_called_with(config_url, self.node_uuid)
mock_unlink.assert_called_with(configdrive_file)