# Copyright (c) 2017 VMware, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Test suite for VMware vCenter FCD driver. """ import ddt import mock from oslo_utils import units from oslo_vmware import image_transfer from oslo_vmware.objects import datastore from oslo_vmware import vim_util from cinder import context from cinder import exception as cinder_exceptions from cinder import test from cinder.tests.unit import fake_snapshot from cinder.tests.unit import fake_volume from cinder.volume import configuration from cinder.volume.drivers.vmware import datastore as hub from cinder.volume.drivers.vmware import fcd from cinder.volume.drivers.vmware import vmdk from cinder.volume.drivers.vmware import volumeops @ddt.ddt class VMwareVStorageObjectDriverTestCase(test.TestCase): IP = 'localhost' PORT = 2321 IMG_TX_TIMEOUT = 10 RESERVED_PERCENTAGE = 0 VMDK_DRIVER = vmdk.VMwareVcVmdkDriver FCD_DRIVER = fcd.VMwareVStorageObjectDriver VC_VERSION = "6.7.0" VOL_ID = 'abcdefab-cdef-abcd-efab-cdefabcdefab' SRC_VOL_ID = '9b3f6f1b-03a9-4f1e-aaff-ae15122b6ccf' DISPLAY_NAME = 'foo' VOL_TYPE_ID = 'd61b8cb3-aa1b-4c9b-b79e-abcdbda8b58a' VOL_SIZE = 2 PROJECT_ID = 'd45beabe-f5de-47b7-b462-0d9ea02889bc' IMAGE_ID = 'eb87f4b0-d625-47f8-bb45-71c43b486d3a' IMAGE_NAME = 'image-1' def setUp(self): super(VMwareVStorageObjectDriverTestCase, self).setUp() self._config = mock.Mock(spec=configuration.Configuration) self._config.vmware_host_ip = self.IP self._config.vmware_host_port = self.PORT self._config.vmware_image_transfer_timeout_secs = self.IMG_TX_TIMEOUT self._config.reserved_percentage = self.RESERVED_PERCENTAGE self._driver = fcd.VMwareVStorageObjectDriver( configuration=self._config) self._driver._vc_version = self.VC_VERSION self._context = context.get_admin_context() @mock.patch.object(VMDK_DRIVER, 'do_setup') @mock.patch.object(FCD_DRIVER, 'volumeops') def test_do_setup(self, vops, vmdk_do_setup): self._driver.do_setup(self._context) vmdk_do_setup.assert_called_once_with(self._context) self.assertFalse(self._driver._storage_policy_enabled) vops.set_vmx_version.assert_called_once_with('vmx-13') self.assertTrue(self._driver._use_fcd_snapshot) @mock.patch.object(VMDK_DRIVER, 'volumeops') @mock.patch.object(VMDK_DRIVER, '_get_datastore_summaries') def test_get_volume_stats(self, _get_datastore_summaries, vops): FREE_GB = 7 TOTAL_GB = 11 class ObjMock(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) _get_datastore_summaries.return_value = \ ObjMock(objects= [ ObjMock(propSet = [ ObjMock(name = "host", val = ObjMock(DatastoreHostMount = [])), ObjMock(name = "summary", val = ObjMock(freeSpace = FREE_GB * units.Gi, capacity = TOTAL_GB * units.Gi, accessible = True)) ]) ]) vops._in_maintenance.return_value = False stats = self._driver.get_volume_stats() self.assertEqual('VMware', stats['vendor_name']) self.assertEqual(self._driver.VERSION, stats['driver_version']) self.assertEqual(self._driver.STORAGE_TYPE, stats['storage_protocol']) self.assertEqual(self.RESERVED_PERCENTAGE, stats['reserved_percentage']) self.assertEqual(TOTAL_GB, stats['total_capacity_gb']) self.assertEqual(FREE_GB, stats['free_capacity_gb']) def _create_volume_dict(self, vol_id=VOL_ID, display_name=DISPLAY_NAME, volume_type_id=VOL_TYPE_ID, status='available', size=VOL_SIZE, attachment=None, project_id=PROJECT_ID): return {'id': vol_id, 'display_name': display_name, 'name': 'volume-%s' % vol_id, 'volume_type_id': volume_type_id, 'status': status, 'size': size, 'volume_attachment': attachment, 'project_id': project_id, } def _create_volume_obj(self, vol_id=VOL_ID, display_name=DISPLAY_NAME, volume_type_id=VOL_TYPE_ID, status='available', size=VOL_SIZE, attachment=None, project_id=PROJECT_ID): vol = self._create_volume_dict( vol_id, display_name, volume_type_id, status, size, attachment, project_id) return fake_volume.fake_volume_obj(self._context, **vol) @mock.patch.object(FCD_DRIVER, '_select_datastore') def test_select_ds_fcd(self, select_datastore): datastore = mock.sentinel.datastore summary = mock.Mock(datastore=datastore) select_datastore.return_value = (mock.ANY, mock.ANY, summary) volume = self._create_volume_obj() ret = self._driver._select_ds_fcd(volume) self.assertEqual(datastore, ret) exp_req = {hub.DatastoreSelector.SIZE_BYTES: volume.size * units.Gi} select_datastore.assert_called_once_with(exp_req) @mock.patch.object(FCD_DRIVER, '_select_datastore') @mock.patch.object(FCD_DRIVER, 'volumeops') def _test_get_temp_image_folder( self, vops, select_datastore, preallocated=False): host = mock.sentinel.host summary = mock.Mock() summary.name = 'ds-1' select_datastore.return_value = (host, mock.ANY, summary) dc_ref = mock.sentinel.dc_ref vops.get_dc.return_value = dc_ref size_bytes = units.Gi ret = self._driver._get_temp_image_folder(size_bytes, preallocated) self.assertEqual( (dc_ref, summary, vmdk.TMP_IMAGES_DATASTORE_FOLDER_PATH), ret) exp_req = {hub.DatastoreSelector.SIZE_BYTES: size_bytes} if preallocated: exp_req[hub.DatastoreSelector.HARD_AFFINITY_DS_TYPE] = ( {hub.DatastoreType.NFS, hub.DatastoreType.VMFS, hub.DatastoreType.NFS41}) select_datastore.assert_called_once_with(exp_req) vops.get_dc.assert_called_once_with(host) vops.create_datastore_folder.assert_called_once_with( summary.name, vmdk.TMP_IMAGES_DATASTORE_FOLDER_PATH, dc_ref) def test_get_temp_image_folder(self): self._test_get_temp_image_folder() def test_get_temp_image_folder_preallocated(self): self._test_get_temp_image_folder(preallocated=True) @mock.patch.object(VMDK_DRIVER, '_get_disk_type') @ddt.data(('eagerZeroedThick', 'eagerZeroedThick'), ('thick', 'preallocated'), ('thin', 'thin')) @ddt.unpack def test_get_disk_type( self, extra_spec_disk_type, exp_ret_val, vmdk_get_disk_type): vmdk_get_disk_type.return_value = extra_spec_disk_type volume = mock.sentinel.volume ret = self._driver._get_disk_type(volume) self.assertEqual(exp_ret_val, ret) @mock.patch.object(FCD_DRIVER, '_select_ds_fcd') @mock.patch.object(FCD_DRIVER, '_get_disk_type') @mock.patch.object(FCD_DRIVER, 'volumeops') def test_create_volume(self, vops, get_disk_type, select_ds_fcd): ds_ref = mock.sentinel.ds_ref select_ds_fcd.return_value = ds_ref disk_type = mock.sentinel.disk_type get_disk_type.return_value = disk_type fcd_loc = mock.Mock() provider_loc = mock.sentinel.provider_loc fcd_loc.provider_location.return_value = provider_loc vops.create_fcd.return_value = fcd_loc volume = self._create_volume_obj() ret = self._driver.create_volume(volume) self.assertEqual({'provider_location': provider_loc}, ret) select_ds_fcd.assert_called_once_with(volume) get_disk_type.assert_called_once_with(volume) vops.create_fcd.assert_called_once_with( volume.name, volume.size * units.Ki, ds_ref, disk_type) @mock.patch.object(volumeops.FcdLocation, 'from_provider_location') @mock.patch.object(FCD_DRIVER, 'volumeops') def test_delete_fcd(self, vops, from_provider_loc): fcd_loc = mock.sentinel.fcd_loc from_provider_loc.return_value = fcd_loc provider_loc = mock.sentinel.provider_loc self._driver._delete_fcd(provider_loc) from_provider_loc.test_assert_called_once_with(provider_loc) vops.delete_fcd.assert_called_once_with(fcd_loc) @mock.patch.object(FCD_DRIVER, '_delete_fcd') def test_delete_volume(self, delete_fcd): volume = self._create_volume_obj() volume.provider_location = 'foo@ds1' self._driver.delete_volume(volume) delete_fcd.assert_called_once_with(volume.provider_location) @mock.patch.object(FCD_DRIVER, '_delete_fcd') def test_delete_volume_empty_provider_location(self, delete_fcd): volume = self._create_volume_obj() self._driver.delete_volume(volume) delete_fcd.assert_not_called() @mock.patch.object(volumeops.FcdLocation, 'from_provider_location') @mock.patch.object(FCD_DRIVER, '_get_adapter_type') def test_initialize_connection( self, get_adapter_type, from_provider_location): fcd_loc = mock.Mock( fcd_id=mock.sentinel.fcd_id, ds_ref_val=mock.sentinel.ds_ref_val) from_provider_location.return_value = fcd_loc adapter_type = mock.sentinel.adapter_type get_adapter_type.return_value = adapter_type volume = self._create_volume_obj() connector = mock.sentinel.connector ret = self._driver.initialize_connection(volume, connector) self.assertEqual(self._driver.STORAGE_TYPE, ret['driver_volume_type']) self.assertEqual(fcd_loc.fcd_id, ret['data']['id']) self.assertEqual(fcd_loc.ds_ref_val, ret['data']['ds_ref_val']) self.assertEqual(adapter_type, ret['data']['adapter_type']) def test_container_format(self): self._driver._validate_container_format('bare', mock.sentinel.image_id) def test_container_format_invalid(self): self.assertRaises(cinder_exceptions.ImageUnacceptable, self._driver._validate_container_format, 'ova', mock.sentinel.image_id) def _create_image_meta(self, _id=IMAGE_ID, name=IMAGE_NAME, disk_format='vmdk', size=1 * units.Gi, container_format='bare', vmware_disktype='streamOptimized', vmware_adaptertype='lsiLogic', is_public=True): return {'id': _id, 'name': name, 'disk_format': disk_format, 'size': size, 'container_format': container_format, 'properties': {'vmware_disktype': vmware_disktype, 'vmware_adaptertype': vmware_adaptertype, }, 'is_public': is_public, } @mock.patch.object(FCD_DRIVER, '_get_temp_image_folder') @mock.patch.object(FCD_DRIVER, '_create_virtual_disk_from_sparse_image') @mock.patch.object(FCD_DRIVER, '_create_virtual_disk_from_preallocated_image') @mock.patch.object(FCD_DRIVER, 'volumeops') @mock.patch.object(datastore, 'DatastoreURL') @ddt.data(vmdk.ImageDiskType.PREALLOCATED, vmdk.ImageDiskType.SPARSE, vmdk.ImageDiskType.STREAM_OPTIMIZED) def test_copy_image_to_volume(self, disk_type, datastore_url_cls, vops, create_disk_from_preallocated_image, create_disk_from_sparse_image, get_temp_image_folder): image_meta = self._create_image_meta(vmware_disktype=disk_type) image_service = mock.Mock() image_service.show.return_value = image_meta dc_ref = mock.sentinel.dc_ref datastore = mock.sentinel.datastore summary = mock.Mock(datastore=datastore) summary.name = 'ds1' folder_path = mock.sentinel.folder_path get_temp_image_folder.return_value = (dc_ref, summary, folder_path) vmdk_path = mock.Mock() vmdk_path.get_descriptor_ds_file_path.return_value = ( "[ds1] cinder_vol/foo.vmdk") if disk_type == vmdk.ImageDiskType.PREALLOCATED: create_disk_from_preallocated_image.return_value = vmdk_path else: create_disk_from_sparse_image.return_value = vmdk_path dc_path = '/test-dc' vops.get_inventory_path.return_value = dc_path ds_url = mock.sentinel.ds_url datastore_url_cls.return_value = ds_url fcd_loc = mock.Mock() provider_location = mock.sentinel.provider_location fcd_loc.provider_location.return_value = provider_location vops.register_disk.return_value = fcd_loc volume = self._create_volume_obj() image_id = self.IMAGE_ID ret = self._driver.copy_image_to_volume( self._context, volume, image_service, image_id) self.assertEqual({'provider_location': provider_location}, ret) get_temp_image_folder.assert_called_once_with(volume.size * units.Gi) if disk_type == vmdk.ImageDiskType.PREALLOCATED: create_disk_from_preallocated_image.assert_called_once_with( self._context, image_service, image_id, image_meta['size'], dc_ref, summary.name, folder_path, volume.id, volumeops.VirtualDiskAdapterType.LSI_LOGIC) else: create_disk_from_sparse_image.assert_called_once_with( self._context, image_service, image_id, image_meta['size'], dc_ref, summary.name, folder_path, volume.id) datastore_url_cls.assert_called_once_with( 'https', self._driver.configuration.vmware_host_ip, 'cinder_vol/foo.vmdk', '/test-dc', 'ds1') vops.register_disk.assert_called_once_with( str(ds_url), volume.name, summary.datastore) @mock.patch.object(volumeops.FcdLocation, 'from_provider_location') @mock.patch.object(FCD_DRIVER, 'volumeops') @mock.patch.object(vim_util, 'get_moref') @mock.patch.object(FCD_DRIVER, '_create_backing') @mock.patch.object(image_transfer, 'upload_image') @mock.patch.object(VMDK_DRIVER, 'session') @mock.patch.object(FCD_DRIVER, '_delete_temp_backing') def test_copy_volume_to_image( self, delete_temp_backing, session, upload_image, create_backing, get_moref, vops, from_provider_loc): fcd_loc = mock.Mock() ds_ref = mock.sentinel.ds_ref fcd_loc.ds_ref.return_value = ds_ref from_provider_loc.return_value = fcd_loc host_ref_val = mock.sentinel.host_ref_val vops.get_connected_hosts.return_value = [host_ref_val] host = mock.sentinel.host get_moref.return_value = host backing = mock.sentinel.backing create_backing.return_value = backing vmdk_file_path = mock.sentinel.vmdk_file_path vops.get_vmdk_path.return_value = vmdk_file_path vops.get_backing_by_uuid.return_value = backing volume = self._create_volume_obj() image_service = mock.sentinel.image_service image_meta = self._create_image_meta() self._driver.copy_volume_to_image( self._context, volume, image_service, image_meta) from_provider_loc.assert_called_once_with(volume.provider_location) vops.get_connected_hosts.assert_called_once_with(ds_ref) create_backing.assert_called_once_with( volume, host, {vmdk.CREATE_PARAM_DISK_LESS: True}) vops.attach_fcd.assert_called_once_with(backing, fcd_loc) vops.get_vmdk_path.assert_called_once_with(backing) conf = self._driver.configuration upload_image.assert_called_once_with( self._context, conf.vmware_image_transfer_timeout_secs, image_service, image_meta['id'], volume.project_id, session=session, host=conf.vmware_host_ip, port=conf.vmware_host_port, vm=backing, vmdk_file_path=vmdk_file_path, vmdk_size=volume.size * units.Gi, image_name=image_meta['name']) vops.detach_fcd.assert_called_once_with(backing, fcd_loc) delete_temp_backing.assert_called_once_with(backing) @mock.patch.object(volumeops.FcdLocation, 'from_provider_location') @mock.patch.object(FCD_DRIVER, 'volumeops') def test_extend_volume(self, vops, from_provider_loc): fcd_loc = mock.sentinel.fcd_loc from_provider_loc.return_value = fcd_loc volume = self._create_volume_obj() new_size = 3 self._driver.extend_volume(volume, new_size) from_provider_loc.assert_called_once_with(volume.provider_location) vops.extend_fcd.assert_called_once_with( fcd_loc, new_size * units.Ki) @mock.patch.object(volumeops.FcdLocation, 'from_provider_location') @mock.patch.object(FCD_DRIVER, 'volumeops') def test_clone_fcd(self, vops, from_provider_loc): fcd_loc = mock.sentinel.fcd_loc from_provider_loc.return_value = fcd_loc dest_fcd_loc = mock.sentinel.dest_fcd_loc vops.clone_fcd.return_value = dest_fcd_loc provider_loc = mock.sentinel.provider_loc name = mock.sentinel.name dest_ds_ref = mock.sentinel.dest_ds_ref disk_type = mock.sentinel.disk_type ret = self._driver._clone_fcd( provider_loc, name, dest_ds_ref, disk_type) self.assertEqual(dest_fcd_loc, ret) from_provider_loc.assert_called_once_with(provider_loc) vops.clone_fcd.assert_called_once_with( name, fcd_loc, dest_ds_ref, disk_type) @mock.patch.object(FCD_DRIVER, '_select_ds_fcd') @mock.patch.object(FCD_DRIVER, '_clone_fcd') @mock.patch.object(FCD_DRIVER, 'volumeops') @mock.patch.object(volumeops.FcdLocation, 'from_provider_location') def _test_create_snapshot( self, from_provider_loc, vops, clone_fcd, select_ds_fcd, use_fcd_snapshot=False): self._driver._use_fcd_snapshot = use_fcd_snapshot provider_location = mock.sentinel.provider_location if use_fcd_snapshot: fcd_loc = mock.sentinel.fcd_loc from_provider_loc.return_value = fcd_loc fcd_snap_loc = mock.Mock() fcd_snap_loc.provider_location.return_value = provider_location vops.create_fcd_snapshot.return_value = fcd_snap_loc else: ds_ref = mock.sentinel.ds_ref select_ds_fcd.return_value = ds_ref dest_fcd_loc = mock.Mock() dest_fcd_loc.provider_location.return_value = provider_location clone_fcd.return_value = dest_fcd_loc volume = self._create_volume_obj() snapshot = fake_snapshot.fake_snapshot_obj( self._context, volume=volume) ret = self._driver.create_snapshot(snapshot) self.assertEqual({'provider_location': provider_location}, ret) if use_fcd_snapshot: vops.create_fcd_snapshot.assert_called_once_with( fcd_loc, description="snapshot-%s" % snapshot.id) else: select_ds_fcd.assert_called_once_with(snapshot.volume) clone_fcd.assert_called_once_with( volume.provider_location, snapshot.name, ds_ref) def test_create_snapshot_legacy(self): self._test_create_snapshot() def test_create_snapshot(self): self._test_create_snapshot(use_fcd_snapshot=True) @mock.patch.object(FCD_DRIVER, '_delete_fcd') @mock.patch.object(volumeops.FcdSnapshotLocation, 'from_provider_location') @mock.patch.object(FCD_DRIVER, 'volumeops') def _test_delete_snapshot( self, vops, from_provider_loc, delete_fcd, empty_provider_loc=False, use_fcd_snapshot=False): volume = self._create_volume_obj() snapshot = fake_snapshot.fake_snapshot_obj( self._context, volume=volume) if empty_provider_loc: snapshot.provider_location = None else: snapshot.provider_location = "test" if use_fcd_snapshot: fcd_snap_loc = mock.sentinel.fcd_snap_loc from_provider_loc.return_value = fcd_snap_loc else: from_provider_loc.return_value = None self._driver.delete_snapshot(snapshot) if empty_provider_loc: delete_fcd.assert_not_called() vops.delete_fcd_snapshot.assert_not_called() elif use_fcd_snapshot: vops.delete_fcd_snapshot.assert_called_once_with(fcd_snap_loc) else: delete_fcd.assert_called_once_with(snapshot.provider_location) def test_delete_snapshot_legacy(self): self._test_delete_snapshot() def test_delete_snapshot_with_empty_provider_loc(self): self._test_delete_snapshot(empty_provider_loc=True) def test_delete_snapshot(self): self._test_delete_snapshot(use_fcd_snapshot=True) @mock.patch.object(FCD_DRIVER, 'volumeops') @ddt.data((1, 1), (1, 2)) @ddt.unpack def test_extend_if_needed(self, cur_size, new_size, vops): fcd_loc = mock.sentinel.fcd_loc self._driver._extend_if_needed(fcd_loc, cur_size, new_size) if new_size > cur_size: vops.extend_fcd.assert_called_once_with( fcd_loc, new_size * units.Ki) else: vops.extend_fcd.assert_not_called() @mock.patch.object(FCD_DRIVER, '_select_ds_fcd') @mock.patch.object(FCD_DRIVER, '_get_disk_type') @mock.patch.object(FCD_DRIVER, '_clone_fcd') @mock.patch.object(FCD_DRIVER, '_extend_if_needed') def test_create_volume_from_fcd( self, extend_if_needed, clone_fcd, get_disk_type, select_ds_fcd): ds_ref = mock.sentinel.ds_ref select_ds_fcd.return_value = ds_ref disk_type = mock.sentinel.disk_type get_disk_type.return_value = disk_type cloned_fcd_loc = mock.Mock() dest_provider_loc = mock.sentinel.dest_provider_loc cloned_fcd_loc.provider_location.return_value = dest_provider_loc clone_fcd.return_value = cloned_fcd_loc provider_loc = mock.sentinel.provider_loc cur_size = 1 volume = self._create_volume_obj() ret = self._driver._create_volume_from_fcd( provider_loc, cur_size, volume) self.assertEqual({'provider_location': dest_provider_loc}, ret) select_ds_fcd.test_assert_called_once_with(volume) get_disk_type.test_assert_called_once_with(volume) clone_fcd.assert_called_once_with( provider_loc, volume.name, ds_ref, disk_type=disk_type) extend_if_needed.assert_called_once_with( cloned_fcd_loc, cur_size, volume.size) @mock.patch.object(FCD_DRIVER, '_create_volume_from_fcd') @mock.patch.object(volumeops.FcdSnapshotLocation, 'from_provider_location') @mock.patch.object(FCD_DRIVER, 'volumeops') @mock.patch.object(FCD_DRIVER, '_extend_if_needed') def _test_create_volume_from_snapshot( self, extend_if_needed, vops, from_provider_loc, create_volume_from_fcd, use_fcd_snapshot=False): src_volume = self._create_volume_obj(vol_id=self.SRC_VOL_ID) snapshot = fake_snapshot.fake_snapshot_obj( self._context, volume=src_volume) volume = self._create_volume_obj(size=self.VOL_SIZE + 1) if use_fcd_snapshot: fcd_snap_loc = mock.sentinel.fcd_snap_loc from_provider_loc.return_value = fcd_snap_loc fcd_loc = mock.Mock() provider_loc = mock.sentinel.provider_loc fcd_loc.provider_location.return_value = provider_loc vops.create_fcd_from_snapshot.return_value = fcd_loc else: from_provider_loc.return_value = None ret = self._driver.create_volume_from_snapshot(volume, snapshot) if use_fcd_snapshot: self.assertEqual({'provider_location': provider_loc}, ret) vops.create_fcd_from_snapshot.assert_called_once_with( fcd_snap_loc, volume.name) extend_if_needed.assert_called_once_with( fcd_loc, snapshot.volume_size, volume.size) else: create_volume_from_fcd.assert_called_once_with( snapshot.provider_location, snapshot.volume.size, volume) def test_create_volume_from_snapshot_legacy(self): self._test_create_volume_from_snapshot() def test_create_volume_from_snapshot(self): self._test_create_volume_from_snapshot(use_fcd_snapshot=True) @mock.patch.object(FCD_DRIVER, '_create_volume_from_fcd') def test_create_cloned_volume(self, create_volume_from_fcd): src_volume = self._create_volume_obj() volume = mock.sentinel.volume self._driver.create_cloned_volume(volume, src_volume) create_volume_from_fcd.assert_called_once_with( src_volume.provider_location, src_volume.size, volume)