Merge "Support Stream Optimized VMDKs"

This commit is contained in:
Zuul 2024-02-16 21:47:31 +00:00 committed by Gerrit Code Review
commit 2fa64aea03
2 changed files with 54 additions and 23 deletions

View File

@ -512,7 +512,7 @@ class VHDXInspector(FileInspector):
# #
# https://www.vmware.com/app/vmdk/?src=vmdk # https://www.vmware.com/app/vmdk/?src=vmdk
class VMDKInspector(FileInspector): 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 This needs to store the 512 byte header and the descriptor region
which should be just after that. The descriptor region is some which should be just after that. The descriptor region is some
@ -582,7 +582,7 @@ class VMDKInspector(FileInspector):
vmdktype = descriptor[type_idx:type_end] vmdktype = descriptor[type_idx:type_end]
else: else:
vmdktype = b'formatnotfound' vmdktype = b'formatnotfound'
if vmdktype != b'monolithicSparse': if vmdktype not in (b'monolithicSparse', b'streamOptimized'):
LOG.warning('Unsupported VMDK format %s', vmdktype) LOG.warning('Unsupported VMDK format %s', vmdktype)
return 0 return 0

View File

@ -51,38 +51,51 @@ class TestFormatInspectors(test_utils.BaseTestCase):
except Exception: except Exception:
pass pass
def _create_img(self, fmt, size): def _create_img(self, fmt, size, subformat=None):
if fmt == 'vhd': if fmt == 'vhd':
# QEMU calls the vhd format vpc # QEMU calls the vhd format vpc
fmt = '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) suffix='.%s' % fmt)
self._created_files.append(fn) self._created_files.append(fn)
subprocess.check_output( 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) shell=True)
return fn 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 # 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 # format_inspector. A way to create one is to first create an empty
# file, and then to convert it with the -S 0 option. # file, and then to convert it with the -S 0 option.
fn = tempfile.mktemp(prefix='glance-unittest-formatinspector-',
suffix='.vmdk')
self._created_files.append(fn)
zeroes = tempfile.mktemp(prefix='glance-unittest-formatinspector-',
suffix='.zero')
self._created_files.append(zeroes)
# Create an empty file 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)
raw = tempfile.mktemp(prefix=prefix, suffix='.raw')
self._created_files.append(raw)
# Create a file with pseudo-random data, otherwise it will get
# compressed in the streamOptimized format
subprocess.check_output( 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) shell=True)
# Convert it to VMDK # Convert it to VMDK
subprocess.check_output( 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) shell=True)
return fn return fn
@ -101,8 +114,9 @@ class TestFormatInspectors(test_utils.BaseTestCase):
wrapper.close() wrapper.close()
return fmt return fmt
def _test_format_at_image_size(self, format_name, image_size): def _test_format_at_image_size(self, format_name, image_size,
img = self._create_img(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 # Some formats have internal alignment restrictions making this not
# always exactly like image_size, so get the real value for comparison # always exactly like image_size, so get the real value for comparison
@ -124,11 +138,12 @@ class TestFormatInspectors(test_utils.BaseTestCase):
'Format used more than 512KiB of memory: %s' % ( 'Format used more than 512KiB of memory: %s' % (
fmt.context_info)) 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 # Try a few different image sizes, including some odd and very small
# sizes # sizes
for image_size in (512, 513, 2057, 7): 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): def test_qcow2(self):
self._test_format('qcow2') self._test_format('qcow2')
@ -142,12 +157,15 @@ class TestFormatInspectors(test_utils.BaseTestCase):
def test_vmdk(self): def test_vmdk(self):
self._test_format('vmdk') 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' format_name = 'vmdk'
image_size = 10 * units.Mi image_size = 10 * units.Mi
descriptorOffsetAddr = 0x1c descriptorOffsetAddr = 0x1c
BAD_ADDRESS = 0x400 BAD_ADDRESS = 0x400
img = self._create_img(format_name, image_size) img = self._create_img(format_name, image_size, subformat=subformat)
# Corrupt the header # Corrupt the header
fd = open(img, 'r+b') fd = open(img, 'r+b')
@ -167,7 +185,13 @@ class TestFormatInspectors(test_utils.BaseTestCase):
'size %i block %i') % (format_name, image_size, 'size %i block %i') % (format_name, image_size,
block_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' format_name = 'vmdk'
image_size = 5 * units.Mi image_size = 5 * units.Mi
virtual_size = 5 * units.Mi virtual_size = 5 * units.Mi
@ -176,7 +200,8 @@ class TestFormatInspectors(test_utils.BaseTestCase):
twoMBInSectors = (2 << 20) // 512 twoMBInSectors = (2 << 20) // 512
# We need a big VMDK because otherwise we will not have enough data to # We need a big VMDK because otherwise we will not have enough data to
# fill-up the CaptureRegion. # 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 # Corrupt the end of descriptor address so it "ends" at 2MB
fd = open(img, 'r+b') fd = open(img, 'r+b')
@ -200,6 +225,12 @@ class TestFormatInspectors(test_utils.BaseTestCase):
'Format used more than 1.5MiB of memory: %s' % ( 'Format used more than 1.5MiB of memory: %s' % (
fmt.context_info)) 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): def test_vdi(self):
self._test_format('vdi') self._test_format('vdi')