NetApp cDOT: Support generic groups for block

Adding support for generic volume groups to NetApp cDOT's FC, and iSCSI
drivers.

CG methods are moved to the 7-mode block 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: I8290734817d171f6797c88d0a0d1a21f8b5789db
Implements: blueprint netapp-add-generic-group-support-cdot
This commit is contained in:
Chuck Fouts 2017-03-24 16:10:42 -04:00
parent 5f29cdc1e2
commit 68139326f6
10 changed files with 694 additions and 363 deletions

View File

@ -529,7 +529,6 @@ VG_VOLUME_SNAPSHOT = {
'id': VG_VOLUME_SNAPSHOT_ID,
'status': 'fake_status',
'volume_id': VG_VOLUME_ID,
}

View File

@ -25,8 +25,10 @@ from lxml import etree
import mock
from oslo_utils import timeutils
from oslo_utils import units
from cinder import exception
from cinder.objects import fields
from cinder import test
import cinder.tests.unit.volume.drivers.netapp.dataontap.client.fakes \
as client_fakes
@ -39,6 +41,7 @@ from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp.dataontap.performance import perf_7mode
from cinder.volume.drivers.netapp.dataontap.utils import utils as dot_utils
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
@ddt.ddt
@ -777,3 +780,188 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
result = self.library._get_backing_flexvol_names()
self.assertEqual('vol2', result[2])
def test_create_cgsnapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_extract_host = self.mock_object(
volume_utils, 'extract_host', return_value=fake.POOL_NAME)
mock_clone_lun = self.mock_object(self.library, '_clone_lun')
mock_busy = self.mock_object(
self.zapi_client, 'wait_for_busy_snapshot')
mock_delete_snapshot = self.mock_object(
self.zapi_client, 'delete_snapshot')
self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot])
mock_extract_host.assert_called_once_with(fake.CG_VOLUME['host'],
level='pool')
self.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_lun.assert_called_once_with(
fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME,
source_snapshot=fake.CG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
mock_delete_snapshot.assert_called_once_with(
fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_create_cgsnapshot_busy_snapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_extract_host = self.mock_object(
volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
mock_clone_lun = self.mock_object(self.library, '_clone_lun')
mock_busy = self.mock_object(
self.zapi_client, 'wait_for_busy_snapshot')
mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name'])
mock_delete_snapshot = self.mock_object(
self.zapi_client, 'delete_snapshot')
mock_mark_snapshot_for_deletion = self.mock_object(
self.zapi_client, 'mark_snapshot_for_deletion')
self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot])
mock_extract_host.assert_called_once_with(
fake.CG_VOLUME['host'], level='pool')
self.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_lun.assert_called_once_with(
fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME,
source_snapshot=fake.CG_SNAPSHOT_ID)
mock_delete_snapshot.assert_not_called()
mock_mark_snapshot_for_deletion.assert_called_once_with(
fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_delete_cgsnapshot(self):
mock_delete_snapshot = self.mock_object(
self.library, '_delete_lun')
self.library.delete_cgsnapshot(fake.CG_SNAPSHOT, [fake.CG_SNAPSHOT])
mock_delete_snapshot.assert_called_once_with(fake.CG_SNAPSHOT['name'])
def test_delete_cgsnapshot_not_found(self):
self.mock_object(block_base, 'LOG')
self.mock_object(self.library, '_get_lun_attr', return_value=None)
self.library.delete_cgsnapshot(fake.CG_SNAPSHOT, [fake.CG_SNAPSHOT])
self.assertEqual(0, block_base.LOG.error.call_count)
self.assertEqual(1, block_base.LOG.warning.call_count)
self.assertEqual(0, block_base.LOG.info.call_count)
def test_create_volume_with_cg(self):
volume_size_in_bytes = int(fake.CG_VOLUME_SIZE) * units.Gi
self._create_volume_test_helper()
self.library.create_volume(fake.CG_VOLUME)
self.library._create_lun.assert_called_once_with(
fake.POOL_NAME, fake.CG_VOLUME_NAME, volume_size_in_bytes,
fake.CG_LUN_METADATA, None)
self.library._get_volume_model_update.assert_called_once_with(
fake.CG_VOLUME)
self.assertEqual(0, self.library.
_mark_qos_policy_group_for_deletion.call_count)
self.assertEqual(0, block_base.LOG.error.call_count)
def _create_volume_test_helper(self):
self.mock_object(na_utils, 'get_volume_extra_specs')
self.mock_object(na_utils, 'log_extra_spec_warnings')
self.mock_object(block_base, 'LOG')
self.mock_object(volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=None)
self.mock_object(self.library, '_create_lun')
self.mock_object(self.library, '_create_lun_handle')
self.mock_object(self.library, '_add_lun_to_table')
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
self.mock_object(self.library, '_get_volume_model_update')
def test_create_consistency_group(self):
model_update = self.library.create_consistencygroup(
fake.CONSISTENCY_GROUP)
self.assertEqual('available', model_update['status'])
def test_delete_consistencygroup_volume_delete_failure(self):
self.mock_object(block_7mode, 'LOG')
self.mock_object(self.library, '_delete_lun', side_effect=Exception)
model_update, volumes = self.library.delete_consistencygroup(
fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('error_deleting', volumes[0]['status'])
self.assertEqual(1, block_7mode.LOG.exception.call_count)
def test_delete_consistencygroup_not_found(self):
self.mock_object(block_7mode, 'LOG')
self.mock_object(self.library, '_get_lun_attr', return_value=None)
model_update, volumes = self.library.delete_consistencygroup(
fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual(0, block_7mode.LOG.error.call_count)
self.assertEqual(0, block_7mode.LOG.info.call_count)
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src_cg_snapshot(self,
volume_model_update):
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination',
return_value=volume_model_update)
actual_return_value = self.library.create_consistencygroup_from_src(
fake.CONSISTENCY_GROUP, [fake.VOLUME], cgsnapshot=fake.CG_SNAPSHOT,
snapshots=[fake.CG_VOLUME_SNAPSHOT])
clone_source_to_destination_args = {
'name': fake.CG_SNAPSHOT['name'],
'size': fake.CG_SNAPSHOT['volume_size'],
}
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
if volume_model_update:
volume_model_update['id'] = fake.VOLUME['id']
expected_return_value = ((None, [volume_model_update])
if volume_model_update else (None, []))
self.assertEqual(expected_return_value, actual_return_value)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src_cg(self, volume_model_update):
lun_name = fake.SOURCE_CG_VOLUME['name']
mock_lun = block_base.NetAppLun(
lun_name, lun_name, '3', {'UUID': 'fake_uuid'})
self.mock_object(self.library, '_get_lun_from_table',
return_value=mock_lun)
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination',
return_value=volume_model_update)
actual_return_value = self.library.create_consistencygroup_from_src(
fake.CONSISTENCY_GROUP, [fake.VOLUME],
source_cg=fake.SOURCE_CONSISTENCY_GROUP,
source_vols=[fake.SOURCE_CG_VOLUME])
clone_source_to_destination_args = {
'name': fake.SOURCE_CG_VOLUME['name'],
'size': fake.SOURCE_CG_VOLUME['size'],
}
if volume_model_update:
volume_model_update['id'] = fake.VOLUME['id']
expected_return_value = ((None, [volume_model_update])
if volume_model_update else (None, []))
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
self.assertEqual(expected_return_value, actual_return_value)

View File

@ -1399,192 +1399,6 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.assertEqual('user1', data['discovery_auth_username'])
self.assertEqual('pass1', data['discovery_auth_password'])
def test_create_cgsnapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_extract_host = self.mock_object(
volume_utils, 'extract_host', return_value=fake.POOL_NAME)
mock_clone_lun = self.mock_object(self.library, '_clone_lun')
mock_busy = self.mock_object(
self.zapi_client, 'wait_for_busy_snapshot')
mock_delete_snapshot = self.mock_object(
self.zapi_client, 'delete_snapshot')
self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot])
mock_extract_host.assert_called_once_with(fake.CG_VOLUME['host'],
level='pool')
self.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_lun.assert_called_once_with(
fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME,
source_snapshot=fake.CG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
mock_delete_snapshot.assert_called_once_with(
fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_create_cgsnapshot_busy_snapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_extract_host = self.mock_object(
volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
mock_clone_lun = self.mock_object(self.library, '_clone_lun')
mock_busy = self.mock_object(
self.zapi_client, 'wait_for_busy_snapshot')
mock_busy.side_effect = exception.SnapshotIsBusy(snapshot['name'])
mock_delete_snapshot = self.mock_object(
self.zapi_client, 'delete_snapshot')
mock_mark_snapshot_for_deletion = self.mock_object(
self.zapi_client, 'mark_snapshot_for_deletion')
self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot])
mock_extract_host.assert_called_once_with(
fake.CG_VOLUME['host'], level='pool')
self.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_lun.assert_called_once_with(
fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME,
source_snapshot=fake.CG_SNAPSHOT_ID)
mock_delete_snapshot.assert_not_called()
mock_mark_snapshot_for_deletion.assert_called_once_with(
fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_delete_cgsnapshot(self):
mock_delete_snapshot = self.mock_object(
self.library, '_delete_lun')
self.library.delete_cgsnapshot(fake.CG_SNAPSHOT, [fake.CG_SNAPSHOT])
mock_delete_snapshot.assert_called_once_with(fake.CG_SNAPSHOT['name'])
def test_delete_cgsnapshot_not_found(self):
self.mock_object(block_base, 'LOG')
self.mock_object(self.library, '_get_lun_attr', return_value=None)
self.library.delete_cgsnapshot(fake.CG_SNAPSHOT, [fake.CG_SNAPSHOT])
self.assertEqual(0, block_base.LOG.error.call_count)
self.assertEqual(1, block_base.LOG.warning.call_count)
self.assertEqual(0, block_base.LOG.info.call_count)
def test_create_volume_with_cg(self):
volume_size_in_bytes = int(fake.CG_VOLUME_SIZE) * units.Gi
self._create_volume_test_helper()
self.library.create_volume(fake.CG_VOLUME)
self.library._create_lun.assert_called_once_with(
fake.POOL_NAME, fake.CG_VOLUME_NAME, volume_size_in_bytes,
fake.CG_LUN_METADATA, None)
self.library._get_volume_model_update.assert_called_once_with(
fake.CG_VOLUME)
self.assertEqual(0, self.library.
_mark_qos_policy_group_for_deletion.call_count)
self.assertEqual(0, block_base.LOG.error.call_count)
def _create_volume_test_helper(self):
self.mock_object(na_utils, 'get_volume_extra_specs')
self.mock_object(na_utils, 'log_extra_spec_warnings')
self.mock_object(block_base, 'LOG')
self.mock_object(volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
self.mock_object(self.library, '_setup_qos_for_volume',
return_value=None)
self.mock_object(self.library, '_create_lun')
self.mock_object(self.library, '_create_lun_handle')
self.mock_object(self.library, '_add_lun_to_table')
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
self.mock_object(self.library, '_get_volume_model_update')
def test_create_consistency_group(self):
model_update = self.library.create_consistencygroup(
fake.CONSISTENCY_GROUP)
self.assertEqual('available', model_update['status'])
def test_delete_consistencygroup_volume_delete_failure(self):
self.mock_object(block_base, 'LOG')
self.mock_object(self.library, '_delete_lun', side_effect=Exception)
model_update, volumes = self.library.delete_consistencygroup(
fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('error_deleting', volumes[0]['status'])
self.assertEqual(1, block_base.LOG.exception.call_count)
def test_delete_consistencygroup_not_found(self):
self.mock_object(block_base, 'LOG')
self.mock_object(self.library, '_get_lun_attr', return_value=None)
model_update, volumes = self.library.delete_consistencygroup(
fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual(0, block_base.LOG.error.call_count)
self.assertEqual(1, block_base.LOG.warning.call_count)
self.assertEqual(0, block_base.LOG.info.call_count)
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src_cg_snapshot(self,
volume_model_update):
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination',
return_value=volume_model_update)
actual_return_value = self.library.create_consistencygroup_from_src(
fake.CONSISTENCY_GROUP, [fake.VOLUME], cgsnapshot=fake.CG_SNAPSHOT,
snapshots=[fake.CG_VOLUME_SNAPSHOT])
clone_source_to_destination_args = {
'name': fake.CG_SNAPSHOT['name'],
'size': fake.CG_SNAPSHOT['volume_size'],
}
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
if volume_model_update:
volume_model_update['id'] = fake.VOLUME['id']
expected_return_value = ((None, [volume_model_update])
if volume_model_update else (None, []))
self.assertEqual(expected_return_value, actual_return_value)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_consistencygroup_from_src_cg(self, volume_model_update):
lun_name = fake.SOURCE_CG_VOLUME['name']
mock_lun = block_base.NetAppLun(
lun_name, lun_name, '3', {'UUID': 'fake_uuid'})
self.mock_object(self.library, '_get_lun_from_table',
return_value=mock_lun)
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination',
return_value=volume_model_update)
actual_return_value = self.library.create_consistencygroup_from_src(
fake.CONSISTENCY_GROUP, [fake.VOLUME],
source_cg=fake.SOURCE_CONSISTENCY_GROUP,
source_vols=[fake.SOURCE_CG_VOLUME])
clone_source_to_destination_args = {
'name': fake.SOURCE_CG_VOLUME['name'],
'size': fake.SOURCE_CG_VOLUME['size'],
}
if volume_model_update:
volume_model_update['id'] = fake.VOLUME['id']
expected_return_value = ((None, [volume_model_update])
if volume_model_update else (None, []))
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
self.assertEqual(expected_return_value, actual_return_value)
def test_add_looping_tasks(self):
mock_add_task = self.mock_object(self.library.loopingcalls, 'add_task')
mock_call_snap_cleanup = self.mock_object(

View File

@ -22,6 +22,7 @@ import ddt
import mock
from cinder import exception
from cinder.objects import fields
from cinder import test
import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
from cinder.tests.unit.volume.drivers.netapp.dataontap.utils import fakes as\
@ -37,6 +38,7 @@ from cinder.volume.drivers.netapp.dataontap.utils import data_motion
from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls
from cinder.volume.drivers.netapp.dataontap.utils import utils as dot_utils
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
@ddt.ddt
@ -404,6 +406,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
'pool_name': 'vola',
'QoS_support': True,
'consistencygroup_support': True,
'consistent_group_snapshot_enabled': True,
'reserved_percentage': 5,
'max_over_subscription_ratio': 10.0,
'multiattach': False,
@ -749,3 +752,178 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library._get_backing_flexvol_names()
mock_ssc_library.assert_called_once_with()
def test_create_group(self):
model_update = self.library.create_group(
fake.VOLUME_GROUP)
self.assertEqual('available', model_update['status'])
def test_delete_group_volume_delete_failure(self):
self.mock_object(block_cmode, 'LOG')
self.mock_object(self.library, '_delete_lun', side_effect=Exception)
model_update, volumes = self.library.delete_group(
fake.VOLUME_GROUP, [fake.VG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('error_deleting', volumes[0]['status'])
self.assertEqual(1, block_cmode.LOG.exception.call_count)
def test_update_group(self):
model_update, add_volumes_update, remove_volumes_update = (
self.library.update_group(fake.VOLUME_GROUP))
self.assertIsNone(model_update)
self.assertIsNone(add_volumes_update)
self.assertIsNone(remove_volumes_update)
def test_delete_group_not_found(self):
self.mock_object(block_cmode, 'LOG')
self.mock_object(self.library, '_get_lun_attr', return_value=None)
model_update, volumes = self.library.delete_group(
fake.VOLUME_GROUP, [fake.VG_VOLUME])
self.assertEqual(0, block_cmode.LOG.error.call_count)
self.assertEqual(0, block_cmode.LOG.info.call_count)
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
def test_create_group_snapshot_raise_exception(self):
self.mock_object(volume_utils, 'is_group_a_cg_snapshot_type',
return_value=True)
mock_extract_host = self.mock_object(
volume_utils, 'extract_host', return_value=fake.POOL_NAME)
self.mock_object(self.zapi_client, 'create_cg_snapshot',
side_effect=netapp_api.NaApiError)
self.assertRaises(exception.NetAppDriverException,
self.library.create_group_snapshot,
fake.VOLUME_GROUP,
[fake.VG_SNAPSHOT])
mock_extract_host.assert_called_once_with(
fake.VG_SNAPSHOT['volume']['host'], level='pool')
def test_create_group_snapshot(self):
self.mock_object(volume_utils, 'is_group_a_cg_snapshot_type',
return_value=False)
fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
fake.LUN_SIZE, fake.LUN_METADATA)
self.mock_object(self.library, '_get_lun_from_table',
return_value=fake_lun)
mock__clone_lun = self.mock_object(self.library, '_clone_lun')
model_update, snapshots_model_update = (
self.library.create_group_snapshot(fake.VOLUME_GROUP,
[fake.SNAPSHOT]))
self.assertIsNone(model_update)
self.assertIsNone(snapshots_model_update)
mock__clone_lun.assert_called_once_with(fake_lun.name,
fake.SNAPSHOT['name'],
space_reserved='false',
is_snapshot=True)
def test_create_consistent_group_snapshot(self):
self.mock_object(volume_utils, 'is_group_a_cg_snapshot_type',
return_value=True)
self.mock_object(volume_utils, 'extract_host',
return_value=fake.POOL_NAME)
mock_create_cg_snapshot = self.mock_object(
self.zapi_client, 'create_cg_snapshot')
mock__clone_lun = self.mock_object(self.library, '_clone_lun')
mock_wait_for_busy_snapshot = self.mock_object(
self.zapi_client, 'wait_for_busy_snapshot')
mock_delete_snapshot = self.mock_object(
self.zapi_client, 'delete_snapshot')
model_update, snapshots_model_update = (
self.library.create_group_snapshot(fake.VOLUME_GROUP,
[fake.VG_SNAPSHOT]))
self.assertIsNone(model_update)
self.assertIsNone(snapshots_model_update)
mock_create_cg_snapshot.assert_called_once_with(
set([fake.POOL_NAME]), fake.VOLUME_GROUP['id'])
mock__clone_lun.assert_called_once_with(
fake.VG_SNAPSHOT['volume']['name'],
fake.VG_SNAPSHOT['name'],
source_snapshot=fake.VOLUME_GROUP['id'])
mock_wait_for_busy_snapshot.assert_called_once_with(
fake.POOL_NAME, fake.VOLUME_GROUP['id'])
mock_delete_snapshot.assert_called_once_with(
fake.POOL_NAME, fake.VOLUME_GROUP['id'])
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_group_from_src_snapshot(self, volume_model_update):
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination',
return_value=volume_model_update)
actual_return_value = self.library.create_group_from_src(
fake.VOLUME_GROUP, [fake.VOLUME], group_snapshot=fake.VG_SNAPSHOT,
snapshots=[fake.VG_VOLUME_SNAPSHOT])
clone_source_to_destination_args = {
'name': fake.VG_SNAPSHOT['name'],
'size': fake.VG_SNAPSHOT['volume_size'],
}
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
if volume_model_update:
volume_model_update['id'] = fake.VOLUME['id']
expected_return_value = ((None, [volume_model_update])
if volume_model_update else (None, []))
self.assertEqual(expected_return_value, actual_return_value)
@ddt.data(None,
{'replication_status': fields.ReplicationStatus.ENABLED})
def test_create_group_from_src_group(self, volume_model_update):
lun_name = fake.SOURCE_VG_VOLUME['name']
mock_lun = block_base.NetAppLun(
lun_name, lun_name, '3', {'UUID': 'fake_uuid'})
self.mock_object(self.library, '_get_lun_from_table',
return_value=mock_lun)
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination',
return_value=volume_model_update)
actual_return_value = self.library.create_group_from_src(
fake.VOLUME_GROUP, [fake.VOLUME],
source_group=fake.SOURCE_VOLUME_GROUP,
source_vols=[fake.SOURCE_VG_VOLUME])
clone_source_to_destination_args = {
'name': fake.SOURCE_VG_VOLUME['name'],
'size': fake.SOURCE_VG_VOLUME['size'],
}
if volume_model_update:
volume_model_update['id'] = fake.VOLUME['id']
expected_return_value = ((None, [volume_model_update])
if volume_model_update else (None, []))
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
self.assertEqual(expected_return_value, actual_return_value)
def test_delete_group_snapshot(self):
mock__delete_lun = self.mock_object(self.library, '_delete_lun')
model_update, snapshots_model_update = (
self.library.delete_group_snapshot(fake.VOLUME_GROUP,
[fake.VG_SNAPSHOT]))
self.assertIsNone(model_update)
self.assertIsNone(snapshots_model_update)
mock__delete_lun.assert_called_once_with(fake.VG_SNAPSHOT['name'])

View File

@ -54,15 +54,11 @@ class NetAppBlockStorageDriverInterfaceTestCase(test.TestCase):
"""
# Get local functions of each driver interface
iscsi_7mode = self._get_local_functions(self.iscsi_7mode_driver)
iscsi_cmode = self._get_local_functions(self.iscsi_cmode_driver)
fc_7mode = self._get_local_functions(self.fc_7mode_driver)
fc_cmode = self._get_local_functions(self.fc_cmode_driver)
# Ensure NetApp block storage driver shims are identical
self.assertSetEqual(iscsi_7mode, iscsi_cmode)
self.assertSetEqual(iscsi_7mode, fc_7mode)
self.assertSetEqual(iscsi_7mode, fc_cmode)
self.assertSetEqual(iscsi_cmode, fc_cmode)
def _get_local_functions(self, obj):
"""Get function names of an object without superclass functions."""

View File

@ -32,6 +32,7 @@ import six
from cinder import exception
from cinder.i18n import _
from cinder.objects import fields
from cinder import utils
from cinder.volume import configuration
from cinder.volume.drivers.netapp.dataontap import block_base
@ -40,6 +41,7 @@ from cinder.volume.drivers.netapp.dataontap.performance import perf_7mode
from cinder.volume.drivers.netapp.dataontap.utils import utils as dot_utils
from cinder.volume.drivers.netapp import options as na_opts
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
@ -471,3 +473,139 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary):
def _get_backing_flexvol_names(self):
"""Returns a list of backing flexvol names."""
return self.volume_list or []
def create_consistencygroup(self, group):
"""Driver entry point for creating a consistency group.
ONTAP does not maintain an actual CG construct. As a result, no
communication 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
def delete_consistencygroup(self, 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_lun(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
def update_consistencygroup(self, 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
def create_cgsnapshot(self, cgsnapshot, snapshots):
"""Creates a Cinder cgsnapshot object.
The Cinder cgsnapshot object is created by making use of an
ephemeral ONTAP CG in order to provide write-order consistency for a
set of flexvol snapshots. First, a list of the flexvols backing the
given Cinder CG must be gathered. An ONTAP cg-snapshot of these
flexvols will create a snapshot copy of all the Cinder volumes in the
CG group. For each Cinder volume in the CG, it is then necessary to
clone its backing LUN from the ONTAP cg-snapshot. The naming convention
used for the clones is what indicates the clone's role as a Cinder
snapshot and its inclusion in a Cinder CG. The ONTAP CG-snapshot of
the flexvols is no longer required after having cloned the LUNs
backing the Cinder volumes in the Cinder CG.
:returns: An implicit update for cgsnapshot and snapshots models that
is interpreted by the manager to set their models to
available.
"""
flexvols = set()
for snapshot in snapshots:
flexvols.add(volume_utils.extract_host(snapshot['volume']['host'],
level='pool'))
self.zapi_client.create_cg_snapshot(flexvols, cgsnapshot['id'])
for snapshot in snapshots:
self._clone_lun(snapshot['volume']['name'], snapshot['name'],
source_snapshot=cgsnapshot['id'])
for flexvol in flexvols:
try:
self.zapi_client.wait_for_busy_snapshot(
flexvol, cgsnapshot['id'])
self.zapi_client.delete_snapshot(
flexvol, cgsnapshot['id'])
except exception.SnapshotIsBusy:
self.zapi_client.mark_snapshot_for_deletion(
flexvol, cgsnapshot['id'])
return None, None
def delete_cgsnapshot(self, cgsnapshot, snapshots):
"""Delete LUNs backing each snapshot in the cgsnapshot.
:returns: An implicit update for snapshots models that is interpreted
by the manager to set their models to deleted.
"""
for snapshot in snapshots:
self._delete_lun(snapshot['name'])
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
return None, None
def create_consistencygroup_from_src(self, 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]))
volume_model_updates = []
if cgsnapshot:
vols = zip(volumes, snapshots)
for volume, snapshot in vols:
source = {
'name': snapshot['name'],
'size': snapshot['volume_size'],
}
volume_model_update = self._clone_source_to_destination(
source, volume)
if volume_model_update is not None:
volume_model_update['id'] = volume['id']
volume_model_updates.append(volume_model_update)
else:
vols = zip(volumes, source_vols)
for volume, old_src_vref in vols:
src_lun = self._get_lun_from_table(old_src_vref['name'])
source = {'name': src_lun.name, 'size': old_src_vref['size']}
volume_model_update = self._clone_source_to_destination(
source, volume)
if volume_model_update is not None:
volume_model_update['id'] = volume['id']
volume_model_updates.append(volume_model_update)
return None, volume_model_updates

View File

@ -312,7 +312,9 @@ class NetAppBlockStorageLibrary(object):
This driver implements snapshots by using efficient single-file
(LUN) cloning.
"""
self._create_snapshot(snapshot)
def _create_snapshot(self, snapshot):
vol_name = snapshot['volume_name']
snapshot_name = snapshot['name']
lun = self._get_lun_from_table(vol_name)
@ -1035,141 +1037,6 @@ class NetAppBlockStorageLibrary(object):
return target_wwpns, init_targ_map, num_paths
def create_consistencygroup(self, group):
"""Driver entry point for creating a consistency group.
ONTAP does not maintain an actual CG construct. As a result, no
communication 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
def delete_consistencygroup(self, 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_lun(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
def update_consistencygroup(self, 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
def create_cgsnapshot(self, cgsnapshot, snapshots):
"""Creates a Cinder cgsnapshot object.
The Cinder cgsnapshot object is created by making use of an
ephemeral ONTAP CG in order to provide write-order consistency for a
set of flexvol snapshots. First, a list of the flexvols backing the
given Cinder CG must be gathered. An ONTAP cg-snapshot of these
flexvols will create a snapshot copy of all the Cinder volumes in the
CG group. For each Cinder volume in the CG, it is then necessary to
clone its backing LUN from the ONTAP cg-snapshot. The naming convention
used for the clones is what indicates the clone's role as a Cinder
snapshot and its inclusion in a Cinder CG. The ONTAP CG-snapshot of
the flexvols is no longer required after having cloned the LUNs
backing the Cinder volumes in the Cinder CG.
:return: An implicit update for cgsnapshot and snapshots models that
is interpreted by the manager to set their models to
available.
"""
flexvols = set()
for snapshot in snapshots:
flexvols.add(volume_utils.extract_host(snapshot['volume']['host'],
level='pool'))
self.zapi_client.create_cg_snapshot(flexvols, cgsnapshot['id'])
for snapshot in snapshots:
self._clone_lun(snapshot['volume']['name'], snapshot['name'],
source_snapshot=cgsnapshot['id'])
for flexvol in flexvols:
try:
self.zapi_client.wait_for_busy_snapshot(
flexvol, cgsnapshot['id'])
self.zapi_client.delete_snapshot(
flexvol, cgsnapshot['id'])
except exception.SnapshotIsBusy:
self.zapi_client.mark_snapshot_for_deletion(
flexvol, cgsnapshot['id'])
return None, None
def delete_cgsnapshot(self, cgsnapshot, snapshots):
"""Delete LUNs backing each snapshot in the cgsnapshot.
:return: An implicit update for snapshots models that is interpreted
by the manager to set their models to deleted.
"""
for snapshot in snapshots:
self._delete_lun(snapshot['name'])
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
return None, None
def create_consistencygroup_from_src(self, 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])
volume_model_updates = []
if cgsnapshot:
vols = zip(volumes, snapshots)
for volume, snapshot in vols:
source = {
'name': snapshot['name'],
'size': snapshot['volume_size'],
}
volume_model_update = self._clone_source_to_destination(
source, volume)
if volume_model_update is not None:
volume_model_update['id'] = volume['id']
volume_model_updates.append(volume_model_update)
else:
vols = zip(volumes, source_vols)
for volume, old_src_vref in vols:
src_lun = self._get_lun_from_table(old_src_vref['name'])
source = {'name': src_lun.name, 'size': old_src_vref['size']}
volume_model_update = self._clone_source_to_destination(
source, volume)
if volume_model_update is not None:
volume_model_update['id'] = volume['id']
volume_model_updates.append(volume_model_update)
return None, volume_model_updates
def _get_backing_flexvol_names(self):
"""Returns a list of backing flexvol names."""
raise NotImplementedError()

View File

@ -40,6 +40,7 @@ from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls
from cinder.volume.drivers.netapp.dataontap.utils import utils as dot_utils
from cinder.volume.drivers.netapp import options as na_opts
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
@ -301,6 +302,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
pool['QoS_support'] = True
pool['multiattach'] = False
pool['consistencygroup_support'] = True
pool['consistent_group_snapshot_enabled'] = True
pool['reserved_percentage'] = self.reserved_percentage
pool['max_over_subscription_ratio'] = (
self.max_over_subscription_ratio)
@ -461,3 +463,152 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
def _get_backing_flexvol_names(self):
"""Returns a list of backing flexvol names."""
return self.ssc_library.get_ssc().keys()
def create_group(self, group):
"""Driver entry point for creating a generic volume group.
ONTAP does not maintain an actual Group construct. As a result, no
communication 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
def delete_group(self, group, volumes):
"""Driver entry point for deleting a 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_lun(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 group could not be "
"deleted.", {'vol': volume})
return model_update, volumes_model_update
def update_group(self, 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
def create_group_snapshot(self, group_snapshot, snapshots):
"""Creates a Cinder group snapshot object.
The Cinder group snapshot object is created by making use of an
ephemeral ONTAP consistency group snapshot in order to provide
write-order consistency for a set of flexvol 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 LUN from
the ONTAP cg-snapshot. The naming convention used 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 no longer
required after having cloned the LUNs backing the Cinder volumes in
the Cinder group.
:returns: An implicit update for group snapshot and snapshots models
that is interpreted by the manager to set their 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._create_snapshot(snapshot)
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):
flexvols = set()
for snapshot in snapshots:
flexvols.add(volume_utils.extract_host(
snapshot['volume']['host'], level='pool'))
self.zapi_client.create_cg_snapshot(flexvols, group_snapshot['id'])
for snapshot in snapshots:
self._clone_lun(snapshot['volume']['name'], snapshot['name'],
source_snapshot=group_snapshot['id'])
for flexvol in flexvols:
try:
self.zapi_client.wait_for_busy_snapshot(
flexvol, group_snapshot['id'])
self.zapi_client.delete_snapshot(
flexvol, group_snapshot['id'])
except exception.SnapshotIsBusy:
self.zapi_client.mark_snapshot_for_deletion(
flexvol, group_snapshot['id'])
def delete_group_snapshot(self, group_snapshot, snapshots):
"""Delete LUNs backing each snapshot in the group snapshot.
:returns: An implicit update for snapshots models that is interpreted
by the manager to set their models to deleted.
"""
for snapshot in snapshots:
self._delete_lun(snapshot['name'])
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
return None, None
def create_group_from_src(self, group, volumes, group_snapshot=None,
snapshots=None, source_group=None,
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]))
volume_model_updates = []
if group_snapshot:
vols = zip(volumes, snapshots)
for volume, snapshot in vols:
source = {
'name': snapshot['name'],
'size': snapshot['volume_size'],
}
volume_model_update = self._clone_source_to_destination(
source, volume)
if volume_model_update is not None:
volume_model_update['id'] = volume['id']
volume_model_updates.append(volume_model_update)
else:
vols = zip(volumes, source_vols)
for volume, old_src_vref in vols:
src_lun = self._get_lun_from_table(old_src_vref['name'])
source = {'name': src_lun.name, 'size': old_src_vref['size']}
volume_model_update = self._clone_source_to_destination(
source, volume)
if volume_model_update is not None:
volume_model_update['id'] = volume['id']
volume_model_updates.append(volume_model_update)
return None, volume_model_updates

View File

@ -106,29 +106,29 @@ class NetAppCmodeFibreChannelDriver(driver.BaseVD,
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def create_group(self, context, group):
return self.library.create_group(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def delete_group(self, context, group, volumes):
return self.library.delete_group(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(group, add_volumes=None,
remove_volumes=None)
def update_group(self, context, group, add_volumes=None,
remove_volumes=None):
return self.library.update_group(group, add_volumes=None,
remove_volumes=None)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def create_group_snapshot(self, context, group_snapshot, snapshots):
return self.library.create_group_snapshot(group_snapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def delete_group_snapshot(self, context, group_snapshot, snapshots):
return self.library.delete_group_snapshot(group_snapshot, snapshots)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
source_cg=source_cg, source_vols=source_vols)
def create_group_from_src(self, context, group, volumes,
group_snapshot=None, snapshots=None,
source_group=None, source_vols=None):
return self.library.create_group_from_src(
group, volumes, group_snapshot=group_snapshot, snapshots=snapshots,
source_group=source_group, source_vols=source_vols)
def failover_host(self, context, volumes, secondary_id=None, groups=None):
return self.library.failover_host(

View File

@ -103,29 +103,29 @@ class NetAppCmodeISCSIDriver(driver.BaseVD,
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def create_group(self, context, group):
return self.library.create_group(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def delete_group(self, context, group, volumes):
return self.library.delete_group(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(group, add_volumes=None,
remove_volumes=None)
def update_group(self, context, group, add_volumes=None,
remove_volumes=None):
return self.library.update_group(group, add_volumes=None,
remove_volumes=None)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def create_group_snapshot(self, context, group_snapshot, snapshots):
return self.library.create_group_snapshot(group_snapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def delete_group_snapshot(self, context, group_snapshot, snapshots):
return self.library.delete_group_snapshot(group_snapshot, snapshots)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
source_cg=source_cg, source_vols=source_vols)
def create_group_from_src(self, context, group, volumes,
group_snapshot=None, snapshots=None,
source_group=None, source_vols=None):
return self.library.create_group_from_src(
group, volumes, group_snapshot=group_snapshot, snapshots=snapshots,
source_group=source_group, source_vols=source_vols)
def failover_host(self, context, volumes, secondary_id=None, groups=None):
return self.library.failover_host(