[NetApp] Add support for FPolicy native mode

This patch adds support for automated creation of FPolicy policies
and association to a share. The FPolicy configuration can be added using
 the extra-specs 'netapp:fpolicy_extensions_to_include',
'netapp:fpolicy_extensions_to_exclude' and 'netapp:fpolicy_file_operations'.

Change-Id: I661de95bfb6f8e68b3a8c58663bb6055e9b809f6
Implements: bp netapp-fpolicy-support
Signed-off-by: Douglas Viroel <viroel@gmail.com>
This commit is contained in:
Douglas Viroel 2021-02-09 18:51:45 -03:00
parent 1515701df0
commit 0b04d8d671
12 changed files with 1938 additions and 72 deletions

View File

@ -40,6 +40,7 @@ EAPINOTFOUND = '13005'
ESNAPSHOTNOTALLOWED = '13023'
EVOLUMEOFFLINE = '13042'
EINTERNALERROR = '13114'
EINVALIDINPUTERROR = '13115'
EDUPLICATEENTRY = '13130'
EVOLNOTCLONE = '13170'
EVOLMOVE_CANNOT_MOVE_TO_CFO = '13633'
@ -57,6 +58,9 @@ EANOTHER_OP_ACTIVE = '17131'
ERELATION_NOT_QUIESCED = '17127'
ESOURCE_IS_DIFFERENT = '17105'
EVOL_CLONE_BEING_SPLIT = '17151'
EPOLICYNOTFOUND = '18251'
EEVENTNOTFOUND = '18253'
ESCOPENOTFOUND = '18259'
ESVMDR_CANNOT_PERFORM_OP_FOR_STATUS = '18815'

View File

@ -4870,3 +4870,403 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
def is_svm_dr_supported(self):
return self.features.SVM_DR
def create_fpolicy_event(self, event_name, protocol, file_operations):
"""Creates a new fpolicy policy event.
:param event_name: name of the new fpolicy event
:param protocol: name of protocol for which event is created. Possible
values are: 'nfsv3', 'nfsv4' or 'cifs'.
:param file_operations: name of file operations to be monitored. Values
should be provided as list of strings.
"""
api_args = {
'event-name': event_name,
'protocol': protocol,
'file-operations': [],
}
for file_op in file_operations:
api_args['file-operations'].append({'fpolicy-operation': file_op})
self.send_request('fpolicy-policy-event-create', api_args)
def delete_fpolicy_event(self, event_name):
"""Deletes a fpolicy policy event.
:param event_name: name of the event to be deleted
"""
try:
self.send_request('fpolicy-policy-event-delete',
{'event-name': event_name})
except netapp_api.NaApiError as e:
if e.code in [netapp_api.EEVENTNOTFOUND,
netapp_api.EOBJECTNOTFOUND]:
msg = _("FPolicy event %s not found.")
LOG.debug(msg, event_name)
else:
raise exception.NetAppException(message=e.message)
def get_fpolicy_events(self, event_name=None, protocol=None,
file_operations=None):
"""Retrives a list of fpolicy events.
:param event_name: name of the fpolicy event
:param protocol: name of protocol. Possible values are: 'nfsv3',
'nfsv4' or 'cifs'.
:param file_operations: name of file operations to be monitored. Values
should be provided as list of strings.
:returns List of policy events or empty list
"""
event_options_config = {}
if event_name:
event_options_config['event-name'] = event_name
if protocol:
event_options_config['protocol'] = protocol
if file_operations:
event_options_config['file-operations'] = []
for file_op in file_operations:
event_options_config['file-operations'].append(
{'fpolicy-operation': file_op})
api_args = {
'query': {
'fpolicy-event-options-config': event_options_config,
},
}
result = self.send_iter_request('fpolicy-policy-event-get-iter',
api_args)
fpolicy_events = []
if self._has_records(result):
try:
fpolicy_events = []
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
for event_info in attributes_list.get_children():
name = event_info.get_child_content('event-name')
proto = event_info.get_child_content('protocol')
file_operations_child = event_info.get_child_by_name(
'file-operations') or netapp_api.NaElement('none')
operations = [operation.get_content()
for operation in
file_operations_child.get_children()]
fpolicy_events.append({
'event-name': name,
'protocol': proto,
'file-operations': operations
})
except AttributeError:
msg = _('Could not retrieve fpolicy policy event information.')
raise exception.NetAppException(msg)
return fpolicy_events
def create_fpolicy_policy(self, fpolicy_name, events, engine='native'):
"""Creates a fpolicy policy resource.
:param fpolicy_name: name of the fpolicy policy to be created.
:param events: list of event names for file access monitoring.
:param engine: name of the engine to be used.
"""
api_args = {
'policy-name': fpolicy_name,
'events': [],
'engine-name': engine
}
for event in events:
api_args['events'].append({'event-name': event})
self.send_request('fpolicy-policy-create', api_args)
def delete_fpolicy_policy(self, policy_name):
"""Deletes a fpolicy policy event.
:param policy_name: name of the policy to be deleted.
"""
try:
self.send_request('fpolicy-policy-delete',
{'policy-name': policy_name})
except netapp_api.NaApiError as e:
if e.code in [netapp_api.EPOLICYNOTFOUND,
netapp_api.EOBJECTNOTFOUND]:
msg = _("FPolicy policy %s not found.")
LOG.debug(msg, policy_name)
else:
raise exception.NetAppException(message=e.message)
def get_fpolicy_policies(self, policy_name=None, engine_name='native',
event_names=[]):
"""Retrieve one or more fpolicy policies.
:param policy_name: name of the policy to be retrieved
:param engine_name: name of the engine
:param event_names: list of event names that must be associated to the
fpolicy policy
:return: list of fpolicy policies or empty list
"""
policy_info = {}
if policy_name:
policy_info['policy-name'] = policy_name
if engine_name:
policy_info['engine-name'] = engine_name
if event_names:
policy_info['events'] = []
for event_name in event_names:
policy_info['events'].append({'event-name': event_name})
api_args = {
'query': {
'fpolicy-policy-info': policy_info,
},
}
result = self.send_iter_request('fpolicy-policy-get-iter', api_args)
fpolicy_policies = []
if self._has_records(result):
try:
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
for policy_info in attributes_list.get_children():
name = policy_info.get_child_content('policy-name')
engine = policy_info.get_child_content('engine-name')
events_child = policy_info.get_child_by_name(
'events') or netapp_api.NaElement('none')
events = [event.get_content()
for event in events_child.get_children()]
fpolicy_policies.append({
'policy-name': name,
'engine-name': engine,
'events': events
})
except AttributeError:
msg = _('Could not retrieve fpolicy policy information.')
raise exception.NetAppException(message=msg)
return fpolicy_policies
def create_fpolicy_scope(self, policy_name, share_name,
extensions_to_include=None,
extensions_to_exclude=None):
"""Assings a file scope to an existing fpolicy policy.
:param policy_name: name of the policy to associate with the new scope.
:param share_name: name of the share to be associated with the new
scope.
:param extensions_to_include: file extensions included for screening.
Values should be provided as comma separated list
:param extensions_to_exclude: file extensions excluded for screening.
Values should be provided as comma separated list
"""
api_args = {
'policy-name': policy_name,
'shares-to-include': {
'string': share_name,
},
'file-extensions-to-include': [],
'file-extensions-to-exclude': [],
}
if extensions_to_include:
for file_ext in extensions_to_include.split(','):
api_args['file-extensions-to-include'].append(
{'string': file_ext.strip()})
if extensions_to_exclude:
for file_ext in extensions_to_exclude.split(','):
api_args['file-extensions-to-exclude'].append(
{'string': file_ext.strip()})
self.send_request('fpolicy-policy-scope-create', api_args)
def modify_fpolicy_scope(self, policy_name, shares_to_include=[],
extensions_to_include=None,
extensions_to_exclude=None):
"""Modify an existing fpolicy scope.
:param policy_name: name of the policy associated to the scope.
:param shares_to_include: list of shares to include for file access
monitoring.
:param extensions_to_include: file extensions included for screening.
Values should be provided as comma separated list
:param extensions_to_exclude: file extensions excluded for screening.
Values should be provided as comma separated list
"""
api_args = {
'policy-name': policy_name,
}
if extensions_to_include:
api_args['file-extensions-to-include'] = []
for file_ext in extensions_to_include.split(','):
api_args['file-extensions-to-include'].append(
{'string': file_ext.strip()})
if extensions_to_exclude:
api_args['file-extensions-to-exclude'] = []
for file_ext in extensions_to_exclude.split(','):
api_args['file-extensions-to-exclude'].append(
{'string': file_ext.strip()})
if shares_to_include:
api_args['shares-to-include'] = [
{'string': share} for share in shares_to_include
]
self.send_request('fpolicy-policy-scope-modify', api_args)
def delete_fpolicy_scope(self, policy_name):
"""Deletes a fpolicy policy scope.
:param policy_name: name of the policy associated to the scope to be
deleted.
"""
try:
self.send_request('fpolicy-policy-scope-delete',
{'policy-name': policy_name})
except netapp_api.NaApiError as e:
if e.code in [netapp_api.ESCOPENOTFOUND,
netapp_api.EOBJECTNOTFOUND]:
msg = _("FPolicy scope %s not found.")
LOG.debug(msg, policy_name)
else:
raise exception.NetAppException(message=e.message)
def get_fpolicy_scopes(self, policy_name=None, extensions_to_include=None,
extensions_to_exclude=None, shares_to_include=None):
"""Retrieve fpolicy scopes.
:param policy_name: name of the policy associated with a scope.
:param extensions_to_include: file extensions included for screening.
Values should be provided as comma separated list
:param extensions_to_exclude: file extensions excluded for screening.
Values should be provided as comma separated list
:param shares_to_include: list of shares to include for file access
monitoring.
:return: list of fpolicy scopes or empty list
"""
policy_scope_info = {}
if policy_name:
policy_scope_info['policy-name'] = policy_name
if shares_to_include:
policy_scope_info['shares-to-include'] = [
{'string': share} for share in shares_to_include
]
if extensions_to_include:
policy_scope_info['file-extensions-to-include'] = []
for file_op in extensions_to_include.split(','):
policy_scope_info['file-extensions-to-include'].append(
{'string': file_op.strip()})
if extensions_to_exclude:
policy_scope_info['file-extensions-to-exclude'] = []
for file_op in extensions_to_exclude.split(','):
policy_scope_info['file-extensions-to-exclude'].append(
{'string': file_op.strip()})
api_args = {
'query': {
'fpolicy-scope-config': policy_scope_info,
},
}
result = self.send_iter_request('fpolicy-policy-scope-get-iter',
api_args)
fpolicy_scopes = []
if self._has_records(result):
try:
fpolicy_scopes = []
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
for policy_scope in attributes_list.get_children():
name = policy_scope.get_child_content('policy-name')
ext_include_child = policy_scope.get_child_by_name(
'file-extensions-to-include') or netapp_api.NaElement(
'none')
ext_include = [ext.get_content()
for ext in ext_include_child.get_children()]
ext_exclude_child = policy_scope.get_child_by_name(
'file-extensions-to-exclude') or netapp_api.NaElement(
'none')
ext_exclude = [ext.get_content()
for ext in ext_exclude_child.get_children()]
shares_child = policy_scope.get_child_by_name(
'shares-to-include') or netapp_api.NaElement('none')
shares_include = [ext.get_content()
for ext in shares_child.get_children()]
fpolicy_scopes.append({
'policy-name': name,
'file-extensions-to-include': ext_include,
'file-extensions-to-exclude': ext_exclude,
'shares-to-include': shares_include,
})
except AttributeError:
msg = _('Could not retrieve fpolicy policy information.')
raise exception.NetAppException(msg)
return fpolicy_scopes
def enable_fpolicy_policy(self, policy_name, sequence_number):
"""Enables a specific named policy.
:param policy_name: name of the policy to be enabled
:param sequence_number: policy sequence number
"""
api_args = {
'policy-name': policy_name,
'sequence-number': sequence_number,
}
self.send_request('fpolicy-enable-policy', api_args)
def disable_fpolicy_policy(self, policy_name):
"""Disables a specific policy.
:param policy_name: name of the policy to be disabled
"""
try:
self.send_request('fpolicy-disable-policy',
{'policy-name': policy_name})
except netapp_api.NaApiError as e:
disabled = "policy is already disabled"
if (e.code in [netapp_api.EPOLICYNOTFOUND,
netapp_api.EOBJECTNOTFOUND] or
(e.code == netapp_api.EINVALIDINPUTERROR and
disabled in e.message)):
msg = _("FPolicy policy %s not found or already disabled.")
LOG.debug(msg, policy_name)
else:
raise exception.NetAppException(message=e.message)
def get_fpolicy_policies_status(self, policy_name=None, status='true'):
policy_status_info = {}
if policy_name:
policy_status_info['policy-name'] = policy_name
policy_status_info['status'] = status
api_args = {
'query': {
'fpolicy-policy-status-info': policy_status_info,
},
}
result = self.send_iter_request('fpolicy-policy-status-get-iter',
api_args)
fpolicy_status = []
if self._has_records(result):
try:
fpolicy_status = []
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
for policy_status in attributes_list.get_children():
name = policy_status.get_child_content('policy-name')
status = policy_status.get_child_content('status')
seq = policy_status.get_child_content('sequence-number')
fpolicy_status.append({
'policy-name': name,
'status': strutils.bool_from_string(status),
'sequence-number': seq
})
except AttributeError:
msg = _('Could not retrieve fpolicy status information.')
raise exception.NetAppException(msg)
return fpolicy_status

View File

@ -34,6 +34,7 @@ from oslo_utils import uuidutils
import six
from manila.common import constants
from manila import coordination
from manila import exception
from manila.i18n import _
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
@ -68,6 +69,9 @@ class NetAppCmodeFileStorageLibrary(object):
STATE_MOVING_VOLUME = 'moving_volume'
STATE_SNAPMIRROR_DATA_COPYING = 'snapmirror_data_copying'
# Maximum number of FPolicis per vServer
FPOLICY_MAX_VSERVER_POLICIES = 10
# Maps NetApp qualified extra specs keys to corresponding backend API
# client library argument keywords. When we expose more backend
# capabilities here, we will add them to this map.
@ -80,11 +84,15 @@ class NetAppCmodeFileStorageLibrary(object):
}
STRING_QUALIFIED_EXTRA_SPECS_MAP = {
'netapp:snapshot_policy': 'snapshot_policy',
'netapp:language': 'language',
'netapp:max_files': 'max_files',
'netapp:adaptive_qos_policy_group': 'adaptive_qos_policy_group',
'netapp:fpolicy_extensions_to_include':
'fpolicy_extensions_to_include',
'netapp:fpolicy_extensions_to_exclude':
'fpolicy_extensions_to_exclude',
'netapp:fpolicy_file_operations': 'fpolicy_file_operations',
}
# Maps standard extra spec keys to legacy NetApp keys
@ -111,11 +119,15 @@ class NetAppCmodeFileStorageLibrary(object):
# Maps the NFS config used by share-servers
NFS_CONFIG_EXTRA_SPECS_MAP = {
'netapp:tcp_max_xfer_size': 'tcp-max-xfer-size',
'netapp:udp_max_xfer_size': 'udp-max-xfer-size',
}
FPOLICY_FILE_OPERATIONS_LIST = [
'close', 'create', 'create_dir', 'delete', 'delete_dir', 'getattr',
'link', 'lookup', 'open', 'read', 'write', 'rename', 'rename_dir',
'setattr', 'symlink']
def __init__(self, driver_name, **kwargs):
na_utils.validate_driver_instantiation(**kwargs)
@ -279,6 +291,17 @@ class NetAppCmodeFileStorageLibrary(object):
return (self.configuration.netapp_snapmirror_policy_name_svm_template
% {'share_server_id': share_server_id.replace('-', '_')})
def _get_backend_fpolicy_policy_name(self, share_id):
"""Get FPolicy policy name according with the configured template."""
return (self.configuration.netapp_fpolicy_policy_name_template
% {'share_id': share_id.replace('-', '_')})
def _get_backend_fpolicy_event_name(self, share_id, protocol):
"""Get FPolicy event name according with the configured template."""
return (self.configuration.netapp_fpolicy_event_name_template
% {'protocol': protocol.lower(),
'share_id': share_id.replace('-', '_')})
@na_utils.trace
def _get_aggregate_space(self):
aggregates = self._find_matching_aggregates()
@ -588,10 +611,11 @@ class NetAppCmodeFileStorageLibrary(object):
if (src_cluster_name != dest_cluster_name or
not self._have_cluster_creds):
# 1. Create a clone on source. We don't need to split from
# clone in order to replicate data
# clone in order to replicate data. We don't need to create
# fpolicies since this copy will be deleted.
self._allocate_container_from_snapshot(
dest_share, snapshot, src_vserver, src_vserver_client,
split=False)
split=False, create_fpolicy=False)
# 2. Create a replica in destination host
self._allocate_container(
dest_share, dest_vserver, dest_vserver_client,
@ -617,8 +641,8 @@ class NetAppCmodeFileStorageLibrary(object):
# If the share exists on the source vserser, we need to
# delete it since it's a temporary share, not managed by the system
dm_session.delete_snapmirror(src_share_instance, dest_share)
self._delete_share(src_share_instance, src_vserver_client,
remove_export=False)
self._delete_share(src_share_instance, src_vserver,
src_vserver_client, remove_export=False)
msg = _('Could not create share %(share_id)s from snapshot '
'%(snapshot_id)s in the destination host %(dest_host)s.')
msg_args = {'share_id': dest_share['id'],
@ -665,7 +689,7 @@ class NetAppCmodeFileStorageLibrary(object):
src_vserver_client = data_motion.get_client_for_backend(
src_backend, vserver_name=src_vserver)
self._delete_share(source_share, src_vserver_client,
self._delete_share(source_share, src_vserver, src_vserver_client,
remove_export=False)
# Delete private storage info
self.private_storage.delete(share['id'])
@ -766,7 +790,7 @@ class NetAppCmodeFileStorageLibrary(object):
dm_session.break_snapmirror(src_share, share)
dm_session.delete_snapmirror(src_share, share)
# 3. Delete the source volume
self._delete_share(src_share, src_vserver_client,
self._delete_share(src_share, src_vserver, src_vserver_client,
remove_export=False)
share_name = self._get_backend_share_name(src_share['id'])
# 4. Set File system size fixed to false
@ -817,7 +841,7 @@ class NetAppCmodeFileStorageLibrary(object):
@na_utils.trace
def _allocate_container(self, share, vserver, vserver_client,
replica=False):
replica=False, create_fpolicy=True):
"""Create new share on aggregate."""
share_name = self._get_backend_share_name(share['id'])
@ -850,6 +874,15 @@ class NetAppCmodeFileStorageLibrary(object):
self._apply_snapdir_visibility(
hide_snapdir, share_name, vserver_client)
if create_fpolicy:
fpolicy_ext_to_include = provisioning_options.get(
'fpolicy_extensions_to_include')
fpolicy_ext_to_exclude = provisioning_options.get(
'fpolicy_extensions_to_exclude')
if fpolicy_ext_to_include or fpolicy_ext_to_exclude:
self._create_fpolicy_for_share(share, vserver, vserver_client,
**provisioning_options)
def _apply_snapdir_visibility(
self, hide_snapdir, share_name, vserver_client):
@ -884,6 +917,9 @@ class NetAppCmodeFileStorageLibrary(object):
if 'netapp:max_files' in extra_specs:
self._check_if_max_files_is_valid(share,
extra_specs['netapp:max_files'])
if 'netapp:fpolicy_file_operations' in extra_specs:
self._check_fpolicy_file_operations(
share, extra_specs['netapp:fpolicy_file_operations'])
@na_utils.trace
def _check_if_max_files_is_valid(self, share, value):
@ -895,6 +931,20 @@ class NetAppCmodeFileStorageLibrary(object):
'in share_type %(type_id)s for share %(share_id)s.')
raise exception.NetAppException(msg % args)
@na_utils.trace
def _check_fpolicy_file_operations(self, share, value):
"""Check if the provided fpolicy file operations are valid."""
for file_op in value.split(','):
if file_op.strip() not in self.FPOLICY_FILE_OPERATIONS_LIST:
args = {'file_op': file_op,
'extra_spec': 'netapp:fpolicy_file_operations',
'type_id': share['share_type_id'],
'share_id': share['id']}
msg = _('Invalid value "%(file_op)s" for extra_spec '
'"%(extra_spec)s" in share_type %(type_id)s for share '
'%(share_id)s.')
raise exception.NetAppException(msg % args)
@na_utils.trace
def _check_boolean_extra_specs_validity(self, share, specs,
keys_of_interest):
@ -1100,6 +1150,25 @@ class NetAppCmodeFileStorageLibrary(object):
'cluster credentials.')
raise exception.NetAppException(msg)
fpolicy_ext_to_include = provisioning_options.get(
'fpolicy_extensions_to_include')
fpolicy_ext_to_exclude = provisioning_options.get(
'fpolicy_extensions_to_exclude')
if provisioning_options.get('fpolicy_file_operations') and not (
fpolicy_ext_to_include or fpolicy_ext_to_exclude):
msg = _('The extra spec "fpolicy_file_operations" can only '
'be configured together with '
'"fpolicy_extensions_to_include" or '
'"fpolicy_extensions_to_exclude".')
raise exception.NetAppException(msg)
if replication_type and (
fpolicy_ext_to_include or fpolicy_ext_to_exclude):
msg = _("The extra specs 'fpolicy_extensions_to_include' and "
"'fpolicy_extensions_to_exclude' are not "
"supported by share replication feature.")
raise exception.NetAppException(msg)
def _get_nve_option(self, specs):
if 'netapp_flexvol_encryption' in specs:
nve = specs['netapp_flexvol_encryption'].lower() == 'true'
@ -1128,7 +1197,8 @@ class NetAppCmodeFileStorageLibrary(object):
@na_utils.trace
def _allocate_container_from_snapshot(
self, share, snapshot, vserver, vserver_client,
snapshot_name_func=_get_backend_snapshot_name, split=None):
snapshot_name_func=_get_backend_snapshot_name, split=None,
create_fpolicy=True):
"""Clones existing share."""
share_name = self._get_backend_share_name(share['id'])
parent_share_name = self._get_backend_share_name(snapshot['share_id'])
@ -1156,13 +1226,26 @@ class NetAppCmodeFileStorageLibrary(object):
self._apply_snapdir_visibility(
hide_snapdir, share_name, vserver_client)
if create_fpolicy:
fpolicy_ext_to_include = provisioning_options.get(
'fpolicy_extensions_to_include')
fpolicy_ext_to_exclude = provisioning_options.get(
'fpolicy_extensions_to_exclude')
if fpolicy_ext_to_include or fpolicy_ext_to_exclude:
self._create_fpolicy_for_share(share, vserver, vserver_client,
**provisioning_options)
@na_utils.trace
def _share_exists(self, share_name, vserver_client):
return vserver_client.volume_exists(share_name)
@na_utils.trace
def _delete_share(self, share, vserver_client, remove_export=True):
def _delete_share(self, share, vserver, vserver_client,
remove_export=True):
share_name = self._get_backend_share_name(share['id'])
# Share doesn't need to exist to be assigned to a fpolicy scope
self._delete_fpolicy_for_share(share, vserver, vserver_client)
if self._share_exists(share_name, vserver_client):
if remove_export:
self._remove_export(share, vserver_client)
@ -1188,7 +1271,7 @@ class NetAppCmodeFileStorageLibrary(object):
"will proceed anyway. Error: %(error)s",
{'share': share['id'], 'error': error})
return
self._delete_share(share, vserver_client)
self._delete_share(share, vserver, vserver_client)
@na_utils.trace
def _deallocate_container(self, share_name, vserver_client):
@ -1436,6 +1519,29 @@ class NetAppCmodeFileStorageLibrary(object):
self.validate_provisioning_options_for_share(provisioning_options,
extra_specs=extra_specs,
qos_specs=qos_specs)
# Check fpolicy extra-specs
fpolicy_ext_include = provisioning_options.get(
'fpolicy_extensions_to_include')
fpolicy_ext_exclude = provisioning_options.get(
'fpolicy_extensions_to_exclude')
fpolicy_file_operations = provisioning_options.get(
'fpolicy_file_operations')
fpolicy_scope = None
if fpolicy_ext_include or fpolicy_ext_include:
fpolicy_scope = self._find_reusable_fpolicy_scope(
share, vserver_client,
fpolicy_extensions_to_include=fpolicy_ext_include,
fpolicy_extensions_to_exclude=fpolicy_ext_exclude,
fpolicy_file_operations=fpolicy_file_operations,
shares_to_include=[volume_name]
)
if fpolicy_scope is None:
msg = _('Volume %(volume)s does not contains the expected '
'fpolicy configuration.')
msg_args = {'volume': volume_name}
raise exception.ManageExistingShareTypeMismatch(
reason=msg % msg_args)
debug_args = {
'share': share_name,
@ -1459,6 +1565,17 @@ class NetAppCmodeFileStorageLibrary(object):
vserver_client.modify_volume(aggregate_name, share_name,
**provisioning_options)
# Update fpolicy to include the new share name and remove the old one
if fpolicy_scope is not None:
shares_to_include = copy.deepcopy(
fpolicy_scope.get('shares-to-include', []))
shares_to_include.remove(volume_name)
shares_to_include.append(share_name)
policy_name = fpolicy_scope.get('policy-name')
# Update
vserver_client.modify_fpolicy_scope(
policy_name, shares_to_include=shares_to_include)
# Save original volume info to private storage
original_data = {
'original_name': volume['name'],
@ -1883,7 +2000,7 @@ class NetAppCmodeFileStorageLibrary(object):
dest_backend, vserver_name=vserver)
self._allocate_container(new_replica, vserver, vserver_client,
replica=True)
replica=True, create_fpolicy=False)
# 2. Setup SnapMirror
dm_session.create_snapmirror(active_replica, new_replica)
@ -2431,7 +2548,30 @@ class NetAppCmodeFileStorageLibrary(object):
self.validate_provisioning_options_for_share(
provisioning_options, extra_specs=extra_specs,
qos_specs=qos_specs)
# Validate destination against fpolicy extra specs
fpolicy_ext_include = provisioning_options.get(
'fpolicy_extensions_to_include')
fpolicy_ext_exclude = provisioning_options.get(
'fpolicy_extensions_to_exclude')
fpolicy_file_operations = provisioning_options.get(
'fpolicy_file_operations')
if fpolicy_ext_include or fpolicy_ext_include:
__, dest_client = self._get_vserver(
share_server=destination_share_server)
fpolicies = dest_client.get_fpolicy_policies_status()
if len(fpolicies) >= self.FPOLICY_MAX_VSERVER_POLICIES:
# If we can't create a new policy for the new share,
# we need to reuse an existing one.
reusable_scopes = self._find_reusable_fpolicy_scope(
destination_share, dest_client,
fpolicy_extensions_to_include=fpolicy_ext_include,
fpolicy_extensions_to_exclude=fpolicy_ext_exclude,
fpolicy_file_operations=fpolicy_file_operations)
if not reusable_scopes:
msg = _(
"Cannot migrate share because the destination "
"reached its maximum number of policies.")
raise exception.NetAppException(msg)
# NOTE (felipe_rodrigues): NetApp only can migrate within the
# same server, so it does not need to check that the
# destination share has the same NFS config as the destination
@ -2448,7 +2588,6 @@ class NetAppCmodeFileStorageLibrary(object):
share_server=share_server)
share_volume = self._get_backend_share_name(
source_share['id'])
# NOTE(dviroel): If source and destination vservers are
# compatible for volume move, the provisioning option
# 'adaptive_qos_policy_group' will also be supported since the
@ -2758,6 +2897,18 @@ class NetAppCmodeFileStorageLibrary(object):
new_share_volume_name,
**provisioning_options)
# Create or reuse fpolicy
fpolicy_ext_to_include = provisioning_options.get(
'fpolicy_extensions_to_include')
fpolicy_ext_to_exclude = provisioning_options.get(
'fpolicy_extensions_to_exclude')
if fpolicy_ext_to_include or fpolicy_ext_to_exclude:
self._create_fpolicy_for_share(destination_share, vserver,
vserver_client,
**provisioning_options)
# Delete old fpolicies if needed
self._delete_fpolicy_for_share(source_share, vserver, vserver_client)
msg = ("Volume move operation for share %(shr)s has completed "
"successfully. Share has been moved from %(src)s to "
"%(dest)s.")
@ -2956,3 +3107,233 @@ class NetAppCmodeFileStorageLibrary(object):
backend_free_capacity += total_pool_free
return size <= backend_free_capacity
def _find_reusable_fpolicy_scope(
self, share, vserver_client, fpolicy_extensions_to_include=None,
fpolicy_extensions_to_exclude=None, fpolicy_file_operations=None,
shares_to_include=None):
"""Searches a fpolicy scope that can be reused for a share."""
protocols = (
['nfsv3', 'nfsv4'] if share['share_proto'].lower() == 'nfs'
else ['cifs'])
protocols.sort()
requested_ext_to_include = []
if fpolicy_extensions_to_include:
requested_ext_to_include = na_utils.convert_string_to_list(
fpolicy_extensions_to_include)
requested_ext_to_include.sort()
requested_ext_to_exclude = []
if fpolicy_extensions_to_exclude:
requested_ext_to_exclude = na_utils.convert_string_to_list(
fpolicy_extensions_to_exclude)
requested_ext_to_exclude.sort()
if fpolicy_file_operations:
requested_file_operations = na_utils.convert_string_to_list(
fpolicy_file_operations)
else:
requested_file_operations = (
self.configuration.netapp_fpolicy_default_file_operations)
requested_file_operations.sort()
reusable_scopes = vserver_client.get_fpolicy_scopes(
extensions_to_exclude=fpolicy_extensions_to_exclude,
extensions_to_include=fpolicy_extensions_to_include,
shares_to_include=shares_to_include)
# NOTE(dviroel): get_fpolicy_scopes can return scopes that don't match
# the exact requirements.
for scope in reusable_scopes[:]:
scope_ext_include = copy.deepcopy(
scope.get('file-extensions-to-include', []))
scope_ext_include.sort()
scope_ext_exclude = copy.deepcopy(
scope.get('file-extensions-to-exclude', []))
scope_ext_exclude.sort()
if scope_ext_include != requested_ext_to_include:
LOG.debug(
"Excluding scope for policy %(policy_name)s because "
"it doesn't match 'file-extensions-to-include' "
"configuration.", {'policy_name': scope['policy-name']})
reusable_scopes.remove(scope)
elif scope_ext_exclude != requested_ext_to_exclude:
LOG.debug(
"Excluding scope for policy %(policy_name)s because "
"it doesn't match 'file-extensions-to-exclude' "
"configuration.", {'policy_name': scope['policy-name']})
reusable_scopes.remove(scope)
for scope in reusable_scopes[:]:
fpolicy_policy = vserver_client.get_fpolicy_policies(
policy_name=scope['policy-name'])
for policy in fpolicy_policy:
event_names = copy.deepcopy(policy.get('events', []))
match_event_protocols = []
for event_name in event_names:
events = vserver_client.get_fpolicy_events(
event_name=event_name)
for event in events:
event_file_ops = copy.deepcopy(
event.get('file-operations', []))
event_file_ops.sort()
if event_file_ops == requested_file_operations:
# Event has same file operations
match_event_protocols.append(event.get('protocol'))
match_event_protocols.sort()
if match_event_protocols != protocols:
LOG.debug(
"Excluding scope for policy %(policy_name)s because "
"it doesn't match 'events' configuration of file "
"operations per protocol.",
{'policy_name': scope['policy-name']})
reusable_scopes.remove(scope)
return reusable_scopes[0] if reusable_scopes else None
def _create_fpolicy_for_share(
self, share, vserver, vserver_client,
fpolicy_extensions_to_include=None,
fpolicy_extensions_to_exclude=None, fpolicy_file_operations=None,
**options):
"""Creates or reuses a fpolicy for a new share."""
share_name = self._get_backend_share_name(share['id'])
@manila_utils.synchronized('netapp-fpolicy-%s' % vserver,
external=True)
def _create_fpolicy_with_lock():
# 1. Try to reuse an existing FPolicy if matches the same
# requirements
reusable_scope = self._find_reusable_fpolicy_scope(
share, vserver_client,
fpolicy_extensions_to_include=fpolicy_extensions_to_include,
fpolicy_extensions_to_exclude=fpolicy_extensions_to_exclude,
fpolicy_file_operations=fpolicy_file_operations)
if reusable_scope:
shares_to_include = copy.deepcopy(
reusable_scope.get('shares-to-include'))
shares_to_include.append(share_name)
# Add the new share to the existing policy scope
vserver_client.modify_fpolicy_scope(
reusable_scope.get('policy-name'),
shares_to_include=shares_to_include)
LOG.debug("Share %(share_id)s was added to an existing "
"fpolicy scope.", {'share_id': share['id']})
return
# 2. Since we can't reuse any scope, start creating a new fpolicy
protocols = (
['nfsv3', 'nfsv4'] if share['share_proto'].lower() == 'nfs'
else ['cifs'])
if fpolicy_file_operations:
file_operations = na_utils.convert_string_to_list(
fpolicy_file_operations)
else:
file_operations = (
self.configuration.netapp_fpolicy_default_file_operations)
# NOTE(dviroel): ONTAP limit of fpolicies for a vserser is 10.
# DHSS==True backends can create new share servers or fail earlier
# in choose_share_server_for_share.
vserver_policies = vserver_client.get_fpolicy_policies_status()
if len(vserver_policies) >= self.FPOLICY_MAX_VSERVER_POLICIES:
msg_args = {'share_id': share['id']}
msg = _("Cannot configure a new FPolicy for share "
"%(share_id)s. The maximum number of fpolicies was "
"already reached.") % msg_args
LOG.exception(msg)
raise exception.NetAppException(message=msg)
seq_number_list = [int(policy['sequence-number'])
for policy in vserver_policies]
available_seq_number = None
for number in range(1, self.FPOLICY_MAX_VSERVER_POLICIES + 1):
if number not in seq_number_list:
available_seq_number = number
break
events = []
policy_name = self._get_backend_fpolicy_policy_name(share['id'])
try:
for protocol in protocols:
event_name = self._get_backend_fpolicy_event_name(
share['id'], protocol)
vserver_client.create_fpolicy_event(event_name,
protocol,
file_operations)
events.append(event_name)
# 2. Create a fpolicy policy
vserver_client.create_fpolicy_policy(policy_name, events)
# 3. Assign a scope to the fpolicy policy
vserver_client.create_fpolicy_scope(
policy_name, share_name,
extensions_to_include=fpolicy_extensions_to_include,
extensions_to_exclude=fpolicy_extensions_to_exclude)
except Exception:
# NOTE(dviroel): Rollback fpolicy policy and events creation
# since they won't be linked to the share, which is made by
# the scope creation.
# Delete fpolicy policy
vserver_client.delete_fpolicy_policy(policy_name)
# Delete fpolicy events
for event in events:
vserver_client.delete_fpolicy_event(event)
msg = _("Failed to configure a FPolicy resources for share "
"%(share_id)s. ") % {'share_id': share['id']}
LOG.exception(msg)
raise exception.NetAppException(message=msg)
# 4. Enable fpolicy policy
vserver_client.enable_fpolicy_policy(policy_name,
available_seq_number)
_create_fpolicy_with_lock()
LOG.debug('A new fpolicy was successfully created and associated to '
'share %(share_id)s', {'share_id': share['id']})
def _delete_fpolicy_for_share(self, share, vserver, vserver_client):
"""Delete all associated fpolicy resources from a share."""
share_name = self._get_backend_share_name(share['id'])
@coordination.synchronized('netapp-fpolicy-%s' % vserver)
def _delete_fpolicy_with_lock():
fpolicy_scopes = vserver_client.get_fpolicy_scopes(
shares_to_include=[share_name])
if fpolicy_scopes:
shares_to_include = copy.copy(
fpolicy_scopes[0].get('shares-to-include'))
shares_to_include.remove(share_name)
policy_name = fpolicy_scopes[0].get('policy-name')
if shares_to_include:
vserver_client.modify_fpolicy_scope(
policy_name, shares_to_include=shares_to_include)
else:
# Delete an empty fpolicy
# 1. Disable fpolicy policy
vserver_client.disable_fpolicy_policy(policy_name)
# 2. Retrieve fpoliocy info
fpolicy_policies = vserver_client.get_fpolicy_policies(
policy_name=policy_name)
# 3. Delete fpolicy scope
vserver_client.delete_fpolicy_scope(policy_name)
# 4. Delete fpolicy policy
vserver_client.delete_fpolicy_policy(policy_name)
# 5. Delete fpolicy events
for policy in fpolicy_policies:
events = policy.get('events', [])
for event in events:
vserver_client.delete_fpolicy_event(event)
_delete_fpolicy_with_lock()

