Merge "VMware: Detach backing vmdk during disconnect"

This commit is contained in:
Zuul 2019-02-20 16:33:45 +00:00 committed by Gerrit Code Review
commit 953cd147a1
2 changed files with 161 additions and 30 deletions

View File

@ -189,7 +189,64 @@ class VmdkConnector(initiator_connector.InitiatorConnector):
cacerts=cacerts)
image_transfer._start_transfer(read_handle, write_handle, timeout_secs)
def _disconnect(self, tmp_file_path, session, ds_ref, dc_ref, vmdk_path):
def _get_disk_device(self, session, backing):
hardware_devices = session.invoke_api(vim_util,
'get_object_property',
session.vim,
backing,
'config.hardware.device')
if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice":
hardware_devices = hardware_devices.VirtualDevice
for device in hardware_devices:
if device.__class__.__name__ == "VirtualDisk":
return device
def _create_spec_for_disk_remove(self, session, disk_device):
cf = session.vim.client.factory
disk_spec = cf.create('ns0:VirtualDeviceConfigSpec')
disk_spec.operation = 'remove'
disk_spec.fileOperation = 'destroy'
disk_spec.device = disk_device
return disk_spec
def _reconfigure_backing(self, session, backing, reconfig_spec):
LOG.debug("Reconfiguring backing VM: %(backing)s with spec: %(spec)s.",
{'backing': backing,
'spec': reconfig_spec})
reconfig_task = session.invoke_api(session.vim,
"ReconfigVM_Task",
backing,
spec=reconfig_spec)
LOG.debug("Task: %s created for reconfiguring backing VM.",
reconfig_task)
session.wait_for_task(reconfig_task)
def _detach_disk_from_backing(self, session, backing, disk_device):
LOG.debug("Reconfiguring backing VM: %(backing)s to remove disk: "
"%(disk_device)s.",
{'backing': backing, 'disk_device': disk_device})
cf = session.vim.client.factory
reconfig_spec = cf.create('ns0:VirtualMachineConfigSpec')
spec = self._create_spec_for_disk_remove(session, disk_device)
reconfig_spec.deviceChange = [spec]
self._reconfigure_backing(session, backing, reconfig_spec)
def _attach_disk_to_backing(self, session, backing, disk_device):
LOG.debug("Reconfiguring backing VM: %(backing)s to add disk: "
"%(disk_device)s.",
{'backing': backing, 'disk_device': disk_device})
cf = session.vim.client.factory
reconfig_spec = cf.create('ns0:VirtualMachineConfigSpec')
disk_spec = cf.create('ns0:VirtualDeviceConfigSpec')
disk_spec.operation = 'add'
disk_spec.device = disk_device
reconfig_spec.deviceChange = [disk_spec]
self._reconfigure_backing(session, backing, reconfig_spec)
def _disconnect(
self, backing, tmp_file_path, session, ds_ref, dc_ref, vmdk_path):
# The restored volume is in compressed (streamOptimized) format.
# So we upload it to a temporary location in vCenter datastore and copy
# the compressed vmdk to the volume vmdk. The copy operation
@ -212,20 +269,13 @@ class VmdkConnector(initiator_connector.InitiatorConnector):
ds_path.rel_path, os.path.getsize(tmp_file_path), cacerts,
self._timeout)
# Delete the current volume vmdk because the copy operation does not
# overwrite.
LOG.debug("Deleting %s", vmdk_path)
disk_mgr = session.vim.service_content.virtualDiskManager
task = session.invoke_api(session.vim,
'DeleteVirtualDisk_Task',
disk_mgr,
name=vmdk_path,
datacenter=dc_ref)
session.wait_for_task(task)
disk_device = self._get_disk_device(session, backing)
self._detach_disk_from_backing(session, backing, disk_device)
src = six.text_type(ds_path)
LOG.debug("Copying %(src)s to %(dest)s", {'src': src,
'dest': vmdk_path})
disk_mgr = session.vim.service_content.virtualDiskManager
task = session.invoke_api(session.vim,
'CopyVirtualDisk_Task',
disk_mgr,
@ -235,6 +285,8 @@ class VmdkConnector(initiator_connector.InitiatorConnector):
destDatacenter=dc_ref)
session.wait_for_task(task)
self._attach_disk_to_backing(session, backing, disk_device)
# Delete the compressed vmdk at the temporary location.
LOG.debug("Deleting %s", src)
file_mgr = session.vim.service_content.fileManager
@ -275,7 +327,7 @@ class VmdkConnector(initiator_connector.InitiatorConnector):
connection_properties['datacenter'], "Datacenter")
vmdk_path = connection_properties['vmdk_path']
self._disconnect(
tmp_file_path, session, ds_ref, dc_ref, vmdk_path)
backing, tmp_file_path, session, ds_ref, dc_ref, vmdk_path)
finally:
os.remove(tmp_file_path)
if session:

