[NetApp] Add FlexGroup volume support
The NetApp driver has been working with FlexVol ONTAP volumes. The driver does not support scaling FlexVol volumes higher than 100 TiB, which was a theoretical limit for the large namespace that these containers were meant to handle. ONTAP's Flexgroup volumes eliminate such limitations. So, added the support for provisioning share as FlexGroup in the NetApp driver. The FlexGroup provision is enabled by new option `netapp_enable_flexgroup`, which will make the driver report a single pool represeting all aggregates. The selection on which aggregates the FlexGroup share will reside is up to ONTAP. If the administrator desires to control that selection through Manila scheduler, it must inform the set of aggregates that formss FlexGroup pool in the new option `netapp_flexgroup_pool`. Each NetApp pool will report now the capability: `netapp_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. DocImpact Depends-On: If525e97a5d456d6ddebb4bf9bc8ff6190c95a555 Depends-On: I646f782c3e2be5ac799254f08a248a22cb9e0358 Implements: bp netapp-flexgroup-support Change-Id: I4f68a9bb33be85f9a22e0be4ccf673647e713459 Signed-off-by: Felipe Rodrigues <felipefuty01@gmail.com>
This commit is contained in:
parent
74d5a1b2cf
commit
9f3c566a10
@ -20,6 +20,12 @@ NetApp Clustered Data ONTAP
|
||||
The Shared File Systems service can be configured to use
|
||||
NetApp Clustered Data ONTAP (cDOT) version 8.2 and later.
|
||||
|
||||
The driver can work with two types of pools: FlexGroup and FlexVol. By default,
|
||||
it only works with FlexVol, if desired, the FlexGroup pool can be enabled
|
||||
together or standalone.
|
||||
|
||||
FlexGroup pool requires ONTAP version 9.8 or later.
|
||||
|
||||
Supported Operations
|
||||
--------------------
|
||||
|
||||
@ -57,6 +63,19 @@ The following operations are supported on Clustered Data ONTAP:
|
||||
- Create a replicated snapshot (DHSS=False)
|
||||
- Delete a replicated snapshot (DHSS=False)
|
||||
- Update a replicated snapshot (DHSS=False)
|
||||
- Migrate share
|
||||
- Migrate share server
|
||||
|
||||
.. note::
|
||||
|
||||
The operations are not fully supported configuring FlexGroup pool:
|
||||
|
||||
- Consistency group operations are only supported configuring the driver
|
||||
without any FlexGroup pool.
|
||||
- For FlexGroup share, create more than one replica is only allowed with
|
||||
ONTAP 9.9.1 and newer.
|
||||
- Migration of FlexGroup shares is not allowed.
|
||||
- Migration of share servers containing FlexGroup share is not allowed.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -797,6 +797,11 @@ class NetAppException(ManilaException):
|
||||
message = _("Exception due to NetApp failure.")
|
||||
|
||||
|
||||
class NetAppBusyAggregateForFlexGroupException(ManilaException):
|
||||
message = _("Exception due to an aggregate being busy while trying to "
|
||||
"provision the FlexGroup.")
|
||||
|
||||
|
||||
class VserverNotFound(NetAppException):
|
||||
message = _("Vserver %(vserver)s not found.")
|
||||
|
||||
|
@ -2608,7 +2608,7 @@ class ShareDriver(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_filter_function(self):
|
||||
def get_filter_function(self, pool=None):
|
||||
"""Get filter_function string.
|
||||
|
||||
Returns either the string from the driver instance or global section
|
||||
@ -2616,14 +2616,16 @@ class ShareDriver(object):
|
||||
find the default filter_function. When None is returned the scheduler
|
||||
will always pass the driver instance.
|
||||
|
||||
:param pool: pool name to get the filter or None
|
||||
:return: a filter_function string or None
|
||||
"""
|
||||
ret_function = self.configuration.filter_function
|
||||
if not ret_function:
|
||||
ret_function = CONF.filter_function
|
||||
if not ret_function:
|
||||
kwargs = {'pool': pool} if pool else {}
|
||||
# pylint: disable=assignment-from-none
|
||||
ret_function = self.get_default_filter_function()
|
||||
ret_function = self.get_default_filter_function(**kwargs)
|
||||
# pylint: enable=assignment-from-none
|
||||
return ret_function
|
||||
|
||||
@ -2646,12 +2648,13 @@ class ShareDriver(object):
|
||||
# pylint: enable=assignment-from-none
|
||||
return ret_function
|
||||
|
||||
def get_default_filter_function(self):
|
||||
def get_default_filter_function(self, pool=None):
|
||||
"""Get the default filter_function string.
|
||||
|
||||
Each driver could overwrite the method to return a well-known
|
||||
default string if it is available.
|
||||
|
||||
:param pool: pool name to get the filter or None
|
||||
:return: None
|
||||
"""
|
||||
return None
|
||||
|
@ -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)
|
||||
ontap_9_10 = self.get_system_version()['version-tuple'] >= (9, 10, 0)
|
||||
|
||||
self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
|
||||
@ -96,6 +98,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)
|
||||
self.features.add_feature('SVM_MIGRATE', supported=ontap_9_10)
|
||||
|
||||
def _invoke_vserver_api(self, na_element, vserver):
|
||||
@ -123,7 +127,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
self.connection.set_vserver(vserver)
|
||||
|
||||
def send_iter_request(self, api_name, api_args=None,
|
||||
max_page_length=DEFAULT_MAX_PAGE_LENGTH):
|
||||
max_page_length=DEFAULT_MAX_PAGE_LENGTH,
|
||||
enable_tunneling=True):
|
||||
"""Invoke an iterator-style getter API."""
|
||||
|
||||
if not api_args:
|
||||
@ -132,7 +137,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
api_args['max-records'] = max_page_length
|
||||
|
||||
# Get first page
|
||||
result = self.send_request(api_name, api_args)
|
||||
result = self.send_request(api_name, api_args,
|
||||
enable_tunneling=enable_tunneling)
|
||||
|
||||
# Most commonly, we can just return here if there is no more data
|
||||
next_tag = result.get_child_content('next-tag')
|
||||
@ -150,7 +156,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
while next_tag is not None:
|
||||
next_api_args = copy.deepcopy(api_args)
|
||||
next_api_args['tag'] = next_tag
|
||||
next_result = self.send_request(api_name, next_api_args)
|
||||
next_result = self.send_request(api_name, next_api_args,
|
||||
enable_tunneling=enable_tunneling)
|
||||
|
||||
next_attributes_list = next_result.get_child_by_name(
|
||||
'attributes-list') or netapp_api.NaElement('none')
|
||||
@ -2044,6 +2051,63 @@ 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,
|
||||
auto_provisioned=False, **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 = {
|
||||
'size': size_gb * units.Gi,
|
||||
'volume-name': volume_name,
|
||||
}
|
||||
if auto_provisioned:
|
||||
api_args['auto-provision-as'] = 'flexgroup'
|
||||
else:
|
||||
api_args['aggr-list'] = [{'aggr-name': aggr}
|
||||
for aggr in aggregate_list]
|
||||
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':
|
||||
@ -2055,8 +2119,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:
|
||||
@ -2070,13 +2133,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):
|
||||
@ -2108,6 +2165,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."""
|
||||
@ -2312,7 +2399,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)
|
||||
@ -2321,7 +2425,6 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'containing-aggregate-name': aggregate_name,
|
||||
'name': volume_name,
|
||||
},
|
||||
},
|
||||
@ -2341,6 +2444,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
|
||||
@ -2373,11 +2486,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)
|
||||
|
||||
@ -2386,15 +2501,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):
|
||||
@ -2470,6 +2597,9 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'desired-attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'aggr-list': {
|
||||
'aggr-name': None,
|
||||
},
|
||||
'containing-aggregate-name': None,
|
||||
'name': None,
|
||||
},
|
||||
@ -2487,6 +2617,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.')
|
||||
@ -2515,9 +2650,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
|
||||
|
||||
@ -2575,12 +2709,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,
|
||||
@ -2613,9 +2751,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'),
|
||||
@ -2625,7 +2773,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
|
||||
|
||||
@ -2640,21 +2790,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,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -2668,30 +2814,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,
|
||||
},
|
||||
},
|
||||
@ -2699,6 +2841,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,
|
||||
@ -2715,6 +2860,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
|
||||
@ -2730,9 +2884,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'),
|
||||
@ -3991,8 +4155,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,
|
||||
@ -4003,7 +4167,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."""
|
||||
@ -4017,7 +4181,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()
|
||||
@ -4167,7 +4331,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
self._ensure_snapmirror_v2()
|
||||
api_args = {
|
||||
'query': {
|
||||
'snapmirror-destination-info': dest_info
|
||||
'snapmirror-destination-info': dest_info,
|
||||
},
|
||||
'relationship-info-only': (
|
||||
'true' if relationship_info_only else 'false'),
|
||||
@ -5479,6 +5643,126 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
"""Checks if the cluster supports SVM Migrate."""
|
||||
return self.features.SVM_MIGRATE
|
||||
|
||||
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.is_flexgroup_supported():
|
||||
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 = _('More than one volume with volume name %(vol)s found.')
|
||||
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
|
||||
|
||||
@na_utils.trace
|
||||
def get_job_state(self, job_id):
|
||||
"""Returns job state for a given job id."""
|
||||
|
||||
api_args = {
|
||||
'query': {
|
||||
'job-info': {
|
||||
'job-id': job_id,
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'job-info': {
|
||||
'job-state': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result = self.send_iter_request('job-get-iter', api_args,
|
||||
enable_tunneling=False)
|
||||
|
||||
attributes_list = result.get_child_by_name(
|
||||
'attributes-list') or netapp_api.NaElement('none')
|
||||
job_info_list = attributes_list.get_children()
|
||||
if not self._has_records(result):
|
||||
msg = _('Could not find job with ID %(id)s.')
|
||||
msg_args = {'id': job_id}
|
||||
raise exception.NetAppException(msg % msg_args)
|
||||
elif len(job_info_list) > 1:
|
||||
msg = _('Could not find unique job for ID %(id)s.')
|
||||
msg_args = {'id': job_id}
|
||||
raise exception.NetAppException(msg % msg_args)
|
||||
|
||||
return job_info_list[0].get_child_content('job-state')
|
||||
|
||||
# ------------------------ REST CALLS ONLY ------------------------
|
||||
|
||||
@na_utils.trace
|
||||
|
@ -167,7 +167,8 @@ class DataMotionSession(object):
|
||||
'last-transfer-end-timestamp'])
|
||||
return snapmirrors
|
||||
|
||||
def create_snapmirror(self, source_share_obj, dest_share_obj, mount=False):
|
||||
def create_snapmirror(self, source_share_obj, dest_share_obj,
|
||||
relationship_type, mount=False):
|
||||
"""Sets up a SnapMirror relationship between two volumes.
|
||||
|
||||
1. Create SnapMirror relationship.
|
||||
@ -188,6 +189,7 @@ class DataMotionSession(object):
|
||||
src_volume_name,
|
||||
dest_vserver,
|
||||
dest_volume_name,
|
||||
relationship_type,
|
||||
schedule='hourly')
|
||||
|
||||
# 2. Initialize async transfer of the initial data
|
||||
@ -204,7 +206,7 @@ class DataMotionSession(object):
|
||||
timeout=replica_config.netapp_mount_replica_timeout)
|
||||
|
||||
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
|
||||
@ -251,21 +253,16 @@ class DataMotionSession(object):
|
||||
vserver_name=src_vserver)
|
||||
except Exception:
|
||||
src_client = None
|
||||
|
||||
# 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)
|
||||
except netapp_api.NaApiError as e:
|
||||
with excutils.save_and_reraise_exception() as exc_context:
|
||||
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
||||
e.code == netapp_api.ESOURCE_IS_DIFFERENT or
|
||||
"(entry doesn't exist)" in e.message):
|
||||
# Handle the case where the snapmirror is already
|
||||
# cleaned up
|
||||
exc_context.reraise = False
|
||||
if src_client:
|
||||
src_config = get_backend_configuration(src_backend)
|
||||
release_timeout = (
|
||||
src_config.netapp_snapmirror_release_timeout)
|
||||
self.wait_for_snapmirror_release_vol(
|
||||
src_vserver, dest_vserver, src_volume_name,
|
||||
dest_volume_name, relationship_info_only, src_client,
|
||||
timeout=release_timeout)
|
||||
|
||||
def update_snapmirror(self, source_share_obj, dest_share_obj):
|
||||
"""Schedule a snapmirror update to happen on the backend."""
|
||||
@ -419,7 +416,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
|
||||
@ -443,11 +441,16 @@ class DataMotionSession(object):
|
||||
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 if 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)
|
||||
@ -471,11 +474,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,
|
||||
@ -793,3 +799,42 @@ class DataMotionSession(object):
|
||||
"because a snapmirror initialize operation is still in "
|
||||
"progress. Retries exhausted. Not retrying.") % msg_args
|
||||
raise exception.NetAppException(message=msg)
|
||||
|
||||
def wait_for_snapmirror_release_vol(self, src_vserver, dest_vserver,
|
||||
src_volume_name, dest_volume_name,
|
||||
relationship_info_only, src_client,
|
||||
timeout=300):
|
||||
interval = 10
|
||||
retries = (timeout / interval or 1)
|
||||
|
||||
@utils.retry(exception.NetAppException, interval=interval,
|
||||
retries=retries, backoff_rate=1)
|
||||
def release_snapmirror():
|
||||
snapmirrors = src_client.get_snapmirror_destinations(
|
||||
source_vserver=src_vserver, dest_vserver=dest_vserver,
|
||||
source_volume=src_volume_name, dest_volume=dest_volume_name)
|
||||
if not snapmirrors:
|
||||
LOG.debug("No snapmirrors to be released in source volume.")
|
||||
else:
|
||||
try:
|
||||
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:
|
||||
if (e.code == netapp_api.EOBJECTNOTFOUND or
|
||||
e.code == netapp_api.ESOURCE_IS_DIFFERENT or
|
||||
"(entry doesn't exist)" in e.message):
|
||||
LOG.debug('Snapmirror relationship does not exist '
|
||||
'anymore.')
|
||||
|
||||
msg = _('Snapmirror release sent to source volume. Waiting '
|
||||
'until it has been released.')
|
||||
raise exception.NetAppException(vserver=msg)
|
||||
|
||||
try:
|
||||
release_snapmirror()
|
||||
except exception.NetAppException:
|
||||
msg = _("Unable to release the snapmirror from source volume %s. "
|
||||
"Retries exhausted. Aborting") % src_volume_name
|
||||
raise exception.NetAppException(message=msg)
|
||||
|
@ -113,13 +113,13 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
|
||||
|
||||
def _update_share_stats(self, data=None):
|
||||
data = self.library.get_share_stats(
|
||||
filter_function=self.get_filter_function(),
|
||||
get_filter_function=self.get_filter_function,
|
||||
goodness_function=self.get_goodness_function())
|
||||
super(NetAppCmodeMultiSvmShareDriver, self)._update_share_stats(
|
||||
data=data)
|
||||
|
||||
def get_default_filter_function(self):
|
||||
return self.library.get_default_filter_function()
|
||||
def get_default_filter_function(self, pool=None):
|
||||
return self.library.get_default_filter_function(pool=pool)
|
||||
|
||||
def get_default_goodness_function(self):
|
||||
return self.library.get_default_goodness_function()
|
||||
|
@ -105,13 +105,13 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
|
||||
|
||||
def _update_share_stats(self, data=None):
|
||||
data = self.library.get_share_stats(
|
||||
filter_function=self.get_filter_function(),
|
||||
get_filter_function=self.get_filter_function,
|
||||
goodness_function=self.get_goodness_function())
|
||||
super(NetAppCmodeSingleSvmShareDriver, self)._update_share_stats(
|
||||
data=data)
|
||||
|
||||
def get_default_filter_function(self):
|
||||
return self.library.get_default_filter_function()
|
||||
def get_default_filter_function(self, pool=None):
|
||||
return self.library.get_default_filter_function(pool=pool)
|
||||
|
||||
def get_default_goodness_function(self):
|
||||
return self.library.get_default_goodness_function()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -65,8 +65,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.')
|
||||
@ -110,6 +115,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||
'pools': {
|
||||
'vserver': None,
|
||||
'aggregates': self._find_matching_aggregates(),
|
||||
'flexgroup_aggregates': self._flexgroup_pools,
|
||||
},
|
||||
}
|
||||
|
||||
@ -124,9 +130,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)]
|
||||
@ -242,23 +254,26 @@ 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()
|
||||
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(),
|
||||
aggregate_names,
|
||||
ipspace_name)
|
||||
# Set up port and broadcast domain for the current ipspace
|
||||
self._create_port_and_broadcast_domain(ipspace_name, network_info)
|
||||
else:
|
||||
LOG.debug('Vserver %s does not exist, creating.', vserver_name)
|
||||
aggr_set = set(aggregate_names).union(
|
||||
self._get_flexgroup_aggr_set())
|
||||
self._client.create_vserver(
|
||||
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)
|
||||
@ -680,8 +695,9 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||
share_server=None, parent_share=None):
|
||||
# NOTE(dviroel): If both parent and child shares are in the same host,
|
||||
# they belong to the same cluster, and we can skip all the processing
|
||||
# below.
|
||||
if parent_share['host'] != share['host']:
|
||||
# below. Group snapshot is always to the same host too, so we can skip.
|
||||
is_group_snapshot = share.get('source_share_group_snapshot_member_id')
|
||||
if not is_group_snapshot and parent_share['host'] != share['host']:
|
||||
# 1. Retrieve source and destination vservers from source and
|
||||
# destination shares
|
||||
dm_session = data_motion.DataMotionSession()
|
||||
@ -922,11 +938,24 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||
method = SERVER_MIGRATE_SVM_DR
|
||||
if (not src_client.is_svm_dr_supported()
|
||||
or not dest_client.is_svm_dr_supported()):
|
||||
msg = _("Cannot perform server migration because at least one of "
|
||||
msg = _("Cannot perform server migration because at leat one of "
|
||||
"the backends doesn't support SVM DR.")
|
||||
LOG.error(msg)
|
||||
return method, False
|
||||
|
||||
# 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 self.is_flexgroup_destination_host(host, dm_session):
|
||||
msg = _("Cannot perform server migration since a "
|
||||
"FlexGroup was encountered in share server to be "
|
||||
"migrated.")
|
||||
LOG.error(msg)
|
||||
return method, False
|
||||
|
||||
# Check capacity.
|
||||
server_total_size = (shares_request_spec.get('shares_size', 0) +
|
||||
shares_request_spec.get('snapshots_size', 0))
|
||||
@ -1175,6 +1204,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||
# requests.
|
||||
dst_client = data_motion.get_client_for_backend(dest_backend_name,
|
||||
vserver_name=None)
|
||||
|
||||
migration_method, compatibility = self._check_for_migration_support(
|
||||
src_client, dst_client, source_share_server, shares_request_spec,
|
||||
src_cluster_name, pools)
|
||||
|
@ -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_aggregates': 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_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_flexgroup', False):
|
||||
continue
|
||||
aggr_names.add(pool_info.get('netapp_aggregate'))
|
||||
|
||||
return list(aggr_names)
|
||||
|
||||
def _get_nodes_for_aggregates(self, aggr_names):
|
||||
|
@ -71,7 +71,8 @@ class NetAppBaseHelper(object):
|
||||
@abc.abstractmethod
|
||||
def create_share(self, share, share_name,
|
||||
clear_current_export_policy=True,
|
||||
ensure_share_already_exists=False, replica=False):
|
||||
ensure_share_already_exists=False, replica=False,
|
||||
is_flexgroup=False):
|
||||
"""Creates NAS share."""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -30,7 +30,8 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
||||
@na_utils.trace
|
||||
def create_share(self, share, share_name,
|
||||
clear_current_export_policy=True,
|
||||
ensure_share_already_exists=False, replica=False):
|
||||
ensure_share_already_exists=False, replica=False,
|
||||
is_flexgroup=False):
|
||||
"""Creates CIFS share if does not exist on Data ONTAP Vserver.
|
||||
|
||||
The new CIFS share has Everyone access, so it removes all access after
|
||||
@ -41,6 +42,7 @@ class NetAppCmodeCIFSHelper(base.NetAppBaseHelper):
|
||||
:param clear_current_export_policy: ignored, NFS only.
|
||||
:param ensure_share_already_exists: ensures that CIFS share exists.
|
||||
:param replica: it is a replica volume (DP type).
|
||||
:param is_flexgroup: whether the share is a FlexGroup or not.
|
||||
"""
|
||||
|
||||
cifs_exist = self._client.cifs_share_exists(share_name)
|
||||
|
@ -42,7 +42,8 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
|
||||
@na_utils.trace
|
||||
def create_share(self, share, share_name,
|
||||
clear_current_export_policy=True,
|
||||
ensure_share_already_exists=False, replica=False):
|
||||
ensure_share_already_exists=False, replica=False,
|
||||
is_flexgroup=False):
|
||||
"""Ensures the share export policy is set correctly.
|
||||
|
||||
The export policy must have the same name as the share. If it matches,
|
||||
@ -57,12 +58,18 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper):
|
||||
the check.
|
||||
:param ensure_share_already_exists: ignored, CIFS only.
|
||||
:param replica: it is a replica volume (DP type).
|
||||
:param is_flexgroup: whether the share is a FlexGroup or not.
|
||||
"""
|
||||
|
||||
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,48 @@ 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.BoolOpt('netapp_enable_flexgroup',
|
||||
default=False,
|
||||
help='Specify if the FlexGroup pool is enabled. When it is '
|
||||
'enabled, the driver will report a single pool '
|
||||
'representing all aggregates (ONTAP chooses on which the '
|
||||
'share will be allocated). If you want to Manila control '
|
||||
'the aggregate selection, you can configure its custom '
|
||||
'FlexGroup pools through netapp_flexgroup_pools option. '
|
||||
'The FlexGroup placement is done either by ONTAP or '
|
||||
'Manila, not both.'),
|
||||
cfg.MultiOpt('netapp_flexgroup_pools',
|
||||
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 times "
|
||||
"as you have FlexGroup pools. Each entry takes the "
|
||||
"dict config form: "
|
||||
"netapp_flexgroup_pools = "
|
||||
"<pool_name>: <aggr_name1> <aggr_name2> .."),
|
||||
cfg.BoolOpt('netapp_flexgroup_pool_only',
|
||||
default=False,
|
||||
help='Specify if the FlexVol pools must not be reported when '
|
||||
'the netapp_enable_flexgroup is enabled.'),
|
||||
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_flexgroup_aggregate_not_busy_timeout',
|
||||
min=60,
|
||||
default=360, # Default to six minutes
|
||||
help='Provisioning FlexGroup share requires that all of its '
|
||||
'aggregates to not be busy deploying another volume. So, '
|
||||
'sets time in seconds to retry to create the FlexGroup '
|
||||
'share.'),
|
||||
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 '
|
||||
'to not be busy with clones after splitting them.'), ]
|
||||
|
||||
netapp_cluster_opts = [
|
||||
cfg.StrOpt('netapp_vserver',
|
||||
|
@ -43,6 +43,15 @@ MIGRATION_STATE_READY_FOR_SOURCE_CLEANUP = 'ready_for_source_cleanup'
|
||||
MIGRATION_STATE_MIGRATE_COMPLETE = 'migrate_complete'
|
||||
MIGRATION_STATE_MIGRATE_PAUSED = 'migrate_paused'
|
||||
|
||||
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'
|
||||
|
||||
FLEXGROUP_DEFAULT_POOL_NAME = 'flexgroup_auto'
|
||||
|
||||
|
||||
def validate_driver_instantiation(**kwargs):
|
||||
"""Checks if a driver is instantiated other than by the unified driver.
|
||||
@ -123,6 +132,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 pools and if it is auto provisioned.
|
||||
|
||||
: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_pools 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 a repeated aggregate name in the '
|
||||
'FlexGroup pool %s definition. Ensure that the '
|
||||
'configuration option netapp_flexgroup_pools 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 an aggregate name in the FlexGroup pool '
|
||||
'%(pool)s that is not in the cluster: %(aggr)s. '
|
||||
'Ensure that the configuration option '
|
||||
'netapp_flexgroup_pools 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_pools '
|
||||
'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'
|
||||
@ -120,6 +122,8 @@ FPOLICY_EXT_TO_INCLUDE_LIST = ['avi']
|
||||
FPOLICY_EXT_TO_EXCLUDE = 'jpg,mp3'
|
||||
FPOLICY_EXT_TO_EXCLUDE_LIST = ['jpg', 'mp3']
|
||||
|
||||
JOB_ID = 123
|
||||
JOB_STATE = 'success'
|
||||
|
||||
NETWORK_INTERFACES = [{
|
||||
'interface_name': 'fake_interface',
|
||||
@ -2057,6 +2061,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>
|
||||
@ -2228,6 +2252,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>
|
||||
@ -2245,6 +2270,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("""
|
||||
@ -2258,6 +2318,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>
|
||||
@ -2271,6 +2332,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'
|
||||
@ -3097,3 +3159,81 @@ FAKE_MIGRATION_JOB_SUCCESS = {
|
||||
"state": "migrate_complete",
|
||||
"uuid": "4ea7a442-86d1-11e0-ae1c-123478563412"
|
||||
}
|
||||
|
||||
|
||||
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>
|
||||
""")
|
||||
|
||||
JOB_GET_STATE_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<num-records>1</num-records>
|
||||
<attributes-list>
|
||||
<job-info>
|
||||
<job-state>%(state)s</job-state>
|
||||
</job-info>
|
||||
</attributes-list>
|
||||
</results>
|
||||
""" % {
|
||||
'state': JOB_STATE,
|
||||
})
|
||||
|
||||
JOB_GET_STATE_NOT_UNIQUE_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<num-records>1</num-records>
|
||||
<attributes-list>
|
||||
<job-info>
|
||||
<job-state>%(state)s</job-state>
|
||||
</job-info>
|
||||
<job-info>
|
||||
<job-state>%(state)s</job-state>
|
||||
</job-info>
|
||||
</attributes-list>
|
||||
</results>
|
||||
""" % {
|
||||
'state': JOB_STATE,
|
||||
})
|
||||
|
@ -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
|
||||
|
||||
@ -317,9 +318,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
args3['tag'] = 'next_tag_2'
|
||||
|
||||
mock_send_request.assert_has_calls([
|
||||
mock.call('storage-disk-get-iter', args1),
|
||||
mock.call('storage-disk-get-iter', args2),
|
||||
mock.call('storage-disk-get-iter', args3),
|
||||
mock.call('storage-disk-get-iter', args1, enable_tunneling=True),
|
||||
mock.call('storage-disk-get-iter', args2, enable_tunneling=True),
|
||||
mock.call('storage-disk-get-iter', args3, enable_tunneling=True),
|
||||
])
|
||||
|
||||
def test_send_iter_request_single_page(self):
|
||||
@ -348,7 +349,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
args['max-records'] = 10
|
||||
|
||||
mock_send_request.assert_has_calls([
|
||||
mock.call('storage-disk-get-iter', args),
|
||||
mock.call('storage-disk-get-iter', args, enable_tunneling=True),
|
||||
])
|
||||
|
||||
def test_send_iter_request_not_found(self):
|
||||
@ -366,7 +367,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
args = {'max-records': client_cmode.DEFAULT_MAX_PAGE_LENGTH}
|
||||
|
||||
mock_send_request.assert_has_calls([
|
||||
mock.call('storage-disk-get-iter', args),
|
||||
mock.call('storage-disk-get-iter', args, enable_tunneling=True),
|
||||
])
|
||||
|
||||
@ddt.data(fake.INVALID_GET_ITER_RESPONSE_NO_ATTRIBUTES,
|
||||
@ -3086,129 +3087,155 @@ 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()
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_create_volume_async(self, auto_provisioned):
|
||||
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,
|
||||
auto_provisioned=auto_provisioned)
|
||||
|
||||
volume_create_args = {
|
||||
'size': 1073741824,
|
||||
'volume-name': fake.SHARE_NAME,
|
||||
}
|
||||
if auto_provisioned:
|
||||
volume_create_args['auto-provision-as'] = 'flexgroup'
|
||||
else:
|
||||
volume_create_args['aggr-list'] = [
|
||||
{'aggr-name': fake.SHARE_AGGREGATE_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):
|
||||
|
||||
@ -3349,6 +3376,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)
|
||||
@ -3443,19 +3517,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,
|
||||
},
|
||||
},
|
||||
@ -3473,10 +3551,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))
|
||||
@ -3550,21 +3637,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]
|
||||
@ -3578,33 +3674,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):
|
||||
|
||||
@ -3865,10 +3994,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))
|
||||
@ -3886,6 +4017,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
'desired-attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'aggr-list': {
|
||||
'aggr-name': None,
|
||||
},
|
||||
'containing-aggregate-name': None,
|
||||
'name': None
|
||||
}
|
||||
@ -3895,7 +4029,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):
|
||||
|
||||
@ -3954,11 +4091,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': {
|
||||
@ -3982,11 +4115,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)
|
||||
|
||||
@ -3998,18 +4127,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))
|
||||
@ -4027,12 +4155,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,
|
||||
@ -4045,7 +4177,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',
|
||||
@ -4053,6 +4186,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)])
|
||||
@ -4078,12 +4214,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,
|
||||
@ -4097,6 +4237,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',
|
||||
@ -4104,6 +4245,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)])
|
||||
@ -4148,31 +4290,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)])
|
||||
@ -4196,22 +4327,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,
|
||||
},
|
||||
},
|
||||
@ -4219,6 +4354,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,
|
||||
@ -4235,8 +4373,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',
|
||||
@ -6144,14 +6290,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
|
||||
@ -6167,14 +6313,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)])
|
||||
@ -6187,7 +6335,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):
|
||||
@ -6200,7 +6349,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'
|
||||
}
|
||||
@ -6295,6 +6444,7 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
self.mock_object(self.client, 'send_request')
|
||||
self.mock_object(self.client, 'get_snapmirror_destinations',
|
||||
mock.Mock(return_value=snapmirror_destinations_list))
|
||||
self.mock_object(self.client, '_ensure_snapmirror_v2')
|
||||
|
||||
self.client.release_snapmirror_vol(
|
||||
fake.SM_SOURCE_VSERVER, fake.SM_SOURCE_VOLUME,
|
||||
@ -6329,7 +6479,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
fake.SM_DEST_VSERVER, fake.SM_DEST_VOLUME)
|
||||
|
||||
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)
|
||||
@ -8518,3 +8670,189 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
fake.FAKE_JOB_ID
|
||||
)
|
||||
self.client.get_job.assert_called_once_with(fake.FAKE_JOB_ID)
|
||||
|
||||
@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)
|
||||
|
||||
def test_get_job_state(self):
|
||||
|
||||
api_response = netapp_api.NaElement(fake.JOB_GET_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=True))
|
||||
|
||||
job_state_res = self.client.get_job_state(fake.JOB_ID)
|
||||
|
||||
job_get_iter_args = {
|
||||
'query': {
|
||||
'job-info': {
|
||||
'job-id': fake.JOB_ID,
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'job-info': {
|
||||
'job-state': None,
|
||||
},
|
||||
},
|
||||
}
|
||||
mock_send_iter_request.assert_called_once_with(
|
||||
'job-get-iter', job_get_iter_args, enable_tunneling=False)
|
||||
mock_has_record.assert_called_once_with(api_response)
|
||||
self.assertEqual(fake.JOB_STATE, job_state_res)
|
||||
|
||||
def test_get_job_state_not_found(self):
|
||||
|
||||
api_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'_has_records',
|
||||
mock.Mock(return_value=False))
|
||||
self.mock_object(self.client,
|
||||
'send_iter_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.client.get_job_state,
|
||||
fake.JOB_ID)
|
||||
|
||||
def test_get_job_state_not_unique(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.JOB_GET_STATE_NOT_UNIQUE_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'_has_records',
|
||||
mock.Mock(return_value=True))
|
||||
self.mock_object(self.client,
|
||||
'send_iter_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.client.get_job_state,
|
||||
fake.JOB_ID)
|
||||
|
@ -26,6 +26,7 @@ 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
|
||||
@ -228,11 +229,12 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
mock.Mock(return_value=mock_backend_config))
|
||||
|
||||
self.dm_session.create_snapmirror(self.fake_src_share,
|
||||
self.fake_dest_share, mount=mount)
|
||||
self.fake_dest_share,
|
||||
'data_protection', mount=mount)
|
||||
|
||||
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,
|
||||
@ -285,6 +287,12 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(side_effect=[mock_dest_client,
|
||||
mock_src_client]))
|
||||
mock_backend_config = na_fakes.create_configuration()
|
||||
mock_backend_config.netapp_snapmirror_release_timeout = 30
|
||||
self.mock_object(data_motion, 'get_backend_configuration',
|
||||
mock.Mock(return_value=mock_backend_config))
|
||||
mock_wait_for_snapmirror_release_vol = self.mock_object(
|
||||
self.dm_session, 'wait_for_snapmirror_release_vol')
|
||||
|
||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||
self.fake_dest_share)
|
||||
@ -297,9 +305,10 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
)
|
||||
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
mock_wait_for_snapmirror_release_vol.assert_called_once_with(
|
||||
self.source_vserver, self.dest_vserver, self.fake_src_vol_name,
|
||||
self.fake_dest_vol_name, False, mock_src_client,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
@ddt.data(True, False)
|
||||
@ -336,6 +345,12 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(side_effect=[mock_dest_client,
|
||||
mock_src_client]))
|
||||
mock_backend_config = na_fakes.create_configuration()
|
||||
mock_backend_config.netapp_snapmirror_release_timeout = 30
|
||||
self.mock_object(data_motion, 'get_backend_configuration',
|
||||
mock.Mock(return_value=mock_backend_config))
|
||||
mock_wait_for_snapmirror_release_vol = self.mock_object(
|
||||
self.dm_session, 'wait_for_snapmirror_release_vol')
|
||||
|
||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||
self.fake_dest_share)
|
||||
@ -348,9 +363,10 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
)
|
||||
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
mock_wait_for_snapmirror_release_vol.assert_called_once_with(
|
||||
self.source_vserver, self.dest_vserver, self.fake_src_vol_name,
|
||||
self.fake_dest_vol_name, False, mock_src_client,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
def test_delete_snapmirror_svm_does_not_exist(self):
|
||||
@ -387,6 +403,12 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(side_effect=[mock_dest_client,
|
||||
mock_src_client]))
|
||||
mock_backend_config = na_fakes.create_configuration()
|
||||
mock_backend_config.netapp_snapmirror_release_timeout = 30
|
||||
self.mock_object(data_motion, 'get_backend_configuration',
|
||||
mock.Mock(return_value=mock_backend_config))
|
||||
mock_wait_for_snapmirror_release_vol = self.mock_object(
|
||||
self.dm_session, 'wait_for_snapmirror_release_vol')
|
||||
|
||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||
self.fake_dest_share)
|
||||
@ -399,9 +421,10 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
)
|
||||
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
mock_wait_for_snapmirror_release_vol.assert_called_once_with(
|
||||
self.source_vserver, self.dest_vserver, self.fake_src_vol_name,
|
||||
self.fake_dest_vol_name, False, mock_src_client,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
def test_delete_snapmirror_svm_error_deleting(self):
|
||||
@ -429,38 +452,14 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
timeout=mock_backend_config.netapp_snapmirror_release_timeout
|
||||
)
|
||||
|
||||
def test_delete_snapmirror_error_releasing(self):
|
||||
"""Ensure delete succeeds when the snapmirror does not exist."""
|
||||
mock_src_client = mock.Mock()
|
||||
mock_dest_client = mock.Mock()
|
||||
mock_src_client.release_snapmirror_vol.side_effect = (
|
||||
netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(side_effect=[mock_dest_client,
|
||||
mock_src_client]))
|
||||
|
||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||
self.fake_dest_share)
|
||||
|
||||
mock_dest_client.abort_snapmirror_vol.assert_called_once_with(
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name, clear_checkpoint=False
|
||||
)
|
||||
mock_dest_client.delete_snapmirror_vol.assert_called_once_with(
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
)
|
||||
mock_src_client.release_snapmirror_vol.assert_called_once_with(
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
)
|
||||
|
||||
def test_delete_snapmirror_without_release(self):
|
||||
mock_src_client = mock.Mock()
|
||||
mock_dest_client = mock.Mock()
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(side_effect=[mock_dest_client,
|
||||
mock_src_client]))
|
||||
mock_wait_for_snapmirror_release_vol = self.mock_object(
|
||||
self.dm_session, 'wait_for_snapmirror_release_vol')
|
||||
|
||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||
self.fake_dest_share,
|
||||
@ -474,14 +473,15 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
)
|
||||
self.assertFalse(mock_src_client.release_snapmirror_vol.called)
|
||||
self.assertFalse(mock_wait_for_snapmirror_release_vol.called)
|
||||
|
||||
def test_delete_snapmirror_source_unreachable(self):
|
||||
mock_src_client = mock.Mock()
|
||||
mock_dest_client = mock.Mock()
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(side_effect=[mock_dest_client,
|
||||
Exception]))
|
||||
mock_wait_for_snapmirror_release_vol = self.mock_object(
|
||||
self.dm_session, 'wait_for_snapmirror_release_vol')
|
||||
|
||||
self.dm_session.delete_snapmirror(self.fake_src_share,
|
||||
self.fake_dest_share)
|
||||
@ -494,8 +494,7 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
mock.ANY, self.fake_src_vol_name, mock.ANY,
|
||||
self.fake_dest_vol_name
|
||||
)
|
||||
|
||||
self.assertFalse(mock_src_client.release_snapmirror_vol.called)
|
||||
self.assertFalse(mock_wait_for_snapmirror_release_vol.called)
|
||||
|
||||
def test_break_snapmirror(self):
|
||||
self.mock_object(self.dm_session, 'quiesce_then_abort')
|
||||
@ -671,6 +670,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,
|
||||
@ -680,12 +681,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(
|
||||
@ -713,6 +716,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,
|
||||
@ -731,12 +736,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,
|
||||
@ -1054,13 +1061,8 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
dest_vserver=fake.VSERVER2),
|
||||
mock.call(source_vserver=fake.VSERVER1,
|
||||
dest_vserver=fake.VSERVER2)])
|
||||
if release_snapmirror_ret.side_effect is None:
|
||||
src_mock_client.release_snapmirror_svm.assert_called_once_with(
|
||||
fake.VSERVER1, fake.VSERVER2)
|
||||
else:
|
||||
src_mock_client.release_snapmirror_svm.assert_called_once_with(
|
||||
fake.VSERVER1, fake.VSERVER2
|
||||
)
|
||||
src_mock_client.release_snapmirror_svm.assert_called_once_with(
|
||||
fake.VSERVER1, fake.VSERVER2)
|
||||
|
||||
def test_wait_for_snapmirror_release_svm_timeout(self):
|
||||
src_mock_client = mock.Mock()
|
||||
@ -1124,3 +1126,56 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
|
||||
mock_client.mount_volume.assert_called_once_with(fake.SHARE_NAME)
|
||||
mock_warning_log.assert_not_called()
|
||||
|
||||
@ddt.data(mock.Mock(),
|
||||
mock.Mock(side_effect=netapp_api.NaApiError(
|
||||
code=netapp_api.EOBJECTNOTFOUND)))
|
||||
def test_wait_for_snapmirror_release_vol(self, release_snapmirror_ret):
|
||||
src_mock_client = mock.Mock()
|
||||
get_snapmirrors_mock = self.mock_object(
|
||||
src_mock_client, 'get_snapmirror_destinations',
|
||||
mock.Mock(side_effect=[['fake_snapmirror'], []]))
|
||||
self.mock_object(src_mock_client, 'release_snapmirror_vol',
|
||||
release_snapmirror_ret)
|
||||
|
||||
self.dm_session.wait_for_snapmirror_release_vol(fake.VSERVER1,
|
||||
fake.VSERVER2,
|
||||
fake.SHARE_NAME,
|
||||
fake.SHARE_NAME2,
|
||||
False,
|
||||
src_mock_client,
|
||||
timeout=20)
|
||||
get_snapmirrors_mock.assert_has_calls([
|
||||
mock.call(source_vserver=fake.VSERVER1,
|
||||
dest_vserver=fake.VSERVER2,
|
||||
source_volume=fake.SHARE_NAME,
|
||||
dest_volume=fake.SHARE_NAME2),
|
||||
mock.call(source_vserver=fake.VSERVER1,
|
||||
dest_vserver=fake.VSERVER2,
|
||||
source_volume=fake.SHARE_NAME,
|
||||
dest_volume=fake.SHARE_NAME2)])
|
||||
src_mock_client.release_snapmirror_vol.assert_called_once_with(
|
||||
fake.VSERVER1, fake.SHARE_NAME, fake.VSERVER2, fake.SHARE_NAME2,
|
||||
relationship_info_only=False)
|
||||
|
||||
def test_wait_for_snapmirror_release_vol_timeout(self):
|
||||
src_mock_client = mock.Mock()
|
||||
get_snapmirrors_mock = self.mock_object(
|
||||
src_mock_client, 'get_snapmirror_destinations',
|
||||
mock.Mock(side_effect=[['fake_snapmirror']]))
|
||||
self.mock_object(src_mock_client, 'release_snapmirror_vol')
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.dm_session.wait_for_snapmirror_release_vol,
|
||||
fake.VSERVER1, fake.VSERVER2, fake.SHARE_NAME,
|
||||
fake.SHARE_NAME2, False, src_mock_client,
|
||||
timeout=10)
|
||||
|
||||
get_snapmirrors_mock.assert_has_calls([
|
||||
mock.call(source_vserver=fake.VSERVER1,
|
||||
dest_vserver=fake.VSERVER2,
|
||||
source_volume=fake.SHARE_NAME,
|
||||
dest_volume=fake.SHARE_NAME2)])
|
||||
src_mock_client.release_snapmirror_vol.assert_called_once_with(
|
||||
fake.VSERVER1, fake.SHARE_NAME, fake.VSERVER2, fake.SHARE_NAME2,
|
||||
relationship_info_only=False)
|
||||
|
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_aggregates': {'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))
|
||||
@ -609,6 +688,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
vserver_name, 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_not_called()
|
||||
|
||||
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))
|
||||
@ -1264,7 +1347,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.fake_vserver, self.fake_new_vserver_name
|
||||
)
|
||||
|
||||
def test_create_share_from_snaphot(self):
|
||||
def test_create_share_from_snapshot(self):
|
||||
fake_parent_share = copy.deepcopy(fake.SHARE)
|
||||
fake_parent_share['id'] = fake.SHARE_ID2
|
||||
mock_create_from_snap = self.mock_object(
|
||||
@ -1280,6 +1363,28 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
parent_share=fake_parent_share
|
||||
)
|
||||
|
||||
def test_create_share_from_snapshot_group(self):
|
||||
share = copy.deepcopy(fake.SHARE)
|
||||
share['source_share_group_snapshot_member_id'] = (
|
||||
fake.CG_SNAPSHOT_MEMBER_ID1)
|
||||
fake_parent_share = copy.deepcopy(fake.SHARE)
|
||||
fake_parent_share['id'] = fake.SHARE_ID2
|
||||
fake_parent_share['host'] = fake.MANILA_HOST_NAME_2
|
||||
mock_create_from_snap = self.mock_object(
|
||||
lib_base.NetAppCmodeFileStorageLibrary,
|
||||
'create_share_from_snapshot')
|
||||
mock_data_session = self.mock_object(data_motion, 'DataMotionSession')
|
||||
|
||||
self.library.create_share_from_snapshot(
|
||||
None, share, fake.SNAPSHOT, share_server=fake.SHARE_SERVER,
|
||||
parent_share=fake_parent_share)
|
||||
|
||||
mock_create_from_snap.assert_called_once_with(
|
||||
None, share, fake.SNAPSHOT, share_server=fake.SHARE_SERVER,
|
||||
parent_share=fake_parent_share
|
||||
)
|
||||
mock_data_session.assert_not_called()
|
||||
|
||||
@ddt.data(
|
||||
{'src_cluster_name': fake.CLUSTER_NAME,
|
||||
'dest_cluster_name': fake.CLUSTER_NAME, 'has_vserver_peers': None},
|
||||
@ -1776,13 +1881,19 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
|
||||
def _init_mocks_for_svm_dr_check_compatibility(
|
||||
self, src_svm_dr_supported=True, dest_svm_dr_supported=True,
|
||||
check_capacity_result=True):
|
||||
check_capacity_result=True, flexgroup_support=False,
|
||||
is_flexgroup_destination_host=False):
|
||||
self.mock_object(self.mock_src_client, 'is_svm_dr_supported',
|
||||
mock.Mock(return_value=src_svm_dr_supported))
|
||||
self.mock_object(self.mock_dest_client, 'is_svm_dr_supported',
|
||||
mock.Mock(return_value=dest_svm_dr_supported))
|
||||
self.mock_object(self.library, '_check_capacity_compatibility',
|
||||
mock.Mock(return_value=check_capacity_result))
|
||||
self.mock_object(self.mock_src_client, 'is_flexgroup_supported',
|
||||
mock.Mock(return_value=flexgroup_support))
|
||||
self.mock_object(data_motion, 'DataMotionSession')
|
||||
self.mock_object(self.library, 'is_flexgroup_destination_host',
|
||||
mock.Mock(return_value=is_flexgroup_destination_host))
|
||||
|
||||
def _configure_mocks_share_server_migration_check_compatibility(
|
||||
self, have_cluster_creds=True,
|
||||
@ -1790,6 +1901,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
dest_cluster_name=fake.CLUSTER_NAME_2,
|
||||
pools=fake.POOLS, is_svm_dr=True, failure_scenario=False):
|
||||
migration_method = 'svm_dr' if is_svm_dr else 'svm_migrate'
|
||||
|
||||
self.library._have_cluster_creds = have_cluster_creds
|
||||
self.mock_object(self.library, '_get_vserver',
|
||||
mock.Mock(return_value=(self.fake_src_vserver,
|
||||
@ -1841,34 +1953,49 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
@ddt.data(
|
||||
{'src_svm_dr_supported': False,
|
||||
'dest_svm_dr_supported': False,
|
||||
'check_capacity_result': False
|
||||
'check_capacity_result': False,
|
||||
'is_flexgroup_destination_host': False,
|
||||
},
|
||||
{'src_svm_dr_supported': True,
|
||||
'dest_svm_dr_supported': True,
|
||||
'check_capacity_result': False
|
||||
'check_capacity_result': False,
|
||||
'is_flexgroup_destination_host': False,
|
||||
},
|
||||
{'src_svm_dr_supported': True,
|
||||
'dest_svm_dr_supported': True,
|
||||
'check_capacity_result': True,
|
||||
'is_flexgroup_destination_host': True,
|
||||
},
|
||||
)
|
||||
@ddt.unpack
|
||||
def test__check_compatibility_svm_dr_not_compatible(
|
||||
self, src_svm_dr_supported, dest_svm_dr_supported,
|
||||
check_capacity_result):
|
||||
check_capacity_result, is_flexgroup_destination_host):
|
||||
server_total_size = (fake.SHARE_REQ_SPEC.get('shares_size', 0) +
|
||||
fake.SHARE_REQ_SPEC.get('snapshots_size', 0))
|
||||
|
||||
self._init_mocks_for_svm_dr_check_compatibility(
|
||||
src_svm_dr_supported=src_svm_dr_supported,
|
||||
dest_svm_dr_supported=dest_svm_dr_supported,
|
||||
check_capacity_result=check_capacity_result)
|
||||
check_capacity_result=check_capacity_result,
|
||||
flexgroup_support=is_flexgroup_destination_host,
|
||||
is_flexgroup_destination_host=is_flexgroup_destination_host)
|
||||
|
||||
method, result = self.library._check_compatibility_using_svm_dr(
|
||||
self.mock_src_client, self.mock_dest_client, fake.SHARE_REQ_SPEC,
|
||||
fake.POOLS)
|
||||
self.mock_src_client, self.mock_dest_client,
|
||||
fake.SERVER_MIGRATION_REQUEST_SPEC, fake.POOLS)
|
||||
|
||||
self.assertEqual(method, 'svm_dr')
|
||||
self.assertEqual(result, False)
|
||||
self.assertTrue(self.mock_src_client.is_svm_dr_supported.called)
|
||||
|
||||
if check_capacity_result and not src_svm_dr_supported:
|
||||
if (src_svm_dr_supported and dest_svm_dr_supported and
|
||||
is_flexgroup_destination_host):
|
||||
self.assertTrue(self.mock_src_client.is_flexgroup_supported.called)
|
||||
self.assertTrue(self.library.is_flexgroup_destination_host.called)
|
||||
|
||||
if (check_capacity_result and not is_flexgroup_destination_host and
|
||||
not src_svm_dr_supported):
|
||||
self.assertFalse(self.mock_dest_client.is_svm_dr_supported.called)
|
||||
self.library._check_capacity_compatibility.assert_called_once_with(
|
||||
fake.POOLS, True, server_total_size)
|
||||
|
@ -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_aggregates': {'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_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'
|
||||
@ -37,6 +38,7 @@ VOLUME_NAME_TEMPLATE = 'share_%(share_id)s'
|
||||
VSERVER_NAME_TEMPLATE = 'os_%s'
|
||||
AGGREGATE_NAME_SEARCH_PATTERN = '(.*)'
|
||||
SHARE_NAME = 'share_7cf7c200_d3af_4e05_b87e_9167c95dfcad'
|
||||
SHARE_NAME2 = 'share_d24e7257_124e_4fb6_b05b_d384f660bc85'
|
||||
SHARE_INSTANCE_NAME = 'share_d24e7257_124e_4fb6_b05b_d384f660bc85'
|
||||
FLEXVOL_NAME = 'fake_volume'
|
||||
JUNCTION_PATH = '/%s' % FLEXVOL_NAME
|
||||
@ -103,6 +105,9 @@ FPOLICY_EXT_TO_INCLUDE_LIST = ['avi']
|
||||
FPOLICY_EXT_TO_EXCLUDE = 'jpg,mp3'
|
||||
FPOLICY_EXT_TO_EXCLUDE_LIST = ['jpg', 'mp3']
|
||||
|
||||
JOB_ID = '123'
|
||||
JOB_STATE = 'success'
|
||||
|
||||
CLIENT_KWARGS = {
|
||||
'username': 'admin',
|
||||
'trace': False,
|
||||
@ -132,6 +137,7 @@ SHARE = {
|
||||
'status': constants.STATUS_AVAILABLE,
|
||||
'share_server': None,
|
||||
'encrypt': False,
|
||||
'share_id': SHARE_ID,
|
||||
}
|
||||
|
||||
SHARE_INSTANCE = {
|
||||
@ -156,6 +162,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)
|
||||
@ -863,6 +870,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': FLEXGROUP_POOL_NAME,
|
||||
'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_flexgroup': True,
|
||||
}
|
||||
|
||||
FLEXGROUP_AGGR_SET = set(FLEXGROUP_POOL_OPT[FLEXGROUP_POOL_NAME])
|
||||
|
||||
AGGREGATE_CAPACITIES_VSERVER_CREDS = {
|
||||
AGGREGATES[0]: {
|
||||
'available': 1181116007, # 1.1 GB
|
||||
@ -878,21 +925,38 @@ SSC_INFO = {
|
||||
'netapp_disk_type': 'FCAL',
|
||||
'netapp_hybrid_aggregate': 'false',
|
||||
'netapp_aggregate': AGGREGATES[0],
|
||||
'netapp_flexgroup': False,
|
||||
},
|
||||
AGGREGATES[1]: {
|
||||
'netapp_raid_type': 'raid_dp',
|
||||
'netapp_disk_type': ['SATA', 'SSD'],
|
||||
'netapp_hybrid_aggregate': 'true',
|
||||
'netapp_aggregate': AGGREGATES[1],
|
||||
'netapp_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_flexgroup': False,
|
||||
},
|
||||
AGGREGATES[1]: {
|
||||
'netapp_aggregate': AGGREGATES[1],
|
||||
'netapp_flexgroup': False,
|
||||
}
|
||||
}
|
||||
|
||||
@ -921,6 +985,7 @@ POOLS = [
|
||||
'revert_to_snapshot_support': True,
|
||||
'qos': True,
|
||||
'security_service_update_support': True,
|
||||
'netapp_flexgroup': False,
|
||||
},
|
||||
{
|
||||
'pool_name': AGGREGATES[1],
|
||||
@ -946,12 +1011,15 @@ POOLS = [
|
||||
'revert_to_snapshot_support': True,
|
||||
'qos': True,
|
||||
'security_service_update_support': True,
|
||||
'netapp_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,
|
||||
@ -964,13 +1032,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_flexgroup': False,
|
||||
},
|
||||
{
|
||||
'pool_name': AGGREGATES[1],
|
||||
@ -986,13 +1053,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_flexgroup': False,
|
||||
},
|
||||
]
|
||||
|
||||
@ -1730,3 +1796,7 @@ def get_network_info(user_network_allocation, admin_network_allocation):
|
||||
net_info['admin_network_allocations'] = admin_network_allocation
|
||||
|
||||
return net_info
|
||||
|
||||
|
||||
def fake_get_filter_function(pool=None):
|
||||
return pool if pool else 'filter'
|
||||
|
@ -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,40 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The NetApp driver has been working with FlexVol ONTAP volumes.
|
||||
The driver does not support scaling FlexVol volumes higher than
|
||||
100 TiB, which was a theoretical limit for the large namespace that
|
||||
these containers were meant to handle. ONTAP's Flexgroup volumes
|
||||
eliminate such limitations. So, added the support for provisioning
|
||||
share as FlexGroup in the NetApp driver.
|
||||
|
||||
The FlexGroup provision is enabled by new option
|
||||
``netapp_enable_flexgroup``, which will make the driver report a single
|
||||
pool represeting all aggregates. The selection on which aggregates the
|
||||
FlexGroup share will reside is up to ONTAP. If the administrator desires
|
||||
to control that selection through Manila scheduler, the configuration
|
||||
option ``netapp_flexgroup_pools`` can be used to tune the storage pool
|
||||
layout.
|
||||
|
||||
When enabling FlexGroup, the FlexVol pools continue enabled by default.
|
||||
For having only FlexGroup, the new option ``netapp_flexgroup_pool_only``
|
||||
must be set to `True`.
|
||||
|
||||
Now, each NetApp pool will report the capability: `netapp_flexgroup` informing
|
||||
which type of share resides there (FlexGroup or FlexVol).
|
||||
|
||||
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;
|
||||
|
||||
FlexGroup feature requires ONTAP version 9.8 or newer.
|
||||
Replication with more than one non-active replica per share requires
|
||||
ONTAP 9.9.1 or newer.
|
Loading…
Reference in New Issue
Block a user