View File

@ -758,22 +758,37 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
return None
nfs_config = None
extra_specs = share_types.get_extra_specs_from_share(share)
if self.is_nfs_config_supported:
extra_specs = share_types.get_extra_specs_from_share(share)
nfs_config = self._get_nfs_config_provisioning_options(extra_specs)
provisioning_options = self._get_provisioning_options(extra_specs)
# Get FPolicy extra specs to avoid incompatible share servers
fpolicy_ext_to_include = provisioning_options.get(
'fpolicy_extensions_to_include')
fpolicy_ext_to_exclude = provisioning_options.get(
'fpolicy_extensions_to_exclude')
fpolicy_file_operations = provisioning_options.get(
'fpolicy_file_operations')
# Avoid the reuse of 'dp_protection' vservers:
for share_server in share_servers:
if self._check_reuse_share_server(share_server, nfs_config,
share_group=share_group):
if self._check_reuse_share_server(
share_server, nfs_config, share=share,
share_group=share_group,
fpolicy_ext_include=fpolicy_ext_to_include,
fpolicy_ext_exclude=fpolicy_ext_to_exclude,
fpolicy_file_operations=fpolicy_file_operations):
return share_server
# There is no compatible share server to be reused
return None
@na_utils.trace
def _check_reuse_share_server(self, share_server, nfs_config,
share_group=None):
def _check_reuse_share_server(self, share_server, nfs_config, share=None,
share_group=None, fpolicy_ext_include=None,
fpolicy_ext_exclude=None,
fpolicy_file_operations=None):
"""Check whether the share_server can be reused or not."""
if (share_group and share_group.get('share_server_id') !=
share_server['id']):
@ -795,6 +810,20 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
# that the share type is an element of the group types.
return self._is_share_server_compatible(share_server, nfs_config)
if fpolicy_ext_include or fpolicy_ext_exclude:
fpolicies = client.get_fpolicy_policies_status()
if len(fpolicies) >= self.FPOLICY_MAX_VSERVER_POLICIES:
# This share server already reached it maximum number of
# policies, we need to check if we can reuse one, otherwise,
# it is not suitable for this share.
reusable_scope = self._find_reusable_fpolicy_scope(
share, client,
fpolicy_extensions_to_include=fpolicy_ext_include,
fpolicy_extensions_to_exclude=fpolicy_ext_exclude,
fpolicy_file_operations=fpolicy_file_operations)
if not reusable_scope:
return False
return True
@na_utils.trace
@ -814,6 +843,10 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
if self.is_nfs_config_supported:
nfs_config = self._get_nfs_config_share_group(share_group_ref)
# NOTE(dviroel): FPolicy extra-specs won't be conflicting, since
# multiple policies can be created. The maximum number of policies or
# the reusability of existing ones, can only be analyzed at share
# instance creation.
for share_server in share_servers:
if self._check_reuse_share_server(share_server, nfs_config):
return share_server
@ -1188,7 +1221,8 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
dest_share_server)
# Rollback resources transferred to the destination
for instance in share_instances:
self._delete_share(instance, dest_client, remove_export=False)
self._delete_share(instance, dest_vserver, dest_client,
remove_export=False)
msg_args = {
'src': source_share_server['id'],
@ -1243,7 +1277,8 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
# 8. Release source share resources
for instance in share_instances:
self._delete_share(instance, src_client, remove_export=True)
self._delete_share(instance, src_vserver, src_client,
remove_export=True)
# NOTE(dviroel): source share server deletion must be triggered by
# the manager after finishing the migration
@ -1270,7 +1305,8 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
dest_share_server)
# Do a simple volume cleanup in the destination vserver
for instance in shares:
self._delete_share(instance, dest_client, remove_export=False)
self._delete_share(instance, dest_vserver, dest_client,
remove_export=False)
except Exception:
msg_args = {

View File

@ -123,7 +123,18 @@ netapp_provisioning_opts = [
cfg.StrOpt('netapp_snapmirror_policy_name_svm_template',
help='NetApp SnapMirror policy name template for Storage '
'Virtual Machines (Vservers).',
default='snapmirror_policy_%(share_server_id)s'), ]
default='snapmirror_policy_%(share_server_id)s'),
cfg.ListOpt('netapp_fpolicy_default_file_operations',
help='NetApp FPolicy file operations to apply to a FPolicy '
'event, when not provided by the user using '
'"netapp:fpolicy_file_operations" extra-spec.',
default=['create', 'write', 'rename']),
cfg.StrOpt('netapp_fpolicy_policy_name_template',
help='NetApp FPolicy policy name template.',
default='fpolicy_policy_%(share_id)s'),
cfg.StrOpt('netapp_fpolicy_event_name_template',
help='NetApp FPolicy policy name template.',
default='fpolicy_event_%(protocol)s_%(share_id)s'), ]
netapp_cluster_opts = [
cfg.StrOpt('netapp_vserver',

View File

@ -112,6 +112,10 @@ def convert_to_list(value):
return [value]
def convert_string_to_list(string, separator=','):
return [elem.strip() for elem in string.split(separator)]
class OpenStackInfo(object):
"""OS/distribution, release, and version.

View File

@ -108,6 +108,18 @@ SM_SOURCE_VOLUME = 'fake_source_volume'
SM_DEST_VSERVER = 'fake_destination_vserver'
SM_DEST_VOLUME = 'fake_destination_volume'
FPOLICY_POLICY_NAME = 'fake_fpolicy_name'
FPOLICY_EVENT_NAME = 'fake_fpolicy_event_name'
FPOLICY_PROTOCOL = 'cifs'
FPOLICY_FILE_OPERATIONS = 'create,write,rename'
FPOLICY_FILE_OPERATIONS_LIST = ['create', 'write', 'rename']
FPOLICY_ENGINE = 'native'
FPOLICY_EXT_TO_INCLUDE = 'avi'
FPOLICY_EXT_TO_INCLUDE_LIST = ['avi']
FPOLICY_EXT_TO_EXCLUDE = 'jpg,mp3'
FPOLICY_EXT_TO_EXCLUDE_LIST = ['jpg', 'mp3']
NETWORK_INTERFACES = [{
'interface_name': 'fake_interface',
'address': IP_ADDRESS,
@ -2766,6 +2778,94 @@ DNS_CONFIG_GET_RESPONSE = etree.XML("""
'vserver_name': VSERVER_NAME,
})
FPOLICY_EVENT_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<fpolicy-event-options-config>
<event-name>%(event_name)s</event-name>
<file-operations>
<fpolicy-operation>create</fpolicy-operation>
<fpolicy-operation>write</fpolicy-operation>
<fpolicy-operation>rename</fpolicy-operation>
</file-operations>
<protocol>%(protocol)s</protocol>
<volume-operation>false</volume-operation>
<vserver>%(vserver_name)s</vserver>
</fpolicy-event-options-config>
</attributes-list>
<num-records>1</num-records>
</results>""" % {
'event_name': FPOLICY_EVENT_NAME,
'protocol': FPOLICY_PROTOCOL,
'vserver_name': VSERVER_NAME,
})
FPOLICY_POLICY_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<fpolicy-policy-info>
<allow-privileged-access>false</allow-privileged-access>
<engine-name>%(engine)s</engine-name>
<events>
<event-name>%(event_name)s</event-name>
</events>
<is-mandatory>true</is-mandatory>
<is-passthrough-read-enabled>false</is-passthrough-read-enabled>
<policy-name>%(policy_name)s</policy-name>
<vserver>%(vserver_name)s</vserver>
</fpolicy-policy-info>
</attributes-list>
<num-records>1</num-records>
</results>""" % {
'engine': FPOLICY_ENGINE,
'event_name': FPOLICY_EVENT_NAME,
'policy_name': FPOLICY_POLICY_NAME,
'vserver_name': VSERVER_NAME,
})
FPOLICY_SCOPE_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<fpolicy-scope-config>
<check-extensions-on-directories>true</check-extensions-on-directories>
<file-extensions-to-exclude>
<string>jpg</string>
<string>mp3</string>
</file-extensions-to-exclude>
<file-extensions-to-include>
<string>avi</string>
</file-extensions-to-include>
<is-monitoring-of-objects-with-no-extension-enabled>false</is-monitoring-of-objects-with-no-extension-enabled>
<policy-name>%(policy_name)s</policy-name>
<shares-to-include>
<string>%(share_name)s</string>
</shares-to-include>
<vserver>%(vserver_name)s</vserver>
</fpolicy-scope-config>
</attributes-list>
<num-records>1</num-records>
</results>""" % {
'policy_name': FPOLICY_POLICY_NAME,
'share_name': SHARE_NAME,
'vserver_name': VSERVER_NAME,
})
FPOLICY_POLICY_STATUS_GET_ITER_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<fpolicy-policy-status-info>
<policy-name>%(policy_name)s</policy-name>
<sequence-number>1</sequence-number>
<status>true</status>
<vserver>%(vserver_name)s</vserver>
</fpolicy-policy-status-info>
</attributes-list>
<num-records>1</num-records>
</results>""" % {
'policy_name': FPOLICY_POLICY_NAME,
'vserver_name': VSERVER_NAME,
})
FAKE_VOL_XML = """<volume-info>
<name>open123</name>
<state>online</state>

View File

@ -7621,3 +7621,361 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.assertEqual(expected_result, result)
self.client.send_request.assert_called_once_with(
'volume-autosize-get', {'volume': fake.SHARE_NAME})
def test_create_fpolicy_event(self):
self.mock_object(self.client, 'send_request')
self.client.create_fpolicy_event(fake.FPOLICY_EVENT_NAME,
fake.FPOLICY_PROTOCOL,
fake.FPOLICY_FILE_OPERATIONS_LIST)
expected_args = {
'event-name': fake.FPOLICY_EVENT_NAME,
'protocol': fake.FPOLICY_PROTOCOL,
'file-operations': [],
}
for file_op in fake.FPOLICY_FILE_OPERATIONS_LIST:
expected_args['file-operations'].append(
{'fpolicy-operation': file_op})
self.client.send_request.assert_called_once_with(
'fpolicy-policy-event-create', expected_args)
@ddt.data(None, netapp_api.EEVENTNOTFOUND)
def test_delete_fpolicy_event(self, send_request_error):
if send_request_error:
send_request_mock = mock.Mock(
side_effect=self._mock_api_error(code=send_request_error))
else:
send_request_mock = mock.Mock()
self.mock_object(self.client, 'send_request', send_request_mock)
self.client.delete_fpolicy_event(fake.FPOLICY_EVENT_NAME)
self.client.send_request.assert_called_once_with(
'fpolicy-policy-event-delete',
{'event-name': fake.FPOLICY_EVENT_NAME})
def test_delete_fpolicy_event_error(self):
eapi_error = self._mock_api_error(code=netapp_api.EAPIERROR)
self.mock_object(
self.client, 'send_request', mock.Mock(side_effect=eapi_error))
self.assertRaises(exception.NetAppException,
self.client.delete_fpolicy_event,
fake.FPOLICY_EVENT_NAME)
self.client.send_request.assert_called_once_with(
'fpolicy-policy-event-delete',
{'event-name': fake.FPOLICY_EVENT_NAME})
def test_get_fpolicy_events(self):
api_response = netapp_api.NaElement(
fake.FPOLICY_EVENT_GET_ITER_RESPONSE)
self.mock_object(self.client, 'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.get_fpolicy_events(
event_name=fake.FPOLICY_EVENT_NAME,
protocol=fake.FPOLICY_PROTOCOL,
file_operations=fake.FPOLICY_FILE_OPERATIONS_LIST)
expected_options = {
'event-name': fake.FPOLICY_EVENT_NAME,
'protocol': fake.FPOLICY_PROTOCOL,
'file-operations': []
}
for file_op in fake.FPOLICY_FILE_OPERATIONS_LIST:
expected_options['file-operations'].append(
{'fpolicy-operation': file_op})
expected_args = {
'query': {
'fpolicy-event-options-config': expected_options,
},
}
expected = [{
'event-name': fake.FPOLICY_EVENT_NAME,
'protocol': fake.FPOLICY_PROTOCOL,
'file-operations': fake.FPOLICY_FILE_OPERATIONS_LIST
}]
self.assertEqual(expected, result)
self.client.send_iter_request.assert_called_once_with(
'fpolicy-policy-event-get-iter', expected_args)
def test_create_fpolicy_policy(self):
self.mock_object(self.client, 'send_request')
self.client.create_fpolicy_policy(fake.FPOLICY_POLICY_NAME,
[fake.FPOLICY_EVENT_NAME],
engine=fake.FPOLICY_ENGINE)
expected_args = {
'policy-name': fake.FPOLICY_POLICY_NAME,
'events': [],
'engine-name': fake.FPOLICY_ENGINE
}
for event in [fake.FPOLICY_EVENT_NAME]:
expected_args['events'].append(
{'event-name': event})
self.client.send_request.assert_called_once_with(
'fpolicy-policy-create', expected_args)
@ddt.data(None, netapp_api.EPOLICYNOTFOUND)
def test_delete_fpolicy_policy(self, send_request_error):
if send_request_error:
send_request_mock = mock.Mock(
side_effect=self._mock_api_error(code=send_request_error))
else:
send_request_mock = mock.Mock()
self.mock_object(self.client, 'send_request', send_request_mock)
self.client.delete_fpolicy_policy(fake.FPOLICY_POLICY_NAME)
self.client.send_request.assert_called_once_with(
'fpolicy-policy-delete',
{'policy-name': fake.FPOLICY_POLICY_NAME})
def test_delete_fpolicy_policy_error(self):
eapi_error = self._mock_api_error(code=netapp_api.EAPIERROR)
self.mock_object(
self.client, 'send_request', mock.Mock(side_effect=eapi_error))
self.assertRaises(exception.NetAppException,
self.client.delete_fpolicy_policy,
fake.FPOLICY_POLICY_NAME)
self.client.send_request.assert_called_once_with(
'fpolicy-policy-delete',
{'policy-name': fake.FPOLICY_POLICY_NAME})
def test_get_fpolicy_policies(self):
api_response = netapp_api.NaElement(
fake.FPOLICY_POLICY_GET_ITER_RESPONSE)
self.mock_object(self.client, 'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.get_fpolicy_policies(
policy_name=fake.FPOLICY_POLICY_NAME,
engine_name=fake.FPOLICY_ENGINE,
event_names=[fake.FPOLICY_EVENT_NAME])
expected_options = {
'policy-name': fake.FPOLICY_POLICY_NAME,
'engine-name': fake.FPOLICY_ENGINE,
'events': []
}
for policy in [fake.FPOLICY_EVENT_NAME]:
expected_options['events'].append(
{'event-name': policy})
expected_args = {
'query': {
'fpolicy-policy-info': expected_options,
},
}
expected = [{
'policy-name': fake.FPOLICY_POLICY_NAME,
'engine-name': fake.FPOLICY_ENGINE,
'events': [fake.FPOLICY_EVENT_NAME]
}]
self.assertEqual(expected, result)
self.client.send_iter_request.assert_called_once_with(
'fpolicy-policy-get-iter', expected_args)
def test_create_fpolicy_scope(self):
self.mock_object(self.client, 'send_request')
self.client.create_fpolicy_scope(
fake.FPOLICY_POLICY_NAME,
fake.SHARE_NAME,
extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE)
expected_args = {
'policy-name': fake.FPOLICY_POLICY_NAME,
'shares-to-include': {
'string': fake.SHARE_NAME,
},
'file-extensions-to-include': [],
'file-extensions-to-exclude': [],
}
for file_ext in fake.FPOLICY_EXT_TO_INCLUDE_LIST:
expected_args['file-extensions-to-include'].append(
{'string': file_ext})
for file_ext in fake.FPOLICY_EXT_TO_EXCLUDE_LIST:
expected_args['file-extensions-to-exclude'].append(
{'string': file_ext})
self.client.send_request.assert_called_once_with(
'fpolicy-policy-scope-create', expected_args)
def test_modify_fpolicy_scope(self):
self.mock_object(self.client, 'send_request')
self.client.modify_fpolicy_scope(
fake.FPOLICY_POLICY_NAME,
shares_to_include=[fake.SHARE_NAME],
extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE)
expected_args = {
'policy-name': fake.FPOLICY_POLICY_NAME,
'file-extensions-to-include': [],
'file-extensions-to-exclude': [],
'shares-to-include': [{
'string': fake.SHARE_NAME,
}],
}
for file_ext in fake.FPOLICY_EXT_TO_INCLUDE_LIST:
expected_args['file-extensions-to-include'].append(
{'string': file_ext})
for file_ext in fake.FPOLICY_EXT_TO_EXCLUDE_LIST:
expected_args['file-extensions-to-exclude'].append(
{'string': file_ext})
self.client.send_request.assert_called_once_with(
'fpolicy-policy-scope-modify', expected_args)
@ddt.data(None, netapp_api.ESCOPENOTFOUND)
def test_delete_fpolicy_scope(self, send_request_error):
if send_request_error:
send_request_mock = mock.Mock(
side_effect=self._mock_api_error(code=send_request_error))
else:
send_request_mock = mock.Mock()
self.mock_object(self.client, 'send_request', send_request_mock)
self.client.delete_fpolicy_scope(fake.FPOLICY_POLICY_NAME)
self.client.send_request.assert_called_once_with(
'fpolicy-policy-scope-delete',
{'policy-name': fake.FPOLICY_POLICY_NAME})
def test_delete_fpolicy_scope_error(self):
eapi_error = self._mock_api_error(code=netapp_api.EAPIERROR)
self.mock_object(
self.client, 'send_request', mock.Mock(side_effect=eapi_error))
self.assertRaises(exception.NetAppException,
self.client.delete_fpolicy_scope,
fake.FPOLICY_POLICY_NAME)
self.client.send_request.assert_called_once_with(
'fpolicy-policy-scope-delete',
{'policy-name': fake.FPOLICY_POLICY_NAME})
def test_get_fpolicy_scopes(self):
api_response = netapp_api.NaElement(
fake.FPOLICY_SCOPE_GET_ITER_RESPONSE)
self.mock_object(self.client, 'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.get_fpolicy_scopes(
policy_name=fake.FPOLICY_POLICY_NAME,
extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
shares_to_include=[fake.SHARE_NAME])
expected_options = {
'policy-name': fake.FPOLICY_POLICY_NAME,
'shares-to-include': [{
'string': fake.SHARE_NAME,
}],
'file-extensions-to-include': [],
'file-extensions-to-exclude': [],
}
for file_ext in fake.FPOLICY_EXT_TO_INCLUDE_LIST:
expected_options['file-extensions-to-include'].append(
{'string': file_ext})
for file_ext in fake.FPOLICY_EXT_TO_EXCLUDE_LIST:
expected_options['file-extensions-to-exclude'].append(
{'string': file_ext})
expected_args = {
'query': {
'fpolicy-scope-config': expected_options,
},
}
expected = [{
'policy-name': fake.FPOLICY_POLICY_NAME,
'file-extensions-to-include': fake.FPOLICY_EXT_TO_INCLUDE_LIST,
'file-extensions-to-exclude': fake.FPOLICY_EXT_TO_EXCLUDE_LIST,
'shares-to-include': [fake.SHARE_NAME],
}]
self.assertEqual(expected, result)
self.client.send_iter_request.assert_called_once_with(
'fpolicy-policy-scope-get-iter', expected_args)
def test_enable_fpolicy_policy(self):
self.mock_object(self.client, 'send_request')
self.client.enable_fpolicy_policy(fake.FPOLICY_POLICY_NAME, 10)
expected_args = {
'policy-name': fake.FPOLICY_POLICY_NAME,
'sequence-number': 10,
}
self.client.send_request.assert_called_once_with(
'fpolicy-enable-policy', expected_args)
@ddt.data(None, netapp_api.EPOLICYNOTFOUND)
def test_disable_fpolicy_policy(self, send_request_error):
if send_request_error:
send_request_mock = mock.Mock(
side_effect=self._mock_api_error(code=send_request_error))
else:
send_request_mock = mock.Mock()
self.mock_object(self.client, 'send_request', send_request_mock)
self.client.disable_fpolicy_policy(fake.FPOLICY_POLICY_NAME)
expected_args = {
'policy-name': fake.FPOLICY_POLICY_NAME,
}
self.client.send_request.assert_called_once_with(
'fpolicy-disable-policy', expected_args)
def test_disable_fpolicy_policy_error(self):
eapi_error = self._mock_api_error(code=netapp_api.EAPIERROR)
self.mock_object(
self.client, 'send_request', mock.Mock(side_effect=eapi_error))
self.assertRaises(exception.NetAppException,
self.client.disable_fpolicy_policy,
fake.FPOLICY_POLICY_NAME)
self.client.send_request.assert_called_once_with(
'fpolicy-disable-policy',
{'policy-name': fake.FPOLICY_POLICY_NAME})
def test_get_fpolicy_status(self):
api_response = netapp_api.NaElement(
fake.FPOLICY_POLICY_STATUS_GET_ITER_RESPONSE)
self.mock_object(self.client, 'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.get_fpolicy_policies_status(
policy_name=fake.FPOLICY_POLICY_NAME)
expected_args = {
'query': {
'fpolicy-policy-status-info': {
'policy-name': fake.FPOLICY_POLICY_NAME,
'status': 'true'
},
},
}
expected = [{
'policy-name': fake.FPOLICY_POLICY_NAME,
'status': True,
'sequence-number': '1'
}]
self.assertEqual(expected, result)
self.client.send_iter_request.assert_called_once_with(
'fpolicy-policy-status-get-iter', expected_args)

View File

@ -826,7 +826,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
if dest_cluster != fake.CLUSTER_NAME:
self.mock_allocate_container_from_snapshot.assert_called_once_with(
self.fake_share, fake.SNAPSHOT, fake.VSERVER1,
self.src_vserver_client, split=False)
self.src_vserver_client, split=False, create_fpolicy=False)
self.mock_allocate_container.assert_called_once_with(
self.fake_share, fake.VSERVER2,
self.dest_vserver_client, replica=True)
@ -863,6 +863,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock.Mock(return_value=True))
mock_deallocate_container = self.mock_object(
self.library, '_deallocate_container')
mock_delete_policy = self.mock_object(self.library,
'_delete_fpolicy_for_share')
self.assertRaises(exception.NetAppException,
self.library.create_share_from_snapshot,
@ -892,6 +894,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.src_vserver_client)
mock_deallocate_container.assert_called_once_with(
fake.SHARE_NAME, self.src_vserver_client)
mock_delete_policy.assert_called_once_with(self.temp_src_share,
fake.VSERVER1,
self.src_vserver_client)
def test__update_create_from_snapshot_status(self):
fake_result = mock.Mock()
@ -959,6 +964,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library, '_deallocate_container')
mock_pvt_storage_delete = self.mock_object(
self.library.private_storage, 'delete')
mock_delete_policy = self.mock_object(self.library,
'_delete_fpolicy_for_share')
result = self.library._update_create_from_snapshot_status(
fake.SHARE, fake.SHARE_SERVER)
@ -980,6 +987,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_deallocate_container.assert_called_once_with(fake.SHARE_NAME,
src_vserver_client)
mock_pvt_storage_delete.assert_called_once_with(fake.SHARE['id'])
mock_delete_policy.assert_called_once_with(fake_src_share,
fake.VSERVER1,
src_vserver_client)
self.assertEqual(expected_result, result)
def _setup_mocks_for_create_from_snapshot_continue(
@ -1213,7 +1223,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
[mock.call(self.fake_src_share['id']),
mock.call(fake.SHARE_ID)])
self.mock__delete_share.assert_called_once_with(
self.fake_src_share, self.src_vserver_client,
self.fake_src_share, fake.VSERVER1, self.src_vserver_client,
remove_export=False)
self.mock_set_vol_size_fixes.assert_called_once_with(
fake.SHARE_NAME, filesys_size_fixed=False)
@ -1252,10 +1262,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_pvt_storage_delete.assert_called_once_with(fake.SHARE_ID)
@ddt.data(False, True)
def test_allocate_container(self, hide_snapdir):
@ddt.data({'hide_snapdir': False, 'create_fpolicy': True},
{'hide_snapdir': True, 'create_fpolicy': False})
@ddt.unpack
def test_allocate_container(self, hide_snapdir, create_fpolicy):
provisioning_options = copy.deepcopy(fake.PROVISIONING_OPTIONS)
provisioning_options = copy.deepcopy(
fake.PROVISIONING_OPTIONS_WITH_FPOLICY)
provisioning_options['hide_snapdir'] = hide_snapdir
self.mock_object(self.library, '_get_backend_share_name', mock.Mock(
return_value=fake.SHARE_NAME))
@ -1264,11 +1277,14 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_get_provisioning_opts = self.mock_object(
self.library, '_get_provisioning_options_for_share',
mock.Mock(return_value=provisioning_options))
mock_create_fpolicy = self.mock_object(
self.library, '_create_fpolicy_for_share')
vserver_client = mock.Mock()
self.library._allocate_container(fake.SHARE_INSTANCE,
fake.VSERVER1,
vserver_client)
vserver_client,
create_fpolicy=create_fpolicy)
mock_get_provisioning_opts.assert_called_once_with(
fake.SHARE_INSTANCE, fake.VSERVER1, vserver_client=vserver_client,
@ -1276,10 +1292,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
vserver_client.create_volume.assert_called_once_with(
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
thin_provisioned=True, snapshot_policy='default',
language='en-US', dedup_enabled=True, split=True, encrypt=False,
compression_enabled=False, max_files=5000, snapshot_reserve=8,
adaptive_qos_policy_group=None)
snapshot_reserve=8, **provisioning_options)
if hide_snapdir:
vserver_client.set_volume_snapdir_access.assert_called_once_with(
@ -1287,6 +1300,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
else:
vserver_client.set_volume_snapdir_access.assert_not_called()
if create_fpolicy:
mock_create_fpolicy.assert_called_once_with(
fake.SHARE_INSTANCE, fake.VSERVER1, vserver_client,
**provisioning_options)
else:
mock_create_fpolicy.assert_not_called()
def test_remap_standard_boolean_extra_specs(self):
extra_specs = copy.deepcopy(fake.OVERLAPPING_EXTRA_SPEC)
@ -1370,7 +1390,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
def test_check_string_extra_specs_validity(self):
result = self.library._check_string_extra_specs_validity(
fake.SHARE_INSTANCE, fake.EXTRA_SPEC)
fake.SHARE_INSTANCE, fake.EXTRA_SPEC_WITH_FPOLICY)
self.assertIsNone(result)
@ -1499,6 +1519,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'split': False,
'encrypt': False,
'hide_snapdir': False,
'fpolicy_extensions_to_exclude': None,
'fpolicy_extensions_to_include': None,
'fpolicy_file_operations': None,
}
self.assertEqual(expected, result)
@ -1624,6 +1647,21 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library._check_if_max_files_is_valid,
fake.SHARE, 'abc')
def test__check_fpolicy_file_operations(self):
result = self.library._check_fpolicy_file_operations(
fake.SHARE, fake.FPOLICY_FILE_OPERATIONS)
self.assertIsNone(result)
def test__check_fpolicy_file_operations_invalid_operation(self):
invalid_ops = copy.deepcopy(fake.FPOLICY_FILE_OPERATIONS)
invalid_ops += ',fake_op'
self.assertRaises(exception.NetAppException,
self.library._check_fpolicy_file_operations,
fake.SHARE,
invalid_ops)
def test_allocate_container_no_pool(self):
vserver_client = mock.Mock()
@ -1657,24 +1695,26 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.EXTRA_SPEC)
@ddt.data({'provider_location': None, 'size': 50, 'hide_snapdir': True,
'split': None},
'split': None, 'create_fpolicy': False},
{'provider_location': 'fake_location', 'size': 30,
'hide_snapdir': False, 'split': True},
'hide_snapdir': False, 'split': True, 'create_fpolicy': True},
{'provider_location': 'fake_location', 'size': 20,
'hide_snapdir': True, 'split': False})
'hide_snapdir': True, 'split': False, 'create_fpolicy': True})
@ddt.unpack
def test_allocate_container_from_snapshot(
self, provider_location, size, hide_snapdir, split):
provisioning_options = copy.deepcopy(fake.PROVISIONING_OPTIONS)
self, provider_location, size, hide_snapdir, split,
create_fpolicy):
provisioning_options = copy.deepcopy(
fake.PROVISIONING_OPTIONS_WITH_FPOLICY)
provisioning_options['hide_snapdir'] = hide_snapdir
mock_get_provisioning_opts = self.mock_object(
self.library, '_get_provisioning_options_for_share',
mock.Mock(return_value=provisioning_options))
mock_create_fpolicy = self.mock_object(
self.library, '_create_fpolicy_for_share')
vserver = fake.VSERVER1
vserver_client = mock.Mock()
original_snapshot_size = 20
expected_split_op = split or fake.PROVISIONING_OPTIONS['split']
fake_share_inst = copy.deepcopy(fake.SHARE_INSTANCE)
fake_share_inst['size'] = size
@ -1682,10 +1722,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake_snapshot['provider_location'] = provider_location
fake_snapshot['size'] = original_snapshot_size
self.library._allocate_container_from_snapshot(fake_share_inst,
fake_snapshot,
vserver,
vserver_client)
self.library._allocate_container_from_snapshot(
fake_share_inst,
fake_snapshot,
vserver,
vserver_client,
create_fpolicy=create_fpolicy)
share_name = self.library._get_backend_share_name(
fake_share_inst['id'])
@ -1697,10 +1739,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake_share_inst, fake.VSERVER1, vserver_client=vserver_client)
vserver_client.create_volume_clone.assert_called_once_with(
share_name, parent_share_name, parent_snapshot_name,
thin_provisioned=True, snapshot_policy='default',
language='en-US', dedup_enabled=True, split=expected_split_op,
encrypt=False, compression_enabled=False, max_files=5000,
adaptive_qos_policy_group=None)
**provisioning_options)
if size > original_snapshot_size:
vserver_client.set_volume_size.assert_called_once_with(
share_name, size)
@ -1713,6 +1752,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
else:
vserver_client.set_volume_snapdir_access.assert_not_called()
if create_fpolicy:
mock_create_fpolicy.assert_called_once_with(
fake_share_inst, vserver, vserver_client,
**provisioning_options)
else:
mock_create_fpolicy.assert_not_called()
def test_share_exists(self):
vserver_client = mock.Mock()
@ -1744,6 +1790,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_remove_export = self.mock_object(self.library, '_remove_export')
mock_deallocate_container = self.mock_object(self.library,
'_deallocate_container')
mock_delete_policy = self.mock_object(self.library,
'_delete_fpolicy_for_share')
self.library.delete_share(self.context,
fake.SHARE,
@ -1756,6 +1804,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_remove_export.assert_called_once_with(fake.SHARE, vserver_client)
mock_deallocate_container.assert_called_once_with(share_name,
vserver_client)
mock_delete_policy.assert_called_once_with(fake.SHARE, fake.VSERVER1,
vserver_client)
(vserver_client.mark_qos_policy_group_for_deletion
.assert_called_once_with(qos_policy_name))
self.assertEqual(0, lib_base.LOG.info.call_count)
@ -1799,6 +1849,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_remove_export = self.mock_object(self.library, '_remove_export')
mock_deallocate_container = self.mock_object(self.library,
'_deallocate_container')
mock_delete_fpolicy = self.mock_object(self.library,
'_delete_fpolicy_for_share')
self.library.delete_share(self.context,
fake.SHARE,
@ -1806,6 +1858,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
mock_share_exists.assert_called_once_with(share_name, vserver_client)
mock_delete_fpolicy.assert_called_once_with(fake.SHARE, fake.VSERVER1,
vserver_client)
self.assertFalse(mock_remove_export.called)
self.assertFalse(mock_deallocate_container.called)
self.assertFalse(
@ -2205,13 +2259,19 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertIsNone(result)
@ddt.data(True, False)
def test_manage_container_with_qos(self, qos):
@ddt.data({'qos': True, 'fpolicy': False}, {'qos': False, 'fpolicy': True})
@ddt.unpack
def test_manage_container(self, qos, fpolicy):
vserver_client = mock.Mock()
self.library._have_cluster_creds = True
qos_policy_group_name = fake.QOS_POLICY_GROUP_NAME if qos else None
extra_specs = fake.EXTRA_SPEC_WITH_QOS if qos else fake.EXTRA_SPEC
if qos:
extra_specs = copy.deepcopy(fake.EXTRA_SPEC_WITH_QOS)
elif fpolicy:
extra_specs = copy.deepcopy(fake.EXTRA_SPEC_WITH_FPOLICY)
else:
extra_specs = copy.deepcopy(fake.EXTRA_SPEC)
provisioning_opts = self.library._get_provisioning_options(extra_specs)
if qos:
provisioning_opts['qos_policy_group'] = fake.QOS_POLICY_GROUP_NAME
@ -2244,6 +2304,15 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_modify_or_create_qos_policy = self.mock_object(
self.library, '_modify_or_create_qos_for_existing_share',
mock.Mock(return_value=qos_policy_group_name))
fake_fpolicy_scope = {
'policy-name': fake.FPOLICY_POLICY_NAME,
'shares-to-include': [fake.FLEXVOL_NAME]
}
mock_find_scope = self.mock_object(
self.library, '_find_reusable_fpolicy_scope',
mock.Mock(return_value=fake_fpolicy_scope))
mock_modify_fpolicy = self.mock_object(
vserver_client, 'modify_fpolicy_scope')
result = self.library._manage_container(share_to_manage,
fake.VSERVER1,
@ -2266,6 +2335,18 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_modify_or_create_qos_policy.assert_called_once_with(
share_to_manage, extra_specs, fake.VSERVER1, vserver_client)
mock_validate_volume_for_manage.assert_called()
if fpolicy:
mock_find_scope.assert_called_once_with(
share_to_manage, vserver_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS,
shares_to_include=[fake.FLEXVOL_NAME])
mock_modify_fpolicy.assert_called_once_with(
fake.FPOLICY_POLICY_NAME, shares_to_include=[fake.SHARE_NAME])
else:
mock_find_scope.assert_not_called()
mock_modify_fpolicy.assert_not_called()
original_data = {
'original_name': fake.FLEXVOL_TO_MANAGE['name'],
@ -2350,6 +2431,35 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.VSERVER1,
vserver_client)
def test_manage_container_invalid_fpolicy(self):
vserver_client = mock.Mock()
extra_spec = copy.deepcopy(fake.EXTRA_SPEC_WITH_FPOLICY)
share_to_manage = copy.deepcopy(fake.SHARE)
share_to_manage['export_location'] = fake.EXPORT_LOCATION
mock_helper = mock.Mock()
mock_helper.get_share_name_for_share.return_value = fake.FLEXVOL_NAME
self.mock_object(self.library,
'_get_helper',
mock.Mock(return_value=mock_helper))
self.mock_object(vserver_client,
'get_volume_to_manage',
mock.Mock(return_value=fake.FLEXVOL_TO_MANAGE))
self.mock_object(self.library, '_validate_volume_for_manage')
self.mock_object(share_types,
'get_extra_specs_from_share',
mock.Mock(return_value=extra_spec))
self.mock_object(self.library, '_check_extra_specs_validity')
self.mock_object(self.library, '_find_reusable_fpolicy_scope',
mock.Mock(return_value=None))
self.assertRaises(exception.ManageExistingShareTypeMismatch,
self.library._manage_container,
share_to_manage,
fake.VSERVER1,
vserver_client)
def test_validate_volume_for_manage(self):
vserver_client = mock.Mock()
@ -5004,7 +5114,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(share_types, 'get_extra_specs_from_share')
self.mock_object(self.library, '_check_extra_specs_validity')
self.mock_object(self.library, '_check_aggregate_extra_specs_validity')
self.mock_object(self.library, '_get_provisioning_options')
self.mock_object(self.library, '_get_provisioning_options',
mock.Mock(return_value={}))
self.mock_object(self.library, '_get_normalized_qos_specs')
self.mock_object(self.library,
'validate_provisioning_options_for_share')
@ -5046,7 +5157,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library, '_check_aggregate_extra_specs_validity')
self.mock_object(self.library, '_get_backend_share_name',
mock.Mock(return_value=fake.SHARE_NAME))
self.mock_object(self.library, '_get_provisioning_options')
self.mock_object(self.library, '_get_provisioning_options',
mock.Mock(return_value={}))
self.mock_object(self.library, '_get_normalized_qos_specs')
self.mock_object(self.library,
'validate_provisioning_options_for_share')
@ -5085,7 +5197,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library, '_get_backend_share_name',
mock.Mock(return_value=fake.SHARE_NAME))
self.mock_object(data_motion, 'get_backend_configuration')
self.mock_object(self.library, '_get_provisioning_options')
self.mock_object(self.library, '_get_provisioning_options',
mock.Mock(return_value={}))
self.mock_object(self.library, '_get_normalized_qos_specs')
self.mock_object(self.library,
'validate_provisioning_options_for_share')
@ -5126,7 +5239,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(share_types, 'get_extra_specs_from_share')
self.mock_object(self.library, '_check_extra_specs_validity')
self.mock_object(self.library, '_check_aggregate_extra_specs_validity')
self.mock_object(self.library, '_get_provisioning_options')
self.mock_object(self.library, '_get_provisioning_options',
mock.Mock(return_value={}))
self.mock_object(self.library, '_get_normalized_qos_specs')
self.mock_object(self.library,
'validate_provisioning_options_for_share')
@ -5167,8 +5281,18 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
[mock.call(share_server=fake.SHARE_SERVER),
mock.call(share_server='dst_srv')])
def test_migration_check_compatibility(self):
@ddt.data(False, True)
def test_migration_check_compatibility(self, fpolicy):
self.library._have_cluster_creds = True
mock_dest_client = mock.Mock()
if fpolicy:
provisioning_options = copy.deepcopy(
fake.PROVISIONING_OPTIONS_WITH_FPOLICY)
get_vserver_side_effect = [(mock.Mock(), mock_dest_client),
(fake.VSERVER1, mock.Mock())]
else:
get_vserver_side_effect = [(fake.VSERVER1, mock.Mock())]
provisioning_options = {}
self.mock_object(share_types, 'get_extra_specs_from_share')
self.mock_object(self.library, '_check_extra_specs_validity')
self.mock_object(self.library, '_check_aggregate_extra_specs_validity')
@ -5176,21 +5300,33 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock.Mock(return_value=fake.SHARE_NAME))
self.mock_object(data_motion, 'get_backend_configuration')
self.mock_object(self.library, '_get_vserver',
mock.Mock(return_value=(fake.VSERVER1, mock.Mock())))
mock.Mock(side_effect=get_vserver_side_effect))
self.mock_object(share_utils, 'extract_host', mock.Mock(
side_effect=['destination_backend', 'destination_pool']))
mock_move_check = self.mock_object(self.client, 'check_volume_move')
self.mock_object(self.library, '_get_dest_flexvol_encryption_value',
mock.Mock(return_value=False))
self.mock_object(self.library, '_get_provisioning_options')
self.mock_object(self.library, '_get_provisioning_options',
mock.Mock(return_value=provisioning_options))
self.mock_object(self.library, '_get_normalized_qos_specs')
self.mock_object(self.library,
'validate_provisioning_options_for_share')
self.mock_object(self.library,
'_check_destination_vserver_for_vol_move')
fpolicies = [
x for x in range(1, self.library.FPOLICY_MAX_VSERVER_POLICIES + 1)]
mock_fpolicy_status = self.mock_object(
mock_dest_client, 'get_fpolicy_policies_status',
mock.Mock(return_value=fpolicies))
mock_reusable_fpolicy = self.mock_object(
self.library, '_find_reusable_fpolicy_scope',
mock.Mock(return_value={'fake'}))
src_instance = fake_share.fake_share_instance()
dst_instance = fake_share.fake_share_instance()
migration_compatibility = self.library.migration_check_compatibility(
self.context, fake_share.fake_share_instance(),
fake_share.fake_share_instance(), share_server=fake.SHARE_SERVER,
destination_share_server='dst_srv')
self.context, src_instance, dst_instance,
share_server=fake.SHARE_SERVER, destination_share_server='dst_srv')
expected_compatibility = {
'compatible': True,
@ -5205,9 +5341,20 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_move_check.assert_called_once_with(
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool',
encrypt_destination=False)
self.library._get_vserver.assert_has_calls(
[mock.call(share_server=fake.SHARE_SERVER),
mock.call(share_server='dst_srv')])
if fpolicy:
self.library._get_vserver.assert_has_calls(
[mock.call(share_server='dst_srv'),
mock.call(share_server=fake.SHARE_SERVER)])
mock_fpolicy_status.assert_called_once()
mock_reusable_fpolicy.assert_called_once_with(
dst_instance, mock_dest_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS
)
else:
self.library._get_vserver.assert_called_once_with(
share_server=fake.SHARE_SERVER)
def test_migration_check_compatibility_destination_type_is_encrypted(self):
self.library._have_cluster_creds = True
@ -5227,7 +5374,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'_check_extra_specs_validity')
self.mock_object(self.library,
'_check_aggregate_extra_specs_validity')
self.mock_object(self.library, '_get_provisioning_options')
self.mock_object(self.library, '_get_provisioning_options',
mock.Mock(return_value={}))
self.mock_object(self.library, '_get_normalized_qos_specs')
self.mock_object(self.library,
'validate_provisioning_options_for_share')
@ -5251,7 +5399,6 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock_move_check.assert_called_once_with(
fake.SHARE_NAME, fake.VSERVER1, 'destination_pool',
encrypt_destination=True)
self.library._get_vserver.assert_has_calls(
[mock.call(share_server=fake.SHARE_SERVER),
mock.call(share_server='dst_srv')])
@ -5529,6 +5676,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'policy_group_name': fake.QOS_POLICY_GROUP_NAME},
{'phase': 'completed',
'provisioning_options': fake.PROVISIONING_OPTIONS,
'policy_group_name': False},
{'phase': 'completed',
'provisioning_options': fake.PROVISIONING_OPTIONS_WITH_FPOLICY,
'policy_group_name': False})
@ddt.unpack
def test_migration_complete(self, phase, provisioning_options,
@ -5564,6 +5714,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock.Mock(side_effect=vol_move_side_effects))
self.mock_object(share_types, 'get_extra_specs_from_share',
mock.Mock(return_value=fake.EXTRA_SPEC))
self.mock_object(self.library, '_check_fpolicy_file_operations')
self.mock_object(
self.library, '_get_provisioning_options',
mock.Mock(return_value=provisioning_options))
@ -5571,6 +5722,11 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library, '_modify_or_create_qos_for_existing_share',
mock.Mock(return_value=policy_group_name))
self.mock_object(vserver_client, 'modify_volume')
mock_create_new_fpolicy = self.mock_object(
self.library, '_create_fpolicy_for_share')
mock_delete_policy = self.mock_object(self.library,
'_delete_fpolicy_for_share')
src_share = fake_share.fake_share_instance(id='source-share-instance')
dest_share = fake_share.fake_share_instance(id='dest-share-instance')
@ -5596,6 +5752,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
vserver_client.modify_volume.assert_called_once_with(
dest_aggr, 'new_share_name', **provisioning_options)
mock_info_log.assert_called_once()
mock_delete_policy.assert_called_once_with(src_share, fake.VSERVER1,
vserver_client)
if phase != 'completed':
self.assertEqual(2, mock_warning_log.call_count)
self.assertFalse(mock_debug_log.called)
@ -5604,6 +5762,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertFalse(mock_warning_log.called)
mock_debug_log.assert_called_once()
mock_move_status_check.assert_called_once()
if provisioning_options.get(
'fpolicy_extensions_to_include') is not None:
mock_create_new_fpolicy.assert_called_once_with(
dest_share, fake.VSERVER1, vserver_client,
**provisioning_options)
else:
mock_create_new_fpolicy.assert_not_called()
def test_modify_or_create_qos_for_existing_share_no_qos_extra_specs(self):
vserver_client = mock.Mock()
@ -6011,7 +6176,16 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
{'provisioning_opts': fake.PROVISIONING_OPTS_WITH_ADAPT_QOS,
'qos_specs': None,
'extra_specs': None,
'cluster_credentials': False},)
'cluster_credentials': False},
{'provisioning_opts': fake.PROVISIONING_OPTIONS_INVALID_FPOLICY,
'qos_specs': None,
'extra_specs': None,
'cluster_credentials': False},
{'provisioning_opts': fake.PROVISIONING_OPTIONS_WITH_FPOLICY,
'qos_specs': None,
'extra_specs': {'replication_type': 'dr'},
'cluster_credentials': False}
)
@ddt.unpack
def test_validate_provisioning_options_for_share_invalid_params(
self, provisioning_opts, qos_specs, extra_specs,
@ -6022,3 +6196,275 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library.validate_provisioning_options_for_share,
provisioning_opts, extra_specs=extra_specs,
qos_specs=qos_specs)
def test__get_backend_fpolicy_policy_name(self):
result = self.library._get_backend_fpolicy_policy_name(
fake.SHARE_ID)
expected = 'fpolicy_policy_' + fake.SHARE_ID.replace('-', '_')
self.assertEqual(expected, result)
def test__get_backend_fpolicy_event_name(self):
result = self.library._get_backend_fpolicy_event_name(
fake.SHARE_ID, 'NFS')
expected = 'fpolicy_event_nfs_' + fake.SHARE_ID.replace('-', '_')
self.assertEqual(expected, result)
@ddt.data({},
{'policy-name': fake.FPOLICY_POLICY_NAME,
'shares-to-include': [fake.SHARE_NAME]})
def test__create_fpolicy_for_share(self, reusable_scope):
vserver_client = mock.Mock()
vserver_name = fake.VSERVER1
new_fake_share = copy.deepcopy(fake.SHARE)
new_fake_share['id'] = 'new_fake_id'
new_fake_share['share_proto'] = 'CIFS'
event_name = 'fpolicy_event_cifs_new_fake_id'
events = [event_name]
policy_name = 'fpolicy_policy_new_fake_id'
shares_to_include = []
if reusable_scope:
shares_to_include = copy.deepcopy(
reusable_scope.get('shares-to-include'))
shares_to_include.append('share_new_fake_id')
mock_reusable_scope = self.mock_object(
self.library, '_find_reusable_fpolicy_scope',
mock.Mock(return_value=reusable_scope))
mock_modify_policy = self.mock_object(
vserver_client, 'modify_fpolicy_scope')
mock_get_policies = self.mock_object(
vserver_client, 'get_fpolicy_policies_status',
mock.Mock(return_value=[]))
mock_create_event = self.mock_object(
vserver_client, 'create_fpolicy_event')
mock_create_fpolicy = self.mock_object(
vserver_client, 'create_fpolicy_policy')
mock_create_scope = self.mock_object(
vserver_client, 'create_fpolicy_scope')
mock_enable_fpolicy = self.mock_object(
vserver_client, 'enable_fpolicy_policy')
self.library._create_fpolicy_for_share(
new_fake_share, vserver_name, vserver_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
mock_reusable_scope.assert_called_once_with(
new_fake_share, vserver_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
if reusable_scope:
mock_modify_policy.assert_called_once_with(
fake.FPOLICY_POLICY_NAME, shares_to_include=shares_to_include)
mock_get_policies.assert_not_called()
mock_create_event.assert_not_called()
mock_create_fpolicy.assert_not_called()
mock_create_scope.assert_not_called()
mock_enable_fpolicy.assert_not_called()
else:
mock_modify_policy.assert_not_called()
mock_get_policies.assert_called_once()
mock_create_event.assert_called_once_with(
event_name, new_fake_share['share_proto'].lower(),
fake.FPOLICY_FILE_OPERATIONS_LIST)
mock_create_fpolicy.assert_called_once_with(policy_name, events)
mock_create_scope.assert_called_once_with(
policy_name, 'share_new_fake_id',
extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE)
mock_enable_fpolicy.assert_called_once_with(policy_name, 1)
def test__create_fpolicy_for_share_max_policies_error(self):
fake_client = mock.Mock()
vserver_name = fake.VSERVER1
mock_reusable_scope = self.mock_object(
self.library, '_find_reusable_fpolicy_scope',
mock.Mock(return_value=None))
policies = [
x for x in range(1, self.library.FPOLICY_MAX_VSERVER_POLICIES + 1)]
mock_get_policies = self.mock_object(
fake_client, 'get_fpolicy_policies_status',
mock.Mock(return_value=policies))
self.assertRaises(
exception.NetAppException,
self.library._create_fpolicy_for_share,
fake.SHARE, vserver_name, fake_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
mock_reusable_scope.assert_called_once_with(
fake.SHARE, fake_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
mock_get_policies.assert_called_once()
def test__create_fpolicy_for_share_client_error(self):
fake_client = mock.Mock()
vserver_name = fake.VSERVER1
new_fake_share = copy.deepcopy(fake.SHARE)
new_fake_share['id'] = 'new_fake_id'
new_fake_share['share_proto'] = 'CIFS'
event_name = 'fpolicy_event_cifs_new_fake_id'
events = [event_name]
policy_name = 'fpolicy_policy_new_fake_id'
mock_reusable_scope = self.mock_object(
self.library, '_find_reusable_fpolicy_scope',
mock.Mock(return_value=None))
mock_get_policies = self.mock_object(
fake_client, 'get_fpolicy_policies_status',
mock.Mock(return_value=[]))
mock_create_event = self.mock_object(
fake_client, 'create_fpolicy_event')
mock_create_fpolicy = self.mock_object(
fake_client, 'create_fpolicy_policy')
mock_create_scope = self.mock_object(
fake_client, 'create_fpolicy_scope',
mock.Mock(side_effect=self._mock_api_error()))
mock_delete_fpolicy = self.mock_object(
fake_client, 'delete_fpolicy_policy')
mock_delete_event = self.mock_object(
fake_client, 'delete_fpolicy_event')
self.assertRaises(
exception.NetAppException,
self.library._create_fpolicy_for_share,
new_fake_share, vserver_name, fake_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
mock_reusable_scope.assert_called_once_with(
new_fake_share, fake_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
mock_get_policies.assert_called_once()
mock_create_event.assert_called_once_with(
event_name, new_fake_share['share_proto'].lower(),
fake.FPOLICY_FILE_OPERATIONS_LIST)
mock_create_fpolicy.assert_called_once_with(policy_name, events)
mock_create_scope.assert_called_once_with(
policy_name, 'share_new_fake_id',
extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE)
mock_delete_fpolicy.assert_called_once_with(policy_name)
mock_delete_event.assert_called_once_with(event_name)
def test__find_reusable_fpolicy_scope(self):
vserver_client = mock.Mock()
new_fake_share = copy.deepcopy(fake.SHARE)
new_fake_share['share_proto'] = 'CIFS'
reusable_scopes = [{
'policy-name': fake.FPOLICY_POLICY_NAME,
'file-extensions-to-include': fake.FPOLICY_EXT_TO_INCLUDE_LIST,
'file-extensions-to-exclude': fake.FPOLICY_EXT_TO_EXCLUDE_LIST,
'shares-to-include': ['any_other_fake_share'],
}]
reusable_policies = [{
'policy-name': fake.FPOLICY_POLICY_NAME,
'engine-name': fake.FPOLICY_ENGINE,
'events': [fake.FPOLICY_EVENT_NAME]
}]
reusable_events = [{
'event-name': fake.FPOLICY_EVENT_NAME,
'protocol': new_fake_share['share_proto'].lower(),
'file-operations': fake.FPOLICY_FILE_OPERATIONS_LIST
}]
mock_get_scopes = self.mock_object(
vserver_client, 'get_fpolicy_scopes',
mock.Mock(return_value=reusable_scopes))
mock_get_policies = self.mock_object(
vserver_client, 'get_fpolicy_policies',
mock.Mock(return_value=reusable_policies))
mocke_get_events = self.mock_object(
vserver_client, 'get_fpolicy_events',
mock.Mock(return_value=reusable_events)
)
result = self.library._find_reusable_fpolicy_scope(
new_fake_share, vserver_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
self.assertEqual(reusable_scopes[0], result)
mock_get_scopes.assert_called_once_with(
extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
shares_to_include=None)
mock_get_policies.assert_called_once_with(
policy_name=fake.FPOLICY_POLICY_NAME)
mocke_get_events.assert_called_once_with(
event_name=fake.FPOLICY_EVENT_NAME)
@ddt.data(False, True)
def test__delete_fpolicy_for_share(self, last_share):
fake_vserver_client = mock.Mock()
fake_vserver_name = fake.VSERVER1
fake_share = copy.deepcopy(fake.SHARE)
share_name = self.library._get_backend_share_name(fake.SHARE_ID)
existing_shares = [share_name]
if not last_share:
existing_shares.append('any_other_share')
scopes = [{
'policy-name': fake.FPOLICY_POLICY_NAME,
'file-extensions-to-include': fake.FPOLICY_EXT_TO_INCLUDE_LIST,
'file-extensions-to-exclude': fake.FPOLICY_EXT_TO_EXCLUDE_LIST,
'shares-to-include': existing_shares,
}]
shares_to_include = copy.copy(scopes[0].get('shares-to-include'))
shares_to_include.remove(share_name)
policies = [{
'policy-name': fake.FPOLICY_POLICY_NAME,
'engine-name': fake.FPOLICY_ENGINE,
'events': [fake.FPOLICY_EVENT_NAME]
}]
mock_get_scopes = self.mock_object(
fake_vserver_client, 'get_fpolicy_scopes',
mock.Mock(return_value=scopes))
mock_modify_scope = self.mock_object(
fake_vserver_client, 'modify_fpolicy_scope')
mock_disable_policy = self.mock_object(
fake_vserver_client, 'disable_fpolicy_policy')
mock_get_policies = self.mock_object(
fake_vserver_client, 'get_fpolicy_policies',
mock.Mock(return_value=policies))
mock_delete_scope = self.mock_object(
fake_vserver_client, 'delete_fpolicy_scope')
mock_delete_policy = self.mock_object(
fake_vserver_client, 'delete_fpolicy_policy')
mock_delete_event = self.mock_object(
fake_vserver_client, 'delete_fpolicy_event')
self.library._delete_fpolicy_for_share(fake_share, fake_vserver_name,
fake_vserver_client)
mock_get_scopes.assert_called_once_with(
shares_to_include=[share_name])
if shares_to_include:
mock_modify_scope.assert_called_once_with(
fake.FPOLICY_POLICY_NAME, shares_to_include=shares_to_include)
else:
mock_disable_policy.assert_called_once_with(
fake.FPOLICY_POLICY_NAME)
mock_get_policies.assert_called_once_with(
policy_name=fake.FPOLICY_POLICY_NAME)
mock_delete_scope.assert_called_once_with(
fake.FPOLICY_POLICY_NAME)
mock_delete_policy.assert_called_once_with(
fake.FPOLICY_POLICY_NAME)
mock_delete_event.assert_called_once_with(
fake.FPOLICY_EVENT_NAME)

View File

@ -2240,7 +2240,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SHARE_INSTANCE, self.mock_src_client,
fake_volume['aggregate'], self.mock_dest_client)
self.library._delete_share.assert_called_once_with(
fake.SHARE_INSTANCE, self.mock_src_client, remove_export=True)
fake.SHARE_INSTANCE, self.fake_src_vserver,
self.mock_src_client, remove_export=True)
def test_share_server_migration_complete_failure_breaking(self):
dm_session_mock = mock.Mock()
@ -2281,7 +2282,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.fake_src_share_server, self.fake_dest_share_server
)
self.library._delete_share.assert_called_once_with(
fake.SHARE_INSTANCE, self.mock_dest_client, remove_export=False)
fake.SHARE_INSTANCE, self.fake_dest_vserver, self.mock_dest_client,
remove_export=False)
def test_share_server_migration_complete_failure_get_new_volume(self):
dm_session_mock = mock.Mock()
@ -2375,7 +2377,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.fake_src_share_server, self.fake_dest_share_server
)
self.library._delete_share.assert_called_once_with(
fake.SHARE_INSTANCE, self.mock_dest_client, remove_export=False)
fake.SHARE_INSTANCE, self.fake_dest_vserver, self.mock_dest_client,
remove_export=False)
def test_share_server_migration_cancel_snapmirror_failure(self):
dm_session_mock = mock.Mock()
@ -2442,6 +2445,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
}
self.mock_object(mock_client, 'get_vserver_info',
mock.Mock(return_value=fake_vserver_info))
mock_get_extra_spec = self.mock_object(
share_types, 'get_extra_specs_from_share',
mock.Mock(return_value='fake_extra_specs'))
mock_get_provisioning_opts = self.mock_object(
self.library, '_get_provisioning_options',
mock.Mock(return_value={}))
result = self.library.choose_share_server_compatible_with_share(
None, [fake.SHARE_SERVER], fake.SHARE_INSTANCE,
@ -2449,6 +2458,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
)
expected_result = fake.SHARE_SERVER if compatible else None
self.assertEqual(expected_result, result)
mock_get_extra_spec.assert_called_once_with(fake.SHARE_INSTANCE)
mock_get_provisioning_opts.assert_called_once_with('fake_extra_specs')
if (share_group and
share_group['share_server_id'] != fake.SHARE_SERVER['id']):
mock_client.get_vserver_info.assert_not_called()
@ -2461,6 +2472,55 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.SHARE_SERVER, backend_name=fake.BACKEND_NAME
)
@ddt.data(
{'policies': [], 'reusable_scope': None, 'compatible': True},
{'policies': "0123456789", 'reusable_scope': {'scope'},
'compatible': True},
{'policies': "0123456789", 'reusable_scope': None,
'compatible': False})
@ddt.unpack
def test_choose_share_server_compatible_with_share_fpolicy(
self, policies, reusable_scope, compatible):
self.library.is_nfs_config_supported = False
mock_client = mock.Mock()
fake_extra_spec = copy.deepcopy(fake.EXTRA_SPEC_WITH_FPOLICY)
mock_get_extra_spec = self.mock_object(
share_types, 'get_extra_specs_from_share',
mock.Mock(return_value=fake_extra_spec))
self.mock_object(self.library, '_get_vserver',
mock.Mock(return_value=(fake.VSERVER1,
mock_client)))
self.mock_object(mock_client, 'get_vserver_info',
mock.Mock(return_value=fake.VSERVER_INFO))
mock_get_policies = self.mock_object(
mock_client, 'get_fpolicy_policies_status',
mock.Mock(return_value=policies))
mock_reusable_scope = self.mock_object(
self.library, '_find_reusable_fpolicy_scope',
mock.Mock(return_value=reusable_scope))
result = self.library.choose_share_server_compatible_with_share(
None, [fake.SHARE_SERVER], fake.SHARE_INSTANCE,
None, None
)
expected_result = fake.SHARE_SERVER if compatible else None
self.assertEqual(expected_result, result)
mock_get_extra_spec.assert_called_once_with(fake.SHARE_INSTANCE)
mock_client.get_vserver_info.assert_called_once_with(
fake.VSERVER1,
)
self.library._get_vserver.assert_called_once_with(
fake.SHARE_SERVER, backend_name=fake.BACKEND_NAME
)
mock_get_policies.assert_called_once()
if len(policies) >= self.library.FPOLICY_MAX_VSERVER_POLICIES:
mock_reusable_scope.assert_called_once_with(
fake.SHARE_INSTANCE, mock_client,
fpolicy_extensions_to_include=fake.FPOLICY_EXT_TO_INCLUDE,
fpolicy_extensions_to_exclude=fake.FPOLICY_EXT_TO_EXCLUDE,
fpolicy_file_operations=fake.FPOLICY_FILE_OPERATIONS)
@ddt.data({'subtype': 'default', 'compatible': True},
{'subtype': 'dp_destination', 'compatible': False})
@ddt.unpack

View File

@ -92,6 +92,16 @@ QOS_EXTRA_SPEC = 'netapp:maxiops'
QOS_SIZE_DEPENDENT_EXTRA_SPEC = 'netapp:maxbpspergib'
QOS_NORMALIZED_SPEC = 'maxiops'
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
FPOLICY_POLICY_NAME = 'fake_fpolicy_name'
FPOLICY_EVENT_NAME = 'fake_fpolicy_event_name'
FPOLICY_PROTOCOL = 'cifs'
FPOLICY_FILE_OPERATIONS = 'create,write,rename'
FPOLICY_FILE_OPERATIONS_LIST = ['create', 'write', 'rename']
FPOLICY_ENGINE = 'native'
FPOLICY_EXT_TO_INCLUDE = 'avi'
FPOLICY_EXT_TO_INCLUDE_LIST = ['avi']
FPOLICY_EXT_TO_EXCLUDE = 'jpg,mp3'
FPOLICY_EXT_TO_EXCLUDE_LIST = ['jpg', 'mp3']
CLIENT_KWARGS = {
'username': 'admin',
@ -200,6 +210,12 @@ EXTRA_SPEC_WITH_REPLICATION.update({
'replication_type': 'dr'
})
EXTRA_SPEC_WITH_FPOLICY = copy.copy(EXTRA_SPEC)
EXTRA_SPEC_WITH_FPOLICY.update(
{'fpolicy_extensions_to_include': FPOLICY_EXT_TO_INCLUDE,
'fpolicy_extensions_to_exclude': FPOLICY_EXT_TO_EXCLUDE,
'fpolicy_file_operations': FPOLICY_FILE_OPERATIONS})
NFS_CONFIG_DEFAULT = {
'tcp-max-xfer-size': 65536,
'udp-max-xfer-size': 32768,
@ -262,6 +278,12 @@ EXTRA_SPEC_WITH_QOS.update({
QOS_EXTRA_SPEC: '3000',
})
EXTRA_SPEC_WITH_FPOLICY = copy.deepcopy(EXTRA_SPEC)
EXTRA_SPEC_WITH_FPOLICY.update(
{'netapp:fpolicy_extensions_to_include': FPOLICY_EXT_TO_INCLUDE,
'netapp:fpolicy_extensions_to_exclude': FPOLICY_EXT_TO_EXCLUDE,
'netapp:fpolicy_file_operations': FPOLICY_FILE_OPERATIONS})
EXTRA_SPEC_WITH_SIZE_DEPENDENT_QOS = copy.deepcopy(EXTRA_SPEC)
EXTRA_SPEC_WITH_SIZE_DEPENDENT_QOS.update({
'qos': True,
@ -289,6 +311,16 @@ PROVISIONING_OPTS_WITH_ADAPT_QOS = copy.deepcopy(PROVISIONING_OPTIONS)
PROVISIONING_OPTS_WITH_ADAPT_QOS.update(
{'adaptive_qos_policy_group': QOS_POLICY_GROUP_NAME})
PROVISIONING_OPTIONS_WITH_FPOLICY = copy.deepcopy(PROVISIONING_OPTIONS)
PROVISIONING_OPTIONS_WITH_FPOLICY.update(
{'fpolicy_extensions_to_include': FPOLICY_EXT_TO_INCLUDE,
'fpolicy_extensions_to_exclude': FPOLICY_EXT_TO_EXCLUDE,
'fpolicy_file_operations': FPOLICY_FILE_OPERATIONS})
PROVISIONING_OPTIONS_INVALID_FPOLICY = copy.deepcopy(PROVISIONING_OPTIONS)
PROVISIONING_OPTIONS_INVALID_FPOLICY.update(
{'fpolicy_file_operations': FPOLICY_FILE_OPERATIONS})
PROVISIONING_OPTIONS_BOOLEAN = {
'thin_provisioned': True,
'dedup_enabled': False,
@ -313,6 +345,9 @@ PROVISIONING_OPTIONS_STRING = {
'language': 'en-US',
'max_files': 5000,
'adaptive_qos_policy_group': None,
'fpolicy_extensions_to_exclude': None,
'fpolicy_extensions_to_include': None,
'fpolicy_file_operations': None,
}
PROVISIONING_OPTIONS_STRING_MISSING_SPECS = {
@ -320,6 +355,9 @@ PROVISIONING_OPTIONS_STRING_MISSING_SPECS = {
'language': 'en-US',
'max_files': None,
'adaptive_qos_policy_group': None,
'fpolicy_extensions_to_exclude': None,
'fpolicy_extensions_to_include': None,
'fpolicy_file_operations': None,
}
PROVISIONING_OPTIONS_STRING_DEFAULT = {
@ -327,6 +365,9 @@ PROVISIONING_OPTIONS_STRING_DEFAULT = {
'language': None,
'max_files': None,
'adaptive_qos_policy_group': None,
'fpolicy_extensions_to_exclude': None,
'fpolicy_extensions_to_include': None,
'fpolicy_file_operations': None,
}
SHORT_BOOLEAN_EXTRA_SPEC = {

View File

@ -0,0 +1,25 @@
---
features:
- |
Added support for FPolicy on NetApp ONTAP driver. FPolicy allows creation
of file policies that specify file operation permissions according to
file type. This feature can be enabled using the following extra-specs:
- ``netapp:fpolicy_extensions_to_include``:
specifies file extensions to be included for screening. Values should be
provided as comma separated list.
- ``netapp:fpolicy_extensions_to_exclude``:
specifies file extensions to be excluded for screening. Values should be
provided as comma separated list.
- ``netapp:fpolicy_file_operations``:
specifies all file operations to be monitored. Values should be provided
as comma separated list.
FPolicy works for backends with and without share server management. When
using NetApp backends with SVM administrator accounts, make sure that the
assigned access-control role has access set to "all" for "vserver fpolicy"
directory.
This feature does not work with share replicas to avoid failures on replica
promotion, due to lack of FPolicy resources in the destination SVM.