View File

@ -221,9 +221,13 @@ class VmdkConnectorTestCase(test_connector.ConnectorTestCase):
@mock.patch('os_brick.initiator.connectors.vmware.open', create=True)
@mock.patch.object(VMDK_CONNECTOR, '_upload_vmdk')
@mock.patch('os.path.getsize')
@mock.patch.object(VMDK_CONNECTOR, '_get_disk_device')
@mock.patch.object(VMDK_CONNECTOR, '_detach_disk_from_backing')
@mock.patch.object(VMDK_CONNECTOR, '_attach_disk_to_backing')
def test_disconnect(
self, getsize, upload_vmdk, file_open, create_temp_ds_folder,
get_ds_by_ref):
self, attach_disk_to_backing, detach_disk_from_backing,
get_disk_device, getsize, upload_vmdk, file_open,
create_temp_ds_folder, get_ds_by_ref):
ds_ref = mock.sentinel.ds_ref
ds_name = 'datastore-1'
dstore = datastore.Datastore(ds_ref, ds_name)
@ -236,20 +240,21 @@ class VmdkConnectorTestCase(test_connector.ConnectorTestCase):
file_open.return_value = file_open_ret
dc_name = mock.sentinel.dc_name
delete_task = mock.sentinel.delete_vdisk_task
copy_task = mock.sentinel.copy_vdisk_task
delete_file_task = mock.sentinel.delete_file_task
session = mock.Mock()
session.invoke_api.side_effect = [
dc_name, delete_task, copy_task, delete_file_task]
session.invoke_api.side_effect = [dc_name, copy_task, delete_file_task]
getsize.return_value = units.Gi
disk_device = mock.sentinel.disk_device
get_disk_device.return_value = disk_device
backing = mock.sentinel.backing
tmp_file_path = '/tmp/foo.vmdk'
dc_ref = mock.sentinel.dc_ref
vmdk_path = mock.sentinel.vmdk_path
self._connector._disconnect(
tmp_file_path, session, ds_ref, dc_ref, vmdk_path)
backing, tmp_file_path, session, ds_ref, dc_ref, vmdk_path)
tmp_folder_path = self._connector.TMP_IMAGES_DATASTORE_FOLDER_PATH
ds_folder_path = '[%s] %s' % (ds_name, tmp_folder_path)
@ -268,30 +273,30 @@ class VmdkConnectorTestCase(test_connector.ConnectorTestCase):
exp_rel_path, units.Gi, self._connector._ca_file,
self._connector._timeout)
disk_mgr = session.vim.service_content.virtualDiskManager
self.assertEqual(
mock.call(session.vim, 'DeleteVirtualDisk_Task', disk_mgr,
name=vmdk_path, datacenter=dc_ref),
session.invoke_api.call_args_list[1])
self.assertEqual(mock.call(delete_task),
session.wait_for_task.call_args_list[0])
get_disk_device.assert_called_once_with(session, backing)
detach_disk_from_backing.assert_called_once_with(
session, backing, disk_device)
src = '[%s] %s' % (ds_name, exp_rel_path)
disk_mgr = session.vim.service_content.virtualDiskManager
self.assertEqual(
mock.call(session.vim, 'CopyVirtualDisk_Task', disk_mgr,
sourceName=src, sourceDatacenter=dc_ref,
destName=vmdk_path, destDatacenter=dc_ref),
session.invoke_api.call_args_list[2])
session.invoke_api.call_args_list[1])
self.assertEqual(mock.call(copy_task),
session.wait_for_task.call_args_list[1])
session.wait_for_task.call_args_list[0])
attach_disk_to_backing.assert_called_once_with(
session, backing, disk_device)
file_mgr = session.vim.service_content.fileManager
self.assertEqual(
mock.call(session.vim, 'DeleteDatastoreFile_Task', file_mgr,
name=src, datacenter=dc_ref),
session.invoke_api.call_args_list[3])
session.invoke_api.call_args_list[2])
self.assertEqual(mock.call(delete_file_task),
session.wait_for_task.call_args_list[2])
session.wait_for_task.call_args_list[1])
@mock.patch('os.path.exists')
def test_disconnect_volume_with_missing_temp_file(self, path_exists):
@ -361,6 +366,80 @@ class VmdkConnectorTestCase(test_connector.ConnectorTestCase):
create_session.assert_called_once_with()
snapshot_exists.assert_called_once_with(session, backing)
disconnect.assert_called_once_with(
path, session, ds_ref, dc_ref, props['vmdk_path'])
backing, path, session, ds_ref, dc_ref, props['vmdk_path'])
remove.assert_called_once_with(path)
session.logout.assert_called_once_with()
def test_get_disk_device(self):
disk_device = mock.Mock()
disk_device.__class__.__name__ = 'VirtualDisk'
controller_device = mock.Mock()
controller_device.__class__.__name__ = 'VirtualLSILogicController'
devices = mock.Mock()
devices.__class__.__name__ = "ArrayOfVirtualDevice"
devices.VirtualDevice = [disk_device, controller_device]
session = mock.Mock()
session.invoke_api.return_value = devices
backing = mock.sentinel.backing
self.assertEqual(disk_device,
self._connector._get_disk_device(session, backing))
session.invoke_api.assert_called_once_with(
vim_util, 'get_object_property', session.vim,
backing, 'config.hardware.device')
def test_create_spec_for_disk_remove(self):
disk_spec = mock.Mock()
session = mock.Mock()
session.vim.client.factory.create.return_value = disk_spec
disk_device = mock.sentinel.disk_device
self._connector._create_spec_for_disk_remove(session, disk_device)
session.vim.client.factory.create.assert_called_once_with(
'ns0:VirtualDeviceConfigSpec')
self.assertEqual('remove', disk_spec.operation)
self.assertEqual('destroy', disk_spec.fileOperation)
self.assertEqual(disk_device, disk_spec.device)
@mock.patch.object(VMDK_CONNECTOR, '_create_spec_for_disk_remove')
@mock.patch.object(VMDK_CONNECTOR, '_reconfigure_backing')
def test_detach_disk_from_backing(self, reconfigure_backing, create_spec):
disk_spec = mock.sentinel.disk_spec
create_spec.return_value = disk_spec
reconfig_spec = mock.Mock()
session = mock.Mock()
session.vim.client.factory.create.return_value = reconfig_spec
backing = mock.sentinel.backing
disk_device = mock.sentinel.disk_device
self._connector._detach_disk_from_backing(
session, backing, disk_device)
create_spec.assert_called_once_with(session, disk_device)
session.vim.client.factory.create.assert_called_once_with(
'ns0:VirtualMachineConfigSpec')
self.assertEqual([disk_spec], reconfig_spec.deviceChange)
reconfigure_backing.assert_called_once_with(
session, backing, reconfig_spec)
@mock.patch.object(VMDK_CONNECTOR, '_reconfigure_backing')
def test_attach_disk_to_backing(self, reconfigure_backing):
reconfig_spec = mock.Mock()
disk_spec = mock.Mock()
session = mock.Mock()
session.vim.client.factory.create.side_effect = [
reconfig_spec, disk_spec]
backing = mock.Mock()
disk_device = mock.sentinel.disk_device
self._connector._attach_disk_to_backing(session, backing, disk_device)
self.assertEqual([disk_spec], reconfig_spec.deviceChange)
self.assertEqual('add', disk_spec.operation)
self.assertEqual(disk_device, disk_spec.device)
reconfigure_backing.assert_called_once_with(
session, backing, reconfig_spec)