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:
parent
9495ef6aa6
commit
f36fc23980
|
@ -61,6 +61,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
CLUSTERS = ["cls-1", "cls-2"]
|
CLUSTERS = ["cls-1", "cls-2"]
|
||||||
DEFAULT_VC_VERSION = '5.5'
|
DEFAULT_VC_VERSION = '5.5'
|
||||||
POOL_SIZE = 20
|
POOL_SIZE = 20
|
||||||
|
SNAPSHOT_FORMAT = 'COW'
|
||||||
|
|
||||||
VOL_ID = 'abcdefab-cdef-abcd-efab-cdefabcdefab'
|
VOL_ID = 'abcdefab-cdef-abcd-efab-cdefabcdefab'
|
||||||
SRC_VOL_ID = '9b3f6f1b-03a9-4f1e-aaff-ae15122b6ccf'
|
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_host_version = self.DEFAULT_VC_VERSION
|
||||||
self._config.vmware_connection_pool_size = self.POOL_SIZE
|
self._config.vmware_connection_pool_size = self.POOL_SIZE
|
||||||
self._config.vmware_adapter_type = self.ADAPTER_TYPE
|
self._config.vmware_adapter_type = self.ADAPTER_TYPE
|
||||||
|
self._config.vmware_snapshot_format = self.SNAPSHOT_FORMAT
|
||||||
|
|
||||||
self._db = mock.Mock()
|
self._db = mock.Mock()
|
||||||
self._driver = vmdk.VMwareVcVmdkDriver(configuration=self._config,
|
self._driver = vmdk.VMwareVcVmdkDriver(configuration=self._config,
|
||||||
|
@ -254,45 +256,197 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
volume,
|
volume,
|
||||||
snap_id=SNAPSHOT_ID,
|
snap_id=SNAPSHOT_ID,
|
||||||
name=SNAPSHOT_NAME,
|
name=SNAPSHOT_NAME,
|
||||||
description=SNAPSHOT_DESCRIPTION):
|
description=SNAPSHOT_DESCRIPTION,
|
||||||
|
provider_location=None):
|
||||||
return {'id': snap_id,
|
return {'id': snap_id,
|
||||||
'volume': volume,
|
'volume': volume,
|
||||||
'volume_name': volume['name'],
|
'volume_name': volume['name'],
|
||||||
'name': name,
|
'name': name,
|
||||||
'display_description': description,
|
'display_description': description,
|
||||||
'volume_size': volume['size']
|
'volume_size': volume['size'],
|
||||||
|
'provider_location': provider_location
|
||||||
}
|
}
|
||||||
|
|
||||||
@mock.patch.object(VMDK_DRIVER, 'volumeops')
|
@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
|
vops.get_backing.return_value = None
|
||||||
|
|
||||||
volume = self._create_volume_dict()
|
volume = self._create_volume_dict()
|
||||||
snapshot = self._create_snapshot_dict(volume)
|
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.get_backing.assert_called_once_with(snapshot['volume_name'])
|
||||||
self.assertFalse(vops.create_snapshot.called)
|
self.assertFalse(vops.create_snapshot.called)
|
||||||
|
|
||||||
|
@mock.patch.object(VMDK_DRIVER, '_in_use', return_value=False)
|
||||||
@mock.patch.object(VMDK_DRIVER, 'volumeops')
|
@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
|
backing = mock.sentinel.backing
|
||||||
vops.get_backing.return_value = backing
|
vops.get_backing.return_value = backing
|
||||||
|
|
||||||
volume = self._create_volume_dict()
|
volume = self._create_volume_dict()
|
||||||
snapshot = self._create_snapshot_dict(volume)
|
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.get_backing.assert_called_once_with(snapshot['volume_name'])
|
||||||
vops.create_snapshot.assert_called_once_with(
|
vops.create_snapshot.assert_called_once_with(
|
||||||
backing, snapshot['name'], snapshot['display_description'])
|
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')
|
volume = self._create_volume_dict(status='in-use')
|
||||||
snapshot = self._create_snapshot_dict(volume)
|
snapshot = self._create_snapshot_dict(volume)
|
||||||
self.assertRaises(cinder_exceptions.InvalidVolume,
|
self.assertRaises(cinder_exceptions.InvalidVolume,
|
||||||
self._driver.create_snapshot, snapshot)
|
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')
|
@mock.patch.object(VMDK_DRIVER, 'volumeops')
|
||||||
def test_delete_snapshot_without_backing(self, vops):
|
def test_delete_snapshot_without_backing(self, vops):
|
||||||
vops.get_backing.return_value = None
|
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.get_snapshot.assert_called_once_with(backing, snapshot.name)
|
||||||
vops.delete_snapshot.assert_not_called()
|
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)
|
@ddt.data('vmdk', 'VMDK', None)
|
||||||
def test_validate_disk_format(self, disk_format):
|
def test_validate_disk_format(self, disk_format):
|
||||||
self._driver._validate_disk_format(disk_format)
|
self._driver._validate_disk_format(disk_format)
|
||||||
|
@ -1605,18 +1779,29 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
self._test_initialize_connection(instance_exists=False)
|
self._test_initialize_connection(instance_exists=False)
|
||||||
|
|
||||||
@mock.patch.object(VMDK_DRIVER, 'volumeops')
|
@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
|
folder = mock.sentinel.folder
|
||||||
vops.create_vm_inventory_folder.return_value = folder
|
vops.create_vm_inventory_folder.return_value = folder
|
||||||
|
|
||||||
datacenter = mock.sentinel.dc
|
datacenter = mock.sentinel.dc
|
||||||
project_id = '63c19a12292549818c09946a5e59ddaf'
|
project_id = '63c19a12292549818c09946a5e59ddaf'
|
||||||
self.assertEqual(folder,
|
self.assertEqual(folder,
|
||||||
self._driver._get_volume_group_folder(datacenter,
|
self._driver._get_volume_group_folder(
|
||||||
project_id))
|
datacenter, project_id, snapshot=snapshot))
|
||||||
project_folder_name = 'Project (%s)' % project_id
|
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(
|
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.'
|
@mock.patch('cinder.volume.drivers.vmware.vmdk.'
|
||||||
'_get_volume_type_extra_spec')
|
'_get_volume_type_extra_spec')
|
||||||
|
@ -1725,6 +1910,53 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
self._test_clone_backing(
|
self._test_clone_backing(
|
||||||
clone_type=volumeops.LINKED_CLONE_TYPE, vc60=True)
|
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, 'volumeops')
|
||||||
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
|
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
|
||||||
def test_create_volume_from_snapshot_without_backing(self, 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, 'volumeops')
|
||||||
@mock.patch.object(VMDK_DRIVER, '_get_clone_type')
|
@mock.patch.object(VMDK_DRIVER, '_get_clone_type')
|
||||||
|
@mock.patch.object(VMDK_DRIVER, '_create_volume_from_template')
|
||||||
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
|
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
|
||||||
def test_create_volume_from_snapshot(self, clone_backing, get_clone_type,
|
def _test_create_volume_from_snapshot(
|
||||||
vops):
|
self, clone_backing, create_volume_from_template, get_clone_type,
|
||||||
|
vops, template=False):
|
||||||
backing = mock.sentinel.backing
|
backing = mock.sentinel.backing
|
||||||
vops.get_backing.return_value = backing
|
vops.get_backing.return_value = backing
|
||||||
|
|
||||||
|
@ -1772,15 +2006,31 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
|
|
||||||
volume = self._create_volume_dict()
|
volume = self._create_volume_dict()
|
||||||
src_vref = self._create_volume_dict(vol_id=self.SRC_VOL_ID)
|
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)
|
self._driver.create_volume_from_snapshot(volume, snapshot)
|
||||||
|
|
||||||
vops.get_backing.assert_called_once_with(snapshot['volume_name'])
|
vops.get_backing.assert_called_once_with(snapshot['volume_name'])
|
||||||
vops.get_snapshot.assert_called_once_with(backing, snapshot['name'])
|
if template:
|
||||||
get_clone_type.assert_called_once_with(volume)
|
create_volume_from_template.assert_called_once_with(
|
||||||
clone_backing.assert_called_once_with(
|
volume, mock.sentinel.inv_path)
|
||||||
volume, backing, snapshot_moref, volumeops.FULL_CLONE_TYPE,
|
else:
|
||||||
snapshot['volume_size'])
|
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')
|
@mock.patch.object(VMDK_DRIVER, 'session')
|
||||||
def test_get_volume_device_uuid(self, 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, 'volumeops')
|
||||||
@mock.patch.object(VMDK_DRIVER, '_get_volume_device_uuid')
|
@mock.patch.object(VMDK_DRIVER, '_get_volume_device_uuid')
|
||||||
@mock.patch('oslo_utils.uuidutils.generate_uuid')
|
@mock.patch('oslo_utils.uuidutils.generate_uuid')
|
||||||
@mock.patch.object(VMDK_DRIVER, '_select_ds_for_volume')
|
def test_create_temp_backing_from_attached_vmdk(
|
||||||
@mock.patch.object(VMDK_DRIVER, '_manage_existing_int')
|
self, generate_uuid, get_volume_device_uuid, vops):
|
||||||
@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):
|
|
||||||
instance = mock.sentinel.instance
|
instance = mock.sentinel.instance
|
||||||
vops.get_backing_by_uuid.return_value = instance
|
vops.get_backing_by_uuid.return_value = instance
|
||||||
|
|
||||||
|
@ -1814,6 +2060,53 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
tmp_name = mock.sentinel.tmp_name
|
tmp_name = mock.sentinel.tmp_name
|
||||||
generate_uuid.return_value = 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
|
host = mock.sentinel.host
|
||||||
rp = mock.sentinel.rp
|
rp = mock.sentinel.rp
|
||||||
folder = mock.sentinel.folder
|
folder = mock.sentinel.folder
|
||||||
|
@ -1822,31 +2115,17 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
select_ds_for_volume.return_value = (host, rp, folder, summary)
|
select_ds_for_volume.return_value = (host, rp, folder, summary)
|
||||||
|
|
||||||
tmp_backing = mock.sentinel.tmp_backing
|
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
|
src_vref = mock.sentinel.src_vref
|
||||||
vops._get_disk_device.return_value = disk_device
|
volume = mock.sentinel.volume
|
||||||
|
|
||||||
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()
|
|
||||||
self._driver._clone_attached_volume(src_vref, 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)
|
select_ds_for_volume.assert_called_once_with(volume)
|
||||||
vops.clone_backing.assert_called_once_with(
|
create_temp_backing_from_attached_vmdk.assert_called_once_with(
|
||||||
tmp_name, instance, None, volumeops.FULL_CLONE_TYPE, datastore,
|
src_vref, host, rp, folder, datastore)
|
||||||
host=host, resource_pool=rp, folder=folder,
|
create_volume_from_temp_backing.assert_called_once_with(
|
||||||
disks_to_clone=[vol_dev_uuid])
|
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, 'volumeops')
|
@mock.patch.object(VMDK_DRIVER, 'volumeops')
|
||||||
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
|
@mock.patch.object(VMDK_DRIVER, '_clone_backing')
|
||||||
|
|
|
@ -1689,6 +1689,16 @@ class VolumeOpsTestCase(test.TestCase):
|
||||||
self.session.vim.service_content.searchIndex,
|
self.session.vim.service_content.searchIndex,
|
||||||
inventoryPath=path)
|
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):
|
def test_get_disk_devices(self):
|
||||||
disk_device = mock.Mock()
|
disk_device = mock.Mock()
|
||||||
disk_device.__class__.__name__ = 'VirtualDisk'
|
disk_device.__class__.__name__ = 'VirtualDisk'
|
||||||
|
@ -1713,6 +1723,12 @@ class VolumeOpsTestCase(test.TestCase):
|
||||||
backing.uuid = uuid
|
backing.uuid = uuid
|
||||||
return mock.Mock(backing=backing)
|
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.'
|
@mock.patch('cinder.volume.drivers.vmware.volumeops.VMwareVolumeOps.'
|
||||||
'_get_disk_devices')
|
'_get_disk_devices')
|
||||||
def test_get_disk_device(self, get_disk_devices):
|
def test_get_disk_device(self, get_disk_devices):
|
||||||
|
|
|
@ -55,3 +55,8 @@ class ClusterNotFoundException(exceptions.VMwareDriverException):
|
||||||
class NoValidHostException(exceptions.VMwareDriverException):
|
class NoValidHostException(exceptions.VMwareDriverException):
|
||||||
"""Thrown when there are no valid ESX hosts."""
|
"""Thrown when there are no valid ESX hosts."""
|
||||||
msg_fmt = _("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.")
|
||||||
|
|
|
@ -137,6 +137,10 @@ vmdk_opts = [
|
||||||
volumeops.VirtualDiskAdapterType.IDE],
|
volumeops.VirtualDiskAdapterType.IDE],
|
||||||
default=volumeops.VirtualDiskAdapterType.LSI_LOGIC,
|
default=volumeops.VirtualDiskAdapterType.LSI_LOGIC,
|
||||||
help='Default adapter type to be used for attaching volumes.'),
|
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
|
CONF = cfg.CONF
|
||||||
|
@ -658,6 +662,34 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
def remove_export(self, context, volume):
|
def remove_export(self, context, volume):
|
||||||
pass
|
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):
|
def _create_snapshot(self, snapshot):
|
||||||
"""Creates a snapshot.
|
"""Creates a snapshot.
|
||||||
|
|
||||||
|
@ -669,49 +701,78 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
volume = snapshot['volume']
|
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 "
|
msg = _("Snapshot of volume not supported in "
|
||||||
"state: %s.") % volume['status']
|
"state: %s.") % volume['status']
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidVolume(msg)
|
raise exception.InvalidVolume(msg)
|
||||||
|
|
||||||
backing = self.volumeops.get_backing(snapshot['volume_name'])
|
backing = self.volumeops.get_backing(snapshot['volume_name'])
|
||||||
if not backing:
|
if not backing:
|
||||||
LOG.info("There is no backing, so will not create "
|
LOG.info("There is no backing, so will not create "
|
||||||
"snapshot: %s.", snapshot['name'])
|
"snapshot: %s.", snapshot['name'])
|
||||||
return
|
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'])
|
LOG.info("Successfully created snapshot: %s.", snapshot['name'])
|
||||||
|
return model_update
|
||||||
|
|
||||||
def create_snapshot(self, snapshot):
|
def create_snapshot(self, snapshot):
|
||||||
"""Creates a snapshot.
|
"""Creates a snapshot.
|
||||||
|
|
||||||
:param snapshot: Snapshot object
|
: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):
|
def _delete_snapshot(self, snapshot):
|
||||||
"""Delete snapshot.
|
"""Delete snapshot.
|
||||||
|
|
||||||
If the volume does not have a backing or the snapshot does not exist
|
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
|
then simply pass, else delete the snapshot. The volume must not be
|
||||||
available volume is supported.
|
attached for deletion of snapshot in COW format.
|
||||||
|
|
||||||
:param snapshot: Snapshot object
|
:param snapshot: Snapshot object
|
||||||
"""
|
"""
|
||||||
|
inv_path = snapshot.provider_location
|
||||||
|
is_template = inv_path is not None
|
||||||
|
|
||||||
backing = self.volumeops.get_backing(snapshot.volume_name)
|
backing = self.volumeops.get_backing(snapshot.volume_name)
|
||||||
if not backing:
|
if not backing:
|
||||||
LOG.debug("Backing does not exist for volume.",
|
LOG.debug("Backing does not exist for volume.",
|
||||||
resource=snapshot.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)
|
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 "
|
msg = _("Delete snapshot of volume not supported in "
|
||||||
"state: %s.") % snapshot.volume.status
|
"state: %s.") % snapshot.volume.status
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InvalidSnapshot(reason=msg)
|
raise exception.InvalidSnapshot(reason=msg)
|
||||||
else:
|
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):
|
def delete_snapshot(self, snapshot):
|
||||||
"""Delete snapshot.
|
"""Delete snapshot.
|
||||||
|
@ -1719,8 +1780,8 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
"%(ip)s.", {'driver': self.__class__.__name__,
|
"%(ip)s.", {'driver': self.__class__.__name__,
|
||||||
'ip': self.configuration.vmware_host_ip})
|
'ip': self.configuration.vmware_host_ip})
|
||||||
|
|
||||||
def _get_volume_group_folder(self, datacenter, project_id):
|
def _get_volume_group_folder(self, datacenter, project_id, snapshot=False):
|
||||||
"""Get inventory folder for organizing volume backings.
|
"""Get inventory folder for organizing volume backings and snapshots.
|
||||||
|
|
||||||
The inventory folder for organizing volume backings has the following
|
The inventory folder for organizing volume backings has the following
|
||||||
hierarchy:
|
hierarchy:
|
||||||
|
@ -1729,13 +1790,19 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
where volume_folder is the vmdk driver config option
|
where volume_folder is the vmdk driver config option
|
||||||
"vmware_volume_folder".
|
"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 datacenter: Reference to the datacenter
|
||||||
:param project_id: OpenStack project ID
|
:param project_id: OpenStack project ID
|
||||||
|
:param snapshot: Return folder for snapshot if True
|
||||||
:return: Reference to the inventory folder
|
:return: Reference to the inventory folder
|
||||||
"""
|
"""
|
||||||
volume_folder_name = self.configuration.vmware_volume_folder
|
volume_folder_name = self.configuration.vmware_volume_folder
|
||||||
project_folder_name = "Project (%s)" % project_id
|
project_folder_name = "Project (%s)" % project_id
|
||||||
folder_names = ['OpenStack', project_folder_name, volume_folder_name]
|
folder_names = ['OpenStack', project_folder_name, volume_folder_name]
|
||||||
|
if snapshot:
|
||||||
|
folder_names.append('Snapshots')
|
||||||
return self.volumeops.create_vm_inventory_folder(datacenter,
|
return self.volumeops.create_vm_inventory_folder(datacenter,
|
||||||
folder_names)
|
folder_names)
|
||||||
|
|
||||||
|
@ -1865,6 +1932,30 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
self._extend_backing(clone, volume['size'])
|
self._extend_backing(clone, volume['size'])
|
||||||
LOG.info("Successfully created clone: %s.", clone)
|
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):
|
def _create_volume_from_snapshot(self, volume, snapshot):
|
||||||
"""Creates a volume from a snapshot.
|
"""Creates a volume from a snapshot.
|
||||||
|
|
||||||
|
@ -1881,17 +1972,22 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
"volume: %(vol)s.",
|
"volume: %(vol)s.",
|
||||||
{'snap': snapshot['name'], 'vol': volume['name']})
|
{'snap': snapshot['name'], 'vol': volume['name']})
|
||||||
return
|
return
|
||||||
snapshot_moref = self.volumeops.get_snapshot(backing,
|
|
||||||
snapshot['name'])
|
inv_path = snapshot.get('provider_location')
|
||||||
if not snapshot_moref:
|
if inv_path:
|
||||||
LOG.info("There is no snapshot point for the snapshotted "
|
self._create_volume_from_template(volume, inv_path)
|
||||||
"volume: %(snap)s. Not creating any backing for "
|
else:
|
||||||
"the volume: %(vol)s.",
|
snapshot_moref = self.volumeops.get_snapshot(backing,
|
||||||
{'snap': snapshot['name'], 'vol': volume['name']})
|
snapshot['name'])
|
||||||
return
|
if not snapshot_moref:
|
||||||
clone_type = VMwareVcVmdkDriver._get_clone_type(volume)
|
LOG.info("There is no snapshot point for the snapshotted "
|
||||||
self._clone_backing(volume, backing, snapshot_moref, clone_type,
|
"volume: %(snap)s. Not creating any backing for "
|
||||||
snapshot['volume_size'])
|
"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):
|
def create_volume_from_snapshot(self, volume, snapshot):
|
||||||
"""Creates a volume from a snapshot.
|
"""Creates a volume from a snapshot.
|
||||||
|
@ -1911,7 +2007,8 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
if opt_val is not None:
|
if opt_val is not None:
|
||||||
return opt_val.value
|
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(
|
instance = self.volumeops.get_backing_by_uuid(
|
||||||
src_vref['volume_attachment'][0]['instance_uuid'])
|
src_vref['volume_attachment'][0]['instance_uuid'])
|
||||||
vol_dev_uuid = self._get_volume_device_uuid(instance, src_vref['id'])
|
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)s.", {'dev': vol_dev_uuid,
|
||||||
'instance': instance})
|
'instance': instance})
|
||||||
|
|
||||||
# Clone the vmdk attached to the instance to create a temporary
|
tmp_name = tmp_name or uuidutils.generate_uuid()
|
||||||
# backing.
|
return self.volumeops.clone_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, instance, None, volumeops.FULL_CLONE_TYPE, datastore,
|
tmp_name, instance, None, volumeops.FULL_CLONE_TYPE, datastore,
|
||||||
host=host, resource_pool=rp, folder=folder,
|
host=host, resource_pool=rp, folder=folder,
|
||||||
disks_to_clone=[vol_dev_uuid])
|
disks_to_clone=[vol_dev_uuid])
|
||||||
|
|
||||||
|
def _create_volume_from_temp_backing(self, volume, tmp_backing):
|
||||||
try:
|
try:
|
||||||
# Create volume from temporary backing.
|
|
||||||
disk_device = self.volumeops._get_disk_device(tmp_backing)
|
disk_device = self.volumeops._get_disk_device(tmp_backing)
|
||||||
self._manage_existing_int(volume, tmp_backing, disk_device)
|
self._manage_existing_int(volume, tmp_backing, disk_device)
|
||||||
finally:
|
finally:
|
||||||
self._delete_temp_backing(tmp_backing)
|
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):
|
def _create_cloned_volume(self, volume, src_vref):
|
||||||
"""Creates volume clone.
|
"""Creates volume clone.
|
||||||
|
|
||||||
|
|
|
@ -1618,6 +1618,10 @@ class VMwareVolumeOps(object):
|
||||||
self._session.vim.service_content.searchIndex,
|
self._session.vim.service_content.searchIndex,
|
||||||
inventoryPath=path)
|
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):
|
def _get_disk_devices(self, vm):
|
||||||
disk_devices = []
|
disk_devices = []
|
||||||
hardware_devices = self._session.invoke_api(vim_util,
|
hardware_devices = self._session.invoke_api(vim_util,
|
||||||
|
@ -1649,3 +1653,7 @@ class VMwareVolumeOps(object):
|
||||||
if (backing.__class__.__name__ == "VirtualDiskFlatVer2BackingInfo"
|
if (backing.__class__.__name__ == "VirtualDiskFlatVer2BackingInfo"
|
||||||
and backing.fileName == vmdk_path):
|
and backing.fileName == vmdk_path):
|
||||||
return disk_device
|
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)
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue