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:
parent
7c8901e764
commit
0215fcc022
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Added generic volume group capability to NetApp cDot drivers with
|
||||
support for write consistent group snapshots.
|
Loading…
Reference in New Issue
Block a user