VMware: Use vSphere template as snapshot format

Adding support for using vSphere template as a volume
snapshot format in vCenter server. The current format
(COW disk) does not allow snapshot of attached volumes
and this limitation is removed by the new template
format.

Change-Id: Id5dd71a785c4cd72ba44f9b4d26319be53079c39
This commit is contained in:
Vipin Balachandran 2017-09-05 15:00:28 -07:00
parent 9495ef6aa6
commit f36fc23980
6 changed files with 497 additions and 76 deletions

View File

@ -61,6 +61,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
CLUSTERS = ["cls-1", "cls-2"]
DEFAULT_VC_VERSION = '5.5'
POOL_SIZE = 20
SNAPSHOT_FORMAT = 'COW'
VOL_ID = 'abcdefab-cdef-abcd-efab-cdefabcdefab'
SRC_VOL_ID = '9b3f6f1b-03a9-4f1e-aaff-ae15122b6ccf'
@ -96,6 +97,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self._config.vmware_host_version = self.DEFAULT_VC_VERSION
self._config.vmware_connection_pool_size = self.POOL_SIZE
self._config.vmware_adapter_type = self.ADAPTER_TYPE
self._config.vmware_snapshot_format = self.SNAPSHOT_FORMAT
self._db = mock.Mock()
self._driver = vmdk.VMwareVcVmdkDriver(configuration=self._config,
@ -254,45 +256,197 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
volume,
snap_id=SNAPSHOT_ID,
name=SNAPSHOT_NAME,
description=SNAPSHOT_DESCRIPTION):
description=SNAPSHOT_DESCRIPTION,
provider_location=None):
return {'id': snap_id,
'volume': volume,
'volume_name': volume['name'],
'name': name,
'display_description': description,
'volume_size': volume['size']
'volume_size': volume['size'],
'provider_location': provider_location
}
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_create_snapshot_without_backing(self, vops):
@mock.patch.object(VMDK_DRIVER, '_get_volume_group_folder')
def test_get_snapshot_group_folder(self, get_volume_group_folder, vops):
dc = mock.sentinel.dc
vops.get_dc.return_value = dc
folder = mock.sentinel.folder
get_volume_group_folder.return_value = folder
volume = self._create_volume_obj()
backing = mock.sentinel.backing
self.assertEqual(folder, self._driver._get_snapshot_group_folder(
volume, backing))
vops.get_dc.assert_called_once_with(backing)
get_volume_group_folder.assert_called_once_with(
dc, volume.project_id, snapshot=True)
@mock.patch.object(VMDK_DRIVER, '_get_snapshot_group_folder')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_in_use')
@mock.patch.object(VMDK_DRIVER, '_create_temp_backing_from_attached_vmdk')
@mock.patch.object(VMDK_DRIVER, '_delete_temp_backing')
def _test_create_snapshot_template_format(
self, delete_temp_backing, create_temp_backing_from_attached_vmdk,
in_use, vops, get_snapshot_group_folder, attached=False,
mark_as_template_error=False):
folder = mock.sentinel.folder
get_snapshot_group_folder.return_value = folder
datastore = mock.sentinel.datastore
vops.get_datastore.return_value = datastore
tmp_backing = mock.sentinel.tmp_backing
if attached:
in_use.return_value = True
create_temp_backing_from_attached_vmdk.return_value = tmp_backing
else:
in_use.return_value = False
vops.clone_backing.return_value = tmp_backing
if mark_as_template_error:
vops.mark_backing_as_template.side_effect = (
exceptions.VimException())
else:
inv_path = mock.sentinel.inv_path
vops.get_inventory_path.return_value = inv_path
volume = self._create_volume_obj()
snapshot = fake_snapshot.fake_snapshot_obj(
self._context, volume=volume)
backing = mock.sentinel.backing
if mark_as_template_error:
self.assertRaises(
exceptions.VimException,
self._driver._create_snapshot_template_format,
snapshot,
backing)
delete_temp_backing.assert_called_once_with(tmp_backing)
else:
exp_result = {'provider_location': inv_path}
self.assertEqual(exp_result,
self._driver._create_snapshot_template_format(
snapshot, backing))
delete_temp_backing.assert_not_called()
get_snapshot_group_folder.test_assert_called_once_with(volume, backing)
vops.get_datastore.assert_called_once_with(backing)
in_use.assert_called_once_with(snapshot.volume)
if attached:
create_temp_backing_from_attached_vmdk.assert_called_once_with(
snapshot.volume, None, None, folder, datastore,
tmp_name=snapshot.name)
else:
vops.clone_backing.assert_called_once_with(
snapshot.name, backing, None, volumeops.FULL_CLONE_TYPE,
datastore, folder=folder)
vops.mark_backing_as_template.assert_called_once_with(tmp_backing)
def test_create_snapshot_template_format(self):
self._test_create_snapshot_template_format()
def test_create_snapshot_template_format_force(self):
self._test_create_snapshot_template_format(attached=True)
def test_create_snapshot_template_format_mark_template_error(self):
self._test_create_snapshot_template_format(mark_as_template_error=True)
@mock.patch.object(VMDK_DRIVER, '_in_use', return_value=False)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_create_snapshot_without_backing(self, vops, in_use):
vops.get_backing.return_value = None
volume = self._create_volume_dict()
snapshot = self._create_snapshot_dict(volume)
self._driver.create_snapshot(snapshot)
ret = self._driver.create_snapshot(snapshot)
self.assertIsNone(ret)
vops.get_backing.assert_called_once_with(snapshot['volume_name'])
self.assertFalse(vops.create_snapshot.called)
@mock.patch.object(VMDK_DRIVER, '_in_use', return_value=False)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_create_snapshot_with_backing(self, vops):
def test_create_snapshot_with_backing(self, vops, in_use):
backing = mock.sentinel.backing
vops.get_backing.return_value = backing
volume = self._create_volume_dict()
snapshot = self._create_snapshot_dict(volume)
self._driver.create_snapshot(snapshot)
ret = self._driver.create_snapshot(snapshot)
self.assertIsNone(ret)
vops.get_backing.assert_called_once_with(snapshot['volume_name'])
vops.create_snapshot.assert_called_once_with(
backing, snapshot['name'], snapshot['display_description'])
def test_create_snapshot_when_attached(self):
@mock.patch.object(VMDK_DRIVER, '_in_use', return_value=True)
def test_create_snapshot_when_attached(self, in_use):
volume = self._create_volume_dict(status='in-use')
snapshot = self._create_snapshot_dict(volume)
self.assertRaises(cinder_exceptions.InvalidVolume,
self._driver.create_snapshot, snapshot)
@mock.patch.object(VMDK_DRIVER, '_in_use', return_value=True)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_create_snapshot_template_format')
def test_create_snapshot_template(
self, create_snapshot_template_format, vops, in_use):
self._driver.configuration.vmware_snapshot_format = 'template'
backing = mock.sentinel.backing
vops.get_backing.return_value = backing
model_update = mock.sentinel.model_update
create_snapshot_template_format.return_value = model_update
volume = self._create_volume_dict()
snapshot = self._create_snapshot_dict(volume)
ret = self._driver.create_snapshot(snapshot)
self.assertEqual(model_update, ret)
vops.get_backing.assert_called_once_with(snapshot['volume_name'])
create_snapshot_template_format.assert_called_once_with(
snapshot, backing)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_get_template_by_inv_path(self, vops):
template = mock.sentinel.template
vops.get_entity_by_inventory_path.return_value = template
inv_path = mock.sentinel.inv_path
self.assertEqual(template,
self._driver._get_template_by_inv_path(inv_path))
vops.get_entity_by_inventory_path.assert_called_once_with(inv_path)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_get_template_by_inv_path_invalid_path(self, vops):
vops.get_entity_by_inventory_path.return_value = None
inv_path = mock.sentinel.inv_path
self.assertRaises(vmdk_exceptions.TemplateNotFoundException,
self._driver._get_template_by_inv_path,
inv_path)
vops.get_entity_by_inventory_path.assert_called_once_with(inv_path)
@mock.patch.object(VMDK_DRIVER, '_get_template_by_inv_path')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_delete_snapshot_template_format(
self, vops, get_template_by_inv_path):
template = mock.sentinel.template
get_template_by_inv_path.return_value = template
inv_path = '/dc-1/vm/foo'
volume = self._create_volume_dict()
snapshot = fake_snapshot.fake_snapshot_obj(self._context,
volume=volume,
provider_location=inv_path)
self._driver._delete_snapshot_template_format(snapshot)
get_template_by_inv_path.assert_called_once_with(inv_path)
vops.delete_backing.assert_called_once_with(template)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_delete_snapshot_without_backing(self, vops):
vops.get_backing.return_value = None
@ -350,6 +504,26 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
vops.get_snapshot.assert_called_once_with(backing, snapshot.name)
vops.delete_snapshot.assert_not_called()
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_in_use', return_value=True)
@mock.patch.object(VMDK_DRIVER, '_delete_snapshot_template_format')
def test_delete_snapshot_template(
self, delete_snapshot_template_format, in_use, vops):
backing = mock.sentinel.backing
vops.get_backing.return_value = backing
inv_path = '/dc-1/vm/foo'
volume = self._create_volume_dict(status='deleting')
snapshot = fake_snapshot.fake_snapshot_obj(self._context,
volume=volume,
provider_location=inv_path)
self._driver.delete_snapshot(snapshot)
vops.get_backing.assert_called_once_with(snapshot.volume_name)
vops.get_snapshot.assert_not_called()
in_use.assert_called_once_with(snapshot.volume)
delete_snapshot_template_format.assert_called_once_with(snapshot)
@ddt.data('vmdk', 'VMDK', None)
def test_validate_disk_format(self, disk_format):
self._driver._validate_disk_format(disk_format)
@ -1605,18 +1779,29 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self._test_initialize_connection(instance_exists=False)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
def test_get_volume_group_folder(self, vops):
def _test_get_volume_group_folder(self, vops, snapshot=False):
folder = mock.sentinel.folder
vops.create_vm_inventory_folder.return_value = folder
datacenter = mock.sentinel.dc
project_id = '63c19a12292549818c09946a5e59ddaf'
self.assertEqual(folder,
self._driver._get_volume_group_folder(datacenter,
project_id))
self._driver._get_volume_group_folder(
datacenter, project_id, snapshot=snapshot))
project_folder_name = 'Project (%s)' % project_id
exp_folder_names = ['OpenStack',
project_folder_name,
self.VOLUME_FOLDER]
if snapshot:
exp_folder_names.append('Snapshots')
vops.create_vm_inventory_folder.assert_called_once_with(
datacenter, ['OpenStack', project_folder_name, self.VOLUME_FOLDER])
datacenter, exp_folder_names)
def test_get_volume_group_folder(self):
self._test_get_volume_group_folder()
def test_get_volume_group_folder_for_snapshot(self):
self._test_get_volume_group_folder(snapshot=True)
@mock.patch('cinder.volume.drivers.vmware.vmdk.'
'_get_volume_type_extra_spec')
@ -1725,6 +1910,53 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
self._test_clone_backing(
clone_type=volumeops.LINKED_CLONE_TYPE, vc60=True)
@mock.patch.object(VMDK_DRIVER, '_get_template_by_inv_path')
@mock.patch('oslo_utils.uuidutils.generate_uuid')
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_get_disk_type')
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_create_volume_from_temp_backing')
def test_create_volume_from_template(
self, create_volume_from_temp_backing, vops, get_disk_type,
select_ds_for_volume, generate_uuid, get_template_by_inv_path):
template = mock.sentinel.template
get_template_by_inv_path.return_value = template
tmp_name = 'de4c648c-8403-4dcc-b14a-d2541b7cba2b'
generate_uuid.return_value = tmp_name
host = mock.sentinel.host
rp = mock.sentinel.rp
folder = mock.sentinel.folder
datastore = mock.sentinel.datastore
summary = mock.Mock(datastore=datastore)
select_ds_for_volume.return_value = (host, rp, folder, summary)
disk_type = mock.sentinel.disk_type
get_disk_type.return_value = disk_type
tmp_backing = mock.sentinel.tmp_backing
vops.clone_backing.return_value = tmp_backing
volume = self._create_volume_obj()
inv_path = mock.sentinel.inv_path
self._driver._create_volume_from_template(volume, inv_path)
get_template_by_inv_path.assert_called_once_with(inv_path)
select_ds_for_volume.assert_called_once_with(volume)
get_disk_type.assert_called_once_with(volume)
vops.clone_backing.assert_called_once_with(tmp_name,
template,
None,
volumeops.FULL_CLONE_TYPE,
datastore,
disk_type=disk_type,
host=host,
resource_pool=rp,
folder=folder)
create_volume_from_temp_backing.assert_called_once_with(volume,
tmp_backing)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
def test_create_volume_from_snapshot_without_backing(self, clone_backing,
@ -1759,9 +1991,11 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_get_clone_type')
@mock.patch.object(VMDK_DRIVER, '_create_volume_from_template')
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
def test_create_volume_from_snapshot(self, clone_backing, get_clone_type,
vops):
def _test_create_volume_from_snapshot(
self, clone_backing, create_volume_from_template, get_clone_type,
vops, template=False):
backing = mock.sentinel.backing
vops.get_backing.return_value = backing
@ -1772,15 +2006,31 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
volume = self._create_volume_dict()
src_vref = self._create_volume_dict(vol_id=self.SRC_VOL_ID)
snapshot = self._create_snapshot_dict(src_vref)
if template:
provider_location = mock.sentinel.inv_path
else:
provider_location = None
snapshot = self._create_snapshot_dict(
src_vref, provider_location=provider_location)
self._driver.create_volume_from_snapshot(volume, snapshot)
vops.get_backing.assert_called_once_with(snapshot['volume_name'])
vops.get_snapshot.assert_called_once_with(backing, snapshot['name'])
get_clone_type.assert_called_once_with(volume)
clone_backing.assert_called_once_with(
volume, backing, snapshot_moref, volumeops.FULL_CLONE_TYPE,
snapshot['volume_size'])
if template:
create_volume_from_template.assert_called_once_with(
volume, mock.sentinel.inv_path)
else:
vops.get_snapshot.assert_called_once_with(backing,
snapshot['name'])
get_clone_type.assert_called_once_with(volume)
clone_backing.assert_called_once_with(
volume, backing, snapshot_moref, volumeops.FULL_CLONE_TYPE,
snapshot['volume_size'])
def test_create_volume_from_snapshot(self):
self._test_create_volume_from_snapshot()
def test_create_volume_from_snapshot_template(self):
self._test_create_volume_from_snapshot(template=True)
@mock.patch.object(VMDK_DRIVER, 'session')
def test_get_volume_device_uuid(self, session):
@ -1799,12 +2049,8 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_get_volume_device_uuid')
@mock.patch('oslo_utils.uuidutils.generate_uuid')
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_manage_existing_int')
@mock.patch.object(VMDK_DRIVER, '_delete_temp_backing')
def test_clone_attached_volume(
self, delete_temp_backing, manage_existing_int,
select_ds_for_volume, generate_uuid, get_volume_device_uuid, vops):
def test_create_temp_backing_from_attached_vmdk(
self, generate_uuid, get_volume_device_uuid, vops):
instance = mock.sentinel.instance
vops.get_backing_by_uuid.return_value = instance
@ -1814,6 +2060,53 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
tmp_name = mock.sentinel.tmp_name
generate_uuid.return_value = tmp_name
tmp_backing = mock.sentinel.tmp_backing
vops.clone_backing.return_value = tmp_backing
instance_uuid = fake_constants.INSTANCE_ID
attachment = fake_volume.fake_db_volume_attachment(
instance_uuid=instance_uuid)
src_vref = self._create_volume_dict(vol_id=fake_constants.VOLUME_ID,
attachment=[attachment])
host = mock.sentinel.host
rp = mock.sentinel.rp
folder = mock.sentinel.folder
datastore = mock.sentinel.datastore
ret = self._driver._create_temp_backing_from_attached_vmdk(
src_vref, host, rp, folder, datastore)
self.assertEqual(tmp_backing, ret)
vops.get_backing_by_uuid.assert_called_once_with(instance_uuid)
get_volume_device_uuid.assert_called_once_with(instance,
src_vref['id'])
vops.clone_backing.assert_called_once_with(
tmp_name, instance, None, volumeops.FULL_CLONE_TYPE, datastore,
host=host, resource_pool=rp, folder=folder,
disks_to_clone=[vol_dev_uuid])
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_manage_existing_int')
@mock.patch.object(VMDK_DRIVER, '_delete_temp_backing')
def test_create_volume_from_temp_backing(
self, delete_temp_backing, manage_existing_int, vops):
disk_device = mock.sentinel.disk_device
vops._get_disk_device.return_value = disk_device
volume = self._create_volume_dict()
tmp_backing = mock.sentinel.tmp_backing
self._driver._create_volume_from_temp_backing(volume, tmp_backing)
vops._get_disk_device.assert_called_once_with(tmp_backing)
manage_existing_int.assert_called_once_with(
volume, tmp_backing, disk_device)
delete_temp_backing.assert_called_once_with(tmp_backing)
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
@mock.patch.object(VMDK_DRIVER, '_create_temp_backing_from_attached_vmdk')
@mock.patch.object(VMDK_DRIVER, '_create_volume_from_temp_backing')
def test_clone_attached_volume(
self, create_volume_from_temp_backing,
create_temp_backing_from_attached_vmdk, select_ds_for_volume):
host = mock.sentinel.host
rp = mock.sentinel.rp
folder = mock.sentinel.folder
@ -1822,31 +2115,17 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
select_ds_for_volume.return_value = (host, rp, folder, summary)
tmp_backing = mock.sentinel.tmp_backing
vops.clone_backing.return_value = tmp_backing
create_temp_backing_from_attached_vmdk.return_value = tmp_backing
disk_device = mock.sentinel.disk_device
vops._get_disk_device.return_value = disk_device
instance_uuid = fake_constants.INSTANCE_ID
attachment = fake_volume.fake_db_volume_attachment(
instance_uuid=instance_uuid)
src_vref = self._create_volume_dict(vol_id=fake_constants.VOLUME_ID,
attachment=[attachment])
volume = self._create_volume_dict()
src_vref = mock.sentinel.src_vref
volume = mock.sentinel.volume
self._driver._clone_attached_volume(src_vref, volume)
vops.get_backing_by_uuid.assert_called_once_with(instance_uuid)
get_volume_device_uuid.assert_called_once_with(instance,
src_vref['id'])
select_ds_for_volume.assert_called_once_with(volume)
vops.clone_backing.assert_called_once_with(
tmp_name, instance, None, volumeops.FULL_CLONE_TYPE, datastore,
host=host, resource_pool=rp, folder=folder,
disks_to_clone=[vol_dev_uuid])
vops._get_disk_device.assert_called_once_with(tmp_backing)
manage_existing_int.assert_called_once_with(
volume, tmp_backing, disk_device)
delete_temp_backing.assert_called_once_with(tmp_backing)
create_temp_backing_from_attached_vmdk.assert_called_once_with(
src_vref, host, rp, folder, datastore)
create_volume_from_temp_backing.assert_called_once_with(
volume, tmp_backing)
@mock.patch.object(VMDK_DRIVER, 'volumeops')
@mock.patch.object(VMDK_DRIVER, '_clone_backing')

View File

@ -1689,6 +1689,16 @@ class VolumeOpsTestCase(test.TestCase):
self.session.vim.service_content.searchIndex,
inventoryPath=path)
def test_get_inventory_path(self):
path = mock.sentinel.path
self.session.invoke_api.return_value = path
entity = mock.sentinel.entity
self.assertEqual(path, self.vops.get_inventory_path(entity))
self.session.invoke_api.assert_called_once_with(
vim_util, 'get_inventory_path', self.session.vim, entity)
def test_get_disk_devices(self):
disk_device = mock.Mock()
disk_device.__class__.__name__ = 'VirtualDisk'
@ -1713,6 +1723,12 @@ class VolumeOpsTestCase(test.TestCase):
backing.uuid = uuid
return mock.Mock(backing=backing)
def test_mark_backing_as_template(self):
backing = mock.Mock()
self.vops.mark_backing_as_template(backing)
self.session.invoke_api.assert_called_once_with(
self.session.vim, 'MarkAsTemplate', backing)
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
'_get_disk_devices')
def test_get_disk_device(self, get_disk_devices):

