VMware: Add support for vStorageObject snapshots

Currently, we clone vStorageObject to create snapshot. vSphere
6.7 added support for vStorageObject snapshot APIs. Updating
the vStorageObject driver to use these APIs. Older snapshots
will continue to work with the latest version of the driver.

Change-Id: Ie080c181c5ecce52f3d8560cff3c1fcb9a7d9c30
This commit is contained in:
Vipin Balachandran 2018-09-07 14:18:33 -07:00
parent 16cde62009
commit 85c13f73f2
5 changed files with 315 additions and 22 deletions

View File

@ -44,8 +44,10 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
IMG_TX_TIMEOUT = 10
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
@ -62,6 +64,7 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
self._config.vmware_image_transfer_timeout_secs = self.IMG_TX_TIMEOUT
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')
@ -71,6 +74,7 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
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)
def test_get_volume_stats(self):
stats = self._driver.get_volume_stats()
@ -420,31 +424,86 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
@mock.patch.object(FCD_DRIVER, '_select_ds_fcd')
@mock.patch.object(FCD_DRIVER, '_clone_fcd')
def test_create_snapshot(self, clone_fcd, select_ds_fcd):
ds_ref = mock.sentinel.ds_ref
select_ds_fcd.return_value = ds_ref
@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
dest_fcd_loc = mock.Mock()
provider_location = mock.sentinel.provider_location
dest_fcd_loc.provider_location.return_value = provider_location
clone_fcd.return_value = dest_fcd_loc
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)
select_ds_fcd.assert_called_once_with(snapshot.volume)
clone_fcd.assert_called_once_with(
volume.provider_location, snapshot.name, ds_ref)
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')
def test_delete_snapshot(self, 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)
delete_fcd.assert_called_once_with(snapshot.provider_location)
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))
@ -489,14 +548,44 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
cloned_fcd_loc, cur_size, volume.size)
@mock.patch.object(FCD_DRIVER, '_create_volume_from_fcd')
def test_create_volume_from_snapshot(self, create_volume_from_fcd):
src_volume = self._create_volume_obj()
@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 = mock.sentinel.volume
self._driver.create_volume_from_snapshot(volume, snapshot)
create_volume_from_fcd.assert_called_once_with(
snapshot.provider_location, snapshot.volume.size, 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):

View File

@ -2085,7 +2085,7 @@ class VolumeOpsTestCase(test.TestCase):
fcd_location = mock.Mock()
fcd_id = mock.sentinel.fcd_id
fcd_location.id.return_value = fcd_id
ds_ref = mock.sentinel.ds_ref
ds_ref = mock.Mock()
fcd_location.ds_ref.return_value = ds_ref
self.vops.attach_fcd(backing, fcd_location)
@ -2120,6 +2120,92 @@ class VolumeOpsTestCase(test.TestCase):
diskId=fcd_id)
self.session.wait_for_task.assert_called_once_with(task)
def test_create_fcd_snapshot(self):
task = mock.sentinel.task
self.session.invoke_api.return_value = task
task_info = mock.Mock()
fcd_snap_id = mock.sentinel.fcd_snap_id
task_info.result.id = fcd_snap_id
self.session.wait_for_task.return_value = task_info
fcd_location = mock.Mock()
fcd_id = mock.sentinel.fcd_id
fcd_location.id.return_value = fcd_id
ds_ref = mock.Mock()
fcd_location.ds_ref.return_value = ds_ref
description = mock.sentinel.description
ret = self.vops.create_fcd_snapshot(fcd_location, description)
self.assertEqual(fcd_snap_id, ret.snap_id)
self.assertEqual(fcd_location, ret.fcd_loc)
self.session.invoke_api.assert_called_once_with(
self.session.vim,
'VStorageObjectCreateSnapshot_Task',
self.session.vim.service_content.vStorageObjectManager,
id=fcd_id,
datastore=ds_ref,
description=description)
self.session.wait_for_task.assert_called_once_with(task)
def test_delete_fcd_snapshot(self):
task = mock.sentinel.task
self.session.invoke_api.return_value = task
fcd_location = mock.Mock()
fcd_id = mock.sentinel.fcd_id
fcd_location.id.return_value = fcd_id
ds_ref = mock.Mock()
fcd_location.ds_ref.return_value = ds_ref
fcd_snap_id = mock.sentinel.fcd_snap_id
fcd_snap_loc = mock.Mock(fcd_loc=fcd_location)
fcd_snap_loc.id.return_value = fcd_snap_id
self.vops.delete_fcd_snapshot(fcd_snap_loc)
self.session.invoke_api.assert_called_once_with(
self.session.vim,
'DeleteSnapshot_Task',
self.session.vim.service_content.vStorageObjectManager,
id=fcd_id,
datastore=ds_ref,
snapshotId=fcd_snap_id)
self.session.wait_for_task(task)
def test_create_fcd_from_snapshot(self):
task = mock.sentinel.task
self.session.invoke_api.return_value = task
task_info = mock.Mock()
fcd_id = mock.sentinel.fcd_id
task_info.result.config.id.id = fcd_id
self.session.wait_for_task.return_value = task_info
fcd_location = mock.Mock()
fcd_id = mock.sentinel.fcd_id
fcd_location.id.return_value = fcd_id
ds_ref_val = mock.sentinel.ds_ref_val
ds_ref = mock.Mock(value=ds_ref_val)
fcd_location.ds_ref.return_value = ds_ref
fcd_snap_id = mock.sentinel.fcd_snap_id
fcd_snap_loc = mock.Mock(fcd_loc=fcd_location)
fcd_snap_loc.id.return_value = fcd_snap_id
name = mock.sentinel.name
ret = self.vops.create_fcd_from_snapshot(fcd_snap_loc, name)
self.assertEqual(fcd_id, ret.fcd_id)
self.assertEqual(ds_ref_val, ret.ds_ref_val)
self.session.invoke_api.assert_called_once_with(
self.session.vim,
'CreateDiskFromSnapshot_Task',
self.session.vim.service_content.vStorageObjectManager,
id=fcd_id,
datastore=ds_ref,
snapshotId=fcd_snap_id,
name=name)
self.session.wait_for_task.assert_called_once_with(task)
class VirtualDiskPathTest(test.TestCase):
"""Unit tests for VirtualDiskPath."""

View File

@ -22,6 +22,7 @@ driver requires a minimum vCenter version of 6.5.
from oslo_log import log as logging
from oslo_utils import units
from oslo_utils import versionutils
from oslo_vmware import image_transfer
from oslo_vmware.objects import datastore
from oslo_vmware import vim_util
@ -42,7 +43,8 @@ class VMwareVStorageObjectDriver(vmdk.VMwareVcVmdkDriver):
"""Volume driver based on VMware VStorageObject"""
# 1.0 - initial version based on vSphere 6.5 vStorageObject APIs
VERSION = '1.0.0'
# 1.1 - support for vStorageObject snapshot APIs
VERSION = '1.1.0'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "VMware_CI"
@ -60,6 +62,8 @@ class VMwareVStorageObjectDriver(vmdk.VMwareVcVmdkDriver):
super(VMwareVStorageObjectDriver, self).do_setup(context)
self._storage_policy_enabled = False
self.volumeops.set_vmx_version('vmx-13')
self._use_fcd_snapshot = versionutils.is_compatible(
'6.7.0', self._vc_version, same_major=False)
def get_volume_stats(self, refresh=False):
"""Collects volume backend stats.
@ -269,6 +273,14 @@ class VMwareVStorageObjectDriver(vmdk.VMwareVcVmdkDriver):
:param snapshot: Information for the snapshot to be created.
"""
if self._use_fcd_snapshot:
fcd_loc = vops.FcdLocation.from_provider_location(
snapshot.volume.provider_location)
description = "snapshot-%s" % snapshot.id
fcd_snap_loc = self.volumeops.create_fcd_snapshot(
fcd_loc, description=description)
return {'provider_location': fcd_snap_loc.provider_location()}
ds_ref = self._select_ds_fcd(snapshot.volume)
cloned_fcd_loc = self._clone_fcd(
snapshot.volume.provider_location, snapshot.name, ds_ref)
@ -279,7 +291,16 @@ class VMwareVStorageObjectDriver(vmdk.VMwareVcVmdkDriver):
:param snapshot: The snapshot to delete.
"""
self._delete_fcd(snapshot.provider_location)
if not snapshot.provider_location:
LOG.debug("FCD snapshot location is empty.")
return
fcd_snap_loc = vops.FcdSnapshotLocation.from_provider_location(
snapshot.provider_location)
if fcd_snap_loc:
self.volumeops.delete_fcd_snapshot(fcd_snap_loc)
else:
self._delete_fcd(snapshot.provider_location)
def _extend_if_needed(self, fcd_loc, cur_size, new_size):
if new_size > cur_size:
@ -300,8 +321,16 @@ class VMwareVStorageObjectDriver(vmdk.VMwareVcVmdkDriver):
:param snapshot: The snapshot from which to create the volume.
:returns: A dict of database updates for the new volume.
"""
return self._create_volume_from_fcd(
snapshot.provider_location, snapshot.volume.size, volume)
fcd_snap_loc = vops.FcdSnapshotLocation.from_provider_location(
snapshot.provider_location)
if fcd_snap_loc:
fcd_loc = self.volumeops.create_fcd_from_snapshot(
fcd_snap_loc, volume.name)
self._extend_if_needed(fcd_loc, snapshot.volume_size, volume.size)
return {'provider_location': fcd_loc.provider_location()}
else:
return self._create_volume_from_fcd(snapshot.provider_location,
snapshot.volume.size, volume)
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume.

