[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:
parent
84105ebde3
commit
9e3c4c8126
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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(
|
||||
|
@ -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),
|
||||
])
|
||||
|
@ -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 = {
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user