[NetApp] Add FlexGroup volume support

The NetApp driver has been working with FlexVol ONTAP volumes. This
type of volume does not work well with large workloads, being over
one single node/aggregate. This patch changes that behavior by enabling
the provision of Manila shares as ONTAP FlexGroup volumes.

The FlexGroup pool is configured with a new option `netapp_flexgroup_pool`.
The pool for FlexGroup is different from the FlexVol one, the driver
can handle those two types of pools, either alone or together. For
having both, the new option `netapp_flexgroup_pool_only` must be
set to `False`. Each NetApp pool will report now the capability:
`netapp_is_flexgroup` informing which type the pool is.

The following operations are allowed with FlexGroup shares (DHSS
True/False and NFS/CIFS):

- Create/Delete share;
- Shrink/Extend share;
- Create/Delete snapshot;
- Revert to snapshot;
- Manage/Unmanage snapshots;
- Create from snapshot;
- Replication[1]
- Manage/Unmanage shares;

The backend with one FlexGroup pool configured will drop the consistent
snapshot support for all pools.

The driver FlexGroup support requires ONTAP version 9.8 or greater.

[1] FlexGroup is limited to one single replica for ONTAP version
lower than 9.9.1.

Implements: bp netapp-flexgroup-support
Change-Id: I4f68a9bb33be85f9a22e0be4ccf673647e713459
Signed-off-by: Felipe Rodrigues <felipefuty01@gmail.com>
This commit is contained in:
Felipe Rodrigues 2021-05-06 04:02:33 -03:00
parent 82ea5d5680
commit a84005cb5c
21 changed files with 2963 additions and 610 deletions

View File

@ -74,6 +74,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
ontapi_1_120 = ontapi_version >= (1, 120)
ontapi_1_140 = ontapi_version >= (1, 140)
ontapi_1_150 = ontapi_version >= (1, 150)
ontapi_1_180 = ontapi_version >= (1, 180)
ontapi_1_191 = ontapi_version >= (1, 191)
self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
self.features.add_feature('SYSTEM_METRICS', supported=ontapi_1_2x)
@ -95,6 +97,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
supported=ontapi_1_150)
self.features.add_feature('LDAP_LDAP_SERVERS',
supported=ontapi_1_120)
self.features.add_feature('FLEXGROUP', supported=ontapi_1_180)
self.features.add_feature('FLEXGROUP_FAN_OUT', supported=ontapi_1_191)
def _invoke_vserver_api(self, na_element, vserver):
server = copy.copy(self.connection)
@ -2022,6 +2026,59 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'containing-aggr-name': aggregate_name,
'size': six.text_type(size_gb) + 'g',
'volume': volume_name,
}
api_args.update(self._get_create_volume_api_args(
volume_name, thin_provisioned, snapshot_policy, language,
snapshot_reserve, volume_type, qos_policy_group, encrypt,
adaptive_qos_policy_group))
self.send_request('volume-create', api_args)
self.update_volume_efficiency_attributes(volume_name,
dedup_enabled,
compression_enabled)
if max_files is not None:
self.set_volume_max_files(volume_name, max_files)
@na_utils.trace
def create_volume_async(self, aggregate_list, volume_name, size_gb,
thin_provisioned=False, snapshot_policy=None,
language=None, snapshot_reserve=None,
volume_type='rw', qos_policy_group=None,
encrypt=False, adaptive_qos_policy_group=None,
**options):
"""Creates a volume asynchronously."""
if adaptive_qos_policy_group and not self.features.ADAPTIVE_QOS:
msg = 'Adaptive QoS not supported on this backend ONTAP version.'
raise exception.NetAppException(msg)
api_args = {
'aggr-list': [{'aggr-name': aggr} for aggr in aggregate_list],
'size': size_gb * units.Gi,
'volume-name': volume_name,
}
api_args.update(self._get_create_volume_api_args(
volume_name, thin_provisioned, snapshot_policy, language,
snapshot_reserve, volume_type, qos_policy_group, encrypt,
adaptive_qos_policy_group))
result = self.send_request('volume-create-async', api_args)
job_info = {
'status': result.get_child_content('result-status'),
'jobid': result.get_child_content('result-jobid'),
'error-code': result.get_child_content('result-error-code'),
'error-message': result.get_child_content('result-error-message')
}
return job_info
def _get_create_volume_api_args(self, volume_name, thin_provisioned,
snapshot_policy, language,
snapshot_reserve, volume_type,
qos_policy_group, encrypt,
adaptive_qos_policy_group):
api_args = {
'volume-type': volume_type,
}
if volume_type != 'dp':
@ -2033,8 +2090,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
if language is not None:
api_args['language-code'] = language
if snapshot_reserve is not None:
api_args['percentage-snapshot-reserve'] = six.text_type(
snapshot_reserve)
api_args['percentage-snapshot-reserve'] = str(snapshot_reserve)
if qos_policy_group is not None:
api_args['qos-policy-group-name'] = qos_policy_group
if adaptive_qos_policy_group is not None:
@ -2048,13 +2104,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
else:
api_args['encrypt'] = 'true'
self.send_request('volume-create', api_args)
self.update_volume_efficiency_attributes(volume_name,
dedup_enabled,
compression_enabled)
if max_files is not None:
self.set_volume_max_files(volume_name, max_files)
return api_args
@na_utils.trace
def enable_dedup(self, volume_name):
@ -2086,6 +2136,36 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
}
self.send_request('sis-set-config', api_args)
@na_utils.trace
def enable_dedupe_async(self, volume_name):
"""Enable deduplication on FlexVol/FlexGroup volume asynchronously."""
api_args = {'volume-name': volume_name}
self.connection.send_request('sis-enable-async', api_args)
@na_utils.trace
def disable_dedupe_async(self, volume_name):
"""Disable deduplication on FlexVol/FlexGroup volume asynchronously."""
api_args = {'volume-name': volume_name}
self.connection.send_request('sis-disable-async', api_args)
@na_utils.trace
def enable_compression_async(self, volume_name):
"""Enable compression on FlexVol/FlexGroup volume asynchronously."""
api_args = {
'volume-name': volume_name,
'enable-compression': 'true'
}
self.connection.send_request('sis-set-config-async', api_args)
@na_utils.trace
def disable_compression_async(self, volume_name):
"""Disable compression on FlexVol/FlexGroup volume asynchronously."""
api_args = {
'volume-name': volume_name,
'enable-compression': 'false'
}
self.connection.send_request('sis-set-config-async', api_args)
@na_utils.trace
def get_volume_efficiency_status(self, volume_name):
"""Get dedupe & compression status for a volume."""
@ -2290,7 +2370,24 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
qos_policy_group=None, hide_snapdir=None,
autosize_attributes=None,
adaptive_qos_policy_group=None, **options):
"""Update backend volume for a share as necessary."""
"""Update backend volume for a share as necessary.
:param aggregate_name: either a list or a string. List for aggregate
names where the FlexGroup resides, while a string for the aggregate
name where FlexVol volume is.
:param volume_name: name of the modified volume.
:param thin_provisioned: volume is thin.
:param snapshot_policy: policy of volume snapshot.
:param language: language of the volume.
:param dedup_enabled: is the deduplication enabled for the volume.
:param compression_enabled: is the compression enabled for the volume.
:param max_files: number of maximum files in the volume.
:param qos_policy_group: name of the QoS policy.
:param hide_snapdir: hide snapshot directory.
:param autosize_attributes: autosize for the volume.
:param adaptive_qos_policy_group: name of the adaptive QoS policy.
"""
if adaptive_qos_policy_group and not self.features.ADAPTIVE_QOS:
msg = 'Adaptive QoS not supported on this backend ONTAP version.'
raise exception.NetAppException(msg)
@ -2299,7 +2396,6 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'query': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': aggregate_name,
'name': volume_name,
},
},
@ -2319,6 +2415,16 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
},
},
}
if isinstance(aggregate_name, str):
is_flexgroup = False
api_args['query']['volume-attributes']['volume-id-attributes'][
'containing-aggregate-name'] = aggregate_name
elif isinstance(aggregate_name, list):
is_flexgroup = True
aggr_list = [{'aggr-name': aggr_name} for aggr_name in
aggregate_name]
api_args['query']['volume-attributes']['volume-id-attributes'][
'aggr-list'] = aggr_list
if language:
api_args['attributes']['volume-attributes'][
'volume-language-attributes']['language'] = language
@ -2351,11 +2457,13 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
# Efficiency options must be handled separately
self.update_volume_efficiency_attributes(volume_name,
dedup_enabled,
compression_enabled)
compression_enabled,
is_flexgroup=is_flexgroup)
@na_utils.trace
def update_volume_efficiency_attributes(self, volume_name, dedup_enabled,
compression_enabled):
compression_enabled,
is_flexgroup=False):
"""Update dedupe & compression attributes to match desired values."""
efficiency_status = self.get_volume_efficiency_status(volume_name)
@ -2364,15 +2472,27 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
# enable/disable dedup if needed
if dedup_enabled and not efficiency_status['dedupe']:
self.enable_dedup(volume_name)
if is_flexgroup:
self.enable_dedupe_async(volume_name)
else:
self.enable_dedup(volume_name)
elif not dedup_enabled and efficiency_status['dedupe']:
self.disable_dedup(volume_name)
if is_flexgroup:
self.disable_dedupe_async(volume_name)
else:
self.disable_dedup(volume_name)
# enable/disable compression if needed
if compression_enabled and not efficiency_status['compression']:
self.enable_compression(volume_name)
if is_flexgroup:
self.enable_compression_async(volume_name)
else:
self.enable_compression(volume_name)
elif not compression_enabled and efficiency_status['compression']:
self.disable_compression(volume_name)
if is_flexgroup:
self.disable_compression_async(volume_name)
else:
self.disable_compression(volume_name)
@na_utils.trace
def volume_exists(self, volume_name):
@ -2448,6 +2568,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'aggr-list': {
'aggr-name': None,
},
'containing-aggregate-name': None,
'name': None,
},
@ -2465,6 +2588,11 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
aggregate = volume_id_attributes.get_child_content(
'containing-aggregate-name')
if not aggregate:
aggr_list_attr = volume_id_attributes.get_child_by_name(
'aggr-list') or netapp_api.NaElement('none')
aggregate = [aggr_elem.get_content()
for aggr_elem in aggr_list_attr.get_children()]
if not aggregate:
msg = _('Could not find aggregate for volume %s.')
@ -2493,9 +2621,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
return self._has_records(result)
@na_utils.trace
def volume_has_junctioned_volumes(self, volume_name):
def volume_has_junctioned_volumes(self, junction_path):
"""Checks if volume has volumes mounted beneath its junction path."""
junction_path = self.get_volume_junction_path(volume_name)
if not junction_path:
return False
@ -2553,12 +2680,16 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'aggr-list': {
'aggr-name': None,
},
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
'owning-vserver-name': None,
'type': None,
'style': None,
'style-extended': None,
},
'volume-qos-attributes': {
'policy-group-name': None,
@ -2591,9 +2722,19 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
volume_space_attributes = volume_attributes.get_child_by_name(
'volume-space-attributes') or netapp_api.NaElement('none')
aggregate = volume_id_attributes.get_child_content(
'containing-aggregate-name')
aggregate_list = []
if not aggregate:
aggregate = ''
aggr_list_attr = volume_id_attributes.get_child_by_name(
'aggr-list') or netapp_api.NaElement('none')
aggregate_list = [aggr_elem.get_content()
for aggr_elem in aggr_list_attr.get_children()]
volume = {
'aggregate': volume_id_attributes.get_child_content(
'containing-aggregate-name'),
'aggregate': aggregate,
'aggr-list': aggregate_list,
'junction-path': volume_id_attributes.get_child_content(
'junction-path'),
'name': volume_id_attributes.get_child_content('name'),
@ -2603,7 +2744,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'style': volume_id_attributes.get_child_content('style'),
'size': volume_space_attributes.get_child_content('size'),
'qos-policy-group-name': volume_qos_attributes.get_child_content(
'policy-group-name')
'policy-group-name'),
'style-extended': volume_id_attributes.get_child_content(
'style-extended')
}
return volume
@ -2618,21 +2761,17 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'volume-attributes': {
'volume-id-attributes': {
'junction-path': junction_path,
'style-extended': '%s|%s' % (
na_utils.FLEXGROUP_STYLE_EXTENDED,
na_utils.FLEXVOL_STYLE_EXTENDED),
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
'type': None,
'style': None,
},
'volume-space-attributes': {
'size': None,
}
},
},
}
@ -2646,30 +2785,26 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'volume-attributes') or netapp_api.NaElement('none')
volume_id_attributes = volume_attributes.get_child_by_name(
'volume-id-attributes') or netapp_api.NaElement('none')
volume_space_attributes = volume_attributes.get_child_by_name(
'volume-space-attributes') or netapp_api.NaElement('none')
volume = {
'aggregate': volume_id_attributes.get_child_content(
'containing-aggregate-name'),
'junction-path': volume_id_attributes.get_child_content(
'junction-path'),
'name': volume_id_attributes.get_child_content('name'),
'type': volume_id_attributes.get_child_content('type'),
'style': volume_id_attributes.get_child_content('style'),
'size': volume_space_attributes.get_child_content('size'),
}
return volume
@na_utils.trace
def get_volume_to_manage(self, aggregate_name, volume_name):
"""Get flexvol to be managed by Manila."""
"""Get flexvol to be managed by Manila.
:param aggregate_name: either a list or a string. List for aggregate
names where the FlexGroup resides, while a string for the aggregate
name where FlexVol volume is.
:param volume_name: name of the managed volume.
"""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': aggregate_name,
'name': volume_name,
},
},
@ -2677,6 +2812,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'aggr-list': {
'aggr-name': None,
},
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
@ -2693,6 +2831,15 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
},
},
}
if isinstance(aggregate_name, str):
api_args['query']['volume-attributes']['volume-id-attributes'][
'containing-aggregate-name'] = aggregate_name
elif isinstance(aggregate_name, list):
aggr_list = [{'aggr-name': aggr_name} for aggr_name in
aggregate_name]
api_args['query']['volume-attributes']['volume-id-attributes'][
'aggr-list'] = aggr_list
result = self.send_iter_request('volume-get-iter', api_args)
if not self._has_records(result):
return None
@ -2708,9 +2855,19 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
volume_space_attributes = volume_attributes.get_child_by_name(
'volume-space-attributes') or netapp_api.NaElement('none')
aggregate = volume_id_attributes.get_child_content(
'containing-aggregate-name')
aggregate_list = []
if not aggregate:
aggregate = ''
aggr_list_attr = volume_id_attributes.get_child_by_name(
'aggr-list') or netapp_api.NaElement('none')
aggregate_list = [aggr_elem.get_content()
for aggr_elem in aggr_list_attr.get_children()]
volume = {
'aggregate': volume_id_attributes.get_child_content(
'containing-aggregate-name'),
'aggregate': aggregate,
'aggr-list': aggregate_list,
'junction-path': volume_id_attributes.get_child_content(
'junction-path'),
'name': volume_id_attributes.get_child_content('name'),
@ -3964,8 +4121,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
@na_utils.trace
def create_snapmirror_vol(self, source_vserver, source_volume,
destination_vserver, destination_volume,
schedule=None, policy=None,
relationship_type='data_protection'):
relationship_type, schedule=None,
policy=na_utils.MIRROR_ALL_SNAP_POLICY):
"""Creates a SnapMirror relationship between volumes."""
self._create_snapmirror(source_vserver, destination_vserver,
source_volume=source_volume,
@ -3976,7 +4133,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
@na_utils.trace
def create_snapmirror_svm(self, source_vserver, destination_vserver,
schedule=None, policy=None,
relationship_type='data_protection',
relationship_type=na_utils.DATA_PROTECTION_TYPE,
identity_preserve=True,
max_transfer_rate=None):
"""Creates a SnapMirror relationship between vServers."""
@ -3990,7 +4147,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
def _create_snapmirror(self, source_vserver, destination_vserver,
source_volume=None, destination_volume=None,
schedule=None, policy=None,
relationship_type='data_protection',
relationship_type=na_utils.DATA_PROTECTION_TYPE,
identity_preserve=None, max_transfer_rate=None):
"""Creates a SnapMirror relationship (cDOT 8.2 or later only)."""
self._ensure_snapmirror_v2()
@ -4125,12 +4282,12 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
source_path, dest_path, source_vserver,
dest_vserver, source_volume, dest_volume)
self._ensure_snapmirror_v2()
dest_info['relationship-info-only'] = (
'true' if relationship_info_only else 'false')
api_args = {
'query': {
'snapmirror-destination-info': dest_info
}
'snapmirror-destination-info': dest_info,
},
'relationship-info-only': (
'true' if relationship_info_only else 'false'),
}
self.send_request('snapmirror-release-iter', api_args,
enable_tunneling=enable_tunneling)
@ -5433,3 +5590,90 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
raise exception.NetAppException(msg)
return fpolicy_status
@na_utils.trace
def get_volume_state(self, name):
"""Returns volume state for a given name"""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': name,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-state-attributes': {
'state': None
}
}
},
}
result = self.send_iter_request('volume-get-iter', api_args)
volume_state = ''
if self._has_records(result):
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes = attributes_list.get_child_by_name(
'volume-attributes') or netapp_api.NaElement('none')
volume_state_attributes = volume_attributes.get_child_by_name(
'volume-state-attributes') or netapp_api.NaElement('none')
volume_state = volume_state_attributes.get_child_content('state')
return volume_state
@na_utils.trace
def is_flexgroup_volume(self, volume_name):
"""Determines if the ONTAP volume is FlexGroup."""
if not self.features.FLEXGROUP:
return False
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'style-extended': None,
},
},
},
}
result = self.send_request('volume-get-iter', api_args)
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes_list = attributes_list.get_children()
if not self._has_records(result):
raise exception.StorageResourceNotFound(name=volume_name)
elif len(volume_attributes_list) > 1:
msg = _('Could not find unique volume %(vol)s.')
msg_args = {'vol': volume_name}
raise exception.NetAppException(msg % msg_args)
volume_attributes = volume_attributes_list[0]
volume_id_attributes = volume_attributes.get_child_by_name(
'volume-id-attributes') or netapp_api.NaElement('none')
return na_utils.is_style_extended_flexgroup(
volume_id_attributes.get_child_content('style-extended'))
@na_utils.trace
def is_flexgroup_supported(self):
return self.features.FLEXGROUP
@na_utils.trace
def is_flexgroup_fan_out_supported(self):
return self.features.FLEXGROUP_FAN_OUT

View File

@ -160,7 +160,8 @@ class DataMotionSession(object):
'last-transfer-end-timestamp'])
return snapmirrors
def create_snapmirror(self, source_share_obj, dest_share_obj):
def create_snapmirror(self, source_share_obj, dest_share_obj,
relationship_type):
"""Sets up a SnapMirror relationship between two volumes.
1. Create SnapMirror relationship
@ -180,6 +181,7 @@ class DataMotionSession(object):
src_volume_name,
dest_vserver,
dest_volume_name,
relationship_type,
schedule='hourly')
# 2. Initialize async transfer of the initial data
@ -189,7 +191,7 @@ class DataMotionSession(object):
dest_volume_name)
def delete_snapmirror(self, source_share_obj, dest_share_obj,
release=True):
release=True, relationship_info_only=False):
"""Ensures all information about a SnapMirror relationship is removed.
1. Abort snapmirror
@ -239,10 +241,10 @@ class DataMotionSession(object):
# 3. Cleanup SnapMirror relationship on source
try:
if src_client:
src_client.release_snapmirror_vol(src_vserver,
src_volume_name,
dest_vserver,
dest_volume_name)
src_client.release_snapmirror_vol(
src_vserver, src_volume_name, dest_vserver,
dest_volume_name,
relationship_info_only=relationship_info_only)
except netapp_api.NaApiError as e:
with excutils.save_and_reraise_exception() as exc_context:
if (e.code == netapp_api.EOBJECTNOTFOUND or
@ -400,7 +402,8 @@ class DataMotionSession(object):
def change_snapmirror_source(self, replica,
orig_source_replica,
new_source_replica, replica_list):
new_source_replica, replica_list,
is_flexgroup=False):
"""Creates SnapMirror relationship from the new source to destination.
1. Delete all snapmirrors involving the replica, but maintain
@ -419,16 +422,24 @@ class DataMotionSession(object):
new_src_volume_name, new_src_vserver, new_src_backend = (
self.get_backend_info_for_share(new_source_replica))
new_src_client = get_client_for_backend(
new_src_backend, vserver_name=new_src_vserver)
# 1. delete
for other_replica in replica_list:
if other_replica['id'] == replica['id']:
continue
# We need to delete ALL snapmirror relationships
# involving this replica but do not remove snapmirror metadata
# so that the new snapmirror relationship is efficient.
self.delete_snapmirror(other_replica, replica, release=False)
self.delete_snapmirror(replica, other_replica, release=False)
# deletes all snapmirror relationships involving this replica to
# ensure new relation can be set. For efficient snapmirror, it
# does not remove the snapshots, only releasing the relationship
# info whether FlexGroup volume.
self.delete_snapmirror(other_replica, replica,
release=is_flexgroup,
relationship_info_only=is_flexgroup)
self.delete_snapmirror(replica, other_replica,
release=is_flexgroup,
relationship_info_only=is_flexgroup)
# 2. vserver operations when driver handles share servers
replica_config = get_backend_configuration(replica_backend)
@ -437,8 +448,6 @@ class DataMotionSession(object):
# create vserver peering if does not exists
if not replica_client.get_vserver_peers(replica_vserver,
new_src_vserver):
new_src_client = get_client_for_backend(
new_src_backend, vserver_name=new_src_vserver)
# Cluster name is needed for setting up the vserver peering
new_src_cluster_name = new_src_client.get_cluster_name()
replica_cluster_name = replica_client.get_cluster_name()
@ -452,11 +461,14 @@ class DataMotionSession(object):
# 3. create
# TODO(ameade): Update the schedule if needed.
relationship_type = na_utils.get_relationship_type(is_flexgroup)
replica_client.create_snapmirror_vol(new_src_vserver,
new_src_volume_name,
replica_vserver,
replica_volume_name,
relationship_type,
schedule='hourly')
# 4. resync
replica_client.resync_snapmirror_vol(new_src_vserver,
new_src_volume_name,
@ -733,3 +745,15 @@ class DataMotionSession(object):
msg = _("Unable to release the snapmirror from source vserver %s. "
"Retries exhausted. Aborting") % source_vserver
raise exception.NetAppException(message=msg)
def is_flexgroup_share(self, share_host):
"""Returns if the share host is over a FlexGroup pool."""
__, config = self.get_backend_name_and_config_obj(share_host)
flexgroup_pools = config.safe_get('netapp_flexgroup_pool')
if not flexgroup_pools:
return False
pool_name = share_utils.extract_host(share_host, level='pool')
pools = na_utils.parse_flexgroup_pool_config(flexgroup_pools)
return pool_name in pools

View File

@ -63,8 +63,13 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
'configuration when the driver is managing share servers.')
raise exception.InvalidInput(reason=msg)
# Ensure FlexGroup support
aggr_list = self._client.list_non_root_aggregates()
self._initialize_flexgroup_pools(set(aggr_list))
# Ensure one or more aggregates are available.
if not self._find_matching_aggregates():
if (self.is_flexvol_pool_configured() and
not self._find_matching_aggregates(aggregate_names=aggr_list)):
msg = _('No aggregates are available for provisioning shares. '
'Ensure that the configuration option '
'netapp_aggregate_name_search_pattern is set correctly.')
@ -108,6 +113,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
'pools': {
'vserver': None,
'aggregates': self._find_matching_aggregates(),
'flexgroup': self._flexgroup_pools,
},
}
@ -122,9 +128,15 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
_handle_housekeeping_tasks())
@na_utils.trace
def _find_matching_aggregates(self):
def _find_matching_aggregates(self, aggregate_names=None):
"""Find all aggregates match pattern."""
aggregate_names = self._client.list_non_root_aggregates()
if not self.is_flexvol_pool_configured():
return []
if not aggregate_names:
aggregate_names = self._client.list_non_root_aggregates()
pattern = self.configuration.netapp_aggregate_name_search_pattern
return [aggr_name for aggr_name in aggregate_names
if re.match(pattern, aggr_name)]
@ -240,13 +252,16 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
ipspace_name = self._client.get_ipspace_name_for_vlan_port(
node_name, port, vlan) or self._create_ipspace(network_info)
aggregate_names = self._find_matching_aggregates()
aggr_set = set(aggregate_names).union(
self._get_flexgroup_aggr_set())
if is_dp_destination:
# Get Data ONTAP aggregate name as pool name.
LOG.debug('Creating a new Vserver (%s) for data protection.',
vserver_name)
self._client.create_vserver_dp_destination(
vserver_name,
self._find_matching_aggregates(),
aggr_set,
ipspace_name)
# Set up port and broadcast domain for the current ipspace
self._create_port_and_broadcast_domain(ipspace_name, network_info)
@ -256,7 +271,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
vserver_name,
self.configuration.netapp_root_volume_aggregate,
self.configuration.netapp_root_volume,
self._find_matching_aggregates(),
aggr_set,
ipspace_name)
vserver_client = self._get_api_client(vserver=vserver_name)
@ -965,7 +980,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
vserver_name=None)
if (not src_client.is_svm_dr_supported()
or not dst_client.is_svm_dr_supported()):
msg = _("Cannot perform server migration because at leat one of "
msg = _("Cannot perform server migration because at least one of "
"the backends doesn't support SVM DR.")
LOG.error(msg)
return not_compatible
@ -1000,6 +1015,18 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
return not_compatible
# TODO(dviroel): disk_type extra-spec
# Check that server does not have any FlexGroup volume.
if src_client.is_flexgroup_supported():
dm_session = data_motion.DataMotionSession()
for req_spec in shares_request_spec.get('shares_req_spec', []):
share_instance = req_spec.get('share_instance_properties', {})
host = share_instance.get('host')
if dm_session.is_flexgroup_share(host):
msg = _("Cannot perform server migration because there is "
"FlexGroup volume in the server.")
LOG.error(msg)
return not_compatible
# Check capacity
server_total_size = (shares_request_spec.get('shares_size', 0) +
shares_request_spec.get('snapshots_size', 0))

View File

@ -63,8 +63,14 @@ class NetAppCmodeSingleSVMFileStorageLibrary(
'match supplied credentials.')
raise exception.InvalidInput(reason=msg)
# Ensure FlexGroup support
vserver_client = self._get_api_client(vserver=self._vserver)
aggr_list = vserver_client.list_vserver_aggregates()
self._initialize_flexgroup_pools(set(aggr_list))
# Ensure one or more aggregates are available to the vserver.
if not self._find_matching_aggregates():
if (self.is_flexvol_pool_configured() and
not self._find_matching_aggregates(aggregate_names=aggr_list)):
msg = _('No aggregates are available to Vserver %s for '
'provisioning shares. Ensure that one or more aggregates '
'are assigned to the Vserver and that the configuration '
@ -105,6 +111,7 @@ class NetAppCmodeSingleSVMFileStorageLibrary(
'pools': {
'vserver': self._vserver,
'aggregates': self._find_matching_aggregates(),
'flexgroup': self._flexgroup_pools,
},
}
@ -123,10 +130,15 @@ class NetAppCmodeSingleSVMFileStorageLibrary(
_handle_housekeeping_tasks())
@na_utils.trace
def _find_matching_aggregates(self):
"""Find all aggregates match pattern."""
vserver_client = self._get_api_client(vserver=self._vserver)
aggregate_names = vserver_client.list_vserver_aggregates()
def _find_matching_aggregates(self, aggregate_names=None):
"""Find all aggregates match pattern if FlexVol pool is configured."""
if not self.is_flexvol_pool_configured():
return []
if not aggregate_names:
vserver_client = self._get_api_client(vserver=self._vserver)
aggregate_names = vserver_client.list_vserver_aggregates()
root_aggregate_names = []
if self._have_cluster_creds:

View File

@ -131,9 +131,15 @@ class PerformanceLibrary(object):
aggr_names = set()
for pool_name, pool_info in aggregate_pools.items():
if pool_info.get('netapp_is_flexgroup', False):
continue
aggr_names.add(pool_info.get('netapp_aggregate'))
for pool_name, pool_info in flexvol_pools.items():
if pool_info.get('netapp_is_flexgroup', False):
continue
aggr_names.add(pool_info.get('netapp_aggregate'))
return list(aggr_names)
def _get_nodes_for_aggregates(self, aggr_names):

View File

@ -30,7 +30,7 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
@na_utils.trace
def create_share(self, share, share_name,
clear_current_export_policy=True,
ensure_share_already_exists=False):
ensure_share_already_exists=False, is_flexgroup=False):
"""Creates CIFS share on Data ONTAP Vserver."""
if not ensure_share_already_exists:
self._client.create_cifs_share(share_name)

View File

@ -42,7 +42,7 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
@na_utils.trace
def create_share(self, share, share_name,
clear_current_export_policy=True,
ensure_share_already_exists=False):
ensure_share_already_exists=False, is_flexgroup=False):
"""Creates NFS share."""
# TODO(dviroel): Ensure that nfs share already exists if
# ensure_share_already_exists is True. Although, no conflicts are
@ -50,7 +50,12 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
if clear_current_export_policy:
self._client.clear_nfs_export_policy_for_volume(share_name)
self._ensure_export_policy(share, share_name)
export_path = self._client.get_volume_junction_path(share_name)
if is_flexgroup:
volume_info = self._client.get_volume(share_name)
export_path = volume_info['junction-path']
else:
export_path = self._client.get_volume_junction_path(share_name)
# Return a callback that may be used for generating export paths
# for this share.

View File

@ -21,6 +21,7 @@ place to ensure re usability and better management of configuration options.
"""
from oslo_config import cfg
from oslo_config import types
netapp_proxy_opts = [
cfg.StrOpt('netapp_storage_family',
@ -134,7 +135,31 @@ netapp_provisioning_opts = [
default=60,
help='The maximum time in seconds that the cached aggregates '
'status will be considered valid. Trying to read the '
'expired cache leads to refreshing it.'), ]
'expired cache leads to refreshing it.'),
cfg.MultiOpt('netapp_flexgroup_pool',
item_type=types.Dict(value_type=types.String()),
default={},
help="Multi opt of dict to represent the FlexGroup pools. "
"A FlexGroup pool is configured with its name and its "
"list of aggregates. Specify this option as many time "
"as you have FlexGroup pools. Each entry takes the "
"dict config form: "
"netapp_flexgroup_pool = "
"<pool_name>: <aggr_name1> <aggr_name2> .."),
cfg.BoolOpt('netapp_flexgroup_pool_only',
default=True,
help='Specify if the FlexVol pools must not be reported when '
'the netapp_flexgroup_pool is defined.'),
cfg.IntOpt('netapp_flexgroup_volume_online_timeout',
min=60,
default=360, # Default to six minutes
help='Sets time in seconds to wait for a FlexGroup volume '
'create to complete and go online.'),
cfg.IntOpt('netapp_delete_busy_flexgroup_snapshot_timeout',
min=60,
default=360, # Default to six minutes
help='Sets time in seconds to wait for a FlexGroup snapshot '
'busy by clone to become free after split the clone.'), ]
netapp_cluster_opts = [
cfg.StrOpt('netapp_vserver',

View File

@ -37,6 +37,13 @@ TRACE_METHOD = False
TRACE_API = False
API_TRACE_PATTERN = '(.*)'
EXTENDED_DATA_PROTECTION_TYPE = 'extended_data_protection'
MIRROR_ALL_SNAP_POLICY = 'MirrorAllSnapshots'
DATA_PROTECTION_TYPE = 'data_protection'
FLEXGROUP_STYLE_EXTENDED = 'flexgroup'
FLEXVOL_STYLE_EXTENDED = 'flexvol'
def validate_driver_instantiation(**kwargs):
"""Checks if a driver is instantiated other than by the unified driver.
@ -117,6 +124,76 @@ def convert_string_to_list(string, separator=','):
return [elem.strip() for elem in string.split(separator)]
def get_relationship_type(is_flexgroup):
"""Returns the snapmirror relationship type."""
return (EXTENDED_DATA_PROTECTION_TYPE if is_flexgroup
else DATA_PROTECTION_TYPE)
def is_style_extended_flexgroup(style_extended):
"""Returns whether the style is extended type or not."""
return style_extended == FLEXGROUP_STYLE_EXTENDED
def parse_flexgroup_pool_config(config, cluster_aggr_set={}, check=False):
"""Returns the dict with the FlexGroup pool name and its aggr list.
:param config: the configuration flexgroup list of dict.
:param cluster_aggr_set: the set of aggregates in the cluster.
:param check: should check the config is correct.
"""
flexgroup_pools_map = {}
aggr_list_used = []
for pool_dic in config:
for pool_name, aggr_str in pool_dic.items():
aggr_name_list = aggr_str.split()
if not check:
aggr_name_list.sort()
flexgroup_pools_map[pool_name] = aggr_name_list
continue
if pool_name in cluster_aggr_set:
msg = _('The %s FlexGroup pool name is not valid, because '
'it is a cluster aggregate name. Ensure that the '
'configuration option netapp_flexgroup_pool is '
'set correctly.')
raise exception.NetAppException(msg % pool_name)
aggr_name_set = set(aggr_name_list)
if len(aggr_name_set) != len(aggr_name_list):
msg = _('There is repeated aggregate name in the '
'FlexGroup pool %s definition. Ensure that the '
'configuration option netapp_flexgroup_pool is '
'set correctly.')
raise exception.NetAppException(msg % pool_name)
not_found_aggr = aggr_name_set - cluster_aggr_set
if not_found_aggr:
not_found_list = [str(s) for s in not_found_aggr]
not_found_str = ", ".join(not_found_list)
msg = _('There is aggregate name in the FlexGroup pool '
'%(pool)s that is not in the cluster: %(aggr)s. '
'Ensure that the configuration option '
'netapp_flexgroup_pool is set correctly.')
msg_args = {'pool': pool_name, 'aggr': not_found_str}
raise exception.NetAppException(msg % msg_args)
aggr_name_list.sort()
aggr_name_list_str = "".join(aggr_name_list)
if aggr_name_list_str in aggr_list_used:
msg = _('The FlexGroup pool %s is duplicated. Ensure that '
'the configuration option netapp_flexgroup_pool '
'is set correctly.')
raise exception.NetAppException(msg % pool_name)
aggr_list_used.append(aggr_name_list_str)
flexgroup_pools_map[pool_name] = aggr_name_list
return flexgroup_pools_map
class OpenStackInfo(object):
"""OS/distribution, release, and version.

View File

@ -56,6 +56,8 @@ SHARE_AGGREGATE_DISK_TYPES = ['SATA', 'SSD']
SHARE_NAME = 'fake_share'
SHARE_SIZE = '1000000000'
SHARE_NAME_2 = 'fake_share_2'
FLEXGROUP_STYLE_EXTENDED = 'flexgroup'
FLEXVOL_STYLE_EXTENDED = 'flexvol'
SNAPSHOT_NAME = 'fake_snapshot'
CG_SNAPSHOT_ID = 'fake_cg_id'
PARENT_SHARE_NAME = 'fake_parent_share'
@ -2056,6 +2058,26 @@ GET_AGGREGATE_FOR_VOLUME_RESPONSE = etree.XML("""
'share': SHARE_NAME
})
GET_AGGREGATE_FOR_FLEXGROUP_VOL_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<volume-attributes>
<volume-id-attributes>
<aggr-list>
<aggr-name>%(aggr)s</aggr-name>
</aggr-list>
<name>%(share)s</name>
<owning-vserver-name>os_aa666789-5576-4835-87b7-868069856459</owning-vserver-name>
</volume-id-attributes>
</volume-attributes>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {
'aggr': SHARE_AGGREGATE_NAME,
'share': SHARE_NAME
})
VOLUME_AUTOSIZE_GET_RESPONSE = etree.XML("""
<results status="passed">
<grow-threshold-percent>%(grow_percent)s</grow-threshold-percent>
@ -2227,6 +2249,7 @@ VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE = etree.XML("""
<owning-vserver-name>%(vserver)s</owning-vserver-name>
<style>flex</style>
<type>rw</type>
<style-extended>%(style-extended)s</style-extended>
</volume-id-attributes>
<volume-space-attributes>
<size>%(size)s</size>
@ -2244,6 +2267,41 @@ VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE = etree.XML("""
'volume': SHARE_NAME,
'size': SHARE_SIZE,
'qos-policy-group-name': QOS_POLICY_GROUP_NAME,
'style-extended': FLEXVOL_STYLE_EXTENDED,
})
VOLUME_GET_ITER_FLEXGROUP_VOLUME_TO_MANAGE_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<volume-attributes>
<volume-id-attributes>
<aggr-list>
<aggr-name>%(aggr)s</aggr-name>
</aggr-list>
<junction-path>/%(volume)s</junction-path>
<name>%(volume)s</name>
<owning-vserver-name>%(vserver)s</owning-vserver-name>
<style>flex</style>
<type>rw</type>
<style-extended>%(style-extended)s</style-extended>
</volume-id-attributes>
<volume-space-attributes>
<size>%(size)s</size>
</volume-space-attributes>
<volume-qos-attributes>
<policy-group-name>%(qos-policy-group-name)s</policy-group-name>
</volume-qos-attributes>
</volume-attributes>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {
'aggr': SHARE_AGGREGATE_NAME,
'vserver': VSERVER_NAME,
'volume': SHARE_NAME,
'size': SHARE_SIZE,
'qos-policy-group-name': QOS_POLICY_GROUP_NAME,
'style-extended': FLEXGROUP_STYLE_EXTENDED,
})
VOLUME_GET_ITER_NO_QOS_RESPONSE = etree.XML("""
@ -2257,6 +2315,7 @@ VOLUME_GET_ITER_NO_QOS_RESPONSE = etree.XML("""
<owning-vserver-name>%(vserver)s</owning-vserver-name>
<style>flex</style>
<type>rw</type>
<style-extended>%(style-extended)s</style-extended>
</volume-id-attributes>
<volume-space-attributes>
<size>%(size)s</size>
@ -2270,6 +2329,7 @@ VOLUME_GET_ITER_NO_QOS_RESPONSE = etree.XML("""
'vserver': VSERVER_NAME,
'volume': SHARE_NAME,
'size': SHARE_SIZE,
'style-extended': FLEXVOL_STYLE_EXTENDED,
})
CLONE_CHILD_1 = 'fake_child_1'
@ -2960,3 +3020,51 @@ FAKE_MANAGE_VOLUME = {
FAKE_KEY_MANAGER_ERROR = "The onboard key manager is not enabled. To enable \
it, run \"security key-manager setup\"."
VOLUME_GET_ITER_STATE_RESPONSE = etree.XML("""
<results status="passed">
<num-records>1</num-records>
<attributes-list>
<volume-attributes>
<volume-state-attributes>
<state>online</state>
</volume-state-attributes>
</volume-attributes>
</attributes-list>
</results>
""")
ASYNC_OPERATION_RESPONSE = etree.XML("""
<results status="passed">
<result-status>in_progress</result-status>
<result-jobid>123</result-jobid>
</results>
""")
VOLUME_GET_ITER_STYLE_FLEXGROUP_RESPONSE = etree.XML("""
<results status="passed">
<num-records>1</num-records>
<attributes-list>
<volume-attributes>
<volume-id-attributes>
<style-extended>%(style)s</style-extended>
</volume-id-attributes>
</volume-attributes>
</attributes-list>
</results>
""" % {
'style': FLEXGROUP_STYLE_EXTENDED,
})
VOLUME_GET_ITER_STYLE_FLEXVOL_RESPONSE = etree.XML("""
<results status="passed">
<num-records>1</num-records>
<attributes-list>
<volume-attributes>
<volume-id-attributes>
<style-extended>flexvol</style-extended>
</volume-id-attributes>
</volume-attributes>
</attributes-list>
</results>
""")

View File

@ -27,6 +27,7 @@ from manila import exception
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
from manila.share.drivers.netapp.dataontap.client import client_base
from manila.share.drivers.netapp.dataontap.client import client_cmode
from manila.share.drivers.netapp import utils as na_utils
from manila import test
from manila.tests.share.drivers.netapp.dataontap.client import fakes as fake
@ -3052,129 +3053,148 @@ class NetAppClientCmodeTestCase(test.TestCase):
mock.call('cifs-domain-preferred-dc-remove',
preferred_dc_add_args)])
def test_create_volume(self):
self.mock_object(self.client, 'send_request')
self.mock_object(self.client, 'update_volume_efficiency_attributes')
self.client.create_volume(
fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME, 100)
volume_create_args = {
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
'size': '100g',
'volume': fake.SHARE_NAME,
'volume-type': 'rw',
'junction-path': '/%s' % fake.SHARE_NAME,
}
self.client.send_request.assert_called_once_with('volume-create',
volume_create_args)
@ddt.data({'qos_policy_group_name': None,
'adaptive_policy_group_name': None},
{'qos_policy_group_name': fake.QOS_POLICY_GROUP_NAME,
'adaptive_policy_group_name': None},
{'qos_policy_group_name': None,
'adaptive_policy_group_name':
fake.ADAPTIVE_QOS_POLICY_GROUP_NAME},
)
@ddt.unpack
def test_create_volume_with_extra_specs(self, qos_policy_group_name,
adaptive_policy_group_name):
@ddt.data(True, False)
def test_create_volume(self, set_max_files):
self.client.features.add_feature('ADAPTIVE_QOS')
self.mock_object(self.client, 'set_volume_max_files')
self.mock_object(self.client, 'enable_dedup')
self.mock_object(self.client, 'enable_compression')
self.mock_object(self.client, 'send_request')
self.mock_object(self.client, 'update_volume_efficiency_attributes')
self.mock_object(
self.client,
'get_volume_efficiency_status',
mock.Mock(return_value={'dedupe': False, 'compression': False}))
self.client, '_get_create_volume_api_args',
mock.Mock(return_value={}))
self.client.create_volume(
fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME, 100,
thin_provisioned=True, language='en-US',
snapshot_policy='default', dedup_enabled=True,
compression_enabled=True, max_files=5000, snapshot_reserve=15,
qos_policy_group=qos_policy_group_name,
adaptive_qos_policy_group=adaptive_policy_group_name)
max_files=fake.MAX_FILES if set_max_files else None)
volume_create_args = {
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
'size': '100g',
'volume': fake.SHARE_NAME,
'junction-path': '/%s' % fake.SHARE_NAME,
'space-reserve': 'none',
'language-code': 'en-US',
'volume-type': 'rw',
'snapshot-policy': 'default',
'percentage-snapshot-reserve': '15',
}
if qos_policy_group_name:
volume_create_args.update(
{'qos-policy-group-name': qos_policy_group_name})
if adaptive_policy_group_name:
volume_create_args.update(
{'qos-adaptive-policy-group-name': adaptive_policy_group_name})
self.client._get_create_volume_api_args.assert_called_once_with(
fake.SHARE_NAME, False, None, None, None, 'rw', None, False, None)
self.client.send_request.assert_called_with('volume-create',
volume_create_args)
self.client.set_volume_max_files.assert_called_once_with(
fake.SHARE_NAME, fake.MAX_FILES)
self.client.enable_dedup.assert_called_once_with(fake.SHARE_NAME)
self.client.enable_compression.assert_called_once_with(fake.SHARE_NAME)
(self.client.update_volume_efficiency_attributes.
assert_called_once_with(fake.SHARE_NAME, False, False))
if set_max_files:
self.client.set_volume_max_files.assert_called_once_with(
fake.SHARE_NAME, fake.MAX_FILES)
else:
self.client.set_volume_max_files.assert_not_called()
def test_create_encrypted_volume(self):
def test_create_volume_adaptive_not_supported(self):
self.client.features.add_feature('ADAPTIVE_QOS', supported=False)
self.mock_object(self.client, 'send_request')
self.mock_object(self.client, 'update_volume_efficiency_attributes')
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
self.client.create_volume(
fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME, 100, encrypt=True)
volume_create_args = {
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
'size': '100g',
'volume': fake.SHARE_NAME,
'volume-type': 'rw',
'junction-path': '/%s' % fake.SHARE_NAME,
'encrypt': 'true',
}
self.client.send_request.assert_called_once_with('volume-create',
volume_create_args)
def test_create_non_encrypted_volume(self):
self.mock_object(self.client, 'send_request')
self.mock_object(self.client, 'update_volume_efficiency_attributes')
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
self.client.create_volume(
fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME, 100, encrypt=False)
volume_create_args = {
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
'size': '100g',
'volume': fake.SHARE_NAME,
'volume-type': 'rw',
'junction-path': '/%s' % fake.SHARE_NAME,
}
self.client.send_request.assert_called_once_with('volume-create',
volume_create_args)
def test_create_encrypted_volume_not_supported(self):
self.assertRaises(exception.NetAppException,
self.client.create_volume,
fake.SHARE_AGGREGATE_NAME,
fake.SHARE_NAME,
100,
encrypt=True)
adaptive_qos_policy_group='fake')
self.client.send_request.assert_not_called()
def test_create_volume_async(self):
api_response = netapp_api.NaElement(fake.ASYNC_OPERATION_RESPONSE)
self.mock_object(self.client, 'send_request',
mock.Mock(return_value=api_response))
self.mock_object(
self.client, '_get_create_volume_api_args',
mock.Mock(return_value={}))
result = self.client.create_volume_async(
[fake.SHARE_AGGREGATE_NAME], fake.SHARE_NAME, 1)
volume_create_args = {
'aggr-list': [{'aggr-name': fake.SHARE_AGGREGATE_NAME}],
'size': 1073741824,
'volume-name': fake.SHARE_NAME,
}
expected_result = {
'status': 'in_progress',
'jobid': '123',
'error-code': None,
'error-message': None,
}
self.client._get_create_volume_api_args.assert_called_once_with(
fake.SHARE_NAME, False, None, None, None, 'rw', None, False, None)
self.client.send_request.assert_called_with('volume-create-async',
volume_create_args)
self.assertEqual(expected_result, result)
def test_create_volume_async_adaptive_not_supported(self):
self.client.features.add_feature('ADAPTIVE_QOS', supported=False)
self.mock_object(self.client, 'send_request')
self.assertRaises(exception.NetAppException,
self.client.create_volume_async,
[fake.SHARE_AGGREGATE_NAME],
fake.SHARE_NAME,
100,
adaptive_qos_policy_group='fake')
self.client.send_request.assert_not_called()
def test_get_create_volume_api_args_with_extra_specs(self):
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
volume_type = 'rw'
thin_provisioned = 'none'
snapshot_policy = 'default'
language = 'en-US'
reserve = 15
qos_name = 'fake_qos'
encrypt = True
qos_adaptive_name = 'fake_adaptive_qos'
result_api_args = self.client._get_create_volume_api_args(
fake.SHARE_NAME, thin_provisioned, snapshot_policy, language,
reserve, volume_type, qos_name, encrypt, qos_adaptive_name)
expected_api_args = {
'volume-type': volume_type,
'junction-path': '/fake_share',
'space-reserve': thin_provisioned,
'snapshot-policy': snapshot_policy,
'language-code': language,
'percentage-snapshot-reserve': str(reserve),
'qos-policy-group-name': qos_name,
'qos-adaptive-policy-group-name': qos_adaptive_name,
'encrypt': 'true',
}
self.assertEqual(expected_api_args, result_api_args)
def test_get_create_volume_api_args_no_extra_specs(self):
self.client.features.add_feature('FLEXVOL_ENCRYPTION')
volume_type = 'dp'
thin_provisioned = False
snapshot_policy = None
language = None
reserve = None
qos_name = None
encrypt = False
qos_adaptive_name = None
result_api_args = self.client._get_create_volume_api_args(
fake.SHARE_NAME, thin_provisioned, snapshot_policy, language,
reserve, volume_type, qos_name, encrypt, qos_adaptive_name)
expected_api_args = {
'volume-type': volume_type,
}
self.assertEqual(expected_api_args, result_api_args)
def test_get_create_volume_api_args_encrypted_not_supported(self):
encrypt = True
self.assertRaises(exception.NetAppException,
self.client._get_create_volume_api_args,
fake.SHARE_NAME, True, 'default', 'en-US',
15, 'rw', 'fake_qos', encrypt, 'fake_qos_adaptive')
def test_is_flexvol_encrypted_unsupported(self):
@ -3315,6 +3335,53 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_called_once_with('sis-set-config',
sis_set_config_args)
def test_enable_dedupe_async(self):
self.mock_object(self.client.connection, 'send_request')
self.client.enable_dedupe_async(fake.SHARE_NAME)
sis_enable_args = {'volume-name': fake.SHARE_NAME}
self.client.connection.send_request.assert_called_once_with(
'sis-enable-async', sis_enable_args)
def test_disable_dedupe_async(self):
self.mock_object(self.client.connection, 'send_request')
self.client.disable_dedupe_async(fake.SHARE_NAME)
sis_enable_args = {'volume-name': fake.SHARE_NAME}
self.client.connection.send_request.assert_called_once_with(
'sis-disable-async', sis_enable_args)
def test_enable_compression_async(self):
self.mock_object(self.client.connection, 'send_request')
self.client.enable_compression_async(fake.SHARE_NAME)
sis_set_config_args = {
'volume-name': fake.SHARE_NAME,
'enable-compression': 'true'
}
self.client.connection.send_request.assert_called_once_with(
'sis-set-config-async', sis_set_config_args)
def test_disable_compression_async(self):
self.mock_object(self.client.connection, 'send_request')
self.client.disable_compression_async(fake.SHARE_NAME)
sis_set_config_args = {
'volume-name': fake.SHARE_NAME,
'enable-compression': 'false'
}
self.client.connection.send_request.assert_called_once_with(
'sis-set-config-async', sis_set_config_args)
def test_get_volume_efficiency_status(self):
api_response = netapp_api.NaElement(fake.SIS_GET_ITER_RESPONSE)
@ -3409,19 +3476,23 @@ class NetAppClientCmodeTestCase(test.TestCase):
'vserver-rename', vserver_api_args
)
def test_modify_volume_no_optional_args(self):
@ddt.data(True, False)
def test_modify_volume_no_optional_args(self, is_flexgroup):
self.mock_object(self.client, 'send_request')
mock_update_volume_efficiency_attributes = self.mock_object(
self.client, 'update_volume_efficiency_attributes')
self.client.modify_volume(fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME)
aggr = fake.SHARE_AGGREGATE_NAME
if is_flexgroup:
aggr = list(fake.SHARE_AGGREGATE_NAMES)
self.client.modify_volume(aggr, fake.SHARE_NAME)
volume_modify_iter_api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME,
'name': fake.SHARE_NAME,
},
},
@ -3439,10 +3510,19 @@ class NetAppClientCmodeTestCase(test.TestCase):
},
}
if is_flexgroup:
volume_modify_iter_api_args['query']['volume-attributes'][
'volume-id-attributes']['aggr-list'] = [
{'aggr-name': aggr[0]}, {'aggr-name': aggr[1]}]
else:
volume_modify_iter_api_args['query']['volume-attributes'][
'volume-id-attributes'][
'containing-aggregate-name'] = aggr
self.client.send_request.assert_called_once_with(
'volume-modify-iter', volume_modify_iter_api_args)
mock_update_volume_efficiency_attributes.assert_called_once_with(
fake.SHARE_NAME, False, False)
fake.SHARE_NAME, False, False, is_flexgroup=is_flexgroup)
@ddt.data((fake.QOS_POLICY_GROUP_NAME, None),
(None, fake.ADAPTIVE_QOS_POLICY_GROUP_NAME))
@ -3516,21 +3596,30 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_called_once_with(
'volume-modify-iter', volume_modify_iter_api_args)
mock_update_volume_efficiency_attributes.assert_called_once_with(
fake.SHARE_NAME, True, False)
fake.SHARE_NAME, True, False, is_flexgroup=False)
@ddt.data(
{'existing': (True, True), 'desired': (True, True)},
{'existing': (True, True), 'desired': (False, False)},
{'existing': (True, True), 'desired': (True, False)},
{'existing': (True, False), 'desired': (True, False)},
{'existing': (True, False), 'desired': (False, False)},
{'existing': (True, False), 'desired': (True, True)},
{'existing': (False, False), 'desired': (False, False)},
{'existing': (False, False), 'desired': (True, False)},
{'existing': (False, False), 'desired': (True, True)},
{'existing': (True, True), 'desired': (True, True), 'fg': False},
{'existing': (True, True), 'desired': (False, False), 'fg': False},
{'existing': (True, True), 'desired': (True, False), 'fg': False},
{'existing': (True, False), 'desired': (True, False), 'fg': False},
{'existing': (True, False), 'desired': (False, False), 'fg': False},
{'existing': (True, False), 'desired': (True, True), 'fg': False},
{'existing': (False, False), 'desired': (False, False), 'fg': False},
{'existing': (False, False), 'desired': (True, False), 'fg': False},
{'existing': (False, False), 'desired': (True, True), 'fg': False},
{'existing': (True, True), 'desired': (True, True), 'fg': True},
{'existing': (True, True), 'desired': (False, False), 'fg': True},
{'existing': (True, True), 'desired': (True, False), 'fg': True},
{'existing': (True, False), 'desired': (True, False), 'fg': True},
{'existing': (True, False), 'desired': (False, False), 'fg': True},
{'existing': (True, False), 'desired': (True, True), 'fg': True},
{'existing': (False, False), 'desired': (False, False), 'fg': True},
{'existing': (False, False), 'desired': (True, False), 'fg': True},
{'existing': (False, False), 'desired': (True, True), 'fg': True},
)
@ddt.unpack
def test_update_volume_efficiency_attributes(self, existing, desired):
def test_update_volume_efficiency_attributes(self, existing, desired, fg):
existing_dedupe = existing[0]
existing_compression = existing[1]
@ -3544,33 +3633,66 @@ class NetAppClientCmodeTestCase(test.TestCase):
'compression': existing_compression}))
mock_enable_compression = self.mock_object(self.client,
'enable_compression')
mock_enable_compression_async = self.mock_object(
self.client, 'enable_compression_async')
mock_disable_compression = self.mock_object(self.client,
'disable_compression')
mock_disable_compression_async = self.mock_object(
self.client, 'disable_compression_async')
mock_enable_dedup = self.mock_object(self.client, 'enable_dedup')
mock_enable_dedup_async = self.mock_object(self.client,
'enable_dedupe_async')
mock_disable_dedup = self.mock_object(self.client, 'disable_dedup')
mock_disable_dedup_async = self.mock_object(self.client,
'disable_dedupe_async')
self.client.update_volume_efficiency_attributes(
fake.SHARE_NAME, desired_dedupe, desired_compression)
fake.SHARE_NAME, desired_dedupe, desired_compression,
is_flexgroup=fg)
if existing_dedupe == desired_dedupe:
self.assertFalse(mock_enable_dedup.called)
self.assertFalse(mock_disable_dedup.called)
if fg:
self.assertFalse(mock_enable_dedup_async.called)
self.assertFalse(mock_disable_dedup_async.called)
else:
self.assertFalse(mock_enable_dedup.called)
self.assertFalse(mock_disable_dedup.called)
elif existing_dedupe and not desired_dedupe:
self.assertFalse(mock_enable_dedup.called)
self.assertTrue(mock_disable_dedup.called)
if fg:
self.assertFalse(mock_enable_dedup_async.called)
self.assertTrue(mock_disable_dedup_async.called)
else:
self.assertFalse(mock_enable_dedup.called)
self.assertTrue(mock_disable_dedup.called)
elif not existing_dedupe and desired_dedupe:
self.assertTrue(mock_enable_dedup.called)
self.assertFalse(mock_disable_dedup.called)
if fg:
self.assertTrue(mock_enable_dedup_async.called)
self.assertFalse(mock_disable_dedup_async.called)
else:
self.assertTrue(mock_enable_dedup.called)
self.assertFalse(mock_disable_dedup.called)
if existing_compression == desired_compression:
self.assertFalse(mock_enable_compression.called)
self.assertFalse(mock_disable_compression.called)
if fg:
self.assertFalse(mock_enable_compression_async.called)
self.assertFalse(mock_disable_compression_async.called)
else:
self.assertFalse(mock_enable_compression.called)
self.assertFalse(mock_disable_compression.called)
elif existing_compression and not desired_compression:
self.assertFalse(mock_enable_compression.called)
self.assertTrue(mock_disable_compression.called)
if fg:
self.assertFalse(mock_enable_compression_async.called)
self.assertTrue(mock_disable_compression_async.called)
else:
self.assertFalse(mock_enable_compression.called)
self.assertTrue(mock_disable_compression.called)
elif not existing_compression and desired_compression:
self.assertTrue(mock_enable_compression.called)
self.assertFalse(mock_disable_compression.called)
if fg:
self.assertTrue(mock_enable_compression_async.called)
self.assertFalse(mock_disable_compression_async.called)
else:
self.assertTrue(mock_enable_compression.called)
self.assertFalse(mock_disable_compression.called)
def test_set_volume_size(self):
@ -3831,10 +3953,12 @@ class NetAppClientCmodeTestCase(test.TestCase):
fake.SNAPSHOT_NAME,
fake.SHARE_NAME)
def test_get_aggregate_for_volume(self):
@ddt.data(True, False)
def test_get_aggregate_for_volume(self, is_flexgroup):
api_response = netapp_api.NaElement(
fake.GET_AGGREGATE_FOR_VOLUME_RESPONSE)
fake.GET_AGGREGATE_FOR_FLEXGROUP_VOL_RESPONSE if is_flexgroup
else fake.GET_AGGREGATE_FOR_VOLUME_RESPONSE)
self.mock_object(self.client,
'send_iter_request',
mock.Mock(return_value=api_response))
@ -3852,6 +3976,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'aggr-list': {
'aggr-name': None,
},
'containing-aggregate-name': None,
'name': None
}
@ -3861,7 +3988,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_iter_request.assert_has_calls([
mock.call('volume-get-iter', volume_get_iter_args)])
self.assertEqual(fake.SHARE_AGGREGATE_NAME, result)
if is_flexgroup:
self.assertEqual([fake.SHARE_AGGREGATE_NAME], result)
else:
self.assertEqual(fake.SHARE_AGGREGATE_NAME, result)
def test_get_aggregate_for_volume_not_found(self):
@ -3920,11 +4050,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
mock.Mock(return_value=api_response))
fake_junction_path = '/%s' % fake.SHARE_NAME
self.mock_object(self.client,
'get_volume_junction_path',
mock.Mock(return_value=fake_junction_path))
result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME)
result = self.client.volume_has_junctioned_volumes(fake_junction_path)
volume_get_iter_args = {
'query': {
@ -3948,11 +4074,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
def test_volume_has_junctioned_volumes_no_junction_path(self):
self.mock_object(self.client,
'get_volume_junction_path',
mock.Mock(return_value=''))
result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME)
result = self.client.volume_has_junctioned_volumes(None)
self.assertFalse(result)
@ -3964,18 +4086,17 @@ class NetAppClientCmodeTestCase(test.TestCase):
mock.Mock(return_value=api_response))
fake_junction_path = '/%s' % fake.SHARE_NAME
self.mock_object(self.client,
'get_volume_junction_path',
mock.Mock(return_value=fake_junction_path))
result = self.client.volume_has_junctioned_volumes(fake.SHARE_NAME)
result = self.client.volume_has_junctioned_volumes(fake_junction_path)
self.assertFalse(result)
def test_get_volume(self):
@ddt.data(True, False)
def test_get_volume(self, is_flexgroup):
api_response = netapp_api.NaElement(
fake.VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE)
fake.VOLUME_GET_ITER_FLEXGROUP_VOLUME_TO_MANAGE_RESPONSE
if is_flexgroup
else fake.VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
@ -3993,12 +4114,16 @@ class NetAppClientCmodeTestCase(test.TestCase):
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'aggr-list': {
'aggr-name': None,
},
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
'owning-vserver-name': None,
'type': None,
'style': None,
'style-extended': None,
},
'volume-space-attributes': {
'size': None,
@ -4011,7 +4136,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
}
expected = {
'aggregate': fake.SHARE_AGGREGATE_NAME,
'aggregate': '' if is_flexgroup else fake.SHARE_AGGREGATE_NAME,
'aggr-list': [fake.SHARE_AGGREGATE_NAME] if is_flexgroup else [],
'junction-path': '/%s' % fake.SHARE_NAME,
'name': fake.SHARE_NAME,
'type': 'rw',
@ -4019,6 +4145,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
'size': fake.SHARE_SIZE,
'owning-vserver-name': fake.VSERVER_NAME,
'qos-policy-group-name': fake.QOS_POLICY_GROUP_NAME,
'style-extended': (fake.FLEXGROUP_STYLE_EXTENDED
if is_flexgroup
else fake.FLEXVOL_STYLE_EXTENDED),
}
self.client.send_request.assert_has_calls([
mock.call('volume-get-iter', volume_get_iter_args)])
@ -4044,12 +4173,16 @@ class NetAppClientCmodeTestCase(test.TestCase):
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'aggr-list': {
'aggr-name': None,
},
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
'owning-vserver-name': None,
'type': None,
'style': None,
'style-extended': None,
},
'volume-space-attributes': {
'size': None,
@ -4063,6 +4196,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
expected = {
'aggregate': fake.SHARE_AGGREGATE_NAME,
'aggr-list': [],
'junction-path': '/%s' % fake.SHARE_NAME,
'name': fake.SHARE_NAME,
'type': 'rw',
@ -4070,6 +4204,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
'size': fake.SHARE_SIZE,
'owning-vserver-name': fake.VSERVER_NAME,
'qos-policy-group-name': None,
'style-extended': fake.FLEXVOL_STYLE_EXTENDED,
}
self.client.send_request.assert_has_calls([
mock.call('volume-get-iter', volume_get_iter_args)])
@ -4114,31 +4249,20 @@ class NetAppClientCmodeTestCase(test.TestCase):
'volume-attributes': {
'volume-id-attributes': {
'junction-path': fake_junction_path,
'style-extended': 'flexgroup|flexvol',
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
'type': None,
'style': None,
},
'volume-space-attributes': {
'size': None,
}
},
},
}
expected = {
'aggregate': fake.SHARE_AGGREGATE_NAME,
'junction-path': fake_junction_path,
'name': fake.SHARE_NAME,
'type': 'rw',
'style': 'flex',
'size': fake.SHARE_SIZE,
}
self.client.send_iter_request.assert_has_calls([
mock.call('volume-get-iter', volume_get_iter_args)])
@ -4162,22 +4286,26 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.assertIsNone(result)
def test_get_volume_to_manage(self):
@ddt.data(True, False)
def test_get_volume_to_manage(self, is_flexgroup):
api_response = netapp_api.NaElement(
fake.VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE)
fake.VOLUME_GET_ITER_FLEXGROUP_VOLUME_TO_MANAGE_RESPONSE
if is_flexgroup
else fake.VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE)
self.mock_object(self.client,
'send_iter_request',
mock.Mock(return_value=api_response))
result = self.client.get_volume_to_manage(fake.SHARE_AGGREGATE_NAME,
fake.SHARE_NAME)
aggr = fake.SHARE_AGGREGATE_NAME
result = self.client.get_volume_to_manage(
[aggr] if is_flexgroup else aggr,
fake.SHARE_NAME)
volume_get_iter_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': fake.SHARE_AGGREGATE_NAME,
'name': fake.SHARE_NAME,
},
},
@ -4185,6 +4313,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'aggr-list': {
'aggr-name': None,
},
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
@ -4201,8 +4332,16 @@ class NetAppClientCmodeTestCase(test.TestCase):
},
},
}
if is_flexgroup:
volume_get_iter_args['query']['volume-attributes'][
'volume-id-attributes']['aggr-list'] = [{'aggr-name': aggr}]
else:
volume_get_iter_args['query']['volume-attributes'][
'volume-id-attributes']['containing-aggregate-name'] = aggr
expected = {
'aggregate': fake.SHARE_AGGREGATE_NAME,
'aggregate': '' if is_flexgroup else aggr,
'aggr-list': [aggr] if is_flexgroup else [],
'junction-path': '/%s' % fake.SHARE_NAME,
'name': fake.SHARE_NAME,
'type': 'rw',
@ -6098,14 +6237,14 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.create_snapmirror_vol(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
schedule=schedule, policy=policy)
na_utils.DATA_PROTECTION_TYPE, schedule=schedule, policy=policy)
snapmirror_create_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'relationship-type': 'data_protection',
'relationship-type': na_utils.DATA_PROTECTION_TYPE,
}
if schedule:
snapmirror_create_args['schedule'] = schedule
@ -6121,14 +6260,16 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.create_snapmirror_vol(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
na_utils.DATA_PROTECTION_TYPE)
snapmirror_create_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'relationship-type': 'data_protection',
'relationship-type': na_utils.DATA_PROTECTION_TYPE,
'policy': na_utils.MIRROR_ALL_SNAP_POLICY,
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-create', snapmirror_create_args)])
@ -6141,7 +6282,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.assertRaises(netapp_api.NaApiError,
self.client.create_snapmirror_vol,
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME,
na_utils.DATA_PROTECTION_TYPE)
self.assertTrue(self.client.send_request.called)
def test_create_snapmirror_svm(self):
@ -6154,7 +6296,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
snapmirror_create_args = {
'source-vserver': fake.SM_SOURCE_VSERVER,
'destination-vserver': fake.SM_DEST_VSERVER,
'relationship-type': 'data_protection',
'relationship-type': na_utils.DATA_PROTECTION_TYPE,
'identity-preserve': 'true',
'max-transfer-rate': 'fake_xfer_rate'
}
@ -6237,6 +6379,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
def test_release_snapmirror(self, relationship_info_only):
self.mock_object(self.client, 'send_request')
self.mock_object(self.client, '_ensure_snapmirror_v2')
self.client.release_snapmirror_vol(
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
@ -6250,10 +6393,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
'source-volume': fake.SM_SOURCE_VOLUME,
'destination-vserver': fake.SM_DEST_VSERVER,
'destination-volume': fake.SM_DEST_VOLUME,
'relationship-info-only': ('true' if relationship_info_only
else 'false'),
}
}
},
},
'relationship-info-only': ('true' if relationship_info_only
else 'false'),
}
self.client.send_request.assert_has_calls([
@ -6261,7 +6404,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
enable_tunneling=True)])
def test_release_snapmirror_svm(self):
self.mock_object(self.client, 'send_request')
self.mock_object(self.client, '_ensure_snapmirror_v2')
self.client.release_snapmirror_svm(
fake.SM_SOURCE_VSERVER, fake.SM_DEST_VSERVER)
@ -6271,9 +6416,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
'snapmirror-destination-info': {
'source-location': fake.SM_SOURCE_VSERVER + ':',
'destination-location': fake.SM_DEST_VSERVER + ':',
'relationship-info-only': 'false'
}
}
},
},
'relationship-info-only': 'false',
}
self.client.send_request.assert_has_calls([
mock.call('snapmirror-release-iter', snapmirror_release_args,
@ -8220,3 +8365,131 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.assertEqual(expected, result)
self.client.send_iter_request.assert_called_once_with(
'fpolicy-policy-status-get-iter', expected_args)
@ddt.data(True, False)
def test_get_volume_state(self, has_record):
api_response = netapp_api.NaElement(
fake.VOLUME_GET_ITER_STATE_RESPONSE)
mock_send_iter_request = self.mock_object(
self.client, 'send_iter_request',
mock.Mock(return_value=api_response))
mock_has_record = self.mock_object(self.client,
'_has_records',
mock.Mock(return_value=has_record))
state = self.client.get_volume_state(fake.SHARE_NAME)
volume_get_iter_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': fake.SHARE_NAME,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-state-attributes': {
'state': None
}
}
},
}
mock_send_iter_request.assert_called_once_with(
'volume-get-iter', volume_get_iter_args)
mock_has_record.assert_called_once_with(api_response)
if has_record:
self.assertEqual('online', state)
else:
self.assertEqual('', state)
@ddt.data(True, False)
def test_is_flexgroup_volume(self, is_flexgroup):
self.client.features.add_feature('FLEXGROUP', supported=True)
api_response = netapp_api.NaElement(
fake.VOLUME_GET_ITER_STYLE_FLEXGROUP_RESPONSE
if is_flexgroup else fake.VOLUME_GET_ITER_STYLE_FLEXVOL_RESPONSE)
mock_send_iter_request = self.mock_object(
self.client, 'send_request',
mock.Mock(return_value=api_response))
mock_has_record = self.mock_object(self.client,
'_has_records',
mock.Mock(return_value=True))
mock_is_style_extended_flexgroup = self.mock_object(
na_utils, 'is_style_extended_flexgroup',
mock.Mock(return_value=is_flexgroup))
is_flexgroup_res = self.client.is_flexgroup_volume(fake.SHARE_NAME)
volume_get_iter_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': fake.SHARE_NAME,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'style-extended': None,
},
},
},
}
mock_send_iter_request.assert_called_once_with(
'volume-get-iter', volume_get_iter_args)
mock_has_record.assert_called_once_with(api_response)
mock_is_style_extended_flexgroup.assert_called_once_with(
fake.FLEXGROUP_STYLE_EXTENDED
if is_flexgroup else fake.FLEXVOL_STYLE_EXTENDED)
self.assertEqual(is_flexgroup, is_flexgroup_res)
def test_is_flexgroup_volume_not_found(self):
self.client.features.add_feature('FLEXGROUP', supported=True)
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
self.assertRaises(exception.StorageResourceNotFound,
self.client.is_flexgroup_volume,
fake.SHARE_NAME)
def test_is_flexgroup_volume_not_unique(self):
self.client.features.add_feature('FLEXGROUP', supported=True)
api_response = netapp_api.NaElement(
fake.VOLUME_GET_ITER_NOT_UNIQUE_RESPONSE)
self.mock_object(self.client,
'send_request',
mock.Mock(return_value=api_response))
self.assertRaises(exception.NetAppException,
self.client.is_flexgroup_volume,
fake.SHARE_NAME)
def test_is_flexgroup_volume_unsupported(self):
self.client.features.add_feature('FLEXGROUP', supported=False)
result = self.client.is_flexgroup_volume(fake.SHARE_NAME)
self.assertFalse(result)
def test_is_flexgroup_supported(self):
self.client.features.add_feature('FLEXGROUP')
result = self.client.is_flexgroup_supported()
self.assertTrue(result)
def test_is_flexgroup_fan_out_supported(self):
self.client.features.add_feature('FLEXGROUP_FAN_OUT')
result = self.client.is_flexgroup_fan_out_supported()
self.assertTrue(result)

View File

@ -26,6 +26,8 @@ from manila.share.drivers.netapp.dataontap.client import api as netapp_api
from manila.share.drivers.netapp.dataontap.client import client_cmode
from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion
from manila.share.drivers.netapp import options as na_opts
from manila.share.drivers.netapp import utils as na_utils
from manila.share import utils as share_utils
from manila import test
from manila.tests.share.drivers.netapp.dataontap import fakes as fake
from manila.tests.share.drivers.netapp import fakes as na_fakes
@ -205,11 +207,12 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
mock.Mock(return_value=mock_dest_client))
self.dm_session.create_snapmirror(self.fake_src_share,
self.fake_dest_share)
self.fake_dest_share,
'data_protection')
mock_dest_client.create_snapmirror_vol.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name, schedule='hourly'
self.fake_dest_vol_name, 'data_protection', schedule='hourly'
)
mock_dest_client.initialize_snapmirror_vol.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
@ -271,7 +274,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
)
mock_src_client.release_snapmirror_vol.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
self.fake_dest_vol_name, relationship_info_only=False
)
@ddt.data(True, False)
@ -322,7 +325,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
)
mock_src_client.release_snapmirror_vol.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
self.fake_dest_vol_name, relationship_info_only=False
)
def test_delete_snapmirror_svm_does_not_exist(self):
@ -373,7 +376,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
)
mock_src_client.release_snapmirror_vol.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
self.fake_dest_vol_name, relationship_info_only=False
)
def test_delete_snapmirror_svm_error_deleting(self):
@ -424,7 +427,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
)
mock_src_client.release_snapmirror_vol.assert_called_once_with(
mock.ANY, self.fake_src_vol_name, mock.ANY,
self.fake_dest_vol_name
self.fake_dest_vol_name, relationship_info_only=False
)
def test_delete_snapmirror_without_release(self):
@ -643,6 +646,8 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
self.mock_src_client,
self.mock_dest_client,
mock_new_src_client]))
self.mock_object(na_utils, 'get_relationship_type',
mock.Mock(return_value=na_utils.DATA_PROTECTION_TYPE))
self.dm_session.change_snapmirror_source(
self.fake_dest_share, self.fake_src_share, fake_new_src_share,
@ -652,12 +657,14 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
self.assertEqual(4, self.dm_session.delete_snapmirror.call_count)
self.dm_session.delete_snapmirror.assert_called_with(
mock.ANY, mock.ANY, release=False
mock.ANY, mock.ANY, release=False, relationship_info_only=False
)
na_utils.get_relationship_type.assert_called_once_with(False)
self.mock_dest_client.create_snapmirror_vol.assert_called_once_with(
mock.ANY, fake_new_src_share_name, mock.ANY,
self.fake_dest_vol_name, schedule='hourly'
self.fake_dest_vol_name, na_utils.DATA_PROTECTION_TYPE,
schedule='hourly'
)
self.mock_dest_client.resync_snapmirror_vol.assert_called_once_with(
@ -685,6 +692,8 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
peer_cluster_name = 'new_src_cluster_name'
self.mock_object(self.mock_src_client, 'get_cluster_name',
mock.Mock(return_value=peer_cluster_name))
self.mock_object(na_utils, 'get_relationship_type',
mock.Mock(return_value=na_utils.DATA_PROTECTION_TYPE))
self.dm_session.change_snapmirror_source(
self.fake_dest_share, self.fake_src_share, fake_new_src_share,
@ -703,12 +712,14 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
self.mock_src_client.accept_vserver_peer.assert_called_once_with(
fake_new_src_ss_name, self.dest_vserver
)
na_utils.get_relationship_type.assert_called_once_with(False)
self.dm_session.delete_snapmirror.assert_called_with(
mock.ANY, mock.ANY, release=False
mock.ANY, mock.ANY, release=False, relationship_info_only=False
)
self.mock_dest_client.create_snapmirror_vol.assert_called_once_with(
mock.ANY, fake_new_src_share_name, mock.ANY,
self.fake_dest_vol_name, schedule='hourly'
self.fake_dest_vol_name, na_utils.DATA_PROTECTION_TYPE,
schedule='hourly'
)
self.mock_dest_client.resync_snapmirror_vol.assert_called_once_with(
mock.ANY, fake_new_src_share_name, mock.ANY,
@ -1051,3 +1062,53 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
src_mock_client.release_snapmirror_svm.assert_called_once_with(
fake.VSERVER1, fake.VSERVER2
)
@ddt.data(None, [{'fg1': fake.AGGREGATE}])
def test_is_flexgroup_share_false(self, flexgroup_pools):
mock_config = mock.Mock()
mock_get_backend = self.mock_object(
self.dm_session, 'get_backend_name_and_config_obj',
mock.Mock(return_value=('fake', mock_config)))
mock_safe_get = self.mock_object(
mock_config, 'safe_get', mock.Mock(return_value=flexgroup_pools))
mock_extract = self.mock_object(
share_utils, 'extract_host',
mock.Mock(return_value=fake.POOL_NAME))
mock_parse = self.mock_object(
na_utils, 'parse_flexgroup_pool_config',
mock.Mock(return_value={}))
result = self.dm_session.is_flexgroup_share(fake.HOST_NAME)
self.assertFalse(result)
mock_get_backend.assert_called_once_with(fake.HOST_NAME)
mock_safe_get.assert_called_once_with('netapp_flexgroup_pool')
if flexgroup_pools:
mock_extract.assert_called_once_with(fake.HOST_NAME, level='pool')
mock_parse.assert_called_once_with(flexgroup_pools)
else:
mock_extract.assert_not_called()
mock_parse.assert_not_called()
def test_is_flexgroup_share_true(self):
flexgroup_pools = [{fake.POOL_NAME: fake.AGGREGATE}]
mock_config = mock.Mock()
mock_get_backend = self.mock_object(
self.dm_session, 'get_backend_name_and_config_obj',
mock.Mock(return_value=('fake', mock_config)))
mock_safe_get = self.mock_object(
mock_config, 'safe_get', mock.Mock(return_value=flexgroup_pools))
mock_extract = self.mock_object(
share_utils, 'extract_host',
mock.Mock(return_value=fake.POOL_NAME))
mock_parse = self.mock_object(
na_utils, 'parse_flexgroup_pool_config',
mock.Mock(return_value=flexgroup_pools[0]))
result = self.dm_session.is_flexgroup_share(fake.HOST_NAME)
self.assertTrue(result)
mock_get_backend.assert_called_once_with(fake.HOST_NAME)
mock_safe_get.assert_called_once_with('netapp_flexgroup_pool')
mock_extract.assert_called_once_with(fake.HOST_NAME, level='pool')
mock_parse.assert_called_once_with(flexgroup_pools)

View File

@ -118,6 +118,14 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
def test_check_for_setup_error_cluster_creds_no_vserver(self):
self.library._have_cluster_creds = True
mock_list_non_root_aggregates = self.mock_object(
self.client, 'list_non_root_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
mock_init_flexgroup = self.mock_object(self.library,
'_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
@ -126,12 +134,23 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library.check_for_setup_error()
mock_list_non_root_aggregates.assert_called_once_with()
mock_init_flexgroup.assert_called_once_with(set(fake.AGGREGATES))
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
mock_super.assert_called_once_with()
def test_check_for_setup_error_cluster_creds_with_vserver(self):
self.library._have_cluster_creds = True
self.library.configuration.netapp_vserver = fake.VSERVER1
mock_list_non_root_aggregates = self.mock_object(
self.client, 'list_non_root_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
mock_init_flexgroup = self.mock_object(self.library,
'_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
@ -141,9 +160,33 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library.check_for_setup_error()
mock_super.assert_called_once_with()
mock_list_non_root_aggregates.assert_called_once_with()
mock_init_flexgroup.assert_called_once_with(set(fake.AGGREGATES))
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
self.assertTrue(lib_multi_svm.LOG.warning.called)
def test_check_for_setup_error_no_aggregates_no_flexvol_pool(self):
self.library._have_cluster_creds = True
mock_list_non_root_aggregates = self.mock_object(
self.client, 'list_non_root_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
mock_init_flexgroup = self.mock_object(self.library,
'_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=False))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=[]))
self.library.check_for_setup_error()
mock_list_non_root_aggregates.assert_called_once_with()
mock_init_flexgroup.assert_called_once_with(set(fake.AGGREGATES))
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
def test_check_for_setup_error_vserver_creds(self):
self.library._have_cluster_creds = False
@ -152,12 +195,24 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
def test_check_for_setup_error_no_aggregates(self):
self.library._have_cluster_creds = True
mock_list_non_root_aggregates = self.mock_object(
self.client, 'list_non_root_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
mock_init_flexgroup = self.mock_object(self.library,
'_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=[]))
self.assertRaises(exception.NetAppException,
self.library.check_for_setup_error)
mock_list_non_root_aggregates.assert_called_once_with()
mock_init_flexgroup.assert_called_once_with(set(fake.AGGREGATES))
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
def test_get_vserver_no_share_server(self):
@ -254,6 +309,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=['aggr1', 'aggr2']))
self.library._flexgroup_pools = {'fg': ['aggr1', 'aggr2']}
result = self.library._get_ems_pool_info()
@ -261,6 +317,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'pools': {
'vserver': None,
'aggregates': ['aggr1', 'aggr2'],
'flexgroup': {'fg': ['aggr1', 'aggr2']},
},
}
self.assertEqual(expected, result)
@ -358,6 +415,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
def test_find_matching_aggregates(self):
mock_is_flexvol_pool_configured = self.mock_object(
self.library, 'is_flexvol_pool_configured',
mock.Mock(return_value=True))
mock_list_non_root_aggregates = self.mock_object(
self.client, 'list_non_root_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
@ -367,7 +427,19 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
result = self.library._find_matching_aggregates()
self.assertListEqual([fake.AGGREGATES[0]], result)
mock_is_flexvol_pool_configured.assert_called_once_with()
mock_list_non_root_aggregates.assert_called_once_with()
mock_list_non_root_aggregates.assert_called_once_with()
def test_find_matching_aggregates_no_flexvol_pool(self):
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=False))
result = self.library._find_matching_aggregates()
self.assertListEqual([], result)
@ddt.data({'nfs_config_support': False},
{'nfs_config_support': True,
@ -523,6 +595,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
self.mock_object(self.library,
'_get_flexgroup_aggr_set',
mock.Mock(return_value=fake.AGGREGATES))
self.mock_object(self.library,
'_create_ipspace',
mock.Mock(return_value=fake.IPSPACE))
@ -547,7 +622,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.NETWORK_INFO)
self.library._client.create_vserver.assert_called_once_with(
vserver_name, fake.ROOT_VOLUME_AGGREGATE, fake.ROOT_VOLUME,
fake.AGGREGATES, fake.IPSPACE)
set(fake.AGGREGATES), fake.IPSPACE)
self.library._get_api_client.assert_called_once_with(
vserver=vserver_name)
self.library._create_vserver_lifs.assert_called_once_with(
@ -561,6 +636,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library._client.setup_security_services.assert_called_once_with(
fake.NETWORK_INFO['security_services'], vserver_client,
vserver_name)
self.library._get_flexgroup_aggr_set.assert_called_once_with()
@ddt.data(None, fake.IPSPACE)
def test_create_vserver_dp_destination(self, existing_ipspace):
@ -584,6 +660,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
self.mock_object(self.library,
'_get_flexgroup_aggr_set',
mock.Mock(return_value=fake.AGGREGATES))
self.mock_object(self.library,
'_create_ipspace',
mock.Mock(return_value=fake.IPSPACE))
@ -606,9 +685,10 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.NETWORK_INFO)
create_server_mock = self.library._client.create_vserver_dp_destination
create_server_mock.assert_called_once_with(
vserver_name, fake.AGGREGATES, fake.IPSPACE)
vserver_name, set(fake.AGGREGATES), fake.IPSPACE)
self.library._create_port_and_broadcast_domain.assert_called_once_with(
fake.IPSPACE, fake.NETWORK_INFO)
self.library._get_flexgroup_aggr_set.assert_called_once_with()
def test_create_vserver_already_present(self):
@ -665,6 +745,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
self.mock_object(self.library,
'_get_flexgroup_aggr_set',
mock.Mock(return_value=fake.AGGREGATES))
self.mock_object(self.library._client,
'get_ipspace_name_for_vlan_port',
mock.Mock(return_value=existing_ipspace))
@ -1780,7 +1863,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
dest_cluster_name=fake.CLUSTER_NAME_2,
src_svm_dr_support=True, dest_svm_dr_support=True,
check_capacity_result=True,
pools=fake.POOLS):
pools=fake.POOLS, flexgroup_support=False):
self.library._have_cluster_creds = have_cluster_creds
self.mock_object(self.library, '_get_vserver',
mock.Mock(return_value=(self.fake_src_vserver,
@ -1797,6 +1880,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
mock.Mock(return_value=dest_svm_dr_support))
self.mock_object(self.library, '_get_pools',
mock.Mock(return_value=pools))
self.mock_object(self.mock_src_client, 'is_flexgroup_supported',
mock.Mock(return_value=flexgroup_support))
self.mock_object(self.library, '_check_capacity_compatibility',
mock.Mock(return_value=check_capacity_result))
@ -1915,6 +2000,36 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertTrue(self.mock_src_client.is_svm_dr_supported.called)
self.assertTrue(self.mock_dest_client.is_svm_dr_supported.called)
def test_share_server_migration_check_compatibility_flexgroup_vol(self):
not_compatible = fake.SERVER_MIGRATION_CHECK_NOT_COMPATIBLE
dm_session_mock = mock.Mock()
dm_session_mock.is_flexgroup_share.return_value = True
self.mock_object(data_motion, "DataMotionSession",
mock.Mock(return_value=dm_session_mock))
self._configure_mocks_share_server_migration_check_compatibility(
flexgroup_support=True
)
result = self.library.share_server_migration_check_compatibility(
None, self.fake_src_share_server,
self.fake_dest_share_server['host'],
fake.SHARE_NETWORK, fake.SHARE_NETWORK,
fake.SERVER_MIGRATION_REQUEST_SPEC)
self.assertEqual(not_compatible, result)
self.library._get_vserver.assert_called_once_with(
self.fake_src_share_server,
backend_name=self.fake_src_backend_name
)
self.assertTrue(self.mock_src_client.get_cluster_name.called)
data_motion.get_client_for_backend.assert_called_once_with(
self.fake_dest_backend_name, vserver_name=None
)
self.assertTrue(self.mock_dest_client.is_svm_dr_supported.called)
self.assertTrue(self.mock_src_client.is_flexgroup_supported.called)
self.assertTrue(data_motion.DataMotionSession.called)
self.assertTrue(dm_session_mock.is_flexgroup_share.called)
def test_share_server_migration_check_compatibility_capacity_false(
self):
not_compatible = fake.SERVER_MIGRATION_CHECK_NOT_COMPATIBLE
@ -1940,6 +2055,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
)
self.assertTrue(self.mock_src_client.is_svm_dr_supported.called)
self.assertTrue(self.mock_dest_client.is_svm_dr_supported.called)
self.assertTrue(self.mock_src_client.is_flexgroup_supported.called)
total_size = (fake.SERVER_MIGRATION_REQUEST_SPEC['shares_size'] +
fake.SERVER_MIGRATION_REQUEST_SPEC['snapshots_size'])
self.library._check_capacity_compatibility.assert_called_once_with(
@ -1978,6 +2094,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
)
self.assertTrue(self.mock_src_client.is_svm_dr_supported.called)
self.assertTrue(self.mock_dest_client.is_svm_dr_supported.called)
self.assertTrue(self.mock_src_client.is_flexgroup_supported.called)
total_size = (fake.SERVER_MIGRATION_REQUEST_SPEC['shares_size'] +
fake.SERVER_MIGRATION_REQUEST_SPEC['snapshots_size'])
self.library._check_capacity_compatibility.assert_called_once_with(

View File

@ -64,6 +64,16 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library._client.vserver_exists.return_value = True
self.library._have_cluster_creds = True
mock_client = mock.Mock()
mock_client.list_vserver_aggregates.return_value = fake.AGGREGATES
self.mock_object(self.library,
'_get_api_client',
mock.Mock(return_value=mock_client))
mock_init_flexgroup = self.mock_object(self.library,
'_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
@ -74,6 +84,10 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.assertTrue(lib_single_svm.LOG.info.called)
mock_super.assert_called_once_with()
mock_client.list_vserver_aggregates.assert_called_once_with()
self.assertTrue(self.library._get_api_client.called)
mock_init_flexgroup.assert_called_once_with(set(fake.AGGREGATES))
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
def test_check_for_setup_error_no_vserver(self):
@ -92,6 +106,16 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library._client.vserver_exists.return_value = True
self.library._have_cluster_creds = False
self.library._client.list_vservers.return_value = [fake.VSERVER1]
mock_client = mock.Mock()
mock_client.list_vserver_aggregates.return_value = fake.AGGREGATES
self.mock_object(self.library,
'_get_api_client',
mock.Mock(return_value=mock_client))
mock_init_flexgroup = self.mock_object(self.library,
'_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=fake.AGGREGATES))
@ -101,6 +125,31 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.library.check_for_setup_error()
mock_super.assert_called_once_with()
mock_client.list_vserver_aggregates.assert_called_once_with()
self.assertTrue(self.library._get_api_client.called)
mock_init_flexgroup.assert_called_once_with(set(fake.AGGREGATES))
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
def test_check_for_setup_error_no_aggregates_no_flexvol_pool(self):
self.library._client.vserver_exists.return_value = True
self.library._have_cluster_creds = True
mock_client = mock.Mock()
mock_client.list_vserver_aggregates.return_value = fake.AGGREGATES
self.mock_object(self.library,
'_get_api_client',
mock.Mock(return_value=mock_client))
self.mock_object(self.library, '_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=False))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=[]))
self.library.check_for_setup_error()
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
def test_check_for_setup_error_cluster_creds_vserver_mismatch(self):
@ -114,12 +163,22 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
def test_check_for_setup_error_no_aggregates(self):
self.library._client.vserver_exists.return_value = True
self.library._have_cluster_creds = True
mock_client = mock.Mock()
mock_client.list_vserver_aggregates.return_value = fake.AGGREGATES
self.mock_object(self.library,
'_get_api_client',
mock.Mock(return_value=mock_client))
self.mock_object(self.library, '_initialize_flexgroup_pools')
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=True))
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=[]))
self.assertRaises(exception.NetAppException,
self.library.check_for_setup_error)
self.assertTrue(self.library.is_flexvol_pool_configured.called)
self.assertTrue(self.library._find_matching_aggregates.called)
def test_get_vserver(self):
@ -155,6 +214,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
self.mock_object(self.library,
'_find_matching_aggregates',
mock.Mock(return_value=['aggr1', 'aggr2']))
self.library._flexgroup_pools = {'fg': ['aggr1', 'aggr2']}
result = self.library._get_ems_pool_info()
@ -162,6 +222,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
'pools': {
'vserver': fake.VSERVER1,
'aggregates': ['aggr1', 'aggr2'],
'flexgroup': {'fg': ['aggr1', 'aggr2']},
},
}
self.assertEqual(expected, result)
@ -189,6 +250,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
@ddt.data(True, False)
def test_find_matching_aggregates(self, have_cluster_creds):
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=True))
self.library._have_cluster_creds = have_cluster_creds
aggregates = fake.AGGREGATES + fake.ROOT_AGGREGATES
mock_vserver_client = mock.Mock()
@ -213,6 +277,16 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
result)
self.assertFalse(mock_client.list_root_aggregates.called)
def test_find_matching_aggregates_no_flexvol_pool(self):
self.mock_object(self.library,
'is_flexvol_pool_configured',
mock.Mock(return_value=False))
result = self.library._find_matching_aggregates()
self.assertListEqual([], result)
def test_get_network_allocations_number(self):
self.assertEqual(0, self.library.get_network_allocations_number())

View File

@ -57,7 +57,11 @@ class PerformanceLibraryTestCase(test.TestCase):
self.fake_aggregates = {
'pool4': {
'netapp_aggregate': 'aggr3',
}
},
'flexgroup_pool': {
'netapp_aggregate': 'aggr1 aggr2',
'netapp_is_flexgroup': True,
},
}
self.fake_aggr_names = ['aggr1', 'aggr2', 'aggr3']
@ -191,6 +195,7 @@ class PerformanceLibraryTestCase(test.TestCase):
'pool2': 75,
'pool3': 75,
'pool4': 75,
'flexgroup_pool': performance.DEFAULT_UTILIZATION,
}
self.assertEqual(expected_pool_utilization,
self.perf_library.pool_utilization)
@ -232,6 +237,7 @@ class PerformanceLibraryTestCase(test.TestCase):
'pool2': performance.DEFAULT_UTILIZATION,
'pool3': performance.DEFAULT_UTILIZATION,
'pool4': performance.DEFAULT_UTILIZATION,
'flexgroup_pool': performance.DEFAULT_UTILIZATION,
}
self.assertEqual(expected_pool_utilization,
self.perf_library.pool_utilization)
@ -271,6 +277,7 @@ class PerformanceLibraryTestCase(test.TestCase):
'pool2': performance.DEFAULT_UTILIZATION,
'pool3': performance.DEFAULT_UTILIZATION,
'pool4': performance.DEFAULT_UTILIZATION,
'flexgroup_pool': performance.DEFAULT_UTILIZATION,
}
self.assertEqual(expected_pool_utilization,
self.perf_library.pool_utilization)
@ -310,6 +317,7 @@ class PerformanceLibraryTestCase(test.TestCase):
'pool2': performance.DEFAULT_UTILIZATION,
'pool3': performance.DEFAULT_UTILIZATION,
'pool4': performance.DEFAULT_UTILIZATION,
'flexgroup_pool': performance.DEFAULT_UTILIZATION,
}
self.assertEqual(expected_pool_utilization,
self.perf_library.pool_utilization)

View File

@ -28,6 +28,7 @@ DRIVER_NAME = 'fake_driver_name'
APP_VERSION = 'fake_app_vsersion'
HOST_NAME = 'fake_host'
POOL_NAME = 'fake_pool'
FLEXGROUP_STYLE_EXTENDED = 'flexgroup'
POOL_NAME_2 = 'fake_pool_2'
VSERVER1 = 'fake_vserver_1'
VSERVER2 = 'fake_vserver_2'
@ -132,6 +133,7 @@ SHARE = {
'status': constants.STATUS_AVAILABLE,
'share_server': None,
'encrypt': False,
'share_id': SHARE_ID,
}
SHARE_INSTANCE = {
@ -156,6 +158,7 @@ FLEXVOL_TO_MANAGE = {
'type': 'rw',
'style': 'flex',
'size': '1610612736', # rounds up to 2 GB
'style-extended': FLEXGROUP_STYLE_EXTENDED,
}
FLEXVOL_WITHOUT_QOS = copy.deepcopy(FLEXVOL_TO_MANAGE)
@ -855,6 +858,46 @@ AGGREGATE_CAPACITIES = {
}
}
FLEXGROUP_POOL_NAME = 'flexgroup_pool'
FLEXGROUP_POOL_AGGR = [AGGREGATES[0], AGGREGATES[1]]
FLEXGROUP_POOL_OPT = {
FLEXGROUP_POOL_NAME: FLEXGROUP_POOL_AGGR,
}
FLEXGROUP_POOL_OPT_RAW = {
FLEXGROUP_POOL_NAME: '%s %s' % (AGGREGATES[0], AGGREGATES[1]),
}
FLEXGROUP_POOL = {
'pool_name': FLEXGROUP_POOL_NAME,
'netapp_aggregate': '%s %s' % (AGGREGATES[0], AGGREGATES[1]),
'total_capacity_gb': 6.6,
'free_capacity_gb': 2.2,
'allocated_capacity_gb': 4.39,
'reserved_percentage': 5,
'max_over_subscription_ratio': 2.0,
'dedupe': [True, False],
'compression': [True, False],
'thin_provisioning': [True, False],
'netapp_flexvol_encryption': True,
'netapp_raid_type': 'raid4 raid_dp',
'netapp_disk_type': ['FCAL', 'SATA', 'SSD'],
'netapp_hybrid_aggregate': 'false true',
'utilization': 50.0,
'filter_function': 'filter',
'goodness_function': 'goodness',
'snapshot_support': True,
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': True,
'qos': True,
'security_service_update_support': True,
'netapp_is_flexgroup': True,
}
FLEXGROUP_AGGR_SET = set(FLEXGROUP_POOL_OPT[FLEXGROUP_POOL_NAME])
AGGREGATE_CAPACITIES_VSERVER_CREDS = {
AGGREGATES[0]: {
'available': 1181116007, # 1.1 GB
@ -870,21 +913,38 @@ SSC_INFO = {
'netapp_disk_type': 'FCAL',
'netapp_hybrid_aggregate': 'false',
'netapp_aggregate': AGGREGATES[0],
'netapp_is_flexgroup': False,
},
AGGREGATES[1]: {
'netapp_raid_type': 'raid_dp',
'netapp_disk_type': ['SATA', 'SSD'],
'netapp_hybrid_aggregate': 'true',
'netapp_aggregate': AGGREGATES[1],
'netapp_is_flexgroup': False,
}
}
SSC_INFO_MAP = {
AGGREGATES[0]: {
'netapp_raid_type': 'raid4',
'netapp_disk_type': ['FCAL'],
'netapp_hybrid_aggregate': 'false',
},
AGGREGATES[1]: {
'netapp_raid_type': 'raid_dp',
'netapp_disk_type': ['SATA', 'SSD'],
'netapp_hybrid_aggregate': 'true',
}
}
SSC_INFO_VSERVER_CREDS = {
AGGREGATES[0]: {
'netapp_aggregate': AGGREGATES[0],
'netapp_is_flexgroup': False,
},
AGGREGATES[1]: {
'netapp_aggregate': AGGREGATES[1],
'netapp_is_flexgroup': False,
}
}
@ -912,6 +972,7 @@ POOLS = [
'revert_to_snapshot_support': True,
'qos': True,
'security_service_update_support': True,
'netapp_is_flexgroup': False,
},
{
'pool_name': AGGREGATES[1],
@ -936,12 +997,15 @@ POOLS = [
'revert_to_snapshot_support': True,
'qos': True,
'security_service_update_support': True,
'netapp_is_flexgroup': False,
},
]
POOLS_VSERVER_CREDS = [
{
'pool_name': AGGREGATES[0],
'filter_function': None,
'goodness_function': None,
'netapp_aggregate': AGGREGATES[0],
'total_capacity_gb': 'unknown',
'free_capacity_gb': 1.1,
@ -953,13 +1017,12 @@ POOLS_VSERVER_CREDS = [
'thin_provisioning': [True, False],
'netapp_flexvol_encryption': True,
'utilization': 50.0,
'filter_function': None,
'goodness_function': None,
'snapshot_support': True,
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': True,
'qos': False,
'security_service_update_support': True,
'netapp_is_flexgroup': False,
},
{
'pool_name': AGGREGATES[1],
@ -974,13 +1037,12 @@ POOLS_VSERVER_CREDS = [
'thin_provisioning': [True, False],
'netapp_flexvol_encryption': True,
'utilization': 50.0,
'filter_function': None,
'goodness_function': None,
'snapshot_support': True,
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': True,
'qos': False,
'security_service_update_support': True,
'netapp_is_flexgroup': False,
},
]

View File

@ -44,14 +44,19 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
def test__escaped_address(self, raw, escaped):
self.assertEqual(escaped, self.helper._escaped_address(raw))
def test_create_share(self):
@ddt.data(True, False)
def test_create_share(self, is_flexgroup):
mock_ensure_export_policy = self.mock_object(self.helper,
'_ensure_export_policy')
self.mock_client.get_volume_junction_path.return_value = (
fake.NFS_SHARE_PATH)
self.mock_client.get_volume.return_value = {
'junction-path': fake.NFS_SHARE_PATH,
}
result = self.helper.create_share(fake.NFS_SHARE, fake.SHARE_NAME)
result = self.helper.create_share(fake.NFS_SHARE, fake.SHARE_NAME,
is_flexgroup=is_flexgroup)
export_addresses = [fake.SHARE_ADDRESS_1, fake.SHARE_ADDRESS_2]
export_paths = [result(address) for address in export_addresses]
@ -63,6 +68,10 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase):
(self.mock_client.clear_nfs_export_policy_for_volume.
assert_called_once_with(fake.SHARE_NAME))
self.assertTrue(mock_ensure_export_policy.called)
if is_flexgroup:
self.assertTrue(self.mock_client.get_volume.called)
else:
self.assertTrue(self.mock_client.get_volume_junction_path.called)
def test_delete_share(self):

View File

@ -26,6 +26,7 @@ from oslo_log import log
from manila import exception
from manila.share.drivers.netapp import utils as na_utils
from manila import test
from manila.tests.share.drivers.netapp.dataontap import fakes as fake
from manila import version
@ -152,6 +153,74 @@ class NetAppDriverUtilsTestCase(test.TestCase):
sorted(na_utils.convert_to_list({'key1': 'value1',
'key2': 'value2'})))
@ddt.data({'is_fg': True, 'type': na_utils.EXTENDED_DATA_PROTECTION_TYPE},
{'is_fg': False, 'type': na_utils.DATA_PROTECTION_TYPE})
@ddt.unpack
def test_get_relationship_type(self, is_fg, type):
relationship_type = na_utils.get_relationship_type(is_fg)
self.assertEqual(type, relationship_type)
@ddt.data({'is_style': True, 'style': na_utils.FLEXGROUP_STYLE_EXTENDED},
{'is_style': False, 'style': na_utils.FLEXVOL_STYLE_EXTENDED})
@ddt.unpack
def test_is_style_extended_flexgroup(self, is_style, style):
res = na_utils.is_style_extended_flexgroup(style)
self.assertEqual(is_style, res)
@ddt.data(True, False)
def test_parse_flexgroup_pool_config(self, check):
result = na_utils.parse_flexgroup_pool_config(
[fake.FLEXGROUP_POOL_OPT_RAW],
cluster_aggr_set=set(fake.FLEXGROUP_POOL_AGGR),
check=check)
self.assertEqual(fake.FLEXGROUP_POOL_OPT, result)
def test_parse_flexgroup_pool_config_raise_invalid_aggr(self):
self.assertRaises(exception.NetAppException,
na_utils.parse_flexgroup_pool_config,
[fake.FLEXGROUP_POOL_OPT_RAW],
cluster_aggr_set=set(),
check=True)
def test_parse_flexgroup_pool_config_raise_duplicated_pool(self):
fake_pool = {
'flexgroup1': fake.FLEXGROUP_POOL_AGGR[0],
'flexgroup2': fake.FLEXGROUP_POOL_AGGR[0],
}
self.assertRaises(exception.NetAppException,
na_utils.parse_flexgroup_pool_config,
[fake_pool],
cluster_aggr_set=set(fake.FLEXGROUP_POOL_AGGR),
check=True)
def test_parse_flexgroup_pool_config_raise_repeated_aggr(self):
aggr_pool = '%s %s' % (fake.FLEXGROUP_POOL_AGGR[0],
fake.FLEXGROUP_POOL_AGGR[0])
self.assertRaises(exception.NetAppException,
na_utils.parse_flexgroup_pool_config,
[{'flexgroup1': aggr_pool}],
cluster_aggr_set=set(fake.FLEXGROUP_POOL_AGGR),
check=True)
def test_parse_flexgroup_pool_config_raise_invalid_pool_name(self):
aggr_pool = '%s %s' % (fake.FLEXGROUP_POOL_AGGR[0],
fake.FLEXGROUP_POOL_AGGR[0])
self.assertRaises(exception.NetAppException,
na_utils.parse_flexgroup_pool_config,
[{fake.FLEXGROUP_POOL_AGGR[0]: aggr_pool}],
cluster_aggr_set=set(fake.FLEXGROUP_POOL_AGGR),
check=True)
class OpenstackInfoTestCase(test.TestCase):

View File

@ -0,0 +1,28 @@
---
features:
- |
The NetApp driver has been working with FlexVol ONTAP volumes.
This type of volume does not work well with large workloads, being
over one single node/aggregate. So, added the support for provisioning
share as FlexGroup in the NetApp driver.
The FlexGroup pool is configured with a new option
`netapp_flexgroup_pool`. The pool for FlexGroup is different from the
FlexVol one, the driver can handle those two types of pools, either alone
or together. For having both, the new option `netapp_flexgroup_pool_only`
must be set to `False`. Each NetApp pool will report now the capability:
`netapp_is_flexgroup` informing which type the pool is.
The following operations are allowed with FlexGroup shares (DHSS
True/False and NFS/CIFS):
- Create/Delete share;
- Shrink/Extend share;
- Create/Delete snapshot;
- Revert to snapshot;
- Manage/Unmanage snapshots;
- Create from snapshot;
- Replication;
- Manage/Unmanage shares;
This feature requires ONTAP version 9.8 or newer.