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:
parent
16cde62009
commit
85c13f73f2
@ -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):
|
||||
|
@ -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."""
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
vSphere 6.7 added support for vStorageObject snapshots. The
|
||||
VMwareVStorageObjectDriver is updated to use VStorageObject
|
||||
snapshots for volume snapshots.
|
Loading…
x
Reference in New Issue
Block a user