NetApp cDOT: Support generic groups for file

Adding support for generic volume groups to NetApp cDOT's NFS driver.

CG methods are moved to the 7-mode NFS driver. Announcement was made
in a previous release for the removal of the NetApp 7-mode drivers.
Generic groups will only be implemented for the NetApp cDOT drivers.

Change-Id: I5edf62ccfbbb7a5f3e64e0baebe9eb4a955cbdf5
Implements: blueprint netapp-add-generic-group-support-cdot
This commit is contained in:
Chuck Fouts 2017-03-30 15:09:19 -04:00
parent 7c8901e764
commit 0215fcc022
8 changed files with 775 additions and 341 deletions

View File

@ -457,6 +457,82 @@ CG_VOLUME_SNAPSHOT = {
}
VG_VOLUME_NAME = 'fake_vg_volume'
VG_GROUP_NAME = 'fake_volume_group'
VG_POOL_NAME = 'cdot'
SOURCE_VG_VOLUME_NAME = 'fake_source_vg_volume'
VG_VOLUME_ID = 'fake_vg_volume_id'
VG_VOLUME_SIZE = 100
SOURCE_VG_VOLUME_ID = 'fake_source_vg_volume_id'
VOLUME_GROUP_NAME = 'fake_vg'
SOURCE_VOLUME_GROUP_ID = 'fake_source_vg_id'
VOLUME_GROUP_ID = 'fake_vg_id'
VG_SNAPSHOT_ID = 'fake_vg_snapshot_id'
VG_SNAPSHOT_NAME = 'snapshot-' + VG_SNAPSHOT_ID
VG_VOLUME_SNAPSHOT_ID = 'fake_vg_volume_snapshot_id'
VG_LUN_METADATA = {
'OsType': None,
'Path': '/vol/aggr1/fake_vg_volume',
'SpaceReserved': 'true',
'Qtree': None,
'Volume': POOL_NAME,
}
SOURCE_VG_VOLUME = {
'name': SOURCE_VG_VOLUME_NAME,
'size': VG_VOLUME_SIZE,
'id': SOURCE_VG_VOLUME_ID,
'host': 'hostname@backend#cdot',
'volumegroup_id': None,
'status': 'fake_status',
'provider_location': PROVIDER_LOCATION,
}
VG_VOLUME = {
'name': VG_VOLUME_NAME,
'size': 100,
'id': VG_VOLUME_ID,
'host': 'hostname@backend#' + VG_POOL_NAME,
'volumegroup_id': VOLUME_GROUP_ID,
'status': 'fake_status',
'provider_location': PROVIDER_LOCATION,
}
SOURCE_VOLUME_GROUP = {
'id': SOURCE_VOLUME_GROUP_ID,
'status': 'fake_status',
}
VOLUME_GROUP = {
'id': VOLUME_GROUP_ID,
'status': 'fake_status',
'name': VG_GROUP_NAME,
}
VG_CONTEXT = {}
VG_SNAPSHOT = {
'id': VG_SNAPSHOT_ID,
'name': VG_SNAPSHOT_NAME,
'volume_size': VG_VOLUME_SIZE,
'volumegroup_id': VOLUME_GROUP_ID,
'status': 'fake_status',
'volume_id': 'fake_source_volume_id',
'volume': VG_VOLUME,
}
VG_VOLUME_SNAPSHOT = {
'name': VG_SNAPSHOT_NAME,
'volume_size': VG_VOLUME_SIZE,
'vgsnapshot_id': VG_SNAPSHOT_ID,
'id': VG_VOLUME_SNAPSHOT_ID,
'status': 'fake_status',
'volume_id': VG_VOLUME_ID,
}
class test_volume(object):
pass

View File

