VMware: Create VMwareImage object for image metadata

This change pulls the nested function _get_image_properties() out of
spawn() and makes it a factory method for the new VMwareImage object.

Linked clone logic is moved from a separate function in vmops.py to
the new get_image_properties() function.

vmware_images.get_vmdk_size_and_properties() is no longer used, and is
removed.

Tests have been updated and/or moved as appropriate. No tests have
been removed.

This change has been split out of
https://review.openstack.org/#/c/87002/, which was written by Shawn
Hartsock.

partial blueprint vmware-spawn-refactor

Co-authored-by: Shawn Hartsock <hartsocks@vmware.com>
Change-Id: I580f173da798318d2675c7c70bbdd19b266259f4
This commit is contained in:
Matthew Booth
2014-06-30 11:58:46 +01:00
parent 1b8581855f
commit d7f097d69e
8 changed files with 469 additions and 306 deletions

View File

@@ -972,13 +972,6 @@ def fake_upload_image(context, image, instance, **kwargs):
pass
def fake_get_vmdk_size_and_properties(context, image_id, instance):
"""Fakes the file size and properties fetch for the image file."""
props = {"vmware_ostype": constants.DEFAULT_OS_TYPE,
"vmware_adaptertype": constants.DEFAULT_ADAPTER_TYPE}
return _FAKE_FILE_SIZE, props
def _get_vm_mdo(vm_ref):
"""Gets the Virtual Machine with the ref from the db."""
if _db_content.get("VirtualMachine", None) is None:

View File

@@ -25,7 +25,6 @@ from nova.tests.virt.vmwareapi import fake
from nova.virt.vmwareapi import driver
from nova.virt.vmwareapi import error_util
from nova.virt.vmwareapi import network_util
from nova.virt.vmwareapi import vmware_images
def fake_get_vim_object(arg):
@@ -65,8 +64,6 @@ def set_stubs(stubs):
"""Set the stubs."""
stubs.Set(network_util, 'get_network_with_the_name',
fake.fake_get_network)
stubs.Set(vmware_images, 'get_vmdk_size_and_properties',
fake.fake_get_vmdk_size_and_properties)
stubs.Set(driver.VMwareAPISession, "_get_vim_object",
fake_get_vim_object)
stubs.Set(driver.VMwareAPISession, "_is_vim_object",

View File

@@ -54,6 +54,7 @@ from nova.tests.virt.vmwareapi import fake as vmwareapi_fake
from nova.tests.virt.vmwareapi import stubs
from nova import utils as nova_utils
from nova.virt import driver as v_driver
from nova.virt.vmwareapi import constants
from nova.virt.vmwareapi import driver
from nova.virt.vmwareapi import ds_util
from nova.virt.vmwareapi import error_util
@@ -693,19 +694,35 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
else:
self.assertFalse(vmwareapi_fake.get_file(str(cache)))
def test_instance_dir_disk_created(self):
@mock.patch.object(nova.virt.vmwareapi.vmware_images.VMwareImage,
'from_image')
def test_instance_dir_disk_created(self, mock_from_image):
"""Test image file is cached when even when use_linked_clone
is False
"""
img_props = vmware_images.VMwareImage(
image_id=self.fake_image_uuid,
linked_clone=False)
mock_from_image.return_value = img_props
self._create_vm()
path = ds_util.DatastorePath(self.ds, self.uuid, '%s.vmdk' % self.uuid)
self.assertTrue(vmwareapi_fake.get_file(str(path)))
self._cached_files_exist()
def test_cache_dir_disk_created(self):
@mock.patch.object(nova.virt.vmwareapi.vmware_images.VMwareImage,
'from_image')
def test_cache_dir_disk_created(self, mock_from_image):
"""Test image disk is cached when use_linked_clone is True."""
self.flags(use_linked_clone=True, group='vmware')
img_props = vmware_images.VMwareImage(
image_id=self.fake_image_uuid,
file_size=1 * units.Ki,
disk_type=constants.DISK_TYPE_SPARSE)
mock_from_image.return_value = img_props
self._create_vm()
path = ds_util.DatastorePath(self.ds, 'vmware_base',
self.fake_image_uuid,
@@ -748,7 +765,18 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.image['disk_format'] = 'iso'
self._create_vm()
def test_iso_disk_cdrom_attach_with_config_drive(self):
@mock.patch.object(nova.virt.vmwareapi.vmware_images.VMwareImage,
'from_image')
def test_iso_disk_cdrom_attach_with_config_drive(self,
mock_from_image):
img_props = vmware_images.VMwareImage(
image_id=self.fake_image_uuid,
file_size=80 * units.Gi,
file_type='iso',
linked_clone=False)
mock_from_image.return_value = img_props
self.flags(force_config_drive=True)
iso_path = [
ds_util.DatastorePath(self.ds, 'vmware_base',
@@ -920,57 +948,32 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self._check_vm_info(info, power_state.RUNNING)
self.assertTrue(vmwareapi_fake.get_file(str(root)))
def test_spawn_disk_extend_sparse(self):
self.mox.StubOutWithMock(vmware_images, 'get_vmdk_size_and_properties')
result = [1024, {"vmware_ostype": "otherGuest",
"vmware_adaptertype": "lsiLogic",
"vmware_disktype": "sparse"}]
vmware_images.get_vmdk_size_and_properties(
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(result)
self.mox.StubOutWithMock(self.conn._vmops, '_extend_virtual_disk')
requested_size = 80 * units.Mi
self.conn._vmops._extend_virtual_disk(mox.IgnoreArg(),
requested_size, mox.IgnoreArg(), mox.IgnoreArg())
self.mox.ReplayAll()
self._create_vm()
info = self.conn.get_info({'uuid': self.uuid,
'node': self.instance_node})
self._check_vm_info(info, power_state.RUNNING)
@mock.patch.object(nova.virt.vmwareapi.vmware_images.VMwareImage,
'from_image')
def test_spawn_disk_extend_sparse(self, mock_from_image):
img_props = vmware_images.VMwareImage(
image_id=self.fake_image_uuid,
file_size=units.Ki,
disk_type=constants.DISK_TYPE_SPARSE,
linked_clone=True)
def test_spawn_disk_extend_insufficient_disk_space(self):
self.flags(use_linked_clone=True, group='vmware')
self.wait_task = self.conn._session._wait_for_task
self.call_method = self.conn._session._call_method
self.task_ref = None
id = self.fake_image_uuid
cached_image = '[%s] vmware_base/%s/%s.80.vmdk' % (self.ds,
id, id)
tmp_file = '[%s] vmware_base/%s/%s.80-flat.vmdk' % (self.ds,
id, id)
mock_from_image.return_value = img_props
def fake_wait_for_task(task_ref):
if task_ref == self.task_ref:
self.task_ref = None
self.assertTrue(vmwareapi_fake.get_file(cached_image))
self.assertTrue(vmwareapi_fake.get_file(tmp_file))
raise exception.NovaException('No space!')
return self.wait_task(task_ref)
def fake_call_method(module, method, *args, **kwargs):
task_ref = self.call_method(module, method, *args, **kwargs)
if method == "ExtendVirtualDisk_Task":
self.task_ref = task_ref
return task_ref
self.stubs.Set(self.conn._session, "_call_method", fake_call_method)
self.stubs.Set(self.conn._session, "_wait_for_task",
fake_wait_for_task)
self.assertRaises(exception.NovaException,
self._create_vm)
self.assertFalse(vmwareapi_fake.get_file(cached_image))
self.assertFalse(vmwareapi_fake.get_file(tmp_file))
with contextlib.nested(
mock.patch.object(self.conn._vmops, '_extend_virtual_disk'),
mock.patch.object(self.conn._vmops, 'get_datacenter_ref_and_name'),
) as (mock_extend, mock_get_dc):
dc_val = mock.Mock()
dc_val.ref = "fake_dc_ref"
dc_val.name = "dc1"
mock_get_dc.return_value = dc_val
self._create_vm()
iid = img_props.image_id
cached_image = ds_util.DatastorePath(self.ds, 'vmware_base',
iid, '%s.80.vmdk' % iid)
mock_extend.assert_called_once_with(
self.instance, self.instance.root_gb * units.Mi,
str(cached_image), "fake_dc_ref")
def test_spawn_disk_extend_failed_copy(self):
# Spawn instance
@@ -1086,28 +1089,63 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.assertRaises(DeleteError, self._create_vm)
self.assertTrue(vmwareapi_fake.get_file(cached_image))
def test_spawn_disk_invalid_disk_size(self):
self.mox.StubOutWithMock(vmware_images, 'get_vmdk_size_and_properties')
result = [82 * units.Gi,
{"vmware_ostype": "otherGuest",
"vmware_adaptertype": "lsiLogic",
"vmware_disktype": "sparse"}]
vmware_images.get_vmdk_size_and_properties(
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(result)
self.mox.ReplayAll()
@mock.patch.object(nova.virt.vmwareapi.vmware_images.VMwareImage,
'from_image')
def test_spawn_disk_invalid_disk_size(self, mock_from_image):
img_props = vmware_images.VMwareImage(
image_id=self.fake_image_uuid,
file_size=82 * units.Gi,
disk_type=constants.DISK_TYPE_SPARSE,
linked_clone=True)
mock_from_image.return_value = img_props
self.assertRaises(exception.InstanceUnacceptable,
self._create_vm)
def test_spawn_invalid_disk_format(self):
self._create_instance()
self.image['disk_format'] = 'invalid'
self.assertRaises(exception.InvalidDiskFormat,
self.conn.spawn, self.context,
self.instance, self.image,
injected_files=[], admin_password=None,
network_info=self.network_info,
block_device_info=None)
@mock.patch.object(nova.virt.vmwareapi.vmware_images.VMwareImage,
'from_image')
def test_spawn_disk_extend_insufficient_disk_space(self, mock_from_image):
img_props = vmware_images.VMwareImage(
image_id=self.fake_image_uuid,
file_size=1024,
disk_type=constants.DISK_TYPE_SPARSE,
linked_clone=True)
mock_from_image.return_value = img_props
cached_image = ds_util.DatastorePath(self.ds, 'vmware_base',
self.fake_image_uuid,
'%s.80.vmdk' %
self.fake_image_uuid)
tmp_file = ds_util.DatastorePath(self.ds, 'vmware_base',
self.fake_image_uuid,
'%s.80-flat.vmdk' %
self.fake_image_uuid)
NoDiskSpace = error_util.get_fault_class('NoDiskSpace')
def fake_wait_for_task(task_ref):
if task_ref == self.task_ref:
self.task_ref = None
raise NoDiskSpace()
return self.wait_task(task_ref)
def fake_call_method(module, method, *args, **kwargs):
task_ref = self.call_method(module, method, *args, **kwargs)
if method == 'ExtendVirtualDisk_Task':
self.task_ref = task_ref
return task_ref
with contextlib.nested(
mock.patch.object(self.conn._session, '_wait_for_task',
fake_wait_for_task),
mock.patch.object(self.conn._session, '_call_method',
fake_call_method)
) as (mock_wait_for_task, mock_call_method):
self.assertRaises(NoDiskSpace, self._create_vm)
self.assertFalse(vmwareapi_fake.get_file(str(cached_image)))
self.assertFalse(vmwareapi_fake.get_file(str(tmp_file)))
def test_spawn_with_move_file_exists_exception(self):
# The test will validate that the spawn completes
@@ -2300,28 +2338,20 @@ class VMwareAPIVCDriverTestCase(VMwareAPIVMTestCase,
network_info=self.network_info,
block_device_info=None)
def test_spawn_with_sparse_image(self):
# Only a sparse disk image triggers the copy
self.mox.StubOutWithMock(vmware_images, 'get_vmdk_size_and_properties')
result = [1024, {"vmware_ostype": "otherGuest",
"vmware_adaptertype": "lsiLogic",
"vmware_disktype": "sparse"}]
vmware_images.get_vmdk_size_and_properties(
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(result)
@mock.patch.object(nova.virt.vmwareapi.vmware_images.VMwareImage,
'from_image')
@mock.patch.object(vmops.VMwareVCVMOps, 'get_copy_virtual_disk_spec')
def test_spawn_with_sparse_image(self, mock_get_copy_virtual_disk_spec,
mock_from_image):
img_info = vmware_images.VMwareImage(
image_id=self.fake_image_uuid,
file_size=1024,
disk_type=constants.DISK_TYPE_SPARSE,
linked_clone=False)
# Ensure VMwareVCVMOps's get_copy_virtual_disk_spec is getting called
# two times
self.mox.StubOutWithMock(vmops.VMwareVCVMOps,
'get_copy_virtual_disk_spec')
self.conn._vmops.get_copy_virtual_disk_spec(
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(None)
self.conn._vmops.get_copy_virtual_disk_spec(
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(None)
mock_from_image.return_value = img_info
mock_get_copy_virtual_disk_spec.return_value = None
self.mox.ReplayAll()
self._create_vm()
info = self.conn.get_info({'uuid': self.uuid,
'node': self.instance_node})

View File

@@ -29,6 +29,7 @@ from nova.tests import fake_instance
import nova.tests.image.fake
from nova.tests.virt.vmwareapi import fake as vmwareapi_fake
from nova.tests.virt.vmwareapi import stubs
from nova.virt.vmwareapi import constants
from nova.virt.vmwareapi import driver
from nova.virt.vmwareapi import ds_util
from nova.virt.vmwareapi import error_util
@@ -38,6 +39,39 @@ from nova.virt.vmwareapi import vmops
from nova.virt.vmwareapi import vmware_images
class VMwareVMOpsSimpleTestCase(test.NoDBTestCase):
@mock.patch.object(vm_util, 'get_res_pool_ref')
@mock.patch.object(ds_util, 'get_datastore')
@mock.patch.object(vmops.VMwareVMOps, 'get_datacenter_ref_and_name')
def test_spawn_disk_invalid_disk_size(self,
mock_get_datacenter_ref_and_name,
mock_get_datastore,
mock_get_res_pool_ref):
image = {
'id': 'c1c8ce3d-c2e0-4247-890c-ccf5cc1c004c',
'disk_format': 'vmdk',
'size': 999999999 * units.Gi,
}
self._context = context.RequestContext('fake_user', 'fake_project')
instance = fake_instance.fake_instance_obj(self._context,
image_ref=nova.tests.image.fake.get_valid_image_id(),
uuid='fake_uuid',
root_gb=1,
node='respool-1001(MyResPoolName)'
)
ops = vmops.VMwareVMOps(mock.Mock(), mock.Mock(), mock.Mock())
self.assertRaises(exception.InstanceUnacceptable,
ops.spawn,
mock.Mock(),
instance,
image,
injected_files=[],
admin_password=None,
network_info=None,
block_device_info=None)
class VMwareVMOpsTestCase(test.NoDBTestCase):
def setUp(self):
super(VMwareVMOpsTestCase, self).setUp()
@@ -60,7 +94,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
'vcpus': 1,
'memory_mb': 512,
'image_ref': self._image_id,
'root_gb': 1,
'root_gb': 10,
'node': 'respool-1001(MyResPoolName)'
}
self._instance = fake_instance.fake_instance_obj(
@@ -122,21 +156,6 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
rxtx_cap=3)
])
def test_get_disk_format_none(self):
format, is_iso = self._vmops._get_disk_format({'disk_format': None})
self.assertIsNone(format)
self.assertFalse(is_iso)
def test_get_disk_format_iso(self):
format, is_iso = self._vmops._get_disk_format({'disk_format': 'iso'})
self.assertEqual('iso', format)
self.assertTrue(is_iso)
def test_get_disk_format_bad(self):
self.assertRaises(exception.InvalidDiskFormat,
self._vmops._get_disk_format,
{'disk_format': 'foo'})
def test_get_machine_id_str(self):
result = vmops.VMwareVMOps._get_machine_id_str(self.network_info)
self.assertEqual(result,
@@ -146,33 +165,6 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
self.pure_IPv6_network_info)
self.assertEqual('DE:AD:BE:EF:00:00;;;;;#', result)
def test_use_linked_clone_override_nf(self):
value = vmops.VMwareVMOps.decide_linked_clone(None, False)
self.assertFalse(value, "No overrides present but still overridden!")
def test_use_linked_clone_override_none_true(self):
value = vmops.VMwareVMOps.decide_linked_clone(None, True)
self.assertTrue(value, "No overrides present but still overridden!")
def test_use_linked_clone_override_ny(self):
value = vmops.VMwareVMOps.decide_linked_clone(None, "yes")
self.assertTrue(value, "No overrides present but still overridden!")
def test_use_linked_clone_override_ft(self):
value = vmops.VMwareVMOps.decide_linked_clone(False, True)
self.assertFalse(value,
"image level metadata failed to override global")
def test_use_linked_clone_override_no_true(self):
value = vmops.VMwareVMOps.decide_linked_clone("no", True)
self.assertFalse(value,
"image level metadata failed to override global")
def test_use_linked_clone_override_yf(self):
value = vmops.VMwareVMOps.decide_linked_clone("yes", False)
self.assertTrue(value,
"image level metadata failed to override global")
def _setup_create_folder_mocks(self):
ops = vmops.VMwareVMOps(mock.Mock(), mock.Mock(), mock.Mock())
base_name = 'folder'
@@ -670,8 +662,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
mock_get_res_pool_ref.assert_called_once_with(
self._session, None, 'fake_node_mo_id')
mock_get_vif_info.assert_called_once_with(
self._session, None, False, network_model.VIF_MODEL_E1000,
network_info)
self._session, None, False,
constants.DEFAULT_VIF_MODEL, network_info)
mock_get_create_spec.assert_called_once_with(
self._session._get_vim().client.factory,
self._instance,

View File

@@ -19,8 +19,11 @@ import contextlib
import mock
from nova import exception
from nova.openstack.common import units
from nova import test
import nova.tests.image.fake
from nova.virt.vmwareapi import constants
from nova.virt.vmwareapi import read_write_util
from nova.virt.vmwareapi import vmware_images
@@ -81,3 +84,135 @@ class VMwareImagesTestCase(test.NoDBTestCase):
write_file_handle=write_file_handle)
image_download.assert_called_once_with(context, instance['image_ref'])
image_show.assert_called_once_with(context, instance['image_ref'])
def _setup_mock_get_remote_image_service(self,
mock_get_remote_image_service,
metadata):
mock_image_service = mock.MagicMock()
mock_image_service.show.return_value = metadata
mock_get_remote_image_service.return_value = [mock_image_service, 'i']
def test_from_image_with_image_ref(self):
raw_disk_size_in_gb = 83
raw_disk_size_in_bytes = raw_disk_size_in_gb * units.Gi
image_id = nova.tests.image.fake.get_valid_image_id()
mdata = {'size': raw_disk_size_in_bytes,
'disk_format': 'vmdk',
'properties': {
"vmware_ostype": constants.DEFAULT_OS_TYPE,
"vmware_adaptertype": constants.DEFAULT_ADAPTER_TYPE,
"vmware_disktype": constants.DEFAULT_DISK_TYPE,
"hw_vif_model": constants.DEFAULT_VIF_MODEL,
vmware_images.LINKED_CLONE_PROPERTY: True}}
img_props = vmware_images.VMwareImage.from_image(image_id, mdata)
image_size_in_kb = raw_disk_size_in_bytes / units.Ki
image_size_in_gb = raw_disk_size_in_bytes / units.Gi
# assert that defaults are set and no value returned is left empty
self.assertEqual(constants.DEFAULT_OS_TYPE, img_props.os_type)
self.assertEqual(constants.DEFAULT_ADAPTER_TYPE,
img_props.adapter_type)
self.assertEqual(constants.DEFAULT_DISK_TYPE, img_props.disk_type)
self.assertEqual(constants.DEFAULT_VIF_MODEL, img_props.vif_model)
self.assertTrue(img_props.linked_clone)
self.assertEqual(image_size_in_kb, img_props.file_size_in_kb)
self.assertEqual(image_size_in_gb, img_props.file_size_in_gb)
def _image_build(self, image_lc_setting, global_lc_setting,
disk_format=constants.DEFAULT_DISK_FORMAT,
os_type=constants.DEFAULT_OS_TYPE,
adapter_type=constants.DEFAULT_ADAPTER_TYPE,
disk_type=constants.DEFAULT_DISK_TYPE,
vif_model=constants.DEFAULT_VIF_MODEL):
self.flags(use_linked_clone=global_lc_setting, group='vmware')
raw_disk_size_in_gb = 93
raw_disk_size_in_btyes = raw_disk_size_in_gb * units.Gi
image_id = nova.tests.image.fake.get_valid_image_id()
mdata = {'size': raw_disk_size_in_btyes,
'disk_format': disk_format,
'properties': {
"vmware_ostype": os_type,
"vmware_adaptertype": adapter_type,
"vmware_disktype": disk_type,
"hw_vif_model": vif_model}}
if image_lc_setting is not None:
mdata['properties'][
vmware_images.LINKED_CLONE_PROPERTY] = image_lc_setting
return vmware_images.VMwareImage.from_image(image_id, mdata)
def test_use_linked_clone_override_nf(self):
image_props = self._image_build(None, False)
self.assertFalse(image_props.linked_clone,
"No overrides present but still overridden!")
def test_use_linked_clone_override_nt(self):
image_props = self._image_build(None, True)
self.assertTrue(image_props.linked_clone,
"No overrides present but still overridden!")
def test_use_linked_clone_override_ny(self):
image_props = self._image_build(None, "yes")
self.assertTrue(image_props.linked_clone,
"No overrides present but still overridden!")
def test_use_linked_clone_override_ft(self):
image_props = self._image_build(False, True)
self.assertFalse(image_props.linked_clone,
"image level metadata failed to override global")
def test_use_linked_clone_override_string_nt(self):
image_props = self._image_build("no", True)
self.assertFalse(image_props.linked_clone,
"image level metadata failed to override global")
def test_use_linked_clone_override_string_yf(self):
image_props = self._image_build("yes", False)
self.assertTrue(image_props.linked_clone,
"image level metadata failed to override global")
def test_use_disk_format_none(self):
image = self._image_build(None, True, disk_format=None)
self.assertIsNone(image.file_type)
self.assertFalse(image.is_iso)
def test_use_disk_format_iso(self):
image = self._image_build(None, True, disk_format='iso')
self.assertEqual('iso', image.file_type)
self.assertTrue(image.is_iso)
def test_use_bad_disk_format(self):
self.assertRaises(exception.InvalidDiskFormat,
self._image_build,
None,
True,
disk_format='bad_disk_format')
def test_image_no_defaults(self):
image = self._image_build(False, False,
disk_format='iso',
os_type='fake-os-type',
adapter_type='fake-adapter-type',
disk_type='fake-disk-type',
vif_model='fake-vif-model')
self.assertEqual('iso', image.file_type)
self.assertEqual('fake-os-type', image.os_type)
self.assertEqual('fake-adapter-type', image.adapter_type)
self.assertEqual('fake-disk-type', image.disk_type)
self.assertEqual('fake-vif-model', image.vif_model)
self.assertFalse(image.linked_clone)
def test_image_defaults(self):
image = vmware_images.VMwareImage(image_id='fake-image-id')
# N.B. We intentially don't use the defined constants here. Amongst
# other potential failures, we're interested in changes to their
# values, which would not otherwise be picked up.
self.assertEqual('otherGuest', image.os_type)
self.assertEqual('lsiLogic', image.adapter_type)
self.assertEqual('preallocated', image.disk_type)
self.assertEqual('e1000', image.vif_model)

View File

@@ -18,8 +18,15 @@ Shared constants across the VMware driver
from nova.network import model as network_model
DISK_FORMAT_ISO = 'iso'
DISK_FORMAT_VMDK = 'vmdk'
DISK_FORMATS_ALL = [DISK_FORMAT_ISO, DISK_FORMAT_VMDK]
DISK_TYPE_SPARSE = 'sparse'
DISK_TYPE_PREALLOCATED = 'preallocated'
DEFAULT_VIF_MODEL = network_model.VIF_MODEL_E1000
DEFAULT_OS_TYPE = "otherGuest"
DEFAULT_ADAPTER_TYPE = "lsiLogic"
DEFAULT_DISK_TYPE = "preallocated"
DEFAULT_DISK_TYPE = DISK_TYPE_PREALLOCATED
DEFAULT_DISK_FORMAT = DISK_FORMAT_VMDK

View File

@@ -36,7 +36,6 @@ from nova.i18n import _, _LE
from nova.openstack.common import excutils
from nova.openstack.common import lockutils
from nova.openstack.common import log as logging
from nova.openstack.common import strutils
from nova.openstack.common import units
from nova.openstack.common import uuidutils
from nova import utils
@@ -67,8 +66,6 @@ VMWARE_POWER_STATES = {
'poweredOn': power_state.RUNNING,
'suspended': power_state.SUSPENDED}
VMWARE_LINKED_CLONE = 'vmware_linked_clone'
RESIZE_TOTAL_STEPS = 4
DcInfo = collections.namedtuple('DcInfo',
@@ -154,11 +151,13 @@ class VMwareVMOps(object):
def _get_vmdk_path(self, ds_name, folder, name):
return str(ds_util.DatastorePath(ds_name, folder, '%s.vmdk' % name))
def _get_disk_format(self, image_meta):
disk_format = image_meta.get('disk_format')
if disk_format not in ['iso', 'vmdk', None]:
raise exception.InvalidDiskFormat(disk_format=disk_format)
return (disk_format, disk_format == 'iso')
def _extend_if_required(self, dc_info, image_info, instance,
root_vmdk_path):
"""Increase the size of the root vmdk if necessary."""
if instance.root_gb > image_info.file_size_in_gb:
size_in_kb = instance.root_gb * units.Mi
self._extend_virtual_disk(instance, size_in_kb,
root_vmdk_path, dc_info.ref)
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info, block_device_info=None,
@@ -198,57 +197,16 @@ class VMwareVMOps(object):
if block_device_mapping:
ebs_root = True
(file_type, is_iso) = self._get_disk_format(image_meta)
client_factory = self._session._get_vim().client.factory
datastore = ds_util.get_datastore(
self._session, self._cluster,
datastore_regex=self._datastore_regex)
dc_info = self.get_datacenter_ref_and_name(datastore.ref)
# TODO(hartsocks): this pattern is confusing, reimplement as methods
# The use of nested functions in this file makes for a confusing and
# hard to maintain file. At some future date, refactor this method to
# be a full-fledged method. This will also make unit testing easier.
def _get_image_properties(root_size):
"""Get the Size of the flat vmdk file that is there on the storage
repository.
"""
image_ref = instance.image_ref
if image_ref:
_image_info = vmware_images.get_vmdk_size_and_properties(
context, image_ref, instance)
else:
# The case that the image may be booted from a volume
_image_info = (root_size, {})
image_size, image_properties = _image_info
vmdk_file_size_in_kb = int(image_size) / 1024
os_type = image_properties.get("vmware_ostype",
constants.DEFAULT_OS_TYPE)
adapter_type = image_properties.get("vmware_adaptertype",
constants.DEFAULT_ADAPTER_TYPE)
disk_type = image_properties.get("vmware_disktype",
constants.DEFAULT_DISK_TYPE)
# Get the network card type from the image properties.
vif_model = image_properties.get("hw_vif_model",
constants.DEFAULT_VIF_MODEL)
# Fetch the image_linked_clone data here. It is retrieved
# with the above network based API call. To retrieve it
# later will necessitate additional network calls using the
# identical method. Consider this a cache.
image_linked_clone = image_properties.get(VMWARE_LINKED_CLONE)
return (vmdk_file_size_in_kb, os_type, adapter_type, disk_type,
vif_model, image_linked_clone)
root_gb_in_kb = instance.root_gb * units.Mi
(vmdk_file_size_in_kb, os_type, adapter_type, disk_type, vif_model,
image_linked_clone) = _get_image_properties(root_gb_in_kb)
if root_gb_in_kb and vmdk_file_size_in_kb > root_gb_in_kb:
image_info = vmware_images.VMwareImage.from_image(instance.image_ref,
image_meta)
if (instance.root_gb != 0 and
image_info.file_size_in_gb > instance.root_gb):
reason = _("Image disk size greater than requested disk size")
raise exception.InstanceUnacceptable(instance_id=instance.uuid,
reason=reason)
@@ -258,7 +216,8 @@ class VMwareVMOps(object):
self._cluster, node_mo_id)
vif_infos = vmwarevif.get_vif_info(self._session, self._cluster,
utils.is_neutron(), vif_model,
utils.is_neutron(),
image_info.vif_model,
network_info)
# Get the instance name. In some cases this may differ from the 'uuid',
@@ -269,7 +228,7 @@ class VMwareVMOps(object):
# Create the VM
config_spec = vm_util.get_vm_create_spec(
client_factory, instance, instance_name,
datastore.name, vif_infos, os_type)
datastore.name, vif_infos, image_info.os_type)
vm_ref = vm_util.create_vm(self._session, instance, dc_info.vmFolder,
config_spec, res_pool_ref)
@@ -292,22 +251,19 @@ class VMwareVMOps(object):
# this logic allows for instances or images to decide
# for themselves which strategy is best for them.
linked_clone = VMwareVMOps.decide_linked_clone(
image_linked_clone,
CONF.vmware.use_linked_clone
)
upload_name = instance.image_ref
upload_folder = '%s/%s' % (self._base_folder, upload_name)
# The vmdk meta-data file
uploaded_file_path = str(datastore.build_path(
upload_folder, "%s.%s" % (upload_name, file_type)))
upload_folder,
"%s.%s" % (upload_name, image_info.file_type)))
session_vim = self._session._get_vim()
cookies = session_vim.client.options.transport.cookiejar
ds_browser = self._get_ds_browser(datastore.ref)
upload_file_name = upload_name + ".%s" % file_type
upload_file_name = upload_name + ".%s" % image_info.file_type
# Check if the timestamp file exists - if so then delete it. This
# will ensure that the aging will not delete a cache image if it
@@ -351,8 +307,8 @@ class VMwareVMOps(object):
upload_path_loc = datastore.build_path(
upload_folder, upload_file_name)
upload_rel_path = upload_path_loc.rel_path
if not is_iso:
if disk_type != "sparse":
if not image_info.is_iso:
if not image_info.is_sparse:
# Create a flat virtual disk and retain the metadata
# file. This will be done in the unique temporary
# directory.
@@ -364,10 +320,10 @@ class VMwareVMOps(object):
datastore.name, instance=instance)
vm_util.create_virtual_disk(self._session,
dc_info.ref,
adapter_type,
disk_type,
image_info.adapter_type,
image_info.disk_type,
str(upload_path_loc),
vmdk_file_size_in_kb)
image_info.file_size_in_kb)
LOG.debug("Virtual disk created on %s.",
datastore.name, instance=instance)
self._delete_datastore_file(instance,
@@ -385,12 +341,13 @@ class VMwareVMOps(object):
upload_rel_path,
cookies=cookies)
if not is_iso and disk_type == "sparse":
if not image_info.is_iso and image_info.is_sparse:
# Copy the sparse virtual disk to a thin virtual disk.
disk_type = "thin"
copy_spec = self.get_copy_virtual_disk_spec(client_factory,
adapter_type,
disk_type)
copy_spec = self.get_copy_virtual_disk_spec(
client_factory,
image_info.adapter_type,
disk_type)
vm_util.copy_virtual_disk(self._session, dc_info.ref,
str(sparse_ds_loc),
str(upload_path_loc),
@@ -416,13 +373,9 @@ class VMwareVMOps(object):
datastore.build_path(
tmp_upload_folder),
dc_info.ref)
else:
# linked clone base disk exists
if disk_type == "sparse":
disk_type = "thin"
if is_iso:
if root_gb_in_kb:
if image_info.is_iso:
if instance.root_gb != 0:
dest_vmdk_path = self._get_vmdk_path(datastore.name,
instance.uuid,
instance_name)
@@ -431,10 +384,10 @@ class VMwareVMOps(object):
datastore.name, instance=instance)
vm_util.create_virtual_disk(self._session,
dc_info.ref,
adapter_type,
disk_type,
image_info.adapter_type,
image_info.disk_type,
dest_vmdk_path,
root_gb_in_kb)
image_info.file_size_in_kb)
LOG.debug("Blank virtual disk created on %s.",
datastore.name, instance=instance)
root_vmdk_path = dest_vmdk_path
@@ -442,24 +395,24 @@ class VMwareVMOps(object):
root_vmdk_path = None
else:
# Extend the disk size if necessary
if not linked_clone:
if not image_info.linked_clone:
# If we are not using linked_clone, copy the image from
# the cache into the instance directory. If we are using
# linked clone it is references from the cache directory
dest_vmdk_path = self._get_vmdk_path(datastore.name,
instance_name, instance_name)
copy_spec = self.get_copy_virtual_disk_spec(client_factory,
adapter_type,
disk_type)
copy_spec = self.get_copy_virtual_disk_spec(
client_factory,
image_info.adapter_type,
image_info.disk_type)
vm_util.copy_virtual_disk(self._session,
dc_info.ref,
uploaded_file_path,
dest_vmdk_path, copy_spec)
root_vmdk_path = dest_vmdk_path
if root_gb_in_kb > vmdk_file_size_in_kb:
self._extend_virtual_disk(instance, root_gb_in_kb,
root_vmdk_path, dc_info.ref)
self._extend_if_required(dc_info, image_info, instance,
root_vmdk_path)
else:
upload_folder = '%s/%s' % (self._base_folder, upload_name)
if instance.root_gb:
@@ -493,7 +446,9 @@ class VMwareVMOps(object):
instance.root_gb)
copy_spec = self.get_copy_virtual_disk_spec(
client_factory, adapter_type, disk_type)
client_factory,
image_info.adapter_type,
image_info.disk_type)
# Create a copy of the base image, ensuring we
# clean up on failure
@@ -524,20 +479,21 @@ class VMwareVMOps(object):
# Resize the copy to the appropriate size. No need
# for cleanup up here, as _extend_virtual_disk
# already does it
if root_gb_in_kb > vmdk_file_size_in_kb:
self._extend_virtual_disk(instance,
root_gb_in_kb,
root_vmdk_path,
dc_info.ref)
self._extend_if_required(dc_info, image_info,
instance, root_vmdk_path)
# Attach the root disk to the VM.
if root_vmdk_path:
if root_vmdk_path is not None:
self._volumeops.attach_disk_to_vm(
vm_ref, instance,
adapter_type, disk_type, root_vmdk_path,
root_gb_in_kb, linked_clone)
vm_ref,
instance,
image_info.adapter_type,
image_info.disk_type,
root_vmdk_path,
instance.root_gb * units.Mi,
image_info.linked_clone)
if is_iso:
if image_info.is_iso:
self._attach_cdrom_to_vm(
vm_ref, instance,
datastore.ref,
@@ -627,52 +583,6 @@ class VMwareVMOps(object):
LOG.debug("Reconfigured VM instance to attach cdrom %s",
file_path, instance=instance)
@staticmethod
def decide_linked_clone(image_linked_clone, global_linked_clone):
"""Explicit decision logic: whether to use linked clone on a vmdk.
This is *override* logic not boolean logic.
1. let the image over-ride if set at all
2. default to the global setting
In math terms, I need to allow:
glance image to override global config.
That is g vs c. "g" for glance. "c" for Config.
So, I need g=True vs c=False to be True.
And, I need g=False vs c=True to be False.
And, I need g=None vs c=True to be True.
Some images maybe independently best tuned for use_linked_clone=True
saving datastorage space. Alternatively a whole OpenStack install may
be tuned to performance use_linked_clone=False but a single image
in this environment may be best configured to save storage space and
set use_linked_clone=True only for itself.
The point is: let each layer of control override the layer beneath it.
rationale:
For technical discussion on the clone strategies and their trade-offs
see: https://www.vmware.com/support/ws5/doc/ws_clone_typeofclone.html
:param image_linked_clone: boolean or string or None
:param global_linked_clone: boolean or string or None
:return: Boolean
"""
value = None
# Consider the values in order of override.
if image_linked_clone is not None:
value = image_linked_clone
else:
# this will never be not-set by this point.
value = global_linked_clone
return strutils.bool_from_string(value)
def get_copy_virtual_disk_spec(self, client_factory, adapter_type,
disk_type):
return vm_util.get_copy_virtual_disk_spec(client_factory,

View File

@@ -14,22 +14,136 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility functions for Image transfer.
Utility functions for Image transfer and manipulation.
"""
import os
from oslo.config import cfg
from nova import exception
from nova import image
from nova.openstack.common import log as logging
from nova.openstack.common import strutils
from nova.openstack.common import units
from nova.virt.vmwareapi import constants
from nova.virt.vmwareapi import io_util
from nova.virt.vmwareapi import read_write_util
# NOTE(mdbooth): We use use_linked_clone below, but don't have to import it
# because nova.virt.vmwareapi.driver is imported first. In fact, it is not
# possible to import it here, as nova.virt.vmwareapi.driver calls
# CONF.register_opts() after the import chain which imports this module. This
# is not a problem as long as the import order doesn't change.
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
IMAGE_API = image.API()
QUEUE_BUFFER_SIZE = 10
LINKED_CLONE_PROPERTY = 'vmware_linked_clone'
class VMwareImage(object):
def __init__(self, image_id,
file_size=0,
os_type=constants.DEFAULT_OS_TYPE,
adapter_type=constants.DEFAULT_ADAPTER_TYPE,
disk_type=constants.DEFAULT_DISK_TYPE,
file_type=constants.DEFAULT_DISK_FORMAT,
linked_clone=None,
vif_model=constants.DEFAULT_VIF_MODEL):
"""VMwareImage holds values for use in building VMs.
image_id (str): uuid of the image
file_size (int): size of file in bytes
os_type (str): name of guest os (use vSphere names only)
adapter_type (str): name of the adapter's type
disk_type (str): type of disk in thin, thick, etc
file_type (str): vmdk or iso
linked_clone(bool): use linked clone, or don't
"""
self.image_id = image_id
self.file_size = file_size
self.os_type = os_type
self.adapter_type = adapter_type
self.disk_type = disk_type
self.file_type = file_type
# NOTE(vui): This should be removed when we restore the
# descriptor-based validation.
if (self.file_type is not None and
self.file_type not in constants.DISK_FORMATS_ALL):
raise exception.InvalidDiskFormat(disk_format=self.file_type)
if linked_clone is not None:
self.linked_clone = linked_clone
else:
self.linked_clone = CONF.vmware.use_linked_clone
self.vif_model = vif_model
@property
def file_size_in_kb(self):
return self.file_size / units.Ki
@property
def file_size_in_gb(self):
return self.file_size / units.Gi
@property
def is_sparse(self):
return self.disk_type == constants.DISK_TYPE_SPARSE
@property
def is_iso(self):
return self.file_type == constants.DISK_FORMAT_ISO
@classmethod
def from_image(cls, image_id, image_meta=None):
"""Returns VMwareImage, the subset of properties the driver uses.
:param image_id - image id of image
:param image_meta - image metadata we are working with
:return: vmware image object
:rtype: nova.virt.vmwareapi.vmware_images.VmwareImage
"""
if image_meta is None:
image_meta = {}
properties = image_meta.get("properties", {})
# calculate linked_clone flag, allow image properties to override the
# global property set in the configurations.
image_linked_clone = properties.get(LINKED_CLONE_PROPERTY,
CONF.vmware.use_linked_clone)
# catch any string values that need to be interpreted as boolean values
linked_clone = strutils.bool_from_string(image_linked_clone)
props = {
'image_id': image_id,
'linked_clone': linked_clone
}
if 'size' in image_meta:
props['file_size'] = image_meta['size']
if 'disk_format' in image_meta:
props['file_type'] = image_meta['disk_format']
props_map = {
'vmware_ostype': 'os_type',
'vmware_adaptertype': 'adapter_type',
'vmware_disktype': 'disk_type',
'hw_vif_model': 'vif_model'
}
for k, v in props_map.iteritems():
if k in properties:
props[v] = properties[k]
return cls(**props)
def start_transfer(context, read_file_handle, data_size,
write_file_handle=None, image_id=None, image_meta=None):
@@ -171,18 +285,3 @@ def upload_image(context, image, instance, **kwargs):
image_id=metadata['id'], image_meta=image_metadata)
LOG.debug("Uploaded image %s to the Glance image server", image,
instance=instance)
def get_vmdk_size_and_properties(context, image, instance):
"""Get size of the vmdk file that is to be downloaded for attach in spawn.
Need this to create the dummy virtual disk for the meta-data file. The
geometry of the disk created depends on the size.
"""
LOG.debug("Getting image size for the image %s", image,
instance=instance)
meta_data = IMAGE_API.get(context, image)
size, properties = meta_data["size"], meta_data["properties"]
LOG.debug("Got image size of %(size)s for the image %(image)s",
{'size': size, 'image': image}, instance=instance)
return size, properties