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:
parent
5f29cdc1e2
commit
68139326f6
cinder
tests/unit/volume/drivers/netapp/dataontap
volume/drivers/netapp/dataontap
@ -529,7 +529,6 @@ VG_VOLUME_SNAPSHOT = {
|
||||
'id': VG_VOLUME_SNAPSHOT_ID,
|
||||
'status': 'fake_status',
|
||||
'volume_id': VG_VOLUME_ID,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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'])
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user