[NetApp driver] Control snapshot folder visibility

By default, every share created allows access of its .snapshot
where files of each taken snapshot can be accessed. As per
some use-cases, it is desirable to not allow access to
the .snapshot folder.

This can now be done by using the extra_spec netapp:hide_snapdir.
When set to True, it will hide the .snapshot directory for every
newly created share.

Also, for existing shares, a config option named
netapp_reset_snapdir_visibility can be used to reset
all existing shares' setting to either hide or display
the .snapshot visibility on driver restarts.

Implements blueprint: netapp-snapdir-visibility
Change-Id: I30619bb13de528538b9887b00f39482f91a8db49
This commit is contained in:
Rodrigo Barbieri 2018-05-15 17:41:39 -03:00
parent 84105ebde3
commit 9e3c4c8126
9 changed files with 249 additions and 16 deletions

View File

@ -1629,6 +1629,37 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
errors[0].get_child_content('error-code'),
errors[0].get_child_content('error-message'))
@na_utils.trace
def set_volume_snapdir_access(self, volume_name, hide_snapdir):
"""Set volume snapshot directory visibility."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-snapshot-attributes': {
'snapdir-access-enabled': six.text_type(
not hide_snapdir).lower(),
},
},
},
}
result = self.send_request('volume-modify-iter', api_args)
failures = result.get_child_content('num-failed')
if failures and int(failures) > 0:
failure_list = result.get_child_by_name(
'failure-list') or netapp_api.NaElement('none')
errors = failure_list.get_children()
if errors:
raise netapp_api.NaApiError(
errors[0].get_child_content('error-code'),
errors[0].get_child_content('error-message'))
@na_utils.trace
def set_volume_security_style(self, volume_name, security_style='unix'):
"""Set volume security style"""
@ -1673,7 +1704,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
thin_provisioned=False, snapshot_policy=None,
language=None, dedup_enabled=False,
compression_enabled=False, max_files=None,
qos_policy_group=None, **options):
qos_policy_group=None, hide_snapdir=None,
**options):
"""Update backend volume for a share as necessary."""
api_args = {
'query': {
@ -1711,6 +1743,12 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'volume-qos-attributes'] = {
'policy-group-name': qos_policy_group,
}
if hide_snapdir in (True, False):
# Value of hide_snapdir needs to be inverted for ZAPI parameter
api_args['attributes']['volume-attributes'][
'volume-snapshot-attributes'][
'snapdir-access-enabled'] = six.text_type(
not hide_snapdir).lower()
self.send_request('volume-modify-iter', api_args)

View File

@ -71,9 +71,6 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
def shrink_share(self, share, new_size, **kwargs):
self.library.shrink_share(share, new_size, **kwargs)
def ensure_share(self, context, share, **kwargs):
pass
def manage_existing(self, share, driver_options):
raise NotImplementedError
@ -237,3 +234,9 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
def get_configured_ip_versions(self):
return self.library.get_configured_ip_versions()
def get_backend_info(self, context):
return self.library.get_backend_info(context)
def ensure_shares(self, context, shares):
return self.library.ensure_shares(context, shares)

View File

@ -71,9 +71,6 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
def shrink_share(self, share, new_size, **kwargs):
self.library.shrink_share(share, new_size, **kwargs)
def ensure_share(self, context, share, **kwargs):
pass
def manage_existing(self, share, driver_options):
return self.library.manage_existing(share, driver_options)
@ -253,3 +250,9 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
def get_configured_ip_versions(self):
return self.library.get_configured_ip_versions()
def get_backend_info(self, context):
return self.library.get_backend_info(context)
def ensure_shares(self, context, shares):
return self.library.ensure_shares(context, shares)

View File

@ -70,9 +70,11 @@ class NetAppCmodeFileStorageLibrary(object):
'netapp:dedup': 'dedup_enabled',
'netapp:compression': 'compression_enabled',
'netapp:split_clone_on_create': 'split',
'netapp:hide_snapdir': 'hide_snapdir',
}
STRING_QUALIFIED_EXTRA_SPECS_MAP = {
'netapp:snapshot_policy': 'snapshot_policy',
'netapp:language': 'language',
'netapp:max_files': 'max_files',
@ -92,6 +94,12 @@ class NetAppCmodeFileStorageLibrary(object):
'netapp:maxbpspergib': 'maxbpspergib',
}
HIDE_SNAPDIR_CFG_MAP = {
'visible': False,
'hidden': True,
'default': None,
}
SIZE_DEPENDENT_QOS_SPECS = {'maxiopspergib', 'maxbpspergib'}
def __init__(self, driver_name, **kwargs):
@ -496,6 +504,8 @@ class NetAppCmodeFileStorageLibrary(object):
# create it as the 'data-protection' type
provisioning_options['volume_type'] = 'dp'
hide_snapdir = provisioning_options.pop('hide_snapdir')
LOG.debug('Creating share %(share)s on pool %(pool)s with '
'provisioning options %(options)s',
{'share': share_name, 'pool': pool_name,
@ -505,6 +515,19 @@ class NetAppCmodeFileStorageLibrary(object):
snapshot_reserve=self.configuration.
netapp_volume_snapshot_reserve_percent, **provisioning_options)
if hide_snapdir:
self._apply_snapdir_visibility(
hide_snapdir, share_name, vserver_client)
def _apply_snapdir_visibility(
self, hide_snapdir, share_name, vserver_client):
LOG.debug('Applying snapshot visibility according to hide_snapdir '
'value of %(hide_snapdir)s on share %(share)s.',
{'hide_snapdir': hide_snapdir, 'share': share_name})
vserver_client.set_volume_snapdir_access(share_name, hide_snapdir)
@na_utils.trace
def _remap_standard_boolean_extra_specs(self, extra_specs):
"""Replace standard boolean extra specs with NetApp-specific ones."""
@ -746,6 +769,8 @@ class NetAppCmodeFileStorageLibrary(object):
provisioning_options = self._get_provisioning_options_for_share(
share, vserver)
hide_snapdir = provisioning_options.pop('hide_snapdir')
LOG.debug('Creating share from snapshot %s', snapshot['id'])
vserver_client.create_volume_clone(share_name, parent_share_name,
parent_snapshot_name,
@ -753,6 +778,10 @@ class NetAppCmodeFileStorageLibrary(object):
if share['size'] > snapshot['size']:
vserver_client.set_volume_size(share_name, share['size'])
if hide_snapdir:
self._apply_snapdir_visibility(
hide_snapdir, share_name, vserver_client)
@na_utils.trace
def _share_exists(self, share_name, vserver_client):
return vserver_client.volume_exists(share_name)
@ -2281,3 +2310,21 @@ class NetAppCmodeFileStorageLibrary(object):
msg = _("Volume move operation did not complete after cut-over "
"was triggered. Retries exhausted. Not retrying.")
raise exception.NetAppException(message=msg)
def get_backend_info(self, context):
snapdir_visibility = self.configuration.netapp_reset_snapdir_visibility
return {
'snapdir_visibility': snapdir_visibility,
}
def ensure_shares(self, context, shares):
cfg_snapdir = self.configuration.netapp_reset_snapdir_visibility
hide_snapdir = self.HIDE_SNAPDIR_CFG_MAP[cfg_snapdir.lower()]
if hide_snapdir is not None:
for share in shares:
share_server = share.get('share_server')
vserver, vserver_client = self._get_vserver(
share_server=share_server)
share_name = self._get_backend_share_name(share['id'])
self._apply_snapdir_visibility(
hide_snapdir, share_name, vserver_client)

View File

@ -99,7 +99,17 @@ netapp_provisioning_opts = [
max=90,
default=5,
help='The percentage of share space set aside as reserve for '
'snapshot usage; valid values range from 0 to 90.'), ]
'snapshot usage; valid values range from 0 to 90.'),
cfg.StrOpt('netapp_reset_snapdir_visibility',
choices=['visible', 'hidden', 'default'],
default="default",
help="This option forces all existing shares to have their "
"snapshot directory visibility set to either 'visible' or "
"'hidden' during driver startup. If set to 'default', "
"nothing will be changed during startup. This will not "
"affect new shares, which will have their snapshot "
"directory always visible, unless toggled by the share "
"type extra spec 'netapp:hide_snapdir'."), ]
netapp_cluster_opts = [
cfg.StrOpt('netapp_vserver',

View File

@ -3009,7 +3009,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
dedup_enabled=True,
compression_enabled=False,
max_files=fake.MAX_FILES,
qos_policy_group=fake.QOS_POLICY_GROUP_NAME)
qos_policy_group=fake.QOS_POLICY_GROUP_NAME,
hide_snapdir=True)
volume_modify_iter_api_args = {
'query': {
@ -3030,6 +3031,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
},
'volume-snapshot-attributes': {
'snapshot-policy': fake.SNAPSHOT_POLICY_NAME,
'snapdir-access-enabled': 'false'
},
'volume-space-attributes': {
'space-guarantee': 'none',
@ -3037,6 +3039,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
'volume-qos-attributes': {
'policy-group-name': fake.QOS_POLICY_GROUP_NAME,
},
},
},
}
@ -3128,6 +3131,49 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_has_calls([
mock.call('volume-modify-iter', volume_modify_iter_args)])
@ddt.data(True, False)
def test_set_volume_snapdir_access(self, hide_snapdir):
api_response = netapp_api.NaElement(
fake.VOLUME_MODIFY_ITER_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
self.client.set_volume_snapdir_access(fake.SHARE_NAME, hide_snapdir)
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': fake.SHARE_NAME
}
}
},
'attributes': {
'volume-attributes': {
'volume-snapshot-attributes': {
'snapdir-access-enabled': six.text_type(
not hide_snapdir).lower(),
},
},
},
}
self.client.send_request.assert_called_once_with(
'volume-modify-iter', api_args)
def test_set_volume_snapdir_access_api_error(self):
api_response = netapp_api.NaElement(
fake.VOLUME_MODIFY_ITER_ERROR_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
self.assertRaises(netapp_api.NaApiError,
self.client.set_volume_size,
fake.SHARE_NAME,
10)
def test_set_volume_size_api_error(self):
api_response = netapp_api.NaElement(

View File

@ -668,14 +668,18 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
vserver_client)
self.assertEqual('fake_export_location', result)
def test_allocate_container(self):
@ddt.data(False, True)
def test_allocate_container(self, hide_snapdir):
provisioning_options = copy.deepcopy(fake.PROVISIONING_OPTIONS)
provisioning_options['hide_snapdir'] = hide_snapdir
self.mock_object(self.library, '_get_backend_share_name', mock.Mock(
return_value=fake.SHARE_NAME))
self.mock_object(share_utils, 'extract_host', mock.Mock(
return_value=fake.POOL_NAME))
mock_get_provisioning_opts = self.mock_object(
self.library, '_get_provisioning_options_for_share',
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
mock.Mock(return_value=provisioning_options))
vserver_client = mock.Mock()
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
@ -691,6 +695,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
language='en-US', dedup_enabled=True, split=True, encrypt=False,
compression_enabled=False, max_files=5000, snapshot_reserve=8)
if hide_snapdir:
vserver_client.set_volume_snapdir_access.assert_called_once_with(
fake.SHARE_NAME, hide_snapdir)
else:
vserver_client.set_volume_snapdir_access.assert_not_called()
def test_remap_standard_boolean_extra_specs(self):
extra_specs = copy.deepcopy(fake.OVERLAPPING_EXTRA_SPEC)
@ -863,6 +873,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'dedup_enabled': False,
'split': False,
'encrypt': False,
'hide_snapdir': False,
}
self.assertEqual(expected, result)
@ -887,6 +898,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'dedup_enabled': False,
'compression_enabled': False,
'split': False,
'hide_snapdir': False,
}
result = self.library._get_boolean_provisioning_options(
@ -1019,15 +1031,20 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.AGGREGATES[1],
fake.EXTRA_SPEC)
@ddt.data({'provider_location': None, 'size': 50},
{'provider_location': 'fake_location', 'size': 30},
{'provider_location': 'fake_location', 'size': 20})
@ddt.data({'provider_location': None, 'size': 50, 'hide_snapdir': True},
{'provider_location': 'fake_location', 'size': 30,
'hide_snapdir': False},
{'provider_location': 'fake_location', 'size': 20,
'hide_snapdir': True})
@ddt.unpack
def test_allocate_container_from_snapshot(self, provider_location, size):
def test_allocate_container_from_snapshot(
self, provider_location, size, hide_snapdir):
provisioning_options = copy.deepcopy(fake.PROVISIONING_OPTIONS)
provisioning_options['hide_snapdir'] = hide_snapdir
mock_get_provisioning_opts = self.mock_object(
self.library, '_get_provisioning_options_for_share',
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
mock.Mock(return_value=provisioning_options))
vserver = fake.VSERVER1
vserver_client = mock.Mock()
original_snapshot_size = 20
@ -1061,6 +1078,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
else:
vserver_client.set_volume_size.assert_not_called()
if hide_snapdir:
vserver_client.set_volume_snapdir_access.assert_called_once_with(
fake.SHARE_NAME, hide_snapdir)
else:
vserver_client.set_volume_snapdir_access.assert_not_called()
def test_share_exists(self):
vserver_client = mock.Mock()
@ -4920,3 +4943,53 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fallback_create.assert_called_once_with(self.context, share_group,
snap_dict,
share_server=fake.SHARE_SERVER)
@ddt.data('default', 'hidden', 'visible')
def test_get_backend_info(self, snapdir):
self.library.configuration.netapp_reset_snapdir_visibility = snapdir
expected = {'snapdir_visibility': snapdir}
result = self.library.get_backend_info(self.context)
self.assertEqual(expected, result)
@ddt.data('default', 'hidden')
def test_ensure_shares(self, snapdir_cfg):
shares = [
fake_share.fake_share_instance(id='s-1',
share_server='fake_server_1'),
fake_share.fake_share_instance(id='s-2',
share_server='fake_server_2'),
fake_share.fake_share_instance(id='s-3',
share_server='fake_server_2')
]
vserver_client = mock.Mock()
self.mock_object(
self.library, '_get_vserver',
mock.Mock(side_effect=[
(fake.VSERVER1, vserver_client),
(fake.VSERVER2, vserver_client),
(fake.VSERVER2, vserver_client)
]))
(self.library.configuration.
netapp_reset_snapdir_visibility) = snapdir_cfg
self.library.ensure_shares(self.context, shares)
if snapdir_cfg == 'default':
self.library._get_vserver.assert_not_called()
vserver_client.set_volume_snapdir_access.assert_not_called()
else:
self.library._get_vserver.assert_has_calls([
mock.call(share_server='fake_server_1'),
mock.call(share_server='fake_server_2'),
mock.call(share_server='fake_server_2'),
])
vserver_client.set_volume_snapdir_access.assert_has_calls([
mock.call('share_s_1', True),
mock.call('share_s_2', True),
mock.call('share_s_3', True),
])

View File

@ -171,6 +171,7 @@ PROVISIONING_OPTIONS = {
'max_files': 5000,
'split': True,
'encrypt': False,
'hide_snapdir': False,
}
PROVISIONING_OPTIONS_WITH_QOS = copy.deepcopy(PROVISIONING_OPTIONS)
@ -182,6 +183,7 @@ PROVISIONING_OPTIONS_BOOLEAN = {
'dedup_enabled': False,
'compression_enabled': False,
'split': False,
'hide_snapdir': False,
}
PROVISIONING_OPTIONS_BOOLEAN_THIN_PROVISIONED_TRUE = {

View File

@ -0,0 +1,11 @@
---
features:
- Snapshot directories of shares created by the NetApp driver
can now be controlled through extra-specs for newly created
shares and through a config option for existing shares.
upgrades:
- A new config option ``netapp_reset_snapdir_visibility`` has
been added to the NetApp driver, allowing existing shares to
have their snapshot directory visibility setting changed at
driver startup.