@ -20,6 +20,8 @@ import mock
from os_brick.remotefs import remotefs as remotefs_brick
from oslo_utils import units
from cinder import exception
from cinder.objects import fields
from cinder import test
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
from cinder.tests.unit.volume.drivers.netapp import fakes as na_fakes
@ -265,3 +267,162 @@ class NetApp7modeNfsDriverTestCase(test.TestCase):
result = self.driver._get_backing_flexvol_names()
self.assertEqual('path', result[0])
def test_create_consistency_group(self):
model_update = self.driver.create_consistencygroup(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP)
self.assertEqual('available', model_update['status'])
def test_update_consistencygroup(self):
model_update, add_volumes_update, remove_volumes_update = (
self.driver.update_consistencygroup(fake.CG_CONTEXT, "foo"))
self.assertIsNone(add_volumes_update)
self.assertIsNone(remove_volumes_update)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src(self, volume_model_update):
volume_model_update = volume_model_update or {}
volume_model_update.update(
{'provider_location': fake.PROVIDER_LOCATION})
mock_create_volume_from_snapshot = self.mock_object(
self.driver, 'create_volume_from_snapshot',
return_value=volume_model_update)
model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.VOLUME],
cgsnapshot=fake.CG_SNAPSHOT, snapshots=[fake.SNAPSHOT]))
expected_volumes_model_updates = [{'id': fake.VOLUME['id']}]
expected_volumes_model_updates[0].update(volume_model_update)
mock_create_volume_from_snapshot.assert_called_once_with(
fake.VOLUME, fake.SNAPSHOT)
self.assertIsNone(model_update)
self.assertEqual(expected_volumes_model_updates, volumes_model_update)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src_source_vols(
self, volume_model_update):
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
fake_snapshot_name = 'snapshot-temp-' + fake.CONSISTENCY_GROUP['id']
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
self.mock_object(self.driver, '_get_volume_model_update',
return_value=volume_model_update)
model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.VOLUME],
source_cg=fake.CONSISTENCY_GROUP,
source_vols=[fake.NFS_VOLUME]))
expected_volumes_model_updates = [{
'id': fake.NFS_VOLUME['id'],
'provider_location': fake.PROVIDER_LOCATION,
}]
if volume_model_update:
expected_volumes_model_updates[0].update(volume_model_update)
mock_get_snapshot_flexvols.assert_called_once_with(
[fake.NFS_VOLUME['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.CG_POOL_NAME]), fake_snapshot_name)
mock_clone_backing_file.assert_called_once_with(
fake.NFS_VOLUME['name'], fake.VOLUME['name'],
fake.NFS_VOLUME['id'], source_snapshot=fake_snapshot_name)
mock_busy.assert_called_once_with(
fake.CG_POOL_NAME, fake_snapshot_name)
self.driver.zapi_client.delete_snapshot.assert_called_once_with(
fake.CG_POOL_NAME, fake_snapshot_name)
self.assertIsNone(model_update)
self.assertEqual(expected_volumes_model_updates, volumes_model_update)
def test_create_consistencygroup_from_src_invalid_parms(self):
model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.VOLUME]))
self.assertIn('error', model_update['status'])
def test_create_cgsnapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
self.driver.create_cgsnapshot(
fake.CG_CONTEXT, fake.CG_SNAPSHOT, [snapshot])
mock_get_snapshot_flexvols.assert_called_once_with(
[snapshot['volume']['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.CG_POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_backing_file.assert_called_once_with(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=fake.CG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
self.driver.zapi_client.delete_snapshot.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_create_cgsnapshot_busy_snapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name'])
mock_mark_snapshot_for_deletion = self.mock_object(
self.driver.zapi_client, 'mark_snapshot_for_deletion')
self.driver.create_cgsnapshot(
fake.CG_CONTEXT, fake.CG_SNAPSHOT, [snapshot])
mock_get_snapshot_flexvols.assert_called_once_with(
[snapshot['volume']['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.CG_POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_backing_file.assert_called_once_with(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=fake.CG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
self.driver.zapi_client.delete_snapshot.assert_not_called()
mock_mark_snapshot_for_deletion.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_delete_consistencygroup_volume_delete_failure(self):
self.mock_object(self.driver, '_delete_file', side_effect=Exception)
model_update, volumes = self.driver.delete_consistencygroup(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('error_deleting', volumes[0]['status'])
def test_delete_consistencygroup(self):
mock_delete_file = self.mock_object(
self.driver, '_delete_file')
model_update, volumes = self.driver.delete_consistencygroup(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
mock_delete_file.assert_called_once_with(
fake.CG_VOLUME_ID, fake.CG_VOLUME_NAME)

View File

@ -919,11 +919,6 @@ class NetAppNfsDriverTestCase(test.TestCase):
volume,
vol_ref)
def test_create_consistency_group(self):
model_update = self.driver.create_consistencygroup(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP)
self.assertEqual('available', model_update['status'])
@ddt.data(True, False)
def test_delete_file(self, volume_not_present):
mock_get_provider_location = self.mock_object(
@ -966,160 +961,6 @@ class NetAppNfsDriverTestCase(test.TestCase):
mock_get_volume_path.assert_not_called()
mock_delete.assert_not_called()
def test_update_consistencygroup(self):
model_update, add_volumes_update, remove_volumes_update = (
self.driver.update_consistencygroup(fake.CG_CONTEXT, "foo"))
self.assertIsNone(add_volumes_update)
self.assertIsNone(remove_volumes_update)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src(self, volume_model_update):
volume_model_update = volume_model_update or {}
volume_model_update.update(
{'provider_location': fake.PROVIDER_LOCATION})
mock_create_volume_from_snapshot = self.mock_object(
self.driver, 'create_volume_from_snapshot',
return_value=volume_model_update)
model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.VOLUME],
cgsnapshot=fake.CG_SNAPSHOT, snapshots=[fake.SNAPSHOT]))
expected_volumes_model_updates = [{'id': fake.VOLUME['id']}]
expected_volumes_model_updates[0].update(volume_model_update)
mock_create_volume_from_snapshot.assert_called_once_with(
fake.VOLUME, fake.SNAPSHOT)
self.assertIsNone(model_update)
self.assertEqual(expected_volumes_model_updates, volumes_model_update)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src_source_vols(
self, volume_model_update):
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
fake_snapshot_name = 'snapshot-temp-' + fake.CONSISTENCY_GROUP['id']
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
self.mock_object(self.driver, '_get_volume_model_update',
return_value=volume_model_update)
model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.VOLUME],
source_cg=fake.CONSISTENCY_GROUP,
source_vols=[fake.NFS_VOLUME]))
expected_volumes_model_updates = [{
'id': fake.NFS_VOLUME['id'],
'provider_location': fake.PROVIDER_LOCATION,
}]
if volume_model_update:
expected_volumes_model_updates[0].update(volume_model_update)
mock_get_snapshot_flexvols.assert_called_once_with(
[fake.NFS_VOLUME['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.CG_POOL_NAME]), fake_snapshot_name)
mock_clone_backing_file.assert_called_once_with(
fake.NFS_VOLUME['name'], fake.VOLUME['name'],
fake.NFS_VOLUME['id'], source_snapshot=fake_snapshot_name)
mock_busy.assert_called_once_with(
fake.CG_POOL_NAME, fake_snapshot_name)
self.driver.zapi_client.delete_snapshot.assert_called_once_with(
fake.CG_POOL_NAME, fake_snapshot_name)
self.assertIsNone(model_update)
self.assertEqual(expected_volumes_model_updates, volumes_model_update)
def test_create_consistencygroup_from_src_invalid_parms(self):
model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.VOLUME]))
self.assertIn('error', model_update['status'])
def test_create_cgsnapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
self.driver.create_cgsnapshot(
fake.CG_CONTEXT, fake.CG_SNAPSHOT, [snapshot])
mock_get_snapshot_flexvols.assert_called_once_with(
[snapshot['volume']['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.CG_POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_backing_file.assert_called_once_with(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=fake.CG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
self.driver.zapi_client.delete_snapshot.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_create_cgsnapshot_busy_snapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.CG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name'])
mock_mark_snapshot_for_deletion = self.mock_object(
self.zapi_client, 'mark_snapshot_for_deletion')
self.driver.create_cgsnapshot(
fake.CG_CONTEXT, fake.CG_SNAPSHOT, [snapshot])
mock_get_snapshot_flexvols.assert_called_once_with(
[snapshot['volume']['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.CG_POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_backing_file.assert_called_once_with(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=fake.CG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
self.driver.zapi_client.delete_snapshot.assert_not_called()
mock_mark_snapshot_for_deletion.assert_called_once_with(
fake.CG_POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_delete_consistencygroup_volume_delete_failure(self):
self.mock_object(self.driver, '_delete_file', side_effect=Exception)
model_update, volumes = self.driver.delete_consistencygroup(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('error_deleting', volumes[0]['status'])
def test_delete_consistencygroup(self):
mock_delete_file = self.mock_object(
self.driver, '_delete_file')
model_update, volumes = self.driver.delete_consistencygroup(
fake.CG_CONTEXT, fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
mock_delete_file.assert_called_once_with(
fake.CG_VOLUME_ID, fake.CG_VOLUME_NAME)
def test_check_for_setup_error(self):
super_check_for_setup_error = self.mock_object(
nfs.NfsDriver, 'check_for_setup_error')
@ -1144,25 +985,3 @@ class NetAppNfsDriverTestCase(test.TestCase):
mock.call(mock_call_snap_cleanup, loopingcalls.ONE_MINUTE,
loopingcalls.ONE_MINUTE),
mock.call(mock_call_ems_logging, loopingcalls.ONE_HOUR)])
def test_delete_snapshots_marked_for_deletion(self):
snapshots = [{
'name': fake.SNAPSHOT_NAME,
'volume_name': fake.VOLUME['name']
}]
mock_get_flexvol_names = self.mock_object(
self.driver, '_get_backing_flexvol_names')
mock_get_flexvol_names.return_value = [fake.VOLUME['name']]
mock_get_snapshots_marked = self.mock_object(
self.zapi_client, 'get_snapshots_marked_for_deletion')
mock_get_snapshots_marked.return_value = snapshots
mock_delete_snapshot = self.mock_object(
self.zapi_client, 'delete_snapshot')
self.driver._delete_snapshots_marked_for_deletion()
mock_get_flexvol_names.assert_called_once_with()
mock_get_snapshots_marked.assert_called_once_with(
[fake.VOLUME['name']])
mock_delete_snapshot.assert_called_once_with(
fake.VOLUME['name'], fake.SNAPSHOT_NAME)

View File

@ -26,6 +26,7 @@ from oslo_utils import units
from cinder import exception
from cinder.image import image_utils
from cinder.objects import fields
from cinder import test
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
from cinder.tests.unit.volume.drivers.netapp.dataontap.utils import fakes as \
@ -161,7 +162,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
'netapp_aggregate': 'aggr1',
'netapp_raid_type': 'raid_dp',
'netapp_disk_type': 'SSD',
'consistencygroup_support': True,
'consistent_group_snapshot_enabled': True,
},
}
mock_get_ssc = self.mock_object(self.driver.ssc_library,
@ -234,6 +235,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
'netapp_raid_type': 'raid_dp',
'netapp_disk_type': 'SSD',
'consistencygroup_support': True,
'consistent_group_snapshot_enabled': True,
'replication_enabled': False,
}]
if replication_backends:
@ -1421,16 +1423,16 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.assertEqual('dev1', actual_active)
self.assertEqual([], vol_updates)
def test_delete_cgsnapshot(self):
def test_delete_group_snapshot(self):
mock_delete_backing_file = self.mock_object(
self.driver, '_delete_backing_file_for_snapshot')
snapshots = [fake.CG_SNAPSHOT]
snapshots = [fake.VG_SNAPSHOT]
model_update, snapshots_model_update = (
self.driver.delete_cgsnapshot(
fake.CG_CONTEXT, fake.CG_SNAPSHOT, snapshots))
self.driver.delete_group_snapshot(
fake.VG_CONTEXT, fake.VG_SNAPSHOT, snapshots))
mock_delete_backing_file.assert_called_once_with(fake.CG_SNAPSHOT)
mock_delete_backing_file.assert_called_once_with(fake.VG_SNAPSHOT)
self.assertIsNone(model_update)
self.assertIsNone(snapshots_model_update)
@ -1467,3 +1469,214 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
self.driver._get_backing_flexvol_names()
mock_ssc_library.assert_called_once_with()
def test_create_group(self):
model_update = self.driver.create_group(
fake.VG_CONTEXT, fake.VOLUME_GROUP)
self.assertEqual('available', model_update['status'])
def test_update_group(self):
model_update, add_volumes_update, remove_volumes_update = (
self.driver.update_group(fake.VG_CONTEXT, "foo"))
self.assertIsNone(add_volumes_update)
self.assertIsNone(remove_volumes_update)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_group_from_src(self, volume_model_update):
volume_model_update = volume_model_update or {}
volume_model_update.update(
{'provider_location': fake.PROVIDER_LOCATION})
mock_create_volume_from_snapshot = self.mock_object(
self.driver, 'create_volume_from_snapshot',
return_value=volume_model_update)
model_update, volumes_model_update = (
self.driver.create_group_from_src(
fake.VG_CONTEXT, fake.VOLUME_GROUP, [fake.VOLUME],
group_snapshot=fake.VG_SNAPSHOT,
sorted_snapshots=[fake.SNAPSHOT]))
expected_volumes_model_updates = [{'id': fake.VOLUME['id']}]
expected_volumes_model_updates[0].update(volume_model_update)
mock_create_volume_from_snapshot.assert_called_once_with(
fake.VOLUME, fake.SNAPSHOT)
self.assertIsNone(model_update)
self.assertEqual(expected_volumes_model_updates, volumes_model_update)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_group_from_src_source_vols(self, volume_model_update):
self.driver.zapi_client = mock.Mock()
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.VG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
fake_snapshot_name = 'snapshot-temp-' + fake.VOLUME_GROUP['id']
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
self.mock_object(self.driver, '_get_volume_model_update',
return_value=volume_model_update)
model_update, volumes_model_update = (
self.driver.create_group_from_src(
fake.VG_CONTEXT, fake.VOLUME_GROUP, [fake.VG_VOLUME],
source_group=fake.VOLUME_GROUP,
sorted_source_vols=[fake.SOURCE_VG_VOLUME]))
expected_volumes_model_updates = [{
'id': fake.VG_VOLUME['id'],
'provider_location': fake.PROVIDER_LOCATION,
}]
if volume_model_update:
expected_volumes_model_updates[0].update(volume_model_update)
mock_get_snapshot_flexvols.assert_called_once_with(
[fake.SOURCE_VG_VOLUME['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.VG_POOL_NAME]), fake_snapshot_name)
mock_clone_backing_file.assert_called_once_with(
fake.SOURCE_VG_VOLUME['name'], fake.VG_VOLUME['name'],
fake.SOURCE_VG_VOLUME['id'], source_snapshot=fake_snapshot_name)
mock_busy.assert_called_once_with(
fake.VG_POOL_NAME, fake_snapshot_name)
self.driver.zapi_client.delete_snapshot.assert_called_once_with(
fake.VG_POOL_NAME, fake_snapshot_name)
self.assertIsNone(model_update)
self.assertEqual(expected_volumes_model_updates, volumes_model_update)
def test_create_group_from_src_invalid_parms(self):
model_update, volumes_model_update = (
self.driver.create_group_from_src(
fake.VG_CONTEXT, fake.VOLUME_GROUP, [fake.VOLUME]))
self.assertIn('error', model_update['status'])
def test_create_group_snapshot_raise_exception(self):
mock_is_cg_snapshot = self.mock_object(
volume_utils, 'is_group_a_cg_snapshot_type', return_value=True)
mock__get_flexvol_names = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
self.mock_object(self.driver.zapi_client, 'create_cg_snapshot',
side_effect=netapp_api.NaApiError)
self.assertRaises(exception.NetAppDriverException,
self.driver.create_group_snapshot,
fake.VG_CONTEXT,
fake.VOLUME_GROUP,
[fake.VG_SNAPSHOT])
mock_is_cg_snapshot.assert_called_once_with(fake.VOLUME_GROUP)
mock__get_flexvol_names.assert_called_once_with(
[fake.VG_SNAPSHOT['volume']['host']])
def test_create_group_snapshot(self):
mock_is_cg_snapshot = self.mock_object(
volume_utils, 'is_group_a_cg_snapshot_type', return_value=False)
mock__clone_backing_file_for_volume = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
model_update, snapshots_model_update = (
self.driver.create_group_snapshot(fake.VG_CONTEXT,
fake.VOLUME_GROUP,
[fake.SNAPSHOT]))
self.assertIsNone(model_update)
self.assertIsNone(snapshots_model_update)
mock_is_cg_snapshot.assert_called_once_with(fake.VOLUME_GROUP)
mock__clone_backing_file_for_volume.assert_called_once_with(
fake.SNAPSHOT['volume_name'], fake.SNAPSHOT['name'],
fake.SNAPSHOT['volume_id'], is_snapshot=True)
def test_create_consistent_group_snapshot(self):
mock_is_cg_snapshot = self.mock_object(
volume_utils, 'is_group_a_cg_snapshot_type', return_value=True)
self.driver.zapi_client = mock.Mock()
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.VG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
model_update, snapshots_model_update = (
self.driver.create_group_snapshot(fake.VG_CONTEXT,
fake.VOLUME_GROUP,
[fake.VG_SNAPSHOT]))
self.assertIsNone(model_update)
self.assertIsNone(snapshots_model_update)
mock_is_cg_snapshot.assert_called_once_with(fake.VOLUME_GROUP)
mock_get_snapshot_flexvols.assert_called_once_with(
[fake.VG_SNAPSHOT['volume']['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.VG_POOL_NAME]), fake.VOLUME_GROUP_ID)
mock_clone_backing_file.assert_called_once_with(
fake.VG_SNAPSHOT['volume']['name'], fake.VG_SNAPSHOT['name'],
fake.VG_SNAPSHOT['volume']['id'],
source_snapshot=fake.VOLUME_GROUP_ID)
mock_busy.assert_called_once_with(
fake.VG_POOL_NAME, fake.VOLUME_GROUP_ID)
self.driver.zapi_client.delete_snapshot.assert_called_once_with(
fake.VG_POOL_NAME, fake.VOLUME_GROUP_ID)
def test_create_group_snapshot_busy_snapshot(self):
self.mock_object(volume_utils, 'is_group_a_cg_snapshot_type',
return_value=True)
self.driver.zapi_client = mock.Mock()
snapshot = fake.VG_SNAPSHOT
snapshot['volume'] = fake.VG_VOLUME
mock_get_snapshot_flexvols = self.mock_object(
self.driver, '_get_flexvol_names_from_hosts')
mock_get_snapshot_flexvols.return_value = (set([fake.VG_POOL_NAME]))
mock_clone_backing_file = self.mock_object(
self.driver, '_clone_backing_file_for_volume')
mock_busy = self.mock_object(
self.driver.zapi_client, 'wait_for_busy_snapshot')
mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name'])
mock_mark_snapshot_for_deletion = self.mock_object(
self.driver.zapi_client, 'mark_snapshot_for_deletion')
self.driver.create_group_snapshot(
fake.VG_CONTEXT, fake.VG_SNAPSHOT, [snapshot])
mock_get_snapshot_flexvols.assert_called_once_with(
[snapshot['volume']['host']])
self.driver.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.VG_POOL_NAME]), fake.VG_SNAPSHOT_ID)
mock_clone_backing_file.assert_called_once_with(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=fake.VG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(
fake.VG_POOL_NAME, fake.VG_SNAPSHOT_ID)
self.driver.zapi_client.delete_snapshot.assert_not_called()
mock_mark_snapshot_for_deletion.assert_called_once_with(
fake.VG_POOL_NAME, fake.VG_SNAPSHOT_ID)
def test_delete_group_volume_delete_failure(self):
self.mock_object(self.driver, '_delete_file', side_effect=Exception)
model_update, volumes = self.driver.delete_group(
fake.VG_CONTEXT, fake.VOLUME_GROUP, [fake.VG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('error_deleting', volumes[0]['status'])
def test_delete_group(self):
mock_delete_file = self.mock_object(
self.driver, '_delete_file')
model_update, volumes = self.driver.delete_group(
fake.VG_CONTEXT, fake.VOLUME_GROUP, [fake.VG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
mock_delete_file.assert_called_once_with(
fake.VG_VOLUME_ID, fake.VG_VOLUME_NAME)

View File

@ -30,6 +30,7 @@ import six
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder.objects import fields
from cinder import utils
from cinder.volume.drivers.netapp.dataontap.client import client_7mode
from cinder.volume.drivers.netapp.dataontap import nfs_base
@ -281,3 +282,151 @@ class NetApp7modeNfsDriver(nfs_base.NetAppNfsDriver):
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
return None, None
@utils.trace_method
def create_consistencygroup(self, context, group):
"""Driver entry point for creating a consistency group.
ONTAP does not maintain an actual CG construct. As a result, no
communtication to the backend is necessary for consistency group
creation.
:returns: Hard-coded model update for consistency group model.
"""
model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE}
return model_update
@utils.trace_method
def delete_consistencygroup(self, context, group, volumes):
"""Driver entry point for deleting a consistency group.
:returns: Updated consistency group model and list of volume models
for the volumes that were deleted.
"""
model_update = {'status': fields.ConsistencyGroupStatus.DELETED}
volumes_model_update = []
for volume in volumes:
try:
self._delete_file(volume['id'], volume['name'])
volumes_model_update.append(
{'id': volume['id'], 'status': 'deleted'})
except Exception:
volumes_model_update.append(
{'id': volume['id'],
'status': 'error_deleting'})
LOG.exception("Volume %(vol)s in the consistency group "
"could not be deleted.", {'vol': volume})
return model_update, volumes_model_update
@utils.trace_method
def update_consistencygroup(self, context, group, add_volumes=None,
remove_volumes=None):
"""Driver entry point for updating a consistency group.
Since no actual CG construct is ever created in ONTAP, it is not
necessary to update any metadata on the backend. Since this is a NO-OP,
there is guaranteed to be no change in any of the volumes' statuses.
"""
return None, None, None
@utils.trace_method
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Creates a Cinder cgsnapshot object.
The Cinder cgsnapshot object is created by making use of an ONTAP CG
snapshot in order to provide write-order consistency for a set of
backing flexvols. First, a list of the flexvols backing the given
Cinder volumes in the CG is determined. An ONTAP CG snapshot of the
flexvols creates a write-order consistent snapshot of each backing
flexvol. For each Cinder volume in the CG, it is then necessary to
clone its volume from the ONTAP CG snapshot. The naming convention
used to create the clones indicates the clone's role as a Cinder
snapshot and its inclusion in a Cinder CG snapshot. The ONTAP CG
snapshots, of each backing flexvol, are deleted after the cloning
operation is completed.
:returns: An implicit update for the cgsnapshot and snapshot models
that is then used by the manager to set the models to
available.
"""
hosts = [snapshot['volume']['host'] for snapshot in snapshots]
flexvols = self._get_flexvol_names_from_hosts(hosts)
# Create snapshot for backing flexvol
self.zapi_client.create_cg_snapshot(flexvols, cgsnapshot['id'])
# Start clone process for snapshot files
for snapshot in snapshots:
self._clone_backing_file_for_volume(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=cgsnapshot['id'])
# Delete backing flexvol snapshots
for flexvol_name in flexvols:
try:
self.zapi_client.wait_for_busy_snapshot(
flexvol_name, cgsnapshot['id'])
self.zapi_client.delete_snapshot(
flexvol_name, cgsnapshot['id'])
except exception.SnapshotIsBusy:
self.zapi_client.mark_snapshot_for_deletion(
flexvol_name, cgsnapshot['id'])
return None, None
@utils.trace_method
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
"""Creates a CG from a either a cgsnapshot or group of cinder vols.
:returns: An implicit update for the volumes model that is
interpreted by the manager as a successful operation.
"""
LOG.debug("VOLUMES %s ", ', '.join([vol['id'] for vol in volumes]))
model_update = None
volumes_model_update = []
if cgsnapshot:
vols = zip(volumes, snapshots)
for volume, snapshot in vols:
update = self.create_volume_from_snapshot(
volume, snapshot)
update['id'] = volume['id']
volumes_model_update.append(update)
elif source_cg and source_vols:
hosts = [source_vol['host'] for source_vol in source_vols]
flexvols = self._get_flexvol_names_from_hosts(hosts)
# Create snapshot for backing flexvol
snapshot_name = 'snapshot-temp-' + source_cg['id']
self.zapi_client.create_cg_snapshot(flexvols, snapshot_name)
# Start clone process for new volumes
vols = zip(volumes, source_vols)
for volume, source_vol in vols:
self._clone_backing_file_for_volume(
source_vol['name'], volume['name'],
source_vol['id'], source_snapshot=snapshot_name)
volume_model_update = (
self._get_volume_model_update(volume) or {})
volume_model_update.update({
'id': volume['id'],
'provider_location': source_vol['provider_location'],
})
volumes_model_update.append(volume_model_update)
# Delete backing flexvol snapshots
for flexvol_name in flexvols:
self.zapi_client.wait_for_busy_snapshot(
flexvol_name, snapshot_name)
self.zapi_client.delete_snapshot(flexvol_name, snapshot_name)
else:
LOG.error("Unexpected set of parameters received when "
"creating consistency group from source.")
model_update = {'status': fields.ConsistencyGroupStatus.ERROR}
return model_update, volumes_model_update

View File

@ -1070,155 +1070,3 @@ class NetAppNfsDriver(driver.ManageableVD,
vol_path = os.path.join(volume['provider_location'], vol_str)
LOG.info('Cinder NFS volume with current path "%(cr)s" is '
'no longer being managed.', {'cr': vol_path})
@utils.trace_method
def create_consistencygroup(self, context, group):
"""Driver entry point for creating a consistency group.
ONTAP does not maintain an actual CG construct. As a result, no
communtication to the backend is necessary for consistency group
creation.
:return: Hard-coded model update for consistency group model.
"""
model_update = {'status': 'available'}
return model_update
@utils.trace_method
def delete_consistencygroup(self, context, group, volumes):
"""Driver entry point for deleting a consistency group.
:return: Updated consistency group model and list of volume models
for the volumes that were deleted.
"""
model_update = {'status': 'deleted'}
volumes_model_update = []
for volume in volumes:
try:
self._delete_file(volume['id'], volume['name'])
volumes_model_update.append(
{'id': volume['id'], 'status': 'deleted'})
except Exception:
volumes_model_update.append(
{'id': volume['id'], 'status': 'error_deleting'})
LOG.exception("Volume %(vol)s in the consistency group "
"could not be deleted.", {'vol': volume})
return model_update, volumes_model_update
@utils.trace_method
def update_consistencygroup(self, context, group, add_volumes=None,
remove_volumes=None):
"""Driver entry point for updating a consistency group.
Since no actual CG construct is ever created in ONTAP, it is not
necessary to update any metadata on the backend. Since this is a NO-OP,
there is guaranteed to be no change in any of the volumes' statuses.
"""
return None, None, None
@utils.trace_method
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Creates a Cinder cgsnapshot object.
The Cinder cgsnapshot object is created by making use of an ONTAP CG
snapshot in order to provide write-order consistency for a set of
backing flexvols. First, a list of the flexvols backing the given
Cinder volumes in the CG is determined. An ONTAP CG snapshot of the
flexvols creates a write-order consistent snapshot of each backing
flexvol. For each Cinder volume in the CG, it is then necessary to
clone its volume from the ONTAP CG snapshot. The naming convention
used to create the clones indicates the clone's role as a Cinder
snapshot and its inclusion in a Cinder CG snapshot. The ONTAP CG
snapshots, of each backing flexvol, are deleted after the cloning
operation is completed.
:return: An implicit update for the cgsnapshot and snapshot models that
is then used by the manager to set the models to available.
"""
hosts = [snapshot['volume']['host'] for snapshot in snapshots]
flexvols = self._get_flexvol_names_from_hosts(hosts)
# Create snapshot for backing flexvol
self.zapi_client.create_cg_snapshot(flexvols, cgsnapshot['id'])
# Start clone process for snapshot files
for snapshot in snapshots:
self._clone_backing_file_for_volume(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=cgsnapshot['id'])
# Delete backing flexvol snapshots
for flexvol_name in flexvols:
try:
self.zapi_client.wait_for_busy_snapshot(
flexvol_name, cgsnapshot['id'])
self.zapi_client.delete_snapshot(
flexvol_name, cgsnapshot['id'])
except exception.SnapshotIsBusy:
self.zapi_client.mark_snapshot_for_deletion(
flexvol_name, cgsnapshot['id'])
return None, None
@utils.trace_method
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Delete files backing each snapshot in the cgsnapshot."""
raise NotImplementedError()
@utils.trace_method
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
"""Creates a CG from a either a cgsnapshot or group of cinder vols.
:return: An implicit update for the volumes model that is
interpreted by the manager as a successful operation.
"""
LOG.debug("VOLUMES %s ", [dict(vol) for vol in volumes])
model_update = None
volumes_model_update = []
if cgsnapshot:
vols = zip(volumes, snapshots)
for volume, snapshot in vols:
update = self.create_volume_from_snapshot(
volume, snapshot)
update['id'] = volume['id']
volumes_model_update.append(update)
elif source_cg and source_vols:
hosts = [source_vol['host'] for source_vol in source_vols]
flexvols = self._get_flexvol_names_from_hosts(hosts)
# Create snapshot for backing flexvol
snapshot_name = 'snapshot-temp-' + source_cg['id']
self.zapi_client.create_cg_snapshot(flexvols, snapshot_name)
# Start clone process for new volumes
vols = zip(volumes, source_vols)
for volume, source_vol in vols:
self._clone_backing_file_for_volume(
source_vol['name'], volume['name'],
source_vol['id'], source_snapshot=snapshot_name)
volume_model_update = (
self._get_volume_model_update(volume) or {})
volume_model_update.update({
'id': volume['id'],
'provider_location': source_vol['provider_location'],
})
volumes_model_update.append(volume_model_update)
# Delete backing flexvol snapshots
for flexvol_name in flexvols:
self.zapi_client.wait_for_busy_snapshot(
flexvol_name, snapshot_name)
self.zapi_client.delete_snapshot(flexvol_name, snapshot_name)
else:
LOG.error("Unexpected set of parameters received when "
"creating consistency group from source.")
model_update = {}
model_update['status'] = 'error'
return model_update, volumes_model_update

View File

@ -269,6 +269,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
# Add driver capabilities and config info
pool['QoS_support'] = True
pool['consistencygroup_support'] = True
pool['consistent_group_snapshot_enabled'] = True
pool['multiattach'] = False
# Add up-to-date capacity info
@ -733,8 +734,8 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
return flexvols
@utils.trace_method
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Delete files backing each snapshot in the cgsnapshot.
def delete_group_snapshot(self, context, group_snapshot, snapshots):
"""Delete files backing each snapshot in the group snapshot.
:return: An implicit update of snapshot models that the manager will
interpret and subsequently set the model state to deleted.
@ -744,3 +745,166 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
return None, None
@utils.trace_method
def create_group(self, context, group):
"""Driver entry point for creating a generic volume group.
ONTAP does not maintain an actual group construct. As a result, no
communtication to the backend is necessary for generic volume group
creation.
:returns: Hard-coded model update for generic volume group model.
"""
model_update = {'status': fields.GroupStatus.AVAILABLE}
return model_update
@utils.trace_method
def delete_group(self, context, group, volumes):
"""Driver entry point for deleting a generic volume group.
:returns: Updated group model and list of volume models for the volumes
that were deleted.
"""
model_update = {'status': fields.GroupStatus.DELETED}
volumes_model_update = []
for volume in volumes:
try:
self._delete_file(volume['id'], volume['name'])
volumes_model_update.append(
{'id': volume['id'], 'status': 'deleted'})
except Exception:
volumes_model_update.append(
{'id': volume['id'],
'status': fields.GroupStatus.ERROR_DELETING})
LOG.exception("Volume %(vol)s in the group could not be "
"deleted.", {'vol': volume})
return model_update, volumes_model_update
@utils.trace_method
def update_group(self, context, group, add_volumes=None,
remove_volumes=None):
"""Driver entry point for updating a generic volume group.
Since no actual group construct is ever created in ONTAP, it is not
necessary to update any metadata on the backend. Since this is a NO-OP,
there is guaranteed to be no change in any of the volumes' statuses.
"""
return None, None, None
@utils.trace_method
def create_group_snapshot(self, context, group_snapshot, snapshots):
"""Creates a Cinder group snapshot object.
The Cinder group snapshot object is created by making use of an ONTAP
consistency group snapshot in order to provide write-order consistency
for a set of flexvols snapshots. First, a list of the flexvols backing
the given Cinder group must be gathered. An ONTAP group-snapshot of
these flexvols will create a snapshot copy of all the Cinder volumes in
the generic volume group. For each Cinder volume in the group, it is
then necessary to clone its backing file from the ONTAP cg-snapshot.
The naming convention used to for the clones is what indicates the
clone's role as a Cinder snapshot and its inclusion in a Cinder group.
The ONTAP cg-snapshot of the flexvols is deleted after the cloning
operation is completed.
:returns: An implicit update for the group snapshot and snapshot models
that is then used by the manager to set the models to
available.
"""
try:
if volume_utils.is_group_a_cg_snapshot_type(group_snapshot):
self._create_consistent_group_snapshot(group_snapshot,
snapshots)
else:
for snapshot in snapshots:
self._clone_backing_file_for_volume(
snapshot['volume_name'], snapshot['name'],
snapshot['volume_id'], is_snapshot=True)
except Exception as ex:
err_msg = (_("Create group snapshot failed (%s).") % ex)
LOG.exception(err_msg, resource=group_snapshot)
raise exception.NetAppDriverException(err_msg)
return None, None
def _create_consistent_group_snapshot(self, group_snapshot, snapshots):
hosts = [snapshot['volume']['host'] for snapshot in snapshots]
flexvols = self._get_flexvol_names_from_hosts(hosts)
# Create snapshot for backing flexvol
self.zapi_client.create_cg_snapshot(flexvols, group_snapshot['id'])
# Start clone process for snapshot files
for snapshot in snapshots:
self._clone_backing_file_for_volume(
snapshot['volume']['name'], snapshot['name'],
snapshot['volume']['id'], source_snapshot=group_snapshot['id'])
# Delete backing flexvol snapshots
for flexvol_name in flexvols:
try:
self.zapi_client.wait_for_busy_snapshot(
flexvol_name, group_snapshot['id'])
self.zapi_client.delete_snapshot(
flexvol_name, group_snapshot['id'])
except exception.SnapshotIsBusy:
self.zapi_client.mark_snapshot_for_deletion(
flexvol_name, group_snapshot['id'])
@utils.trace_method
def create_group_from_src(self, context, group, volumes,
group_snapshot=None, sorted_snapshots=None,
source_group=None, sorted_source_vols=None):
"""Creates a group from a group snapshot or a group of cinder vols.
:returns: An implicit update for the volumes model that is
interpreted by the manager as a successful operation.
"""
LOG.debug("VOLUMES %s ", ', '.join([vol['id'] for vol in volumes]))
model_update = None
volumes_model_update = []
if group_snapshot:
vols = zip(volumes, sorted_snapshots)
for volume, snapshot in vols:
update = self.create_volume_from_snapshot(
volume, snapshot)
update['id'] = volume['id']
volumes_model_update.append(update)
elif source_group and sorted_source_vols:
hosts = [source_vol['host'] for source_vol in sorted_source_vols]
flexvols = self._get_flexvol_names_from_hosts(hosts)
# Create snapshot for backing flexvol
snapshot_name = 'snapshot-temp-' + source_group['id']
self.zapi_client.create_cg_snapshot(flexvols, snapshot_name)
# Start clone process for new volumes
vols = zip(volumes, sorted_source_vols)
for volume, source_vol in vols:
self._clone_backing_file_for_volume(
source_vol['name'], volume['name'],
source_vol['id'], source_snapshot=snapshot_name)
volume_model_update = (
self._get_volume_model_update(volume) or {})
volume_model_update.update({
'id': volume['id'],
'provider_location': source_vol['provider_location'],
})
volumes_model_update.append(volume_model_update)
# Delete backing flexvol snapshots
for flexvol_name in flexvols:
self.zapi_client.wait_for_busy_snapshot(
flexvol_name, snapshot_name)
self.zapi_client.delete_snapshot(flexvol_name, snapshot_name)
else:
LOG.error("Unexpected set of parameters received when "
"creating group from source.")
model_update = {'status': fields.GroupStatus.ERROR}
return model_update, volumes_model_update

View File

@ -0,0 +1,4 @@
---
features:
- Added generic volume group capability to NetApp cDot drivers with
support for write consistent group snapshots.