# 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 gzip import shutil import tempfile from unittest import mock from ironic_lib import disk_partitioner from ironic_lib import disk_utils from ironic_lib import exception from ironic_lib import utils from oslo_concurrency import processutils import requests from ironic_python_agent import partition_utils from ironic_python_agent.tests.unit import base @mock.patch.object(shutil, 'copyfileobj', autospec=True) @mock.patch.object(requests, 'get', autospec=True) class GetConfigdriveTestCase(base.IronicAgentTest): @mock.patch.object(gzip, 'GzipFile', autospec=True) def test_get_configdrive(self, mock_gzip, mock_requests, mock_copy): mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy') tempdir = tempfile.mkdtemp() (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', 'fake-node-uuid', tempdir=tempdir) self.assertTrue(path.startswith(tempdir)) 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) def test_get_configdrive_binary(self, mock_requests, mock_copy): mock_requests.return_value = mock.MagicMock(content=b'content') tempdir = tempfile.mkdtemp() (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', 'fake-node-uuid', tempdir=tempdir) self.assertTrue(path.startswith(tempdir)) self.assertEqual(b'content', open(path, 'rb').read()) mock_requests.assert_called_once_with('http://1.2.3.4/cd') self.assertFalse(mock_copy.called) @mock.patch.object(gzip, 'GzipFile', autospec=True) def test_get_configdrive_base64_string(self, mock_gzip, mock_requests, mock_copy): partition_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) def test_get_configdrive_bad_url(self, mock_requests, mock_copy): mock_requests.side_effect = requests.exceptions.RequestException self.assertRaises(exception.InstanceDeployFailure, partition_utils.get_configdrive, 'http://1.2.3.4/cd', 'fake-node-uuid') self.assertFalse(mock_copy.called) def test_get_configdrive_base64_error(self, mock_requests, mock_copy): self.assertRaises(exception.InstanceDeployFailure, partition_utils.get_configdrive, 'malformed', 'fake-node-uuid') self.assertFalse(mock_copy.called) @mock.patch.object(gzip, 'GzipFile', autospec=True) def test_get_configdrive_gzip_error(self, mock_gzip, mock_requests, mock_copy): mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy') mock_copy.side_effect = IOError self.assertRaises(exception.InstanceDeployFailure, partition_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.patch.object(utils, 'execute', autospec=True) class GetLabelledPartitionTestCases(base.IronicAgentTest): def setUp(self): super(GetLabelledPartitionTestCases, 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): lsblk_output = 'NAME="fake12" LABEL="config-2"\n' part_result = '/dev/fake12' mock_execute.side_effect = [(None, ''), (lsblk_output, '')] result = partition_utils.get_labelled_partition(self.dev, self.config_part_label, self.node_uuid) self.assertEqual(part_result, result) execute_calls = [ mock.call('partprobe', self.dev, run_as_root=True, attempts=10), mock.call('lsblk', '-Po', 'name,label', self.dev, check_exit_code=[0, 1], use_standard_locale=True, run_as_root=True) ] mock_execute.assert_has_calls(execute_calls) def test_get_partition_present_uppercase(self, mock_execute): lsblk_output = 'NAME="fake12" LABEL="CONFIG-2"\n' part_result = '/dev/fake12' mock_execute.side_effect = [(None, ''), (lsblk_output, '')] result = partition_utils.get_labelled_partition(self.dev, self.config_part_label, self.node_uuid) self.assertEqual(part_result, result) execute_calls = [ mock.call('partprobe', self.dev, run_as_root=True, attempts=10), mock.call('lsblk', '-Po', 'name,label', self.dev, check_exit_code=[0, 1], 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 = partition_utils.get_labelled_partition(self.dev, self.config_part_label, self.node_uuid) self.assertIsNone(result) execute_calls = [ mock.call('partprobe', self.dev, run_as_root=True, attempts=10), mock.call('lsblk', '-Po', 'name,label', self.dev, check_exit_code=[0, 1], use_standard_locale=True, run_as_root=True) ] mock_execute.assert_has_calls(execute_calls) def test_get_partition_DeployFail_exc(self, mock_execute): label = 'config-2' lsblk_output = ('NAME="fake12" LABEL="%s"\n' 'NAME="fake13" LABEL="%s"\n' % (label, label)) mock_execute.side_effect = [(None, ''), (lsblk_output, '')] self.assertRaisesRegex(exception.InstanceDeployFailure, 'fake .*fake12 .*fake13', partition_utils.get_labelled_partition, self.dev, self.config_part_label, self.node_uuid) execute_calls = [ mock.call('partprobe', self.dev, run_as_root=True, attempts=10), mock.call('lsblk', '-Po', 'name,label', self.dev, check_exit_code=[0, 1], use_standard_locale=True, run_as_root=True) ] mock_execute.assert_has_calls(execute_calls) @mock.patch.object(partition_utils.LOG, 'error', autospec=True) 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', partition_utils.get_labelled_partition, self.dev, self.config_part_label, self.node_uuid) execute_calls = [ mock.call('partprobe', self.dev, run_as_root=True, attempts=10), mock.call('lsblk', '-Po', 'name,label', self.dev, check_exit_code=[0, 1], use_standard_locale=True, run_as_root=True) ] mock_execute.assert_has_calls(execute_calls) self.assertEqual(1, mock_log.call_count) @mock.patch.object(utils, 'execute', autospec=True) class IsDiskLargerThanMaxSizeTestCases(base.IronicAgentTest): dev = "/dev/fake" node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" def _test_is_disk_larger_than_max_size(self, mock_execute, blk_out): mock_execute.return_value = ('%s\n' % blk_out, '') result = partition_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(partition_utils.LOG, 'error', autospec=True) 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', partition_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) @mock.patch.object(disk_partitioner.DiskPartitioner, 'commit', lambda _: None) class WorkOnDiskTestCase(base.IronicAgentTest): 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.node_uuid = "12345678-1234-1234-1234-1234567890abcxyz" self.dev = '/dev/fake' self.swap_part = '/dev/fake-part1' self.root_part = '/dev/fake-part2' self.mock_ibd_obj = mock.patch.object( disk_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( disk_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( disk_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, partition_utils.work_on_disk, self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.ephemeral_format, self.image_path, self.node_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, self.node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label=None, cpu_arch="") 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, partition_utils.work_on_disk, self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.ephemeral_format, self.image_path, self.node_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, self.node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label=None, cpu_arch="") 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, partition_utils.work_on_disk, self.dev, self.root_mb, self.swap_mb, ephemeral_mb, ephemeral_format, self.image_path, self.node_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, self.node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label=None, cpu_arch="") @mock.patch.object(utils, 'unlink_without_raise', autospec=True) @mock.patch.object(partition_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, partition_utils.work_on_disk, self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.ephemeral_format, self.image_path, self.node_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, self.node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label=None, cpu_arch="") mock_unlink.assert_called_once_with('fake-path') @mock.patch.object(utils, 'mkfs', lambda fs, path, label=None: None) @mock.patch.object(disk_utils, 'block_uuid', lambda p: 'uuid') @mock.patch.object(disk_utils, 'populate_image', autospec=True) def test_without_image(self, mock_populate): 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.return_value = True calls = [mock.call(root_part), mock.call(swap_part), mock.call(ephemeral_part)] res = partition_utils.work_on_disk(self.dev, self.root_mb, self.swap_mb, ephemeral_mb, ephemeral_format, None, self.node_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, self.node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label=None, cpu_arch="") self.assertEqual(root_part, res['partitions']['root']) self.assertEqual('uuid', res['root uuid']) self.assertFalse(mock_populate.called) @mock.patch.object(utils, 'mkfs', lambda fs, path, label=None: None) @mock.patch.object(disk_utils, 'block_uuid', lambda p: 'uuid') @mock.patch.object(disk_utils, 'populate_image', lambda image_path, root_path, conv_flags=None: None) def test_gpt_disk_label(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.return_value = True calls = [mock.call(root_part), mock.call(swap_part), mock.call(ephemeral_part)] partition_utils.work_on_disk(self.dev, self.root_mb, self.swap_mb, ephemeral_mb, ephemeral_format, self.image_path, self.node_uuid, disk_label='gpt', conv_flags=None) 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, self.node_uuid, commit=True, boot_option="netboot", boot_mode="bios", disk_label='gpt', cpu_arch="") @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_uefi_localboot(self, mock_mkfs, mock_populate_image, mock_block_uuid): """Test that we create a fat filesystem with UEFI localboot.""" root_part = '/dev/fake-part1' efi_part = '/dev/fake-part2' self.mock_mp.return_value = {'root': root_part, 'efi system partition': efi_part} self.mock_ibd.return_value = True mock_ibd_calls = [mock.call(root_part), mock.call(efi_part)] partition_utils.work_on_disk(self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.ephemeral_format, self.image_path, self.node_uuid, boot_option="local", boot_mode="uefi") self.mock_mp.assert_called_once_with(self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.configdrive_mb, self.node_uuid, commit=True, boot_option="local", boot_mode="uefi", disk_label=None, cpu_arch="") self.assertEqual(self.mock_ibd.call_args_list, mock_ibd_calls) mock_mkfs.assert_called_once_with(fs='vfat', path=efi_part, label='efi-part') mock_populate_image.assert_called_once_with(self.image_path, root_part, conv_flags=None) mock_block_uuid.assert_any_call(root_part) mock_block_uuid.assert_any_call(efi_part) @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_preserve_ephemeral(self, mock_mkfs, mock_populate_image, mock_block_uuid): """Test that ephemeral partition doesn't get overwritten.""" ephemeral_part = '/dev/fake-part1' root_part = '/dev/fake-part2' ephemeral_mb = 256 ephemeral_format = 'exttest' self.mock_mp.return_value = {'ephemeral': ephemeral_part, 'root': root_part} self.mock_ibd.return_value = True calls = [mock.call(root_part), mock.call(ephemeral_part)] partition_utils.work_on_disk(self.dev, self.root_mb, self.swap_mb, ephemeral_mb, ephemeral_format, self.image_path, self.node_uuid, preserve_ephemeral=True) 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, self.node_uuid, commit=False, boot_option="netboot", boot_mode="bios", disk_label=None, cpu_arch="") self.assertFalse(mock_mkfs.called) @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_ppc64le_prep_part(self, mock_mkfs, mock_populate_image, mock_block_uuid): """Test that PReP partition uuid is returned.""" prep_part = '/dev/fake-part1' root_part = '/dev/fake-part2' self.mock_mp.return_value = {'PReP Boot partition': prep_part, 'root': root_part} self.mock_ibd.return_value = True calls = [mock.call(root_part), mock.call(prep_part)] partition_utils.work_on_disk(self.dev, self.root_mb, self.swap_mb, self.ephemeral_mb, self.ephemeral_format, self.image_path, self.node_uuid, boot_option="local", cpu_arch='ppc64le') 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, self.node_uuid, commit=True, boot_option="local", boot_mode="bios", disk_label=None, cpu_arch="ppc64le") self.assertFalse(mock_mkfs.called) @mock.patch.object(disk_utils, 'block_uuid', autospec=True) @mock.patch.object(disk_utils, 'populate_image', autospec=True) @mock.patch.object(utils, 'mkfs', autospec=True) def test_convert_to_sparse(self, mock_mkfs, mock_populate_image, mock_block_uuid): 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.return_value = True partition_utils.work_on_disk(self.dev, self.root_mb, self.swap_mb, ephemeral_mb, ephemeral_format, self.image_path, self.node_uuid, disk_label='gpt', conv_flags='sparse') mock_populate_image.assert_called_once_with(self.image_path, root_part, conv_flags='sparse') class CreateConfigDriveTestCases(base.IronicAgentTest): def setUp(self): super(CreateConfigDriveTestCases, 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_partition', autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) @mock.patch.object(disk_utils, 'list_partitions', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_utils, 'get_configdrive', autospec=True) def test_create_partition_exists(self, mock_get_configdrive, mock_get_labelled_partition, mock_list_partitions, mock_table_type, mock_fix_gpt_partition, 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) partition_utils.create_config_drive_partition(self.node_uuid, self.dev, config_url) mock_fix_gpt_partition.assert_called_with(self.dev, self.node_uuid) 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_execute.called) self.assertFalse(mock_table_type.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_partition', autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) @mock.patch.object(disk_utils, 'list_partitions', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_utils, 'get_configdrive', autospec=True) def test_create_partition_gpt(self, mock_get_configdrive, mock_get_labelled_partition, mock_list_partitions, mock_table_type, mock_fix_gpt_partition, 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_table_type.return_value = 'gpt' mock_list_partitions.side_effect = [initial_partitions, updated_partitions] expected_part = '/dev/fake4' partition_utils.create_config_drive_partition(self.node_uuid, self.dev, config_url) mock_execute.assert_has_calls([ mock.call('sgdisk', '-n', '0:-64MB:0', self.dev, run_as_root=True), mock.call('sync'), mock.call('udevadm', 'settle'), mock.call('partprobe', self.dev, attempts=10, run_as_root=True), mock.call('sgdisk', '-v', self.dev, run_as_root=True), mock.call('udevadm', 'settle'), mock.call('test', '-e', expected_part, attempts=15, delay_on_retry=True) ]) self.assertEqual(2, mock_list_partitions.call_count) mock_table_type.assert_called_with(self.dev) mock_fix_gpt_partition.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(disk_utils, 'count_mbr_partitions', autospec=True) @mock.patch.object(utils, 'execute', autospec=True) @mock.patch.object(partition_utils.LOG, 'warning', autospec=True) @mock.patch.object(utils, 'unlink_without_raise', autospec=True) @mock.patch.object(disk_utils, 'dd', autospec=True) @mock.patch.object(partition_utils, '_is_disk_larger_than_max_size', autospec=True) @mock.patch.object(disk_utils, 'fix_gpt_partition', autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) @mock.patch.object(disk_utils, 'list_partitions', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_utils, 'get_configdrive', autospec=True) def _test_create_partition_mbr(self, mock_get_configdrive, mock_get_labelled_partition, mock_list_partitions, mock_table_type, mock_fix_gpt_partition, mock_disk_exceeds, mock_dd, mock_unlink, mock_log, mock_execute, mock_count, disk_size_exceeds_max=False, is_iscsi_device=False, is_nvme_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, updated_partitions] # 2 primary partitions, 0 logical partitions mock_count.return_value = (2, 0) mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) mock_get_labelled_partition.return_value = None 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 elif is_nvme_device: self.dev = '/dev/nvmefake0' expected_part = '%sp4' % self.dev else: expected_part = '/dev/fake4' partition_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) parted_call = mock.call('parted', '-a', 'optimal', '-s', '--', self.dev, 'mkpart', 'primary', 'fat32', 2097087, 2097151, run_as_root=True) else: self.assertEqual(0, mock_log.call_count) parted_call = mock.call('parted', '-a', 'optimal', '-s', '--', self.dev, 'mkpart', 'primary', 'fat32', '-64MiB', '-0', run_as_root=True) mock_execute.assert_has_calls([ parted_call, mock.call('sync'), mock.call('udevadm', 'settle'), mock.call('partprobe', self.dev, attempts=10, run_as_root=True), mock.call('sgdisk', '-v', self.dev, run_as_root=True), mock.call('udevadm', 'settle'), mock.call('test', '-e', expected_part, attempts=15, delay_on_retry=True) ]) self.assertEqual(2, mock_list_partitions.call_count) mock_table_type.assert_called_with(self.dev) mock_fix_gpt_partition.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) mock_count.assert_called_with(self.dev) def test__create_partition_mbr_disk_under_2TB(self): self._test_create_partition_mbr(disk_size_exceeds_max=False, is_iscsi_device=True, is_nvme_device=False) def test__create_partition_mbr_disk_under_2TB_nvme(self): self._test_create_partition_mbr(disk_size_exceeds_max=False, is_iscsi_device=False, is_nvme_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, is_nvme_device=False) def test__create_partition_mbr_disk_exceeds_2TB_nvme(self): self._test_create_partition_mbr(disk_size_exceeds_max=True, is_iscsi_device=False, is_nvme_device=True) @mock.patch.object(disk_utils, 'count_mbr_partitions', autospec=True) @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(partition_utils, '_is_disk_larger_than_max_size', autospec=True) @mock.patch.object(disk_utils, 'fix_gpt_partition', autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) @mock.patch.object(disk_utils, 'list_partitions', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_utils, 'get_configdrive', autospec=True) def test_create_partition_part_create_fail(self, mock_get_configdrive, mock_get_labelled_partition, mock_list_partitions, mock_table_type, mock_fix_gpt_partition, mock_disk_exceeds, mock_dd, mock_unlink, mock_execute, mock_count): 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_disk_exceeds.return_value = False mock_list_partitions.side_effect = [initial_partitions, initial_partitions, updated_partitions] # 2 primary partitions, 0 logical partitions mock_count.return_value = (2, 0) self.assertRaisesRegex(exception.InstanceDeployFailure, 'Disk partitioning failed on device', partition_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_has_calls([ mock.call('parted', '-a', 'optimal', '-s', '--', self.dev, 'mkpart', 'primary', 'fat32', '-64MiB', '-0', run_as_root=True), mock.call('sync'), mock.call('udevadm', 'settle'), mock.call('partprobe', self.dev, attempts=10, run_as_root=True), mock.call('sgdisk', '-v', self.dev, run_as_root=True), ]) self.assertEqual(2, mock_list_partitions.call_count) mock_fix_gpt_partition.assert_called_with(self.dev, self.node_uuid) mock_table_type.assert_called_with(self.dev) mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid) self.assertFalse(mock_dd.called) mock_unlink.assert_called_with(configdrive_file) mock_count.assert_called_once_with(self.dev) @mock.patch.object(disk_utils, 'count_mbr_partitions', autospec=True) @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(partition_utils, '_is_disk_larger_than_max_size', autospec=True) @mock.patch.object(disk_utils, 'fix_gpt_partition', autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) @mock.patch.object(disk_utils, 'list_partitions', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_utils, 'get_configdrive', autospec=True) def test_create_partition_part_create_exc(self, mock_get_configdrive, mock_get_labelled_partition, mock_list_partitions, mock_table_type, mock_fix_gpt_partition, mock_disk_exceeds, mock_dd, mock_unlink, mock_execute, mock_count): 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_disk_exceeds.return_value = False mock_list_partitions.side_effect = [initial_partitions, initial_partitions] # 2 primary partitions, 0 logical partitions mock_count.return_value = (2, 0) mock_execute.side_effect = processutils.ProcessExecutionError self.assertRaisesRegex(exception.InstanceDeployFailure, 'Failed to create config drive on disk', partition_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', 'fat32', '-64MiB', '-0', run_as_root=True) self.assertEqual(1, mock_list_partitions.call_count) mock_fix_gpt_partition.assert_called_with(self.dev, self.node_uuid) mock_table_type.assert_called_with(self.dev) mock_disk_exceeds.assert_called_with(self.dev, self.node_uuid) self.assertFalse(mock_dd.called) mock_unlink.assert_called_with(configdrive_file) mock_count.assert_called_once_with(self.dev) @mock.patch.object(disk_utils, 'count_mbr_partitions', 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_partition', autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) @mock.patch.object(disk_utils, 'list_partitions', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_utils, 'get_configdrive', autospec=True) def test_create_partition_num_parts_exceed(self, mock_get_configdrive, mock_get_labelled_partition, mock_list_partitions, mock_table_type, mock_fix_gpt_partition, mock_dd, mock_unlink, mock_count): 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_list_partitions.side_effect = [partitions, partitions] # 4 primary partitions, 0 logical partitions mock_count.return_value = (4, 0) self.assertRaisesRegex(exception.InstanceDeployFailure, 'Config drive cannot be created for node', partition_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(1, mock_list_partitions.call_count) mock_fix_gpt_partition.assert_called_with(self.dev, self.node_uuid) mock_table_type.assert_called_with(self.dev) self.assertFalse(mock_dd.called) mock_unlink.assert_called_with(configdrive_file) mock_count.assert_called_once_with(self.dev) @mock.patch.object(utils, 'execute', autospec=True) @mock.patch.object(utils, 'unlink_without_raise', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_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', partition_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) @mock.patch.object(disk_utils, 'count_mbr_partitions', autospec=True) @mock.patch.object(utils, 'execute', autospec=True) @mock.patch.object(utils, 'unlink_without_raise', autospec=True) @mock.patch.object(disk_utils, 'fix_gpt_partition', autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @mock.patch.object(partition_utils, 'get_configdrive', autospec=True) def test_create_partition_conf_drive_error_counting( self, mock_get_configdrive, mock_get_labelled_partition, mock_table_type, mock_fix_gpt_partition, mock_unlink, mock_execute, mock_count): config_url = 'http://1.2.3.4/cd' configdrive_file = '/tmp/xyz' configdrive_mb = 10 mock_get_configdrive.return_value = (configdrive_mb, configdrive_file) mock_get_labelled_partition.return_value = None mock_count.side_effect = ValueError('Booooom') self.assertRaisesRegex(exception.InstanceDeployFailure, 'Failed to check the number of primary ', partition_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) mock_fix_gpt_partition.assert_called_with(self.dev, self.node_uuid) mock_table_type.assert_called_with(self.dev) mock_count.assert_called_once_with(self.dev)