[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:
parent
1515701df0
commit
0b04d8d671
@ -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'
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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 = {
|
||||
|
@ -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',
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user