diff --git a/nova/tests/virt/vmwareapi/test_vmops.py b/nova/tests/virt/vmwareapi/test_vmops.py index fb08c8652065..743af7f91bcc 100644 --- a/nova/tests/virt/vmwareapi/test_vmops.py +++ b/nova/tests/virt/vmwareapi/test_vmops.py @@ -40,39 +40,6 @@ 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() @@ -771,6 +738,59 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): dest_file, None) + @mock.patch.object(vmops.VMwareVCVMOps, 'get_copy_virtual_disk_spec', + return_value=None) + @mock.patch.object(ds_util, 'get_datastore') + @mock.patch.object(vmops.VMwareVCVMOps, 'get_datacenter_ref_and_name') + def _test_get_spawn_vm_config_info(self, + mock_get_datacenter_ref_and_name, + mock_get_datastore, + mock_get_copy_virtual_disk_spec, + image_size_bytes=0, + instance_name=None): + image_info = vmware_images.VMwareImage( + image_id=self._image_id, + file_size=image_size_bytes, + linked_clone=True) + + mock_get_datastore.return_value = self._ds + mock_get_datacenter_ref_and_name.return_value = self._dc_info + + vi = self._vmops._get_vm_config_info( + self._instance, image_info, instance_name=instance_name) + self.assertEqual(image_info, vi.ii) + self.assertEqual(self._ds, vi.datastore) + self.assertEqual(vi.root_gb, self._instance.root_gb) + self.assertEqual(vi.instance, self._instance) + if instance_name: + self.assertEqual(vi.instance_name, instance_name) + else: + self.assertEqual(vi.instance_name, self._instance['uuid']) + + cache_image_path = '[%s] vmware_base/%s/%s.vmdk' % ( + self._ds.name, self._image_id, self._image_id) + self.assertEqual(str(vi.cache_image_path), cache_image_path) + + cache_image_folder = '[%s] vmware_base/%s' % ( + self._ds.name, self._image_id) + self.assertEqual(str(vi.cache_image_folder), cache_image_folder) + + def test_get_spawn_vm_config_info(self): + image_size = (self._instance.root_gb) * units.Gi / 2 + self._test_get_spawn_vm_config_info(image_size_bytes=image_size) + + def test_get_spawn_vm_config_info_image_too_big(self): + image_size = (self._instance.root_gb + 1) * units.Gi + self.assertRaises(exception.InstanceUnacceptable, + self._test_get_spawn_vm_config_info, + image_size_bytes=image_size) + + def test_get_spawn_vm_config_info_with_instance_name(self): + image_size = (self._instance.root_gb) * units.Gi / 2 + self._test_get_spawn_vm_config_info( + image_size_bytes=image_size, + instance_name="foo_instance_name") + def test_spawn(self): self._test_spawn() diff --git a/nova/virt/vmwareapi/imagecache.py b/nova/virt/vmwareapi/imagecache.py index 5051c2108510..73bd4508e530 100644 --- a/nova/virt/vmwareapi/imagecache.py +++ b/nova/virt/vmwareapi/imagecache.py @@ -179,3 +179,7 @@ class ImageCacheManager(imagecache.ImageCacheManager): images = self._list_datastore_images(ds_path, datastore) self.originals = images['originals'] self._age_cached_images(context, datastore, dc_info, ds_path) + + def get_image_cache_folder(self, datastore, image_id): + """Returns datastore path of folder containing the image.""" + return datastore.build_path(self._base_folder, image_id) diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index c28b3b5a04b6..b53e6e11bf99 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -73,6 +73,43 @@ DcInfo = collections.namedtuple('DcInfo', ['ref', 'name', 'vmFolder']) +class VirtualMachineInstanceConfigInfo(object): + """Parameters needed to create and configure a new instance.""" + + def __init__(self, instance, instance_name, image_info, + datastore, dc_info, image_cache): + + # Some methods called during spawn take the instance parameter purely + # for logging purposes. + # TODO(vui) Clean them up, so we no longer need to keep this variable + self.instance = instance + + # Get the instance name. In some cases this may differ from the 'uuid', + # for example when the spawn of a rescue instance takes place. + self.instance_name = instance_name or instance.uuid + + self.ii = image_info + self.root_gb = instance.root_gb + self.datastore = datastore + self.dc_info = dc_info + self._image_cache = image_cache + + @property + def cache_image_folder(self): + if self.ii.image_id is None: + return + return self._image_cache.get_image_cache_folder( + self.datastore, self.ii.image_id) + + @property + def cache_image_path(self): + if self.ii.image_id is None: + return + cached_image_file_name = "%s.%s" % (self.ii.image_id, + self.ii.file_type) + return self.cache_image_folder.join(cached_image_file_name) + + class VMwareVMOps(object): """Management class for VM-related tasks.""" @@ -220,60 +257,47 @@ class VMwareVMOps(object): allocations[key] = type(value) return allocations - def spawn(self, context, instance, image_meta, injected_files, - admin_password, network_info, block_device_info=None, - instance_name=None, power_on=True): - """Creates a VM instance. + def _get_vm_config_info(self, instance, image_info, instance_name=None): + """Captures all relevant information from the spawn parameters.""" - Steps followed are: - - #. Create a VM with no disk and the specifics in the instance object - like RAM size. - #. For flat disk - - #. Create a dummy vmdk of the size of the disk file that is to be - uploaded. This is required just to create the metadata file. - #. Delete the -flat.vmdk file created in the above step and retain - the metadata .vmdk file. - #. Upload the disk file. - - #. For sparse disk - - #. Upload the disk file to a -sparse.vmdk file. - #. Copy/Clone the -sparse.vmdk file to a thin vmdk. - #. Delete the -sparse.vmdk file. - - #. Attach the disk to the VM by reconfiguring the same. - #. Power on the VM. - - """ - - # NOTE(hartsocks): some of the logic below relies on instance_name - # even when it is not set by the caller. - if instance_name is None: - instance_name = instance.uuid - - 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) - - 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) + datastore = ds_util.get_datastore( + self._session, self._cluster, None, self._datastore_regex) + dc_info = self.get_datacenter_ref_and_name(datastore.ref) + + return VirtualMachineInstanceConfigInfo(instance, + instance_name, + image_info, + datastore, + dc_info, + self._imagecache) + + def spawn(self, context, instance, image_meta, injected_files, + admin_password, network_info, block_device_info=None, + instance_name=None, power_on=True): + + client_factory = self._session._get_vim().client.factory + image_info = vmware_images.VMwareImage.from_image(instance.image_ref, + image_meta) + vi = self._get_vm_config_info(instance, image_info, instance_name) + + # NOTE(vui): temporarily retaining some local variables until + # the spawn refactoring is complete. + instance_name = vi.instance_name + datastore = vi.datastore + dc_info = vi.dc_info # Creates the virtual machine. The virtual machine reference returned # is unique within Virtual Center. vm_ref = self.build_virtual_machine(instance, - instance_name, + vi.instance_name, image_info, - dc_info, - datastore, + vi.dc_info, + vi.datastore, network_info) # Cache the vm_ref. This saves a remote call to the VC. This uses the @@ -569,8 +593,8 @@ class VMwareVMOps(object): if configdrive.required_by(instance): self._configure_config_drive( - instance, vm_ref, dc_info, datastore, injected_files, - admin_password) + instance, vm_ref, vi.dc_info, vi.datastore, + injected_files, admin_password) if power_on: vm_util.power_on_instance(self._session, instance, vm_ref=vm_ref)