[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:
parent
82ea5d5680
commit
a84005cb5c
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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))
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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>
|
||||
""")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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(
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue