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:
parent
7aac631fbc
commit
7e926fd3fb
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user