From d0a35d54134b1d29d198c4b2e170bb407cdf334d Mon Sep 17 00:00:00 2001 From: Elod Illes Date: Thu, 19 Sep 2024 11:42:21 +0200 Subject: [PATCH 1/6] [CI] Remove openstack-tox-functional-py36-fips The job definition does not exist anymore and zuul config reports error because of this, so let's get rid of this from the check queue (gate queue does not even have it). Change-Id: I0b211b7c8fcfebeaf9f1bc9bbee0c6527faa53d9 --- .zuul.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 3827f02a41..484cd36741 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -306,7 +306,6 @@ - release-notes-jobs-python3 check: jobs: - - openstack-tox-functional-py36-fips - openstack-tox-functional-py39 - glance-tox-functional-py39-rbac-defaults - glance-ceph-thin-provisioning: From 7815f4ed695e0c4f00988aaa06f8a4996167f378 Mon Sep 17 00:00:00 2001 From: elajkat Date: Wed, 18 Sep 2024 12:54:44 +0200 Subject: [PATCH 2/6] [unmaintained-only] Cap setuptools via virtualenv<20.26.4 py39 jobs (on ubuntu-focal) started to fail due to recent virtualenv release (20.26.4 that bundles setuptools 74.1.2) on Yoga, because we have 'packaging==21.3' in this branch that is not compatible with the new setuptools [1]. setuptools is bundled in virtualenv, so it has to be capped via the virtualenv package. tox also needed to be capped (<4) as gate uses tox 3.28.0, but with capping virtualenv we pull in latest tox as well, which would cause other errors. [1] https://github.com/pypa/setuptools/issues/4483 Change-Id: I668557933c51123ce84275a0c718b6e79b3df174 --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index 826aee78b5..261d6773ff 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,12 @@ skip_missing_interpreters = true # this allows tox to infer the base python from the environment name # and override any basepython configured in this file ignore_basepython_conflict=true +# Cap setuptools via virtualenv to prevent compatibility issue with yoga +# branch's upper constraint of 'packaging' package (21.3). +requires = + virtualenv<20.26.4 + tox<4 + setuptools<71.0.0 [testenv] # Set default python version From 95fae81787624b5dfe61f6bccc3183ebe50ee296 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Wed, 26 Jun 2024 08:41:02 -0700 Subject: [PATCH 3/6] Add safety check and detection support to FI tool This adds a safety check and detection mechanism to the tools/test_format_inspector.py utility for verifying those features outside of glance. Change-Id: I447e7e51315472f8fa6013d4c4852f54c1e0c43d (cherry picked from commit b27040b87e43ff4acfb6870ef0d13d54e5ca5caa) (cherry picked from commit b394ef00c3426771092d099cf1d96077bfa4b919) (cherry picked from commit aa12d39b453068d9ee8367591eb60d4a15cddece) (cherry picked from commit a1c94b47d02a7bd3ac076ea02a23e456da2d5a62) (cherry picked from commit a5ec69ba1c0a92cc9b91b52b4101b81a330ea684) --- tools/test_format_inspector.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/test_format_inspector.py b/tools/test_format_inspector.py index aa554386ef..63e23210cc 100755 --- a/tools/test_format_inspector.py +++ b/tools/test_format_inspector.py @@ -102,6 +102,13 @@ def main(): else: print('Confirmed size with qemu-img') + print('Image safety check: %s' % ( + fmt.safety_check() and 'passed' or 'FAILED')) + if args.input: + detected_fmt = format_inspector.detect_file_format(args.input) + print('Detected inspector for image as: %s' % ( + detected_fmt.__class__.__name__)) + if __name__ == '__main__': sys.exit(main()) From 8fb607e002926360fc87d1eec4d5078b7699a261 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 9 May 2024 09:30:57 -0400 Subject: [PATCH 4/6] Adjust integrated-storage-import jobs We have to address two issues simultaneously: 1. handle multiple cirros images The change[1] configures multiple images for the devstack-tempest job and all the jobs deriving from it. This causes problem with the current post-check-metadata-injection.yaml playbook since we only expect one cirros image and perform steps accordingly. This change modifies the playbook to handle multiple cirros images. Failure in the glance-multistore-cinder-import job can be seen here[2]. [1] https://review.opendev.org/c/openstack/tempest/+/831018 [2] https://zuul.opendev.org/t/openstack/build/68a4d3ec6ce04c87b21d73a333f5b5cd/log/job-output.txt#23161 This was change I70b4a970d7e1. 2. change the the job config to address resource constraints * partial backport of I073216d1bbdd to change swap size (we don't include the change from that patch that was needed to address changes in Bobcat-era devstack) * backporting Ieb96eb6ceb6f to change tempest concurrency Remove openstack-tox-functional-py36-fips as there is no job definition for it. Change-Id: I6cd5b8bbf39f0cfae466f0800faea5a74e960e69 (cherry picked from commit 065e2d7e9e0c605cd4e60c9b150337b6ac6a9fde) (cherry picked from commit 28b05a7cc190a211f7b67ea6a3a9217dc1609ff8) --- .zuul.yaml | 2 ++ playbooks/post-check-metadata-injection.yaml | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 484cd36741..3a94e8f5f8 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -213,6 +213,8 @@ The regular tempest-integrated-storage job but with glance metadata injection post-run: playbooks/post-check-metadata-injection.yaml vars: + configure_swap_size: 8192 + tempest_concurrency: 3 zuul_copy_output: /etc/glance-remote: logs devstack_localrc: diff --git a/playbooks/post-check-metadata-injection.yaml b/playbooks/post-check-metadata-injection.yaml index 178866ba1b..2c273693a5 100644 --- a/playbooks/post-check-metadata-injection.yaml +++ b/playbooks/post-check-metadata-injection.yaml @@ -9,12 +9,16 @@ set -xe cirrosimg=$(glance image-list | grep cirros | cut -d" " -f 2) - echo "Dumping the cirros image for debugging..." - glance image-show $cirrosimg + # There could be more than one cirros image so traverse through the list + for image in $cirrosimg + do + echo "Dumping the cirros image for debugging..." + glance image-show $image - echo "Checking that the cirros image was decorated with metdata on import..." - glance image-list --property-filter 'glance_devstack_test=doyouseeme?' | grep cirros + echo "Checking that the cirros image was decorated with metdata on import..." + glance image-list --property-filter 'glance_devstack_test=doyouseeme?' | grep $image - echo "Checking that the cirros image was converted to raw on import..." - glance image-show $cirrosimg | egrep -e 'disk_format.*raw' + echo "Checking that the cirros image was converted to raw on import..." + glance image-show $image | egrep -e 'disk_format.*raw' + done environment: '{{ zuul | zuul_legacy_vars }}' From 7e06597fae3fc245ab04693883684c8c3a97fe2e Mon Sep 17 00:00:00 2001 From: Guillaume Espanel Date: Wed, 25 Jan 2023 11:53:09 +0100 Subject: [PATCH 5/6] Limit CaptureRegion sizes in format_inspector for VMDK and VHDX VMDK: When parsing a VMDK file to calculate its size, the format_inspector determines the location of the Descriptor section by reading two uint64 from the headers of the file and uses them to create the descriptor CaptureRegion. It would be possible to craft a VMDK file that commands the format_inspector to create a very big CaptureRegion, thus exhausting resources on the glance-api process. This patch binds the beginning of the descriptor to 0x200 and limits the size of the CaptureRegion to 1MB, similar to how the VMDK descriptor is parsed by qemu. VHDX: It is a bit more involved, but similar: when looking for the VIRTUAL_DISK_SIZE metadata, the format_inspector was creating an unbounded CaptureRegion. In the same way as it seems to be done in Qemu, we now limit the upper bound of this CaptureRegion. Closes-Bug: #2006490 Change-Id: I3ec5a33df20e1cfb6673f4ff1c7c91aacd065532 (cherry picked from commit d4d33ee30f303f783c0640cd72acb31b313e1164) (cherry picked from commit 06a18202ab52c64803f044b8f848ed1c160905d2) --- glance/common/format_inspector.py | 22 +++- .../unit/common/test_format_inspector.py | 120 ++++++++++++++++++ 2 files changed, 139 insertions(+), 3 deletions(-) diff --git a/glance/common/format_inspector.py b/glance/common/format_inspector.py index 351c300ddb..550cceadbb 100755 --- a/glance/common/format_inspector.py +++ b/glance/common/format_inspector.py @@ -345,6 +345,7 @@ class VHDXInspector(FileInspector): """ METAREGION = '8B7CA206-4790-4B9A-B8FE-575F050F886E' VIRTUAL_DISK_SIZE = '2FA54224-CD1B-4876-B211-5DBED83BF4B8' + VHDX_METADATA_TABLE_MAX_SIZE = 32 * 2048 # From qemu def __init__(self, *a, **k): super(VHDXInspector, self).__init__(*a, **k) @@ -459,6 +460,8 @@ def _find_meta_entry(self, desired_guid): item_offset, item_length, _reserved = struct.unpack( ' Date: Fri, 2 Feb 2024 11:48:24 +0100 Subject: [PATCH 6/6] Support Stream Optimized VMDKs Stream optimized VMDKs are also monolithic disks images, and start with the same sparse extend header as normal monolithic sparse files, so we can parse the virtual disk size in the same manner. See "VMware Virtual Disks Virtual Disk Format 1.1" p. 17. > Header and Footer > The header and the footer are both described by the same SparseExtentHeader > structure shown in Hosted Sparse Extent Header on page 8. Closes-Bug: #2052291 Change-Id: I7d63951ff080dc699b8d11babc0a5998d90621e4 Co-Authored-By: Rajiv Mucheli (cherry picked from commit 5e7e6bfb80ed6ede57f348e688c96dbdfe0e1c2b) (cherry picked from commit 9d45e8d4b87e992be23974a831811bff563ce721) (cherry picked from commit ef22a7425a5fe4f7006f9434a33dd092e2594830) (cherry picked from commit bcfc1e4dee268dc182d73f54729a840194c90d49) --- glance/common/format_inspector.py | 4 +- .../unit/common/test_format_inspector.py | 71 +++++++++++++------ 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/glance/common/format_inspector.py b/glance/common/format_inspector.py index 550cceadbb..d9576f1f8d 100755 --- a/glance/common/format_inspector.py +++ b/glance/common/format_inspector.py @@ -512,7 +512,7 @@ def __str__(self): # # https://www.vmware.com/app/vmdk/?src=vmdk class VMDKInspector(FileInspector): - """vmware VMDK format (monolithicSparse variant only) + """vmware VMDK format (monolithicSparse and streamOptimized variants only) This needs to store the 512 byte header and the descriptor region which should be just after that. The descriptor region is some @@ -582,7 +582,7 @@ def virtual_size(self): vmdktype = descriptor[type_idx:type_end] else: vmdktype = b'formatnotfound' - if vmdktype != b'monolithicSparse': + if vmdktype not in (b'monolithicSparse', b'streamOptimized'): LOG.warning('Unsupported VMDK format %s', vmdktype) return 0 diff --git a/glance/tests/unit/common/test_format_inspector.py b/glance/tests/unit/common/test_format_inspector.py index db6a9830bd..38f8caeb41 100644 --- a/glance/tests/unit/common/test_format_inspector.py +++ b/glance/tests/unit/common/test_format_inspector.py @@ -51,38 +51,51 @@ def tearDown(self): except Exception: pass - def _create_img(self, fmt, size): + def _create_img(self, fmt, size, subformat=None): if fmt == 'vhd': # QEMU calls the vhd format vpc fmt = 'vpc' - fn = tempfile.mktemp(prefix='glance-unittest-formatinspector-', + opt = '' + prefix = 'glance-unittest-formatinspector-' + + if subformat: + opt = ' -o subformat=%s' % subformat + prefix += subformat + '-' + + fn = tempfile.mktemp(prefix=prefix, suffix='.%s' % fmt) self._created_files.append(fn) subprocess.check_output( - 'qemu-img create -f %s %s %i' % (fmt, fn, size), + 'qemu-img create -f %s %s %s %i' % (fmt, opt, fn, size), shell=True) return fn - def _create_allocated_vmdk(self, size_mb): + def _create_allocated_vmdk(self, size_mb, subformat=None): # We need a "big" VMDK file to exercise some parts of the code of the # format_inspector. A way to create one is to first create an empty # file, and then to convert it with the -S 0 option. - fn = tempfile.mktemp(prefix='glance-unittest-formatinspector-', - suffix='.vmdk') + + if subformat is None: + # Matches qemu-img default, see `qemu-img convert -O vmdk -o help` + subformat = 'monolithicSparse' + + prefix = 'glance-unittest-formatinspector-%s-' % subformat + fn = tempfile.mktemp(prefix=prefix, suffix='.vmdk') self._created_files.append(fn) - zeroes = tempfile.mktemp(prefix='glance-unittest-formatinspector-', - suffix='.zero') - self._created_files.append(zeroes) + raw = tempfile.mktemp(prefix=prefix, suffix='.raw') + self._created_files.append(raw) - # Create an empty file + # Create a file with pseudo-random data, otherwise it will get + # compressed in the streamOptimized format subprocess.check_output( - 'dd if=/dev/zero of=%s bs=1M count=%i' % (zeroes, size_mb), + 'dd if=/dev/urandom of=%s bs=1M count=%i' % (raw, size_mb), shell=True) # Convert it to VMDK subprocess.check_output( - 'qemu-img convert -f raw -O vmdk -S 0 %s %s' % (zeroes, fn), + 'qemu-img convert -f raw -O vmdk -o subformat=%s -S 0 %s %s' % ( + subformat, raw, fn), shell=True) return fn @@ -101,8 +114,9 @@ def _test_format_at_block_size(self, format_name, img, block_size): wrapper.close() return fmt - def _test_format_at_image_size(self, format_name, image_size): - img = self._create_img(format_name, image_size) + def _test_format_at_image_size(self, format_name, image_size, + subformat=None): + img = self._create_img(format_name, image_size, subformat=subformat) # Some formats have internal alignment restrictions making this not # always exactly like image_size, so get the real value for comparison @@ -124,11 +138,12 @@ def _test_format_at_image_size(self, format_name, image_size): 'Format used more than 512KiB of memory: %s' % ( fmt.context_info)) - def _test_format(self, format_name): + def _test_format(self, format_name, subformat=None): # Try a few different image sizes, including some odd and very small # sizes for image_size in (512, 513, 2057, 7): - self._test_format_at_image_size(format_name, image_size * units.Mi) + self._test_format_at_image_size(format_name, image_size * units.Mi, + subformat=subformat) def test_qcow2(self): self._test_format('qcow2') @@ -142,12 +157,15 @@ def test_vhdx(self): def test_vmdk(self): self._test_format('vmdk') - def test_vmdk_bad_descriptor_offset(self): + def test_vmdk_stream_optimized(self): + self._test_format('vmdk', 'streamOptimized') + + def _test_vmdk_bad_descriptor_offset(self, subformat=None): format_name = 'vmdk' image_size = 10 * units.Mi descriptorOffsetAddr = 0x1c BAD_ADDRESS = 0x400 - img = self._create_img(format_name, image_size) + img = self._create_img(format_name, image_size, subformat=subformat) # Corrupt the header fd = open(img, 'r+b') @@ -167,7 +185,13 @@ def test_vmdk_bad_descriptor_offset(self): 'size %i block %i') % (format_name, image_size, block_size)) - def test_vmdk_bad_descriptor_mem_limit(self): + def test_vmdk_bad_descriptor_offset(self): + self._test_vmdk_bad_descriptor_offset() + + def test_vmdk_bad_descriptor_offset_stream_optimized(self): + self._test_vmdk_bad_descriptor_offset(subformat='streamOptimized') + + def _test_vmdk_bad_descriptor_mem_limit(self, subformat=None): format_name = 'vmdk' image_size = 5 * units.Mi virtual_size = 5 * units.Mi @@ -176,7 +200,8 @@ def test_vmdk_bad_descriptor_mem_limit(self): twoMBInSectors = (2 << 20) // 512 # We need a big VMDK because otherwise we will not have enough data to # fill-up the CaptureRegion. - img = self._create_allocated_vmdk(image_size // units.Mi) + img = self._create_allocated_vmdk(image_size // units.Mi, + subformat=subformat) # Corrupt the end of descriptor address so it "ends" at 2MB fd = open(img, 'r+b') @@ -200,6 +225,12 @@ def test_vmdk_bad_descriptor_mem_limit(self): 'Format used more than 1.5MiB of memory: %s' % ( fmt.context_info)) + def test_vmdk_bad_descriptor_mem_limit(self): + self._test_vmdk_bad_descriptor_mem_limit() + + def test_vmdk_bad_descriptor_mem_limit_stream_optimized(self): + self._test_vmdk_bad_descriptor_mem_limit(subformat='streamOptimized') + def test_vdi(self): self._test_format('vdi')