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 <rajiv.mucheli@gmail.com> (cherry picked from commit 5e7e6bfb80ed6ede57f348e688c96dbdfe0e1c2b) (cherry picked from commit 9d45e8d4b87e992be23974a831811bff563ce721)
This commit is contained in:
parent
a45667b714
commit
ef22a7425a
glance
@ -512,7 +512,7 @@ class VHDXInspector(FileInspector):
|
||||
#
|
||||
# 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 @@ class VMDKInspector(FileInspector):
|
||||
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
|
||||
|
||||
|
@ -51,38 +51,51 @@ class TestFormatInspectors(test_utils.BaseTestCase):
|
||||
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')
|
||||
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(
|
||||
'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 @@ class TestFormatInspectors(test_utils.BaseTestCase):
|
||||
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 @@ class TestFormatInspectors(test_utils.BaseTestCase):
|
||||
'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 @@ class TestFormatInspectors(test_utils.BaseTestCase):
|
||||
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 @@ class TestFormatInspectors(test_utils.BaseTestCase):
|
||||
'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 @@ class TestFormatInspectors(test_utils.BaseTestCase):
|
||||
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 @@ class TestFormatInspectors(test_utils.BaseTestCase):
|
||||
'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')
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user