View File

@ -55,3 +55,8 @@ class ClusterNotFoundException(exceptions.VMwareDriverException):
class NoValidHostException(exceptions.VMwareDriverException):
"""Thrown when there are no valid ESX hosts."""
msg_fmt = _("There are no valid ESX hosts.")
class TemplateNotFoundException(exceptions.VMwareDriverException):
"""Thrown when template cannot be found."""
msg_fmt = _("Template cannot be found at path: %(path)s.")

View File

@ -137,6 +137,10 @@ vmdk_opts = [
volumeops.VirtualDiskAdapterType.IDE],
default=volumeops.VirtualDiskAdapterType.LSI_LOGIC,
help='Default adapter type to be used for attaching volumes.'),
cfg.StrOpt('vmware_snapshot_format',
choices=['template', 'COW'],
default='template',
help='Volume snapshot format in vCenter server.'),
]
CONF = cfg.CONF
@ -658,6 +662,34 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
def remove_export(self, context, volume):
pass
def _get_snapshot_group_folder(self, volume, backing):
dc = self.volumeops.get_dc(backing)
return self._get_volume_group_folder(
dc, volume.project_id, snapshot=True)
def _create_snapshot_template_format(self, snapshot, backing):
volume = snapshot.volume
folder = self._get_snapshot_group_folder(volume, backing)
datastore = self.volumeops.get_datastore(backing)
if self._in_use(volume):
tmp_backing = self._create_temp_backing_from_attached_vmdk(
volume, None, None, folder, datastore, tmp_name=snapshot.name)
else:
tmp_backing = self.volumeops.clone_backing(
snapshot.name, backing, None, volumeops.FULL_CLONE_TYPE,
datastore, folder=folder)
try:
self.volumeops.mark_backing_as_template(tmp_backing)
except exceptions.VimException:
with excutils.save_and_reraise_exception():
LOG.error("Error marking temporary backing as template.")
self._delete_temp_backing(tmp_backing)
return {'provider_location':
self.volumeops.get_inventory_path(tmp_backing)}
def _create_snapshot(self, snapshot):
"""Creates a snapshot.
@ -669,49 +701,78 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
"""
volume = snapshot['volume']
if volume['status'] != 'available':
snapshot_format = self.configuration.vmware_snapshot_format
if self._in_use(volume) and snapshot_format == 'COW':
msg = _("Snapshot of volume not supported in "
"state: %s.") % volume['status']
LOG.error(msg)
raise exception.InvalidVolume(msg)
backing = self.volumeops.get_backing(snapshot['volume_name'])
if not backing:
LOG.info("There is no backing, so will not create "
"snapshot: %s.", snapshot['name'])
return
self.volumeops.create_snapshot(backing, snapshot['name'],
snapshot['display_description'])
model_update = None
if snapshot_format == 'COW':
self.volumeops.create_snapshot(backing, snapshot['name'],
snapshot['display_description'])
else:
model_update = self._create_snapshot_template_format(
snapshot, backing)
LOG.info("Successfully created snapshot: %s.", snapshot['name'])
return model_update
def create_snapshot(self, snapshot):
"""Creates a snapshot.
:param snapshot: Snapshot object
"""
self._create_snapshot(snapshot)
return self._create_snapshot(snapshot)
def _get_template_by_inv_path(self, inv_path):
template = self.volumeops.get_entity_by_inventory_path(inv_path)
if template is None:
LOG.error("Template not found at path: %s.", inv_path)
raise vmdk_exceptions.TemplateNotFoundException(path=inv_path)
else:
return template
def _delete_snapshot_template_format(self, snapshot):
template = self._get_template_by_inv_path(snapshot.provider_location)
self.volumeops.delete_backing(template)
def _delete_snapshot(self, snapshot):
"""Delete snapshot.
If the volume does not have a backing or the snapshot does not exist
then simply pass, else delete the snapshot. Snapshot deletion of only
available volume is supported.
then simply pass, else delete the snapshot. The volume must not be
attached for deletion of snapshot in COW format.
:param snapshot: Snapshot object
"""
inv_path = snapshot.provider_location
is_template = inv_path is not None
backing = self.volumeops.get_backing(snapshot.volume_name)
if not backing:
LOG.debug("Backing does not exist for volume.",
resource=snapshot.volume)
elif not self.volumeops.get_snapshot(backing, snapshot.name):
elif (not is_template and
not self.volumeops.get_snapshot(backing, snapshot.name)):
LOG.debug("Snapshot does not exist in backend.", resource=snapshot)
elif self._in_use(snapshot.volume):
elif self._in_use(snapshot.volume) and not is_template:
msg = _("Delete snapshot of volume not supported in "
"state: %s.") % snapshot.volume.status
LOG.error(msg)
raise exception.InvalidSnapshot(reason=msg)
else:
self.volumeops.delete_snapshot(backing, snapshot.name)
if is_template:
self._delete_snapshot_template_format(snapshot)
else:
self.volumeops.delete_snapshot(backing, snapshot.name)
def delete_snapshot(self, snapshot):
"""Delete snapshot.
@ -1719,8 +1780,8 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
"%(ip)s.", {'driver': self.__class__.__name__,
'ip': self.configuration.vmware_host_ip})
def _get_volume_group_folder(self, datacenter, project_id):
"""Get inventory folder for organizing volume backings.
def _get_volume_group_folder(self, datacenter, project_id, snapshot=False):
"""Get inventory folder for organizing volume backings and snapshots.
The inventory folder for organizing volume backings has the following
hierarchy:
@ -1729,13 +1790,19 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
where volume_folder is the vmdk driver config option
"vmware_volume_folder".
A sub-folder named 'Snapshots' under volume_folder is used for
organizing snapshots in template format.
:param datacenter: Reference to the datacenter
:param project_id: OpenStack project ID
:param snapshot: Return folder for snapshot if True
:return: Reference to the inventory folder
"""
volume_folder_name = self.configuration.vmware_volume_folder
project_folder_name = "Project (%s)" % project_id
folder_names = ['OpenStack', project_folder_name, volume_folder_name]
if snapshot:
folder_names.append('Snapshots')
return self.volumeops.create_vm_inventory_folder(datacenter,
folder_names)
@ -1865,6 +1932,30 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
self._extend_backing(clone, volume['size'])
LOG.info("Successfully created clone: %s.", clone)
def _create_volume_from_template(self, volume, path):
LOG.debug("Creating backing for volume: %(volume_id)s from template "
"at path: %(path)s.",
{'volume_id': volume.id,
'path': path})
template = self._get_template_by_inv_path(path)
# Create temporary backing by cloning the template.
tmp_name = uuidutils.generate_uuid()
(host, rp, folder, summary) = self._select_ds_for_volume(volume)
datastore = summary.datastore
disk_type = VMwareVcVmdkDriver._get_disk_type(volume)
tmp_backing = self.volumeops.clone_backing(tmp_name,
template,
None,
volumeops.FULL_CLONE_TYPE,
datastore,
disk_type=disk_type,
host=host,
resource_pool=rp,
folder=folder)
self._create_volume_from_temp_backing(volume, tmp_backing)
def _create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot.
@ -1881,17 +1972,22 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
"volume: %(vol)s.",
{'snap': snapshot['name'], 'vol': volume['name']})
return
snapshot_moref = self.volumeops.get_snapshot(backing,
snapshot['name'])
if not snapshot_moref:
LOG.info("There is no snapshot point for the snapshotted "
"volume: %(snap)s. Not creating any backing for "
"the volume: %(vol)s.",
{'snap': snapshot['name'], 'vol': volume['name']})
return
clone_type = VMwareVcVmdkDriver._get_clone_type(volume)
self._clone_backing(volume, backing, snapshot_moref, clone_type,
snapshot['volume_size'])
inv_path = snapshot.get('provider_location')
if inv_path:
self._create_volume_from_template(volume, inv_path)
else:
snapshot_moref = self.volumeops.get_snapshot(backing,
snapshot['name'])
if not snapshot_moref:
LOG.info("There is no snapshot point for the snapshotted "
"volume: %(snap)s. Not creating any backing for "
"the volume: %(vol)s.",
{'snap': snapshot['name'], 'vol': volume['name']})
return
clone_type = VMwareVcVmdkDriver._get_clone_type(volume)
self._clone_backing(volume, backing, snapshot_moref, clone_type,
snapshot['volume_size'])
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot.
@ -1911,7 +2007,8 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
if opt_val is not None:
return opt_val.value
def _clone_attached_volume(self, src_vref, volume):
def _create_temp_backing_from_attached_vmdk(
self, src_vref, host, rp, folder, datastore, tmp_name=None):
instance = self.volumeops.get_backing_by_uuid(
src_vref['volume_attachment'][0]['instance_uuid'])
vol_dev_uuid = self._get_volume_device_uuid(instance, src_vref['id'])
@ -1919,23 +2016,28 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
"%(instance)s.", {'dev': vol_dev_uuid,
'instance': instance})
# Clone the vmdk attached to the instance to create a temporary
# backing.
tmp_name = uuidutils.generate_uuid()
(host, rp, folder, summary) = self._select_ds_for_volume(volume)
datastore = summary.datastore
tmp_backing = self.volumeops.clone_backing(
tmp_name = tmp_name or uuidutils.generate_uuid()
return self.volumeops.clone_backing(
tmp_name, instance, None, volumeops.FULL_CLONE_TYPE, datastore,
host=host, resource_pool=rp, folder=folder,
disks_to_clone=[vol_dev_uuid])
def _create_volume_from_temp_backing(self, volume, tmp_backing):
try:
# Create volume from temporary backing.
disk_device = self.volumeops._get_disk_device(tmp_backing)
self._manage_existing_int(volume, tmp_backing, disk_device)
finally:
self._delete_temp_backing(tmp_backing)
def _clone_attached_volume(self, src_vref, volume):
# Clone the vmdk attached to the instance to create a temporary
# backing.
(host, rp, folder, summary) = self._select_ds_for_volume(volume)
datastore = summary.datastore
tmp_backing = self._create_temp_backing_from_attached_vmdk(
src_vref, host, rp, folder, datastore)
self._create_volume_from_temp_backing(volume, tmp_backing)
def _create_cloned_volume(self, volume, src_vref):
"""Creates volume clone.

View File

@ -1618,6 +1618,10 @@ class VMwareVolumeOps(object):
self._session.vim.service_content.searchIndex,
inventoryPath=path)
def get_inventory_path(self, entity):
return self._session.invoke_api(
vim_util, 'get_inventory_path', self._session.vim, entity)
def _get_disk_devices(self, vm):
disk_devices = []
hardware_devices = self._session.invoke_api(vim_util,
@ -1649,3 +1653,7 @@ class VMwareVolumeOps(object):
if (backing.__class__.__name__ == "VirtualDiskFlatVer2BackingInfo"
and backing.fileName == vmdk_path):
return disk_device
def mark_backing_as_template(self, backing):
LOG.debug("Marking backing: %s as template.", backing)
self._session.invoke_api(self._session.vim, 'MarkAsTemplate', backing)

View File

@ -0,0 +1,11 @@
---
features:
- |
VMware VMDK driver now supports vSphere template as a
volume snapshot format in vCenter server. The snapshot
format in vCenter server can be specified using driver
config option ``vmware_snapshot_format``.
upgrade:
- |
VMware VMDK driver will use vSphere template as the
default snapshot format in vCenter server.