NetApp cDOT driver configurable clone split
Define a NetApp extra spec that is used to define a share type that selects whether an initial clone split will be processed as part of the create-from-snapshot workflow. Change-Id: I7370f8342b8a5a10861e6e8080555959b0c88d62 Implements: blueprint netapp-cdot-clone-split-control
This commit is contained in:
parent
4c2a69c7e1
commit
d42b3f801c
@ -1207,7 +1207,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
thin_provisioned=False, snapshot_policy=None,
|
thin_provisioned=False, snapshot_policy=None,
|
||||||
language=None, dedup_enabled=False,
|
language=None, dedup_enabled=False,
|
||||||
compression_enabled=False, max_files=None,
|
compression_enabled=False, max_files=None,
|
||||||
snapshot_reserve=None, volume_type='rw'):
|
snapshot_reserve=None, volume_type='rw', **options):
|
||||||
|
|
||||||
"""Creates a volume."""
|
"""Creates a volume."""
|
||||||
api_args = {
|
api_args = {
|
||||||
@ -1361,7 +1361,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
def manage_volume(self, aggregate_name, volume_name,
|
def manage_volume(self, aggregate_name, volume_name,
|
||||||
thin_provisioned=False, snapshot_policy=None,
|
thin_provisioned=False, snapshot_policy=None,
|
||||||
language=None, dedup_enabled=False,
|
language=None, dedup_enabled=False,
|
||||||
compression_enabled=False, max_files=None):
|
compression_enabled=False, max_files=None, **options):
|
||||||
"""Update volume as needed to bring under management as a share."""
|
"""Update volume as needed to bring under management as a share."""
|
||||||
api_args = {
|
api_args = {
|
||||||
'query': {
|
'query': {
|
||||||
@ -1703,7 +1703,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def create_volume_clone(self, volume_name, parent_volume_name,
|
def create_volume_clone(self, volume_name, parent_volume_name,
|
||||||
parent_snapshot_name=None):
|
parent_snapshot_name=None, split=False, **options):
|
||||||
"""Clones a volume."""
|
"""Clones a volume."""
|
||||||
api_args = {
|
api_args = {
|
||||||
'volume': volume_name,
|
'volume': volume_name,
|
||||||
@ -1713,6 +1713,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
}
|
}
|
||||||
self.send_request('volume-clone-create', api_args)
|
self.send_request('volume-clone-create', api_args)
|
||||||
|
|
||||||
|
if split:
|
||||||
|
self.split_volume_clone(volume_name)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def split_volume_clone(self, volume_name):
|
def split_volume_clone(self, volume_name):
|
||||||
"""Begins splitting a clone from its parent."""
|
"""Begins splitting a clone from its parent."""
|
||||||
|
@ -62,6 +62,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
'netapp:thin_provisioned': 'thin_provisioned',
|
'netapp:thin_provisioned': 'thin_provisioned',
|
||||||
'netapp:dedup': 'dedup_enabled',
|
'netapp:dedup': 'dedup_enabled',
|
||||||
'netapp:compression': 'compression_enabled',
|
'netapp:compression': 'compression_enabled',
|
||||||
|
'netapp:split_clone_on_create': 'split',
|
||||||
}
|
}
|
||||||
STRING_QUALIFIED_EXTRA_SPECS_MAP = {
|
STRING_QUALIFIED_EXTRA_SPECS_MAP = {
|
||||||
'netapp:snapshot_policy': 'snapshot_policy',
|
'netapp:snapshot_policy': 'snapshot_policy',
|
||||||
@ -390,10 +391,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
msg = _("Pool is not available in the share host field.")
|
msg = _("Pool is not available in the share host field.")
|
||||||
raise exception.InvalidHost(reason=msg)
|
raise exception.InvalidHost(reason=msg)
|
||||||
|
|
||||||
extra_specs = share_types.get_extra_specs_from_share(share)
|
provisioning_options = self._get_provisioning_options_for_share(share)
|
||||||
extra_specs = self._remap_standard_boolean_extra_specs(extra_specs)
|
|
||||||
self._check_extra_specs_validity(share, extra_specs)
|
|
||||||
provisioning_options = self._get_provisioning_options(extra_specs)
|
|
||||||
if replica:
|
if replica:
|
||||||
# If this volume is intended to be a replication destination,
|
# If this volume is intended to be a replication destination,
|
||||||
# create it as the 'data-protection' type
|
# create it as the 'data-protection' type
|
||||||
@ -527,6 +526,20 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
# provisioning methods from the client API library.
|
# provisioning methods from the client API library.
|
||||||
return dict(zip(provisioning_args, provisioning_values))
|
return dict(zip(provisioning_args, provisioning_values))
|
||||||
|
|
||||||
|
@na_utils.trace
|
||||||
|
def _get_provisioning_options_for_share(self, share):
|
||||||
|
"""Return provisioning options from a share.
|
||||||
|
|
||||||
|
Starting with a share, this method gets the extra specs, rationalizes
|
||||||
|
NetApp vs. standard extra spec values, ensures their validity, and
|
||||||
|
returns them in a form suitable for passing to various API client
|
||||||
|
methods.
|
||||||
|
"""
|
||||||
|
extra_specs = share_types.get_extra_specs_from_share(share)
|
||||||
|
extra_specs = self._remap_standard_boolean_extra_specs(extra_specs)
|
||||||
|
self._check_extra_specs_validity(share, extra_specs)
|
||||||
|
return self._get_provisioning_options(extra_specs)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _get_provisioning_options(self, specs):
|
def _get_provisioning_options(self, specs):
|
||||||
"""Return a merged result of string and binary provisioning options."""
|
"""Return a merged result of string and binary provisioning options."""
|
||||||
@ -567,9 +580,13 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
parent_snapshot_name = snapshot_name_func(self, snapshot['id'])
|
parent_snapshot_name = snapshot_name_func(self, snapshot['id'])
|
||||||
else:
|
else:
|
||||||
parent_snapshot_name = snapshot['provider_location']
|
parent_snapshot_name = snapshot['provider_location']
|
||||||
|
|
||||||
|
provisioning_options = self._get_provisioning_options_for_share(share)
|
||||||
|
|
||||||
LOG.debug('Creating share from snapshot %s', snapshot['id'])
|
LOG.debug('Creating share from snapshot %s', snapshot['id'])
|
||||||
vserver_client.create_volume_clone(share_name, parent_share_name,
|
vserver_client.create_volume_clone(share_name, parent_share_name,
|
||||||
parent_snapshot_name)
|
parent_snapshot_name,
|
||||||
|
**provisioning_options)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
def _share_exists(self, share_name, vserver_client):
|
def _share_exists(self, share_name, vserver_client):
|
||||||
|
@ -2981,6 +2981,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
def test_create_volume_clone(self):
|
def test_create_volume_clone(self):
|
||||||
|
|
||||||
self.mock_object(self.client, 'send_request')
|
self.mock_object(self.client, 'send_request')
|
||||||
|
self.mock_object(self.client, 'split_volume_clone')
|
||||||
|
|
||||||
self.client.create_volume_clone(fake.SHARE_NAME,
|
self.client.create_volume_clone(fake.SHARE_NAME,
|
||||||
fake.PARENT_SHARE_NAME,
|
fake.PARENT_SHARE_NAME,
|
||||||
@ -2995,6 +2996,33 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.client.send_request.assert_has_calls([
|
self.client.send_request.assert_has_calls([
|
||||||
mock.call('volume-clone-create', volume_clone_create_args)])
|
mock.call('volume-clone-create', volume_clone_create_args)])
|
||||||
|
self.assertFalse(self.client.split_volume_clone.called)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_create_volume_clone_split(self, split):
|
||||||
|
|
||||||
|
self.mock_object(self.client, 'send_request')
|
||||||
|
self.mock_object(self.client, 'split_volume_clone')
|
||||||
|
|
||||||
|
self.client.create_volume_clone(fake.SHARE_NAME,
|
||||||
|
fake.PARENT_SHARE_NAME,
|
||||||
|
fake.PARENT_SNAPSHOT_NAME,
|
||||||
|
split=split)
|
||||||
|
|
||||||
|
volume_clone_create_args = {
|
||||||
|
'volume': fake.SHARE_NAME,
|
||||||
|
'parent-volume': fake.PARENT_SHARE_NAME,
|
||||||
|
'parent-snapshot': fake.PARENT_SNAPSHOT_NAME,
|
||||||
|
'junction-path': '/%s' % fake.SHARE_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.send_request.assert_has_calls([
|
||||||
|
mock.call('volume-clone-create', volume_clone_create_args)])
|
||||||
|
if split:
|
||||||
|
self.client.split_volume_clone.assert_called_once_with(
|
||||||
|
fake.SHARE_NAME)
|
||||||
|
else:
|
||||||
|
self.assertFalse(self.client.split_volume_clone.called)
|
||||||
|
|
||||||
@ddt.data(None,
|
@ddt.data(None,
|
||||||
mock.Mock(side_effect=netapp_api.NaApiError(
|
mock.Mock(side_effect=netapp_api.NaApiError(
|
||||||
|
@ -597,14 +597,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
return_value=fake.SHARE_NAME))
|
return_value=fake.SHARE_NAME))
|
||||||
self.mock_object(share_utils, 'extract_host', mock.Mock(
|
self.mock_object(share_utils, 'extract_host', mock.Mock(
|
||||||
return_value=fake.POOL_NAME))
|
return_value=fake.POOL_NAME))
|
||||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
self.mock_object(
|
||||||
mock.Mock(return_value=fake.EXTRA_SPEC))
|
self.library, '_get_provisioning_options_for_share',
|
||||||
mock_remap_standard_boolean_extra_specs = self.mock_object(
|
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
|
||||||
self.library, '_remap_standard_boolean_extra_specs',
|
|
||||||
mock.Mock(return_value=fake.EXTRA_SPEC))
|
|
||||||
self.mock_object(self.library, '_check_boolean_extra_specs_validity')
|
|
||||||
self.mock_object(self.library, '_get_boolean_provisioning_options',
|
|
||||||
mock.Mock(return_value=fake.PROVISIONING_OPTIONS))
|
|
||||||
vserver_client = mock.Mock()
|
vserver_client = mock.Mock()
|
||||||
|
|
||||||
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
|
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
|
||||||
@ -613,10 +608,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
vserver_client.create_volume.assert_called_once_with(
|
vserver_client.create_volume.assert_called_once_with(
|
||||||
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
|
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
|
||||||
thin_provisioned=True, snapshot_policy='default',
|
thin_provisioned=True, snapshot_policy='default',
|
||||||
language='en-US', dedup_enabled=True,
|
language='en-US', dedup_enabled=True, split=True,
|
||||||
compression_enabled=False, max_files=5000, snapshot_reserve=8)
|
compression_enabled=False, max_files=5000, snapshot_reserve=8)
|
||||||
mock_remap_standard_boolean_extra_specs.assert_called_once_with(
|
|
||||||
fake.EXTRA_SPEC)
|
|
||||||
|
|
||||||
def test_remap_standard_boolean_extra_specs(self):
|
def test_remap_standard_boolean_extra_specs(self):
|
||||||
|
|
||||||
@ -631,12 +624,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
return_value=fake.SHARE_NAME))
|
return_value=fake.SHARE_NAME))
|
||||||
self.mock_object(share_utils, 'extract_host', mock.Mock(
|
self.mock_object(share_utils, 'extract_host', mock.Mock(
|
||||||
return_value=fake.POOL_NAME))
|
return_value=fake.POOL_NAME))
|
||||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
self.mock_object(
|
||||||
mock.Mock(return_value=fake.EXTRA_SPEC))
|
self.library, '_get_provisioning_options_for_share',
|
||||||
|
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
|
||||||
self.mock_object(self.library, '_check_boolean_extra_specs_validity')
|
|
||||||
self.mock_object(self.library, '_get_boolean_provisioning_options',
|
|
||||||
mock.Mock(return_value=fake.PROVISIONING_OPTIONS))
|
|
||||||
vserver_client = mock.Mock()
|
vserver_client = mock.Mock()
|
||||||
|
|
||||||
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
|
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
|
||||||
@ -645,7 +635,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
vserver_client.create_volume.assert_called_once_with(
|
vserver_client.create_volume.assert_called_once_with(
|
||||||
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
|
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
|
||||||
thin_provisioned=True, snapshot_policy='default',
|
thin_provisioned=True, snapshot_policy='default',
|
||||||
language='en-US', dedup_enabled=True,
|
language='en-US', dedup_enabled=True, split=True,
|
||||||
compression_enabled=False, max_files=5000,
|
compression_enabled=False, max_files=5000,
|
||||||
snapshot_reserve=8, volume_type='dp')
|
snapshot_reserve=8, volume_type='dp')
|
||||||
|
|
||||||
@ -729,6 +719,32 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
fake.EXTRA_SPEC_SHARE, fake.INVALID_EXTRA_SPEC_COMBO,
|
fake.EXTRA_SPEC_SHARE, fake.INVALID_EXTRA_SPEC_COMBO,
|
||||||
list(self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP))
|
list(self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP))
|
||||||
|
|
||||||
|
def test_get_provisioning_options_for_share(self):
|
||||||
|
|
||||||
|
mock_get_extra_specs_from_share = self.mock_object(
|
||||||
|
share_types, 'get_extra_specs_from_share',
|
||||||
|
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||||
|
mock_remap_standard_boolean_extra_specs = self.mock_object(
|
||||||
|
self.library, '_remap_standard_boolean_extra_specs',
|
||||||
|
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||||
|
mock_check_extra_specs_validity = self.mock_object(
|
||||||
|
self.library, '_check_extra_specs_validity')
|
||||||
|
mock_get_provisioning_options = self.mock_object(
|
||||||
|
self.library, '_get_provisioning_options',
|
||||||
|
mock.Mock(return_value=fake.PROVISIONING_OPTIONS))
|
||||||
|
|
||||||
|
result = self.library._get_provisioning_options_for_share(
|
||||||
|
fake.EXTRA_SPEC_SHARE)
|
||||||
|
|
||||||
|
self.assertEqual(fake.PROVISIONING_OPTIONS, result)
|
||||||
|
mock_get_extra_specs_from_share.assert_called_once_with(
|
||||||
|
fake.EXTRA_SPEC_SHARE)
|
||||||
|
mock_remap_standard_boolean_extra_specs.assert_called_once_with(
|
||||||
|
fake.EXTRA_SPEC)
|
||||||
|
mock_check_extra_specs_validity.assert_called_once_with(
|
||||||
|
fake.EXTRA_SPEC_SHARE, fake.EXTRA_SPEC)
|
||||||
|
mock_get_provisioning_options.assert_called_once_with(fake.EXTRA_SPEC)
|
||||||
|
|
||||||
def test_get_provisioning_options(self):
|
def test_get_provisioning_options(self):
|
||||||
result = self.library._get_provisioning_options(fake.EXTRA_SPEC)
|
result = self.library._get_provisioning_options(fake.EXTRA_SPEC)
|
||||||
|
|
||||||
@ -752,6 +768,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
'thin_provisioned': False,
|
'thin_provisioned': False,
|
||||||
'compression_enabled': False,
|
'compression_enabled': False,
|
||||||
'dedup_enabled': False,
|
'dedup_enabled': False,
|
||||||
|
'split': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
@ -775,6 +792,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
'thin_provisioned': False,
|
'thin_provisioned': False,
|
||||||
'dedup_enabled': False,
|
'dedup_enabled': False,
|
||||||
'compression_enabled': False,
|
'compression_enabled': False,
|
||||||
|
'split': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
result = self.library._get_boolean_provisioning_options(
|
result = self.library._get_boolean_provisioning_options(
|
||||||
@ -846,23 +864,31 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
|||||||
fake.AGGREGATES[1],
|
fake.AGGREGATES[1],
|
||||||
fake.EXTRA_SPEC)
|
fake.EXTRA_SPEC)
|
||||||
|
|
||||||
def test_allocate_container_from_snapshot(self):
|
@ddt.data(None, 'fake_location')
|
||||||
|
def test_allocate_container_from_snapshot(self, provider_location):
|
||||||
|
|
||||||
|
self.mock_object(
|
||||||
|
self.library, '_get_provisioning_options_for_share',
|
||||||
|
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
|
||||||
vserver_client = mock.Mock()
|
vserver_client = mock.Mock()
|
||||||
|
|
||||||
|
fake_snapshot = copy.deepcopy(fake.SNAPSHOT)
|
||||||
|
fake_snapshot['provider_location'] = provider_location
|
||||||
|
|
||||||
self.library._allocate_container_from_snapshot(fake.SHARE,
|
self.library._allocate_container_from_snapshot(fake.SHARE,
|
||||||
fake.SNAPSHOT,
|
fake_snapshot,
|
||||||
vserver_client)
|
vserver_client)
|
||||||
|
|
||||||
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
|
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
|
||||||
parent_share_name = self.library._get_backend_share_name(
|
parent_share_name = self.library._get_backend_share_name(
|
||||||
fake.SNAPSHOT['share_id'])
|
fake.SNAPSHOT['share_id'])
|
||||||
parent_snapshot_name = self.library._get_backend_snapshot_name(
|
parent_snapshot_name = self.library._get_backend_snapshot_name(
|
||||||
fake.SNAPSHOT['id'])
|
fake.SNAPSHOT['id']) if not provider_location else 'fake_location'
|
||||||
vserver_client.create_volume_clone.assert_called_once_with(
|
vserver_client.create_volume_clone.assert_called_once_with(
|
||||||
share_name,
|
share_name, parent_share_name, parent_snapshot_name,
|
||||||
parent_share_name,
|
thin_provisioned=True, snapshot_policy='default',
|
||||||
parent_snapshot_name)
|
language='en-US', dedup_enabled=True, split=True,
|
||||||
|
compression_enabled=False, max_files=5000)
|
||||||
|
|
||||||
def test_share_exists(self):
|
def test_share_exists(self):
|
||||||
|
|
||||||
|
@ -109,6 +109,7 @@ EXTRA_SPEC = {
|
|||||||
'netapp:dedup': 'True',
|
'netapp:dedup': 'True',
|
||||||
'netapp:compression': 'false',
|
'netapp:compression': 'false',
|
||||||
'netapp:max_files': 5000,
|
'netapp:max_files': 5000,
|
||||||
|
'netapp:split_clone_on_create': 'true',
|
||||||
'netapp_disk_type': 'FCAL',
|
'netapp_disk_type': 'FCAL',
|
||||||
'netapp_raid_type': 'raid4',
|
'netapp_raid_type': 'raid4',
|
||||||
}
|
}
|
||||||
@ -120,12 +121,14 @@ PROVISIONING_OPTIONS = {
|
|||||||
'dedup_enabled': True,
|
'dedup_enabled': True,
|
||||||
'compression_enabled': False,
|
'compression_enabled': False,
|
||||||
'max_files': 5000,
|
'max_files': 5000,
|
||||||
|
'split': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
PROVISIONING_OPTIONS_BOOLEAN = {
|
PROVISIONING_OPTIONS_BOOLEAN = {
|
||||||
'thin_provisioned': True,
|
'thin_provisioned': True,
|
||||||
'dedup_enabled': False,
|
'dedup_enabled': False,
|
||||||
'compression_enabled': False,
|
'compression_enabled': False,
|
||||||
|
'split': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
PROVISIONING_OPTIONS_BOOLEAN_THIN_PROVISIONED_TRUE = {
|
PROVISIONING_OPTIONS_BOOLEAN_THIN_PROVISIONED_TRUE = {
|
||||||
@ -135,6 +138,7 @@ PROVISIONING_OPTIONS_BOOLEAN_THIN_PROVISIONED_TRUE = {
|
|||||||
'dedup_enabled': False,
|
'dedup_enabled': False,
|
||||||
'compression_enabled': False,
|
'compression_enabled': False,
|
||||||
'max_files': None,
|
'max_files': None,
|
||||||
|
'split': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
PROVISIONING_OPTIONS_STRING = {
|
PROVISIONING_OPTIONS_STRING = {
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- NetApp cDOT driver now supports a scoped extra-spec
|
||||||
|
``netapp:split_clone_on_create`` to be used in share types when creating
|
||||||
|
shares (NetApp FlexClone) from snapshots. If this extra-spec is not
|
||||||
|
included, or set to ``false``, the cDOT driver will perform the clone-split
|
||||||
|
only if/when the parent snapshot is being deleted.
|
Loading…
x
Reference in New Issue
Block a user