diff options
-rw-r--r-- | ironic_python_agent/efi_utils.py | 10 | ||||
-rw-r--r-- | ironic_python_agent/partition_utils.py | 45 | ||||
-rw-r--r-- | ironic_python_agent/tests/unit/test_partition_utils.py | 31 | ||||
-rw-r--r-- | releasenotes/notes/configdrive-partuuid-3259cfb7428c1483.yaml | 17 | ||||
-rw-r--r-- | releasenotes/notes/detect-endianness-f53a6c4571aba3fe.yaml | 6 | ||||
-rw-r--r-- | tox.ini | 8 | ||||
-rw-r--r-- | zuul.d/ironic-python-agent-jobs.yaml | 64 | ||||
-rw-r--r-- | zuul.d/project.yaml | 22 |
8 files changed, 153 insertions, 50 deletions
diff --git a/ironic_python_agent/efi_utils.py b/ironic_python_agent/efi_utils.py index 54d1e62f..456698f5 100644 --- a/ironic_python_agent/efi_utils.py +++ b/ironic_python_agent/efi_utils.py @@ -297,8 +297,14 @@ def _run_efibootmgr(valid_efi_bootloaders, device, efi_partition, 'File: %s', v_bl) # These files are always UTF-16 encoded, sometimes have a header. # Positive bonus is python silently drops the FEFF header. - with open(mount_point + '/' + v_bl, 'r', encoding='utf-16') as csv: - contents = str(csv.read()) + try: + with open(mount_point + '/' + v_bl, 'r', + encoding='utf-16') as csv: + contents = str(csv.read()) + except UnicodeError: + with open(mount_point + '/' + v_bl, 'r', + encoding='utf-16-le') as csv: + contents = str(csv.read()) csv_contents = contents.split(',', maxsplit=3) csv_filename = v_bl.split('/')[-1] v_efi_bl_path = v_bl.replace(csv_filename, str(csv_contents[0])) diff --git a/ironic_python_agent/partition_utils.py b/ironic_python_agent/partition_utils.py index 42b90445..d82d0072 100644 --- a/ironic_python_agent/partition_utils.py +++ b/ironic_python_agent/partition_utils.py @@ -35,6 +35,7 @@ from oslo_config import cfg from oslo_log import log from oslo_utils import excutils from oslo_utils import units +from oslo_utils import uuidutils import requests from ironic_python_agent import errors @@ -377,14 +378,18 @@ def create_config_drive_partition(node_uuid, device, configdrive): "%(part)s", {'node': node_uuid, 'part': config_drive_part}) else: - cur_parts = set(part['number'] - for part in disk_utils.list_partitions(device)) - + part_uuid = None if disk_utils.get_partition_table_type(device) == 'gpt': + part_uuid = uuidutils.generate_uuid() create_option = '0:-%dMB:0' % MAX_CONFIG_DRIVE_SIZE_MB - utils.execute('sgdisk', '-n', create_option, device, + uuid_option = '0:%s' % part_uuid + utils.execute('sgdisk', '-n', create_option, + '-u', uuid_option, device, run_as_root=True) else: + cur_parts = set(part['number'] + for part in disk_utils.list_partitions(device)) + # 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 @@ -426,17 +431,29 @@ def create_config_drive_partition(node_uuid, device, configdrive): # Trigger device rescan disk_utils.trigger_device_rescan(device) - upd_parts = set(part['number'] - for part in disk_utils.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 retrieve config drive partition information.' - % {'device': device}) + if part_uuid is None: + new_parts = {part['number']: part + for part in disk_utils.list_partitions(device)} + new_part = set(new_parts) - set(cur_parts) + if len(new_part) != 1: + raise exception.InstanceDeployFailure( + 'Disk partitioning failed on device %(device)s. ' + 'Unable to retrieve config drive partition ' + 'information.' % {'device': device}) - config_drive_part = disk_utils.partition_index_to_path( - device, new_part.pop()) + config_drive_part = disk_utils.partition_index_to_path( + device, new_part.pop()) + else: + try: + config_drive_part = get_partition(device, part_uuid) + except errors.DeviceNotFound: + msg = ('Failed to create config drive on disk %(disk)s ' + 'for node %(node)s. Partition with UUID %(uuid)s ' + 'has not been found after creation.') % { + 'disk': device, 'node': node_uuid, + 'uuid': part_uuid} + LOG.error(msg) + raise exception.InstanceDeployFailure(msg) disk_utils.udev_settle() diff --git a/ironic_python_agent/tests/unit/test_partition_utils.py b/ironic_python_agent/tests/unit/test_partition_utils.py index 88122ed5..1d232490 100644 --- a/ironic_python_agent/tests/unit/test_partition_utils.py +++ b/ironic_python_agent/tests/unit/test_partition_utils.py @@ -656,6 +656,7 @@ class CreateConfigDriveTestCases(base.IronicAgentTest): mock_dd.assert_called_with(configdrive_file, configdrive_part) mock_unlink.assert_called_with(configdrive_file) + @mock.patch('oslo_utils.uuidutils.generate_uuid', lambda: 'fake-uuid') @mock.patch.object(utils, 'execute', autospec=True) @mock.patch.object(utils, 'unlink_without_raise', autospec=True) @@ -665,7 +666,7 @@ class CreateConfigDriveTestCases(base.IronicAgentTest): autospec=True) @mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True) - @mock.patch.object(disk_utils, 'list_partitions', + @mock.patch.object(partition_utils, 'get_partition', autospec=True) @mock.patch.object(partition_utils, 'get_labelled_partition', autospec=True) @@ -673,42 +674,25 @@ class CreateConfigDriveTestCases(base.IronicAgentTest): autospec=True) def test_create_partition_gpt(self, mock_get_configdrive, mock_get_labelled_partition, - mock_list_partitions, mock_table_type, + mock_get_partition_by_uuid, + 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' + mock_get_partition_by_uuid.return_value = expected_part 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('sgdisk', '-n', '0:-64MB:0', '-u', '0:fake-uuid', + self.dev, run_as_root=True), mock.call('sync'), mock.call('udevadm', 'settle'), mock.call('partprobe', self.dev, attempts=10, run_as_root=True), @@ -719,7 +703,6 @@ class CreateConfigDriveTestCases(base.IronicAgentTest): 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) diff --git a/releasenotes/notes/configdrive-partuuid-3259cfb7428c1483.yaml b/releasenotes/notes/configdrive-partuuid-3259cfb7428c1483.yaml new file mode 100644 index 00000000..95344273 --- /dev/null +++ b/releasenotes/notes/configdrive-partuuid-3259cfb7428c1483.yaml @@ -0,0 +1,17 @@ +--- +issues: + - | + Creating a configdrive partition on a devicemapper device (e.g. a multipath + storage device) with MBR partitioning may fail with the following error:: + + Command execution failed: Failed to create config drive on disk /dev/dm-0 + for node 168af30d-0fad-4d67-af99-b28b3238e977. Error: Unexpected error + while running command. + + Use GPT partitioning instead. +fixes: + - | + Fixes creating a configdrive partition on a devicemapper device (e.g. + a multipath storage device) with GPT partitioning. The newly created + partition is now detected by a pre-generated UUID rather than by comparing + partition numbers. diff --git a/releasenotes/notes/detect-endianness-f53a6c4571aba3fe.yaml b/releasenotes/notes/detect-endianness-f53a6c4571aba3fe.yaml new file mode 100644 index 00000000..82d90886 --- /dev/null +++ b/releasenotes/notes/detect-endianness-f53a6c4571aba3fe.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + In case the CSV file used for the bootloader hint does not have BOM + we fail reading its content as utf-16 codec is too generic. + Fail over to utf-16-le as Little Endian is mostly used. @@ -15,7 +15,7 @@ setenv = LANGUAGE=en_US LC_ALL=en_US.utf-8 deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run {posargs} @@ -71,7 +71,7 @@ setenv = PYTHONHASHSEED=0 sitepackages = False # NOTE(dtantsur): documentation building process requires importing IPA deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = @@ -89,7 +89,7 @@ commands = [testenv:releasenotes] usedevelop = False deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html @@ -128,7 +128,7 @@ commands = [testenv:bandit] usedevelop = False deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/yoga} -r{toxinidir}/test-requirements.txt commands = bandit -r ironic_python_agent -x tests -n5 -ll -c tools/bandit.yml diff --git a/zuul.d/ironic-python-agent-jobs.yaml b/zuul.d/ironic-python-agent-jobs.yaml index a5cb2977..06b2d894 100644 --- a/zuul.d/ironic-python-agent-jobs.yaml +++ b/zuul.d/ironic-python-agent-jobs.yaml @@ -14,6 +14,12 @@ - ^tools/.*$ - ^tox.ini$ required-projects: + - name: openstack/devstack + override-checkout: stable/yoga + - name: openstack/requirements + override-checkout: stable/yoga + - name: openstack/ironic + override-checkout: bugfix/20.0 - openstack/ironic-lib vars: # The default is 1GB, we need a little more to prevent OOMs killing the jobs @@ -128,6 +134,16 @@ name: ironic-standalone-ipa-src parent: ironic-standalone description: Test ironic standalone with IPA from source + required-projects: + - name: openstack/devstack + override-checkout: stable/yoga + - name: openstack/requirements + override-checkout: stable/yoga + - name: openstack/ironic + override-checkout: bugfix/20.0 + - name: openstack/ironic-python-agent-builder + override-checkout: stable/yoga + - openstack/ironic-lib vars: devstack_localrc: IRONIC_BUILD_DEPLOY_RAMDISK: True @@ -140,8 +156,17 @@ name: metalsmith-integration-ipa-src-uefi parent: metalsmith-integration-glance-localboot-centos8-uefi required-projects: + - name: openstack/devstack + override-checkout: stable/yoga + - name: openstack/requirements + override-checkout: stable/yoga + - name: openstack/ironic + override-checkout: bugfix/20.0 + - name: openstack/metalsmith + override-checkout: stable/yoga - openstack/ironic-python-agent - - openstack/ironic-python-agent-builder + - name: openstack/ironic-python-agent-builder + override-checkout: stable/yoga - openstack/ironic-lib vars: devstack_localrc: @@ -157,3 +182,40 @@ devstack_localrc: IRONIC_RAMDISK_TYPE: tinyipa IRONIC_VM_SPECS_RAM: 1024 + +- project-template: + name: openstack-python3-yoga-jobs-ironic-bugfix + description: | + Runs unit tests for an OpenStack Python project under the CPython + version 3 releases designated for testing in the Yoga release. + check: + jobs: + - openstack-tox-pep8: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + - openstack-tox-py36: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + - openstack-tox-py39: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + gate: + jobs: + - openstack-tox-pep8: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + - openstack-tox-py36: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + - openstack-tox-py39: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + post: + jobs: + - publish-openstack-python-branch-tarball diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index fa87feaf..c04ee4d5 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -1,14 +1,19 @@ - project: templates: - check-requirements - - openstack-cover-jobs - - openstack-lower-constraints-master-branch-jobs - - openstack-python3-yoga-jobs + - openstack-python3-yoga-jobs-ironic-bugfix - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - - openstack-tox-functional + - openstack-tox-functional: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + - openstack-tox-cover: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga - ipa-tox-examples # NOTE(iurygregory) Only run this two jobs since we are testing # wholedisk + partition on tempest @@ -30,7 +35,14 @@ gate: queue: ironic jobs: - - openstack-tox-functional + - openstack-tox-functional: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga + - openstack-tox-cover: + required-projects: + - name: openstack/requirements + override-checkout: stable/yoga - ipa-tempest-bios-ipmi-direct-src - ipa-tempest-uefi-redfish-vmedia-src - metalsmith-integration-ipa-src-uefi |