View File

@ -17,6 +17,8 @@
Implements operations on volumes residing on VMware datastores.
"""
import json
from oslo_log import log as logging
from oslo_utils import units
from oslo_vmware import exceptions
@ -1888,6 +1890,58 @@ class VMwareVolumeOps(object):
diskId=fcd_location.id(cf))
self._session.wait_for_task(task)
def create_fcd_snapshot(self, fcd_location, description):
LOG.debug("Creating fcd snapshot for %s.", fcd_location)
vstorage_mgr = self._session.vim.service_content.vStorageObjectManager
cf = self._session.vim.client.factory
task = self._session.invoke_api(self._session.vim,
'VStorageObjectCreateSnapshot_Task',
vstorage_mgr,
id=fcd_location.id(cf),
datastore=fcd_location.ds_ref(),
description=description)
task_info = self._session.wait_for_task(task)
fcd_snap_loc = FcdSnapshotLocation(fcd_location, task_info.result.id)
LOG.debug("Created fcd snapshot: %s.", fcd_snap_loc)
return fcd_snap_loc
def delete_fcd_snapshot(self, fcd_snap_loc):
LOG.debug("Deleting fcd snapshot: %s.", fcd_snap_loc)
vstorage_mgr = self._session.vim.service_content.vStorageObjectManager
cf = self._session.vim.client.factory
task = self._session.invoke_api(
self._session.vim,
'DeleteSnapshot_Task',
vstorage_mgr,
id=fcd_snap_loc.fcd_loc.id(cf),
datastore=fcd_snap_loc.fcd_loc.ds_ref(),
snapshotId=fcd_snap_loc.id(cf))
self._session.wait_for_task(task)
def create_fcd_from_snapshot(self, fcd_snap_loc, name):
LOG.debug("Creating fcd with name: %(name)s from fcd snapshot: "
"%(snap)s.", {'name': name, 'snap': fcd_snap_loc})
vstorage_mgr = self._session.vim.service_content.vStorageObjectManager
cf = self._session.vim.client.factory
task = self._session.invoke_api(
self._session.vim,
'CreateDiskFromSnapshot_Task',
vstorage_mgr,
id=fcd_snap_loc.fcd_loc.id(cf),
datastore=fcd_snap_loc.fcd_loc.ds_ref(),
snapshotId=fcd_snap_loc.id(cf),
name=name)
task_info = self._session.wait_for_task(task)
fcd_loc = FcdLocation.create(task_info.result.config.id,
fcd_snap_loc.fcd_loc.ds_ref())
LOG.debug("Created fcd: %s.", fcd_loc)
return fcd_loc
class FcdLocation(object):
@ -1917,3 +1971,32 @@ class FcdLocation(object):
def __str__(self):
return self.provider_location()
class FcdSnapshotLocation(object):
def __init__(self, fcd_location, snapshot_id):
self.fcd_loc = fcd_location
self.snap_id = snapshot_id
def provider_location(self):
loc = {"fcd_location": self.fcd_loc.provider_location(),
"fcd_snapshot_id": self.snap_id}
return json.dumps(loc)
def id(self, cf):
id_obj = cf.create('ns0:ID')
id_obj.id = self.snap_id
return id_obj
@classmethod
def from_provider_location(cls, provider_location):
try:
loc = json.loads(provider_location)
fcd_loc = FcdLocation.from_provider_location(loc['fcd_location'])
return cls(fcd_loc, loc['fcd_snapshot_id'])
except ValueError:
pass
def __str__(self):
return self.provider_location()

View File

@ -0,0 +1,6 @@
---
features:
- |
vSphere 6.7 added support for vStorageObject snapshots. The
VMwareVStorageObjectDriver is updated to use VStorageObject
snapshots for volume snapshots.