NetApp cDOT: Add support for QoS/throughput ceilings
ONTAP supports assigning QoS policy groups to storage objects and workloads. [1] Expose this functionality through the ONTAP manila drivers (DHSS=True/False, NFS, CIFS). The drivers will set the capability "qos" to True if the configured credentials have access to create qos policy groups on the configured ONTAP backend. When 'qos' extra-spec is set in share types, scoped extra-specs can be used to specify QoS ceiling values in iops or bps. The drivers support the following QoS specs: 'netapp:maxiops', 'netapp:maxiopspergib', 'netapp:maxbps', 'netapp:maxbpspergib'. Policies are created on-demand and manipulated as and when shares are manipulated through manila. [1] http://docs.netapp.com/ontap-9/index.jsp?topic=%2Fcom.netapp.doc.pow-perf-mon%2FGUID-38357C43-FB36-419D-B31F-6FD75B47254D.html Implements: blueprint netapp-cdot-qos Change-Id: I6f82c012ea60cfb1e9f82a696e2346ee95c60df3
This commit is contained in:
parent
96a037fb67
commit
10395c9aea
@ -220,7 +220,7 @@ More information: :ref:`capabilities_and_extra_specs`
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
|
||||
| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
|
||||
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J | O | \- |
|
||||
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | P | J | O | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
|
||||
| EMC VMAX | O | \- | \- | \- | \- | O | \- | O | \- | \- |
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
|
||||
|
@ -1422,7 +1422,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
thin_provisioned=False, snapshot_policy=None,
|
||||
language=None, dedup_enabled=False,
|
||||
compression_enabled=False, max_files=None,
|
||||
snapshot_reserve=None, volume_type='rw', **options):
|
||||
snapshot_reserve=None, volume_type='rw',
|
||||
qos_policy_group=None, **options):
|
||||
|
||||
"""Creates a volume."""
|
||||
api_args = {
|
||||
@ -1442,6 +1443,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
if snapshot_reserve is not None:
|
||||
api_args['percentage-snapshot-reserve'] = six.text_type(
|
||||
snapshot_reserve)
|
||||
if qos_policy_group is not None:
|
||||
api_args['qos-policy-group-name'] = qos_policy_group
|
||||
self.send_request('volume-create', api_args)
|
||||
|
||||
# cDOT compression requires that deduplication be enabled.
|
||||
@ -1573,11 +1576,12 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
self.send_request('volume-rename', api_args)
|
||||
|
||||
@na_utils.trace
|
||||
def manage_volume(self, aggregate_name, volume_name,
|
||||
def modify_volume(self, aggregate_name, volume_name,
|
||||
thin_provisioned=False, snapshot_policy=None,
|
||||
language=None, dedup_enabled=False,
|
||||
compression_enabled=False, max_files=None, **options):
|
||||
"""Update volume as needed to bring under management as a share."""
|
||||
compression_enabled=False, max_files=None,
|
||||
qos_policy_group=None, **options):
|
||||
"""Update backend volume for a share as necessary."""
|
||||
api_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
@ -1594,7 +1598,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'volume-snapshot-attributes': {},
|
||||
'volume-space-attributes': {
|
||||
'space-guarantee': ('none' if thin_provisioned else
|
||||
'volume')
|
||||
'volume'),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1609,6 +1613,11 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
api_args['attributes']['volume-attributes'][
|
||||
'volume-snapshot-attributes'][
|
||||
'snapshot-policy'] = snapshot_policy
|
||||
if qos_policy_group:
|
||||
api_args['attributes']['volume-attributes'][
|
||||
'volume-qos-attributes'] = {
|
||||
'policy-group-name': qos_policy_group,
|
||||
}
|
||||
|
||||
self.send_request('volume-modify-iter', api_args)
|
||||
|
||||
@ -1766,9 +1775,12 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'type': None,
|
||||
'style': None,
|
||||
},
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': None,
|
||||
},
|
||||
'volume-space-attributes': {
|
||||
'size': None,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1789,6 +1801,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
|
||||
volume_id_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-id-attributes') or netapp_api.NaElement('none')
|
||||
volume_qos_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-qos-attributes') or netapp_api.NaElement('none')
|
||||
volume_space_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-space-attributes') or netapp_api.NaElement('none')
|
||||
|
||||
@ -1803,6 +1817,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'type': volume_id_attributes.get_child_content('type'),
|
||||
'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')
|
||||
}
|
||||
return volume
|
||||
|
||||
@ -1883,9 +1899,12 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'style': None,
|
||||
'owning-vserver-name': None,
|
||||
},
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': None,
|
||||
},
|
||||
'volume-space-attributes': {
|
||||
'size': None,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1899,6 +1918,8 @@ 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_qos_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-qos-attributes') or netapp_api.NaElement('none')
|
||||
volume_space_attributes = volume_attributes.get_child_by_name(
|
||||
'volume-space-attributes') or netapp_api.NaElement('none')
|
||||
|
||||
@ -1913,12 +1934,16 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'owning-vserver-name': volume_id_attributes.get_child_content(
|
||||
'owning-vserver-name'),
|
||||
'size': volume_space_attributes.get_child_content('size'),
|
||||
'qos-policy-group-name': volume_qos_attributes.get_child_content(
|
||||
'policy-group-name')
|
||||
|
||||
}
|
||||
return volume
|
||||
|
||||
@na_utils.trace
|
||||
def create_volume_clone(self, volume_name, parent_volume_name,
|
||||
parent_snapshot_name=None, split=False, **options):
|
||||
parent_snapshot_name=None, split=False,
|
||||
qos_policy_group=None, **options):
|
||||
"""Clones a volume."""
|
||||
api_args = {
|
||||
'volume': volume_name,
|
||||
@ -1926,6 +1951,10 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'parent-snapshot': parent_snapshot_name,
|
||||
'junction-path': '/%s' % volume_name,
|
||||
}
|
||||
|
||||
if qos_policy_group is not None:
|
||||
api_args['qos-policy-group-name'] = qos_policy_group
|
||||
|
||||
self.send_request('volume-clone-create', api_args)
|
||||
|
||||
if split:
|
||||
@ -2515,6 +2544,27 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
}
|
||||
self.send_request('volume-modify-iter', api_args)
|
||||
|
||||
@na_utils.trace
|
||||
def set_qos_policy_group_for_volume(self, volume_name,
|
||||
qos_policy_group_name):
|
||||
api_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'name': volume_name,
|
||||
},
|
||||
},
|
||||
},
|
||||
'attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': qos_policy_group_name,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
self.send_request('volume-modify-iter', api_args)
|
||||
|
||||
@na_utils.trace
|
||||
def get_nfs_export_policy_for_volume(self, volume_name):
|
||||
"""Get the name of the export policy for a volume."""
|
||||
@ -3443,3 +3493,132 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
||||
'phase': volume_move_info.get_child_content('phase'),
|
||||
}
|
||||
return status_info
|
||||
|
||||
@na_utils.trace
|
||||
def qos_policy_group_exists(self, qos_policy_group_name):
|
||||
"""Checks if a QoS policy group exists."""
|
||||
try:
|
||||
self.qos_policy_group_get(qos_policy_group_name)
|
||||
except exception.NetAppException:
|
||||
return False
|
||||
return True
|
||||
|
||||
@na_utils.trace
|
||||
def qos_policy_group_get(self, qos_policy_group_name):
|
||||
"""Checks if a QoS policy group exists."""
|
||||
api_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': qos_policy_group_name,
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': None,
|
||||
'vserver': None,
|
||||
'max-throughput': None,
|
||||
'num-workloads': None
|
||||
},
|
||||
},
|
||||
}
|
||||
result = self.send_request('qos-policy-group-get-iter', api_args,
|
||||
False)
|
||||
if not self._has_records(result):
|
||||
msg = _("No QoS policy group found with name %s.")
|
||||
raise exception.NetAppException(msg % qos_policy_group_name)
|
||||
|
||||
attributes_list = result.get_child_by_name(
|
||||
'attributes-list') or netapp_api.NaElement('none')
|
||||
|
||||
qos_policy_group_info = attributes_list.get_child_by_name(
|
||||
'qos-policy-group-info') or netapp_api.NaElement('none')
|
||||
|
||||
policy_info = {
|
||||
'policy-group': qos_policy_group_info.get_child_content(
|
||||
'policy-group'),
|
||||
'vserver': qos_policy_group_info.get_child_content('vserver'),
|
||||
'max-throughput': qos_policy_group_info.get_child_content(
|
||||
'max-throughput'),
|
||||
'num-workloads': int(qos_policy_group_info.get_child_content(
|
||||
'num-workloads')),
|
||||
}
|
||||
return policy_info
|
||||
|
||||
@na_utils.trace
|
||||
def qos_policy_group_create(self, qos_policy_group_name, vserver,
|
||||
max_throughput=None):
|
||||
"""Creates a QoS policy group."""
|
||||
api_args = {
|
||||
'policy-group': qos_policy_group_name,
|
||||
'vserver': vserver,
|
||||
}
|
||||
if max_throughput:
|
||||
api_args['max-throughput'] = max_throughput
|
||||
return self.send_request('qos-policy-group-create', api_args, False)
|
||||
|
||||
@na_utils.trace
|
||||
def qos_policy_group_modify(self, qos_policy_group_name, max_throughput):
|
||||
"""Modifies a QoS policy group."""
|
||||
api_args = {
|
||||
'policy-group': qos_policy_group_name,
|
||||
'max-throughput': max_throughput,
|
||||
}
|
||||
return self.send_request('qos-policy-group-modify', api_args, False)
|
||||
|
||||
@na_utils.trace
|
||||
def qos_policy_group_delete(self, qos_policy_group_name):
|
||||
"""Attempts to delete a QoS policy group."""
|
||||
api_args = {'policy-group': qos_policy_group_name}
|
||||
return self.send_request('qos-policy-group-delete', api_args, False)
|
||||
|
||||
@na_utils.trace
|
||||
def qos_policy_group_rename(self, qos_policy_group_name, new_name):
|
||||
"""Renames a QoS policy group."""
|
||||
api_args = {
|
||||
'policy-group-name': qos_policy_group_name,
|
||||
'new-name': new_name,
|
||||
}
|
||||
return self.send_request('qos-policy-group-rename', api_args, False)
|
||||
|
||||
@na_utils.trace
|
||||
def mark_qos_policy_group_for_deletion(self, qos_policy_group_name):
|
||||
"""Soft delete backing QoS policy group for a manila share."""
|
||||
# NOTE(gouthamr): ONTAP deletes storage objects asynchronously. As
|
||||
# long as garbage collection hasn't occurred, assigned QoS policy may
|
||||
# still be tagged "in use". So, we rename the QoS policy group using a
|
||||
# specific pattern and later attempt on a best effort basis to
|
||||
# delete any QoS policy groups matching that pattern.
|
||||
|
||||
if self.qos_policy_group_exists(qos_policy_group_name):
|
||||
new_name = DELETED_PREFIX + qos_policy_group_name
|
||||
try:
|
||||
self.qos_policy_group_rename(qos_policy_group_name, new_name)
|
||||
except netapp_api.NaApiError as ex:
|
||||
msg = ('Rename failure in cleanup of cDOT QoS policy '
|
||||
'group %(name)s: %(ex)s')
|
||||
msg_args = {'name': qos_policy_group_name, 'ex': ex}
|
||||
LOG.warning(msg, msg_args)
|
||||
# Attempt to delete any QoS policies named "deleted_manila-*".
|
||||
self.remove_unused_qos_policy_groups()
|
||||
|
||||
@na_utils.trace
|
||||
def remove_unused_qos_policy_groups(self):
|
||||
"""Deletes all QoS policy groups that are marked for deletion."""
|
||||
api_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': '%s*' % DELETED_PREFIX,
|
||||
}
|
||||
},
|
||||
'max-records': 3500,
|
||||
'continue-on-failure': 'true',
|
||||
'return-success-list': 'false',
|
||||
'return-failure-list': 'false',
|
||||
}
|
||||
|
||||
try:
|
||||
self.send_request('qos-policy-group-delete-iter', api_args, False)
|
||||
except netapp_api.NaApiError as ex:
|
||||
msg = 'Could not delete QoS policy groups. Details: %(ex)s'
|
||||
msg_args = {'ex': ex}
|
||||
LOG.debug(msg, msg_args)
|
||||
|
@ -92,6 +92,12 @@ class DataMotionSession(object):
|
||||
'share_id': share_obj['id'].replace('-', '_')}
|
||||
return volume_name
|
||||
|
||||
def _get_backend_qos_policy_group_name(self, share):
|
||||
"""Get QoS policy name according to QoS policy group name template."""
|
||||
__, config = self._get_backend_config_obj(share)
|
||||
return config.netapp_qos_policy_group_name_template % {
|
||||
'share_id': share['id'].replace('-', '_')}
|
||||
|
||||
def get_vserver_from_share(self, share_obj):
|
||||
share_server = share_obj.get('share_server')
|
||||
if share_server:
|
||||
@ -99,11 +105,14 @@ class DataMotionSession(object):
|
||||
if backend_details:
|
||||
return backend_details.get('vserver_name')
|
||||
|
||||
def get_backend_info_for_share(self, share_obj):
|
||||
def _get_backend_config_obj(self, share_obj):
|
||||
backend_name = share_utils.extract_host(
|
||||
share_obj['host'], level='backend_name')
|
||||
|
||||
config = get_backend_configuration(backend_name)
|
||||
return backend_name, config
|
||||
|
||||
def get_backend_info_for_share(self, share_obj):
|
||||
backend_name, config = self._get_backend_config_obj(share_obj)
|
||||
vserver = (self.get_vserver_from_share(share_obj) or
|
||||
config.netapp_vserver)
|
||||
volume_name = self._get_backend_volume_name(
|
||||
@ -379,3 +388,23 @@ class DataMotionSession(object):
|
||||
new_src_volume_name,
|
||||
replica_vserver,
|
||||
replica_volume_name)
|
||||
|
||||
@na_utils.trace
|
||||
def remove_qos_on_old_active_replica(self, orig_active_replica):
|
||||
old_active_replica_qos_policy = (
|
||||
self._get_backend_qos_policy_group_name(orig_active_replica)
|
||||
)
|
||||
replica_volume_name, replica_vserver, replica_backend = (
|
||||
self.get_backend_info_for_share(orig_active_replica))
|
||||
replica_client = get_client_for_backend(
|
||||
replica_backend, vserver_name=replica_vserver)
|
||||
try:
|
||||
replica_client.set_qos_policy_group_for_volume(
|
||||
replica_volume_name, 'none')
|
||||
replica_client.mark_qos_policy_group_for_deletion(
|
||||
old_active_replica_qos_policy)
|
||||
except exception.StorageCommunicationException:
|
||||
LOG.exception("Could not communicate with the backend "
|
||||
"for replica %s to unset QoS policy and mark "
|
||||
"the QoS policy group for deletion.",
|
||||
orig_active_replica['id'])
|
||||
|
@ -82,6 +82,15 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
'compression': 'netapp:compression',
|
||||
}
|
||||
|
||||
QOS_SPECS = {
|
||||
'netapp:maxiops': 'maxiops',
|
||||
'netapp:maxiopspergib': 'maxiopspergib',
|
||||
'netapp:maxbps': 'maxbps',
|
||||
'netapp:maxbpspergib': 'maxbpspergib',
|
||||
}
|
||||
|
||||
SIZE_DEPENDENT_QOS_SPECS = {'maxiopspergib', 'maxbpspergib'}
|
||||
|
||||
def __init__(self, driver_name, **kwargs):
|
||||
na_utils.validate_driver_instantiation(**kwargs)
|
||||
|
||||
@ -207,6 +216,11 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
"""Get snapshot name according to snapshot name template."""
|
||||
return 'share_cg_snapshot_' + snapshot_id.replace('-', '_')
|
||||
|
||||
def _get_backend_qos_policy_group_name(self, share_id):
|
||||
"""Get QoS policy name according to QoS policy group name template."""
|
||||
return self.configuration.netapp_qos_policy_group_name_template % {
|
||||
'share_id': share_id.replace('-', '_')}
|
||||
|
||||
@na_utils.trace
|
||||
def _get_aggregate_space(self):
|
||||
aggregates = self._find_matching_aggregates()
|
||||
@ -273,9 +287,12 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
aggr_space = self._get_aggregate_space()
|
||||
aggregates = aggr_space.keys()
|
||||
|
||||
# Get up-to-date node utilization metrics just once.
|
||||
if self._have_cluster_creds:
|
||||
# Get up-to-date node utilization metrics just once.
|
||||
self._perf_library.update_performance_cache({}, self._ssc_stats)
|
||||
qos_support = True
|
||||
else:
|
||||
qos_support = False
|
||||
|
||||
for aggr_name in sorted(aggregates):
|
||||
|
||||
@ -298,7 +315,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
'total_capacity_gb': total_capacity_gb,
|
||||
'free_capacity_gb': free_capacity_gb,
|
||||
'allocated_capacity_gb': allocated_capacity_gb,
|
||||
'qos': 'False',
|
||||
'qos': qos_support,
|
||||
'reserved_percentage': reserved_percentage,
|
||||
'dedupe': [True, False],
|
||||
'compression': [True, False],
|
||||
@ -423,7 +440,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
def create_share(self, context, share, share_server):
|
||||
"""Creates new share."""
|
||||
vserver, vserver_client = self._get_vserver(share_server=share_server)
|
||||
self._allocate_container(share, vserver_client)
|
||||
self._allocate_container(share, vserver, vserver_client)
|
||||
return self._create_export(share, share_server, vserver,
|
||||
vserver_client)
|
||||
|
||||
@ -432,12 +449,14 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
share_server=None):
|
||||
"""Creates new share from snapshot."""
|
||||
vserver, vserver_client = self._get_vserver(share_server=share_server)
|
||||
self._allocate_container_from_snapshot(share, snapshot, vserver_client)
|
||||
self._allocate_container_from_snapshot(
|
||||
share, snapshot, vserver, vserver_client)
|
||||
return self._create_export(share, share_server, vserver,
|
||||
vserver_client)
|
||||
|
||||
@na_utils.trace
|
||||
def _allocate_container(self, share, vserver_client, replica=False):
|
||||
def _allocate_container(self, share, vserver, vserver_client,
|
||||
replica=False):
|
||||
"""Create new share on aggregate."""
|
||||
share_name = self._get_backend_share_name(share['id'])
|
||||
|
||||
@ -447,7 +466,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
msg = _("Pool is not available in the share host field.")
|
||||
raise exception.InvalidHost(reason=msg)
|
||||
|
||||
provisioning_options = self._get_provisioning_options_for_share(share)
|
||||
provisioning_options = self._get_provisioning_options_for_share(
|
||||
share, vserver, replica=replica)
|
||||
|
||||
if replica:
|
||||
# If this volume is intended to be a replication destination,
|
||||
@ -582,8 +602,55 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
# provisioning methods from the client API library.
|
||||
return dict(zip(provisioning_args, provisioning_values))
|
||||
|
||||
def _get_normalized_qos_specs(self, extra_specs):
|
||||
if not extra_specs.get('qos'):
|
||||
return {}
|
||||
|
||||
normalized_qos_specs = {
|
||||
self.QOS_SPECS[key.lower()]: value
|
||||
for key, value in extra_specs.items()
|
||||
if self.QOS_SPECS.get(key.lower())
|
||||
}
|
||||
if not normalized_qos_specs:
|
||||
msg = _("The extra-spec 'qos' is set to True, but no netapp "
|
||||
"supported qos-specs have been specified in the share "
|
||||
"type. Cannot provision a QoS policy. Specify any of the "
|
||||
"following extra-specs and try again: %s")
|
||||
raise exception.NetAppException(msg % list(self.QOS_SPECS))
|
||||
|
||||
# TODO(gouthamr): Modify check when throughput floors are allowed
|
||||
if len(normalized_qos_specs) > 1:
|
||||
msg = _('Only one NetApp QoS spec can be set at a time. '
|
||||
'Specified QoS limits: %s')
|
||||
raise exception.NetAppException(msg % normalized_qos_specs)
|
||||
|
||||
return normalized_qos_specs
|
||||
|
||||
def _get_max_throughput(self, share_size, qos_specs):
|
||||
# QoS limits are exclusive of one another.
|
||||
if 'maxiops' in qos_specs:
|
||||
return '%siops' % qos_specs['maxiops']
|
||||
elif 'maxiopspergib' in qos_specs:
|
||||
return '%siops' % six.text_type(
|
||||
int(qos_specs['maxiopspergib']) * int(share_size))
|
||||
elif 'maxbps' in qos_specs:
|
||||
return '%sB/s' % qos_specs['maxbps']
|
||||
elif 'maxbpspergib' in qos_specs:
|
||||
return '%sB/s' % six.text_type(
|
||||
int(qos_specs['maxbpspergib']) * int(share_size))
|
||||
|
||||
@na_utils.trace
|
||||
def _get_provisioning_options_for_share(self, share):
|
||||
def _create_qos_policy_group(self, share, vserver, qos_specs):
|
||||
max_throughput = self._get_max_throughput(share['size'], qos_specs)
|
||||
qos_policy_group_name = self._get_backend_qos_policy_group_name(
|
||||
share['id'])
|
||||
self._client.qos_policy_group_create(qos_policy_group_name, vserver,
|
||||
max_throughput=max_throughput)
|
||||
return qos_policy_group_name
|
||||
|
||||
@na_utils.trace
|
||||
def _get_provisioning_options_for_share(self, share, vserver,
|
||||
replica=False):
|
||||
"""Return provisioning options from a share.
|
||||
|
||||
Starting with a share, this method gets the extra specs, rationalizes
|
||||
@ -594,7 +661,13 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
extra_specs = share_types.get_extra_specs_from_share(share)
|
||||
extra_specs = self._remap_standard_boolean_extra_specs(extra_specs)
|
||||
self._check_extra_specs_validity(share, extra_specs)
|
||||
return self._get_provisioning_options(extra_specs)
|
||||
provisioning_options = self._get_provisioning_options(extra_specs)
|
||||
qos_specs = self._get_normalized_qos_specs(extra_specs)
|
||||
if qos_specs and not replica:
|
||||
qos_policy_group = self._create_qos_policy_group(
|
||||
share, vserver, qos_specs)
|
||||
provisioning_options['qos_policy_group'] = qos_policy_group
|
||||
return provisioning_options
|
||||
|
||||
@na_utils.trace
|
||||
def _get_provisioning_options(self, specs):
|
||||
@ -627,7 +700,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
|
||||
@na_utils.trace
|
||||
def _allocate_container_from_snapshot(
|
||||
self, share, snapshot, vserver_client,
|
||||
self, share, snapshot, vserver, vserver_client,
|
||||
snapshot_name_func=_get_backend_snapshot_name):
|
||||
"""Clones existing share."""
|
||||
share_name = self._get_backend_share_name(share['id'])
|
||||
@ -637,7 +710,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
else:
|
||||
parent_snapshot_name = snapshot['provider_location']
|
||||
|
||||
provisioning_options = self._get_provisioning_options_for_share(share)
|
||||
provisioning_options = self._get_provisioning_options_for_share(
|
||||
share, vserver)
|
||||
|
||||
LOG.debug('Creating share from snapshot %s', snapshot['id'])
|
||||
vserver_client.create_volume_clone(share_name, parent_share_name,
|
||||
@ -667,6 +741,10 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
if self._share_exists(share_name, vserver_client):
|
||||
self._remove_export(share, vserver_client)
|
||||
self._deallocate_container(share_name, vserver_client)
|
||||
qos_policy_for_share = self._get_backend_qos_policy_group_name(
|
||||
share['id'])
|
||||
self._client.mark_qos_policy_group_for_deletion(
|
||||
qos_policy_for_share)
|
||||
else:
|
||||
LOG.info("Share %s does not exist.", share['id'])
|
||||
|
||||
@ -856,7 +934,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
@na_utils.trace
|
||||
def manage_existing(self, share, driver_options):
|
||||
vserver, vserver_client = self._get_vserver(share_server=None)
|
||||
share_size = self._manage_container(share, vserver_client)
|
||||
share_size = self._manage_container(share, vserver, vserver_client)
|
||||
export_locations = self._create_export(share, None, vserver,
|
||||
vserver_client)
|
||||
return {'size': share_size, 'export_locations': export_locations}
|
||||
@ -866,7 +944,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
pass
|
||||
|
||||
@na_utils.trace
|
||||
def _manage_container(self, share, vserver_client):
|
||||
def _manage_container(self, share, vserver, vserver_client):
|
||||
"""Bring existing volume under management as a share."""
|
||||
|
||||
protocol_helper = self._get_helper(share)
|
||||
@ -891,6 +969,9 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
msg_args = {'volume': volume_name, 'aggr': aggregate_name}
|
||||
raise exception.ManageInvalidShare(reason=msg % msg_args)
|
||||
|
||||
# When calculating the size, round up to the next GB.
|
||||
volume_size = int(math.ceil(float(volume['size']) / units.Gi))
|
||||
|
||||
# Ensure volume is manageable
|
||||
self._validate_volume_for_manage(volume, vserver_client)
|
||||
|
||||
@ -918,8 +999,13 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
vserver_client.set_volume_name(volume_name, share_name)
|
||||
vserver_client.mount_volume(share_name)
|
||||
|
||||
qos_policy_group_name = self._modify_or_create_qos_for_existing_share(
|
||||
share, extra_specs, vserver, vserver_client)
|
||||
if qos_policy_group_name:
|
||||
provisioning_options['qos_policy_group'] = qos_policy_group_name
|
||||
|
||||
# Modify volume to match extra specs
|
||||
vserver_client.manage_volume(aggregate_name, share_name,
|
||||
vserver_client.modify_volume(aggregate_name, share_name,
|
||||
**provisioning_options)
|
||||
|
||||
# Save original volume info to private storage
|
||||
@ -929,8 +1015,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
}
|
||||
self.private_storage.update(share['id'], original_data)
|
||||
|
||||
# When calculating the size, round up to the next GB.
|
||||
return int(math.ceil(float(volume['size']) / units.Gi))
|
||||
return volume_size
|
||||
|
||||
@na_utils.trace
|
||||
def _validate_volume_for_manage(self, volume, vserver_client):
|
||||
@ -1039,7 +1124,7 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
for clone in clone_list:
|
||||
|
||||
self._allocate_container_from_snapshot(
|
||||
clone['share'], clone['snapshot'], vserver_client,
|
||||
clone['share'], clone['snapshot'], vserver, vserver_client,
|
||||
NetAppCmodeFileStorageLibrary._get_backend_cg_snapshot_name)
|
||||
|
||||
export_locations = self._create_export(clone['share'],
|
||||
@ -1151,6 +1236,27 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
|
||||
return None, None
|
||||
|
||||
@na_utils.trace
|
||||
def _adjust_qos_policy_with_volume_resize(self, share, new_size,
|
||||
vserver_client):
|
||||
# Adjust QoS policy on a share if any
|
||||
if self._have_cluster_creds:
|
||||
share_name = self._get_backend_share_name(share['id'])
|
||||
share_on_the_backend = vserver_client.get_volume(share_name)
|
||||
qos_policy_on_share = share_on_the_backend['qos-policy-group-name']
|
||||
if qos_policy_on_share is None:
|
||||
return
|
||||
|
||||
extra_specs = share_types.get_extra_specs_from_share(share)
|
||||
qos_specs = self._get_normalized_qos_specs(extra_specs)
|
||||
size_dependent_specs = {k: v for k, v in qos_specs.items() if k in
|
||||
self.SIZE_DEPENDENT_QOS_SPECS}
|
||||
if size_dependent_specs:
|
||||
max_throughput = self._get_max_throughput(
|
||||
new_size, size_dependent_specs)
|
||||
self._client.qos_policy_group_modify(
|
||||
qos_policy_on_share, max_throughput)
|
||||
|
||||
@na_utils.trace
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
"""Extends size of existing share."""
|
||||
@ -1159,6 +1265,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
LOG.debug('Extending share %(name)s to %(size)s GB.',
|
||||
{'name': share_name, 'size': new_size})
|
||||
vserver_client.set_volume_size(share_name, new_size)
|
||||
self._adjust_qos_policy_with_volume_resize(share, new_size,
|
||||
vserver_client)
|
||||
|
||||
@na_utils.trace
|
||||
def shrink_share(self, share, new_size, share_server=None):
|
||||
@ -1168,6 +1276,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
LOG.debug('Shrinking share %(name)s to %(size)s GB.',
|
||||
{'name': share_name, 'size': new_size})
|
||||
vserver_client.set_volume_size(share_name, new_size)
|
||||
self._adjust_qos_policy_with_volume_resize(share, new_size,
|
||||
vserver_client)
|
||||
|
||||
@na_utils.trace
|
||||
def update_access(self, context, share, access_rules, add_rules,
|
||||
@ -1288,7 +1398,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
vserver_client = data_motion.get_client_for_backend(
|
||||
dest_backend, vserver_name=vserver)
|
||||
|
||||
self._allocate_container(new_replica, vserver_client, replica=True)
|
||||
self._allocate_container(new_replica, vserver, vserver_client,
|
||||
replica=True)
|
||||
|
||||
# 2. Setup SnapMirror
|
||||
dm_session.create_snapmirror(active_replica, new_replica)
|
||||
@ -1454,8 +1565,52 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
replica_list)
|
||||
new_replica_list.append(r)
|
||||
|
||||
self._handle_qos_on_replication_change(dm_session,
|
||||
new_active_replica,
|
||||
orig_active_replica,
|
||||
share_server=share_server)
|
||||
|
||||
return new_replica_list
|
||||
|
||||
def _handle_qos_on_replication_change(self, dm_session, new_active_replica,
|
||||
orig_active_replica,
|
||||
share_server=None):
|
||||
# QoS operations: Remove and purge QoS policy on old active replica
|
||||
# if any and create a new policy on the destination if necessary.
|
||||
extra_specs = share_types.get_extra_specs_from_share(
|
||||
orig_active_replica)
|
||||
qos_specs = self._get_normalized_qos_specs(extra_specs)
|
||||
|
||||
if qos_specs and self._have_cluster_creds:
|
||||
dm_session.remove_qos_on_old_active_replica(orig_active_replica)
|
||||
# Check if a QoS policy already exists for the promoted replica,
|
||||
# if it does, modify it as necessary, else create it:
|
||||
try:
|
||||
new_active_replica_qos_policy = (
|
||||
self._get_backend_qos_policy_group_name(
|
||||
new_active_replica['id']))
|
||||
vserver, vserver_client = self._get_vserver(
|
||||
share_server=share_server)
|
||||
|
||||
volume_name_on_backend = self._get_backend_share_name(
|
||||
new_active_replica['id'])
|
||||
if not self._client.qos_policy_group_exists(
|
||||
new_active_replica_qos_policy):
|
||||
self._create_qos_policy_group(
|
||||
new_active_replica, vserver, qos_specs)
|
||||
else:
|
||||
max_throughput = self._get_max_throughput(
|
||||
new_active_replica['size'], qos_specs)
|
||||
self._client.qos_policy_group_modify(
|
||||
new_active_replica_qos_policy, max_throughput)
|
||||
vserver_client.set_qos_policy_group_for_volume(
|
||||
volume_name_on_backend, new_active_replica_qos_policy)
|
||||
|
||||
LOG.info("QoS policy applied successfully for promoted "
|
||||
"replica: %s", new_active_replica['id'])
|
||||
except Exception:
|
||||
LOG.exception("Could not apply QoS to the promoted replica.")
|
||||
|
||||
def _convert_destination_replica_to_independent(
|
||||
self, context, dm_session, orig_active_replica, replica,
|
||||
access_rules, share_server=None):
|
||||
@ -1721,12 +1876,13 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
destination_host, level='backend_name')
|
||||
destination_aggregate = share_utils.extract_host(
|
||||
destination_host, level='pool')
|
||||
# Validate new share type extra-specs are valid on the
|
||||
# destination
|
||||
# Validate new extra-specs are valid on the destination
|
||||
extra_specs = share_types.get_extra_specs_from_share(
|
||||
destination_share)
|
||||
self._check_extra_specs_validity(
|
||||
destination_share, extra_specs)
|
||||
# TODO(gouthamr): Check whether QoS min-throughputs can be
|
||||
# honored on the destination aggregate when supported.
|
||||
self._check_aggregate_extra_specs_validity(
|
||||
destination_aggregate, extra_specs)
|
||||
|
||||
@ -1916,11 +2072,15 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
extra_specs = share_types.get_extra_specs_from_share(
|
||||
destination_share)
|
||||
provisioning_options = self._get_provisioning_options(extra_specs)
|
||||
qos_policy_group_name = self._modify_or_create_qos_for_existing_share(
|
||||
destination_share, extra_specs, vserver, vserver_client)
|
||||
if qos_policy_group_name:
|
||||
provisioning_options['qos_policy_group'] = qos_policy_group_name
|
||||
destination_aggregate = share_utils.extract_host(
|
||||
destination_share['host'], level='pool')
|
||||
|
||||
# Modify volume to match extra specs
|
||||
vserver_client.manage_volume(destination_aggregate,
|
||||
vserver_client.modify_volume(destination_aggregate,
|
||||
new_share_volume_name,
|
||||
**provisioning_options)
|
||||
|
||||
@ -1954,6 +2114,70 @@ class NetAppCmodeFileStorageLibrary(object):
|
||||
'snapshot_updates': snapshot_updates,
|
||||
}
|
||||
|
||||
@na_utils.trace
|
||||
def _modify_or_create_qos_for_existing_share(self, share, extra_specs,
|
||||
vserver, vserver_client):
|
||||
"""Gets/Creates QoS policy for an existing FlexVol.
|
||||
|
||||
The share's assigned QoS policy is renamed and adjusted if the policy
|
||||
is exclusive to the FlexVol. If the policy includes other workloads
|
||||
besides the FlexVol, a new policy is created with the specs necessary.
|
||||
"""
|
||||
qos_specs = self._get_normalized_qos_specs(extra_specs)
|
||||
if not qos_specs:
|
||||
return
|
||||
|
||||
backend_share_name = self._get_backend_share_name(share['id'])
|
||||
qos_policy_group_name = self._get_backend_qos_policy_group_name(
|
||||
share['id'])
|
||||
|
||||
create_new_qos_policy_group = True
|
||||
|
||||
backend_volume = vserver_client.get_volume(
|
||||
backend_share_name)
|
||||
backend_volume_size = int(
|
||||
math.ceil(float(backend_volume['size']) / units.Gi))
|
||||
|
||||
LOG.debug("Checking for a pre-existing QoS policy group that "
|
||||
"is exclusive to the volume %s." % backend_share_name)
|
||||
|
||||
# Does the volume have an exclusive QoS policy that we can rename?
|
||||
if backend_volume['qos-policy-group-name'] is not None:
|
||||
existing_qos_policy_group = self._client.qos_policy_group_get(
|
||||
backend_volume['qos-policy-group-name'])
|
||||
if existing_qos_policy_group['num-workloads'] == 1:
|
||||
# Yay, can set max-throughput and rename
|
||||
|
||||
msg = ("Found pre-existing QoS policy %(policy)s and it is "
|
||||
"exclusive to the volume %(volume)s. Modifying and "
|
||||
"renaming this policy to %(new_policy)s.")
|
||||
msg_args = {
|
||||
'policy': backend_volume['qos-policy-group-name'],
|
||||
'volume': backend_share_name,
|
||||
'new_policy': qos_policy_group_name,
|
||||
}
|
||||
LOG.debug(msg, msg_args)
|
||||
|
||||
max_throughput = self._get_max_throughput(
|
||||
backend_volume_size, qos_specs)
|
||||
self._client.qos_policy_group_modify(
|
||||
backend_volume['qos-policy-group-name'], max_throughput)
|
||||
self._client.qos_policy_group_rename(
|
||||
backend_volume['qos-policy-group-name'],
|
||||
qos_policy_group_name)
|
||||
create_new_qos_policy_group = False
|
||||
|
||||
if create_new_qos_policy_group:
|
||||
share_obj = {
|
||||
'size': backend_volume_size,
|
||||
'id': share['id'],
|
||||
}
|
||||
LOG.debug("No existing QoS policy group found for "
|
||||
"volume. Creating a new one with name %s.",
|
||||
qos_policy_group_name)
|
||||
self._create_qos_policy_group(share_obj, vserver, qos_specs)
|
||||
return qos_policy_group_name
|
||||
|
||||
def _wait_for_cutover_completion(self, source_share, share_server):
|
||||
|
||||
retries = (self.configuration.netapp_volume_move_cutover_timeout / 5
|
||||
|
@ -101,6 +101,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
|
||||
"""Handle various cleanup activities."""
|
||||
self._client.prune_deleted_nfs_export_policies()
|
||||
self._client.prune_deleted_snapshots()
|
||||
self._client.remove_unused_qos_policy_groups()
|
||||
|
||||
(super(NetAppCmodeMultiSVMFileStorageLibrary, self).
|
||||
_handle_housekeeping_tasks())
|
||||
|
@ -115,6 +115,10 @@ class NetAppCmodeSingleSVMFileStorageLibrary(
|
||||
vserver_client.prune_deleted_nfs_export_policies()
|
||||
vserver_client.prune_deleted_snapshots()
|
||||
|
||||
if self._have_cluster_creds:
|
||||
# Harvest soft-deleted QoS policy groups
|
||||
vserver_client.remove_unused_qos_policy_groups()
|
||||
|
||||
(super(NetAppCmodeSingleSVMFileStorageLibrary, self).
|
||||
_handle_housekeeping_tasks())
|
||||
|
||||
|
@ -72,6 +72,9 @@ netapp_provisioning_opts = [
|
||||
cfg.StrOpt('netapp_vserver_name_template',
|
||||
default='os_%s',
|
||||
help='Name template to use for new Vserver.'),
|
||||
cfg.StrOpt('netapp_qos_policy_group_name_template',
|
||||
help='NetApp QoS policy group name template.',
|
||||
default='qos_share_%(share_id)s'),
|
||||
cfg.StrOpt('netapp_port_name_search_pattern',
|
||||
default='(.*)',
|
||||
help='Pattern for overriding the selection of network ports '
|
||||
|
@ -67,6 +67,8 @@ DELETED_EXPORT_POLICIES = {
|
||||
'deleted_manila_fake_policy_3',
|
||||
],
|
||||
}
|
||||
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
|
||||
QOS_MAX_THROUGHPUT = '5000B/s'
|
||||
|
||||
USER_NAME = 'fake_user'
|
||||
|
||||
@ -142,6 +144,13 @@ EMS_MESSAGE = {
|
||||
'auto-support': 'false',
|
||||
}
|
||||
|
||||
QOS_POLICY_GROUP = {
|
||||
'policy-group': QOS_POLICY_GROUP_NAME,
|
||||
'vserver': VSERVER_NAME,
|
||||
'max-throughput': QOS_MAX_THROUGHPUT,
|
||||
'num-workloads': 1,
|
||||
}
|
||||
|
||||
NO_RECORDS_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<num-records>0</num-records>
|
||||
@ -152,6 +161,13 @@ PASSED_RESPONSE = etree.XML("""
|
||||
<results status="passed" />
|
||||
""")
|
||||
|
||||
PASSED_FAILED_ITER_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<num-failed>0</num-failed>
|
||||
<num-succeeded>1</num-succeeded>
|
||||
</results>
|
||||
""")
|
||||
|
||||
INVALID_GET_ITER_RESPONSE_NO_ATTRIBUTES = etree.XML("""
|
||||
<results status="passed">
|
||||
<num-records>1</num-records>
|
||||
@ -1983,6 +1999,36 @@ VOLUME_GET_ITER_JUNCTIONED_VOLUMES_RESPONSE = etree.XML("""
|
||||
""")
|
||||
|
||||
VOLUME_GET_ITER_VOLUME_TO_MANAGE_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<volume-attributes>
|
||||
<volume-id-attributes>
|
||||
<containing-aggregate-name>%(aggr)s</containing-aggregate-name>
|
||||
<junction-path>/%(volume)s</junction-path>
|
||||
<name>%(volume)s</name>
|
||||
<owning-vserver-name>%(vserver)s</owning-vserver-name>
|
||||
<style>flex</style>
|
||||
<type>rw</type>
|
||||
</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,
|
||||
})
|
||||
|
||||
VOLUME_GET_ITER_NO_QOS_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<volume-attributes>
|
||||
@ -2332,6 +2378,23 @@ NET_ROUTES_CREATE_RESPONSE = etree.XML("""
|
||||
'subnet': SUBNET,
|
||||
})
|
||||
|
||||
QOS_POLICY_GROUP_GET_ITER_RESPONSE = etree.XML("""
|
||||
<results status="passed">
|
||||
<attributes-list>
|
||||
<qos-policy-group-info>
|
||||
<max-throughput>%(max_througput)s</max-throughput>
|
||||
<num-workloads>1</num-workloads>
|
||||
<policy-group>%(qos_policy_group_name)s</policy-group>
|
||||
<vserver>%(vserver)s</vserver>
|
||||
</qos-policy-group-info>
|
||||
</attributes-list>
|
||||
<num-records>1</num-records>
|
||||
</results>""" % {
|
||||
'qos_policy_group_name': QOS_POLICY_GROUP_NAME,
|
||||
'vserver': VSERVER_NAME,
|
||||
'max_througput': QOS_MAX_THROUGHPUT,
|
||||
})
|
||||
|
||||
FAKE_VOL_XML = """<volume-info xmlns='http://www.netapp.com/filer/admin'>
|
||||
<name>open123</name>
|
||||
<state>online</state>
|
||||
|
@ -2483,7 +2483,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
self.client.send_request.assert_called_once_with('volume-create',
|
||||
volume_create_args)
|
||||
|
||||
def test_create_volume_with_extra_specs(self):
|
||||
@ddt.data(None, fake.QOS_POLICY_GROUP_NAME)
|
||||
def test_create_volume_with_extra_specs(self, qos_policy_group_name):
|
||||
|
||||
self.mock_object(self.client, 'set_volume_max_files')
|
||||
self.mock_object(self.client, 'enable_dedup')
|
||||
@ -2494,7 +2495,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
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)
|
||||
compression_enabled=True, max_files=5000, snapshot_reserve=15,
|
||||
qos_policy_group=qos_policy_group_name)
|
||||
|
||||
volume_create_args = {
|
||||
'containing-aggr-name': fake.SHARE_AGGREGATE_NAME,
|
||||
@ -2508,6 +2510,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
'percentage-snapshot-reserve': '15',
|
||||
}
|
||||
|
||||
if qos_policy_group_name:
|
||||
volume_create_args.update(
|
||||
{'qos-policy-group-name': qos_policy_group_name})
|
||||
|
||||
self.client.send_request.assert_called_with('volume-create',
|
||||
volume_create_args)
|
||||
self.client.set_volume_max_files.assert_called_once_with(
|
||||
@ -2645,13 +2651,13 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'volume-rename', volume_rename_api_args)
|
||||
|
||||
def test_manage_volume_no_optional_args(self):
|
||||
def test_modify_volume_no_optional_args(self):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
mock_update_volume_efficiency_attributes = self.mock_object(
|
||||
self.client, 'update_volume_efficiency_attributes')
|
||||
|
||||
self.client.manage_volume(fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME)
|
||||
self.client.modify_volume(fake.SHARE_AGGREGATE_NAME, fake.SHARE_NAME)
|
||||
|
||||
volume_modify_iter_api_args = {
|
||||
'query': {
|
||||
@ -2679,20 +2685,21 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
mock_update_volume_efficiency_attributes.assert_called_once_with(
|
||||
fake.SHARE_NAME, False, False)
|
||||
|
||||
def test_manage_volume_all_optional_args(self):
|
||||
def test_modify_volume_all_optional_args(self):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
mock_update_volume_efficiency_attributes = self.mock_object(
|
||||
self.client, 'update_volume_efficiency_attributes')
|
||||
|
||||
self.client.manage_volume(fake.SHARE_AGGREGATE_NAME,
|
||||
self.client.modify_volume(fake.SHARE_AGGREGATE_NAME,
|
||||
fake.SHARE_NAME,
|
||||
thin_provisioned=True,
|
||||
snapshot_policy=fake.SNAPSHOT_POLICY_NAME,
|
||||
language=fake.LANGUAGE,
|
||||
dedup_enabled=True,
|
||||
compression_enabled=False,
|
||||
max_files=fake.MAX_FILES)
|
||||
max_files=fake.MAX_FILES,
|
||||
qos_policy_group=fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
volume_modify_iter_api_args = {
|
||||
'query': {
|
||||
@ -2717,6 +2724,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
'volume-space-attributes': {
|
||||
'space-guarantee': 'none',
|
||||
},
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': fake.QOS_POLICY_GROUP_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -3093,7 +3103,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
},
|
||||
'volume-space-attributes': {
|
||||
'size': None,
|
||||
}
|
||||
},
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': None,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -3106,6 +3119,58 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
'style': 'flex',
|
||||
'size': fake.SHARE_SIZE,
|
||||
'owning-vserver-name': fake.VSERVER_NAME,
|
||||
'qos-policy-group-name': fake.QOS_POLICY_GROUP_NAME,
|
||||
}
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||
self.assertDictEqual(expected, result)
|
||||
|
||||
def test_get_volume_no_qos(self):
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.VOLUME_GET_ITER_NO_QOS_RESPONSE)
|
||||
self.mock_object(self.client,
|
||||
'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
result = self.client.get_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': {
|
||||
'containing-aggregate-name': None,
|
||||
'junction-path': None,
|
||||
'name': None,
|
||||
'owning-vserver-name': None,
|
||||
'type': None,
|
||||
'style': None,
|
||||
},
|
||||
'volume-space-attributes': {
|
||||
'size': None,
|
||||
},
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': None,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected = {
|
||||
'aggregate': fake.SHARE_AGGREGATE_NAME,
|
||||
'junction-path': '/%s' % fake.SHARE_NAME,
|
||||
'name': fake.SHARE_NAME,
|
||||
'type': 'rw',
|
||||
'style': 'flex',
|
||||
'size': fake.SHARE_SIZE,
|
||||
'owning-vserver-name': fake.VSERVER_NAME,
|
||||
'qos-policy-group-name': None,
|
||||
}
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||
@ -3230,7 +3295,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
},
|
||||
'volume-space-attributes': {
|
||||
'size': None,
|
||||
}
|
||||
},
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': None,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -3241,7 +3309,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
'type': 'rw',
|
||||
'style': 'flex',
|
||||
'size': fake.SHARE_SIZE,
|
||||
'owning-vserver-name': fake.VSERVER_NAME
|
||||
'owning-vserver-name': fake.VSERVER_NAME,
|
||||
'qos-policy-group-name': fake.QOS_POLICY_GROUP_NAME,
|
||||
}
|
||||
self.client.send_iter_request.assert_has_calls([
|
||||
mock.call('volume-get-iter', volume_get_iter_args)])
|
||||
@ -3259,14 +3328,16 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_create_volume_clone(self):
|
||||
@ddt.data(None, fake.QOS_POLICY_GROUP_NAME)
|
||||
def test_create_volume_clone(self, qos_policy_group_name):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
self.mock_object(self.client, 'split_volume_clone')
|
||||
|
||||
self.client.create_volume_clone(fake.SHARE_NAME,
|
||||
fake.PARENT_SHARE_NAME,
|
||||
fake.PARENT_SNAPSHOT_NAME)
|
||||
fake.PARENT_SNAPSHOT_NAME,
|
||||
qos_policy_group=qos_policy_group_name)
|
||||
|
||||
volume_clone_create_args = {
|
||||
'volume': fake.SHARE_NAME,
|
||||
@ -3275,6 +3346,10 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
'junction-path': '/%s' % fake.SHARE_NAME
|
||||
}
|
||||
|
||||
if qos_policy_group_name:
|
||||
volume_clone_create_args.update(
|
||||
{'qos-policy-group-name': fake.QOS_POLICY_GROUP_NAME})
|
||||
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-clone-create', volume_clone_create_args)])
|
||||
self.assertFalse(self.client.split_volume_clone.called)
|
||||
@ -4269,6 +4344,32 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
self.client.send_request.assert_has_calls([
|
||||
mock.call('volume-modify-iter', volume_modify_iter_args)])
|
||||
|
||||
def test_set_qos_policy_group_for_volume(self):
|
||||
|
||||
self.mock_object(self.client, 'send_request')
|
||||
|
||||
self.client.set_qos_policy_group_for_volume(fake.SHARE_NAME,
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
volume_modify_iter_args = {
|
||||
'query': {
|
||||
'volume-attributes': {
|
||||
'volume-id-attributes': {
|
||||
'name': fake.SHARE_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
'attributes': {
|
||||
'volume-attributes': {
|
||||
'volume-qos-attributes': {
|
||||
'policy-group-name': fake.QOS_POLICY_GROUP_NAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'volume-modify-iter', volume_modify_iter_args)
|
||||
|
||||
def test_get_nfs_export_policy_for_volume(self):
|
||||
|
||||
api_response = netapp_api.NaElement(
|
||||
@ -5759,3 +5860,215 @@ class NetAppClientCmodeTestCase(test.TestCase):
|
||||
self.assertDictMatch(expected_status_info, actual_status_info)
|
||||
self.client.send_iter_request.assert_called_once_with(
|
||||
'volume-move-get-iter', expected_api_args)
|
||||
|
||||
def test_qos_policy_group_exists_no_records(self):
|
||||
self.mock_object(self.client, 'qos_policy_group_get', mock.Mock(
|
||||
side_effect=exception.NetAppException))
|
||||
|
||||
policy_exists = self.client.qos_policy_group_exists(
|
||||
'i-dont-exist-but-i-am')
|
||||
|
||||
self.assertIs(False, policy_exists)
|
||||
|
||||
def test_qos_policy_group_exists(self):
|
||||
self.mock_object(self.client, 'qos_policy_group_get',
|
||||
mock.Mock(return_value=fake.QOS_POLICY_GROUP))
|
||||
|
||||
policy_exists = self.client.qos_policy_group_exists(
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
self.assertIs(True, policy_exists)
|
||||
|
||||
def test_qos_policy_group_get_none_found(self):
|
||||
no_records_response = netapp_api.NaElement(fake.NO_RECORDS_RESPONSE)
|
||||
self.mock_object(self.client, 'send_request',
|
||||
mock.Mock(return_value=no_records_response))
|
||||
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.client.qos_policy_group_get,
|
||||
'non-existent-qos-policy')
|
||||
|
||||
qos_policy_group_get_iter_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': 'non-existent-qos-policy',
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': None,
|
||||
'vserver': None,
|
||||
'max-throughput': None,
|
||||
'num-workloads': None
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'qos-policy-group-get-iter', qos_policy_group_get_iter_args, False)
|
||||
|
||||
def test_qos_policy_group_get(self):
|
||||
api_response = netapp_api.NaElement(
|
||||
fake.QOS_POLICY_GROUP_GET_ITER_RESPONSE)
|
||||
self.mock_object(self.client, 'send_request',
|
||||
mock.Mock(return_value=api_response))
|
||||
|
||||
qos_info = self.client.qos_policy_group_get(fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
qos_policy_group_get_iter_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
},
|
||||
},
|
||||
'desired-attributes': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': None,
|
||||
'vserver': None,
|
||||
'max-throughput': None,
|
||||
'num-workloads': None
|
||||
},
|
||||
},
|
||||
}
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'qos-policy-group-get-iter', qos_policy_group_get_iter_args, False)
|
||||
self.assertDictMatch(fake.QOS_POLICY_GROUP, qos_info)
|
||||
|
||||
@ddt.data(None, fake.QOS_MAX_THROUGHPUT)
|
||||
def test_qos_policy_group_create(self, max_throughput):
|
||||
self.mock_object(self.client, 'send_request',
|
||||
mock.Mock(return_value=fake.PASSED_RESPONSE))
|
||||
|
||||
self.client.qos_policy_group_create(
|
||||
fake.QOS_POLICY_GROUP_NAME, fake.VSERVER_NAME,
|
||||
max_throughput=max_throughput)
|
||||
|
||||
qos_policy_group_create_args = {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
'vserver': fake.VSERVER_NAME,
|
||||
}
|
||||
if max_throughput:
|
||||
qos_policy_group_create_args.update(
|
||||
{'max-throughput': max_throughput})
|
||||
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'qos-policy-group-create', qos_policy_group_create_args, False)
|
||||
|
||||
def test_qos_policy_group_modify(self):
|
||||
self.mock_object(self.client, 'send_request',
|
||||
mock.Mock(return_value=fake.PASSED_RESPONSE))
|
||||
|
||||
self.client.qos_policy_group_modify(fake.QOS_POLICY_GROUP_NAME,
|
||||
'3000iops')
|
||||
|
||||
qos_policy_group_modify_args = {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
'max-throughput': '3000iops',
|
||||
}
|
||||
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'qos-policy-group-modify', qos_policy_group_modify_args, False)
|
||||
|
||||
def test_qos_policy_group_delete(self):
|
||||
self.mock_object(self.client, 'send_request',
|
||||
mock.Mock(return_value=fake.PASSED_RESPONSE))
|
||||
|
||||
self.client.qos_policy_group_delete(fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
qos_policy_group_delete_args = {
|
||||
'policy-group': fake.QOS_POLICY_GROUP_NAME,
|
||||
}
|
||||
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'qos-policy-group-delete', qos_policy_group_delete_args, False)
|
||||
|
||||
def test_qos_policy_group_rename(self):
|
||||
self.mock_object(self.client, 'send_request',
|
||||
mock.Mock(return_value=fake.PASSED_RESPONSE))
|
||||
|
||||
self.client.qos_policy_group_rename(
|
||||
fake.QOS_POLICY_GROUP_NAME, 'new_' + fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
qos_policy_group_rename_args = {
|
||||
'policy-group-name': fake.QOS_POLICY_GROUP_NAME,
|
||||
'new-name': 'new_' + fake.QOS_POLICY_GROUP_NAME,
|
||||
}
|
||||
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'qos-policy-group-rename', qos_policy_group_rename_args, False)
|
||||
|
||||
def test_mark_qos_policy_group_for_deletion_rename_failure(self):
|
||||
self.mock_object(self.client, 'qos_policy_group_exists',
|
||||
mock.Mock(return_value=True))
|
||||
self.mock_object(self.client, 'qos_policy_group_rename',
|
||||
mock.Mock(side_effect=netapp_api.NaApiError))
|
||||
self.mock_object(client_cmode.LOG, 'warning')
|
||||
self.mock_object(self.client, 'remove_unused_qos_policy_groups')
|
||||
|
||||
retval = self.client.mark_qos_policy_group_for_deletion(
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
client_cmode.LOG.warning.assert_called_once()
|
||||
self.client.qos_policy_group_exists.assert_called_once_with(
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
self.client.qos_policy_group_rename.assert_called_once_with(
|
||||
fake.QOS_POLICY_GROUP_NAME,
|
||||
client_cmode.DELETED_PREFIX + fake.QOS_POLICY_GROUP_NAME)
|
||||
self.client.remove_unused_qos_policy_groups.assert_called_once_with()
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_mark_qos_policy_group_for_deletion_policy_exists(self, exists):
|
||||
self.mock_object(self.client, 'qos_policy_group_exists',
|
||||
mock.Mock(return_value=exists))
|
||||
self.mock_object(self.client, 'qos_policy_group_rename')
|
||||
mock_remove_unused_policies = self.mock_object(
|
||||
self.client, 'remove_unused_qos_policy_groups')
|
||||
self.mock_object(client_cmode.LOG, 'warning')
|
||||
|
||||
retval = self.client.mark_qos_policy_group_for_deletion(
|
||||
fake.QOS_POLICY_GROUP_NAME)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
|
||||
if exists:
|
||||
self.client.qos_policy_group_rename.assert_called_once_with(
|
||||
fake.QOS_POLICY_GROUP_NAME,
|
||||
client_cmode.DELETED_PREFIX + fake.QOS_POLICY_GROUP_NAME)
|
||||
mock_remove_unused_policies.assert_called_once_with()
|
||||
else:
|
||||
self.assertFalse(self.client.qos_policy_group_rename.called)
|
||||
self.assertFalse(
|
||||
self.client.remove_unused_qos_policy_groups.called)
|
||||
self.assertFalse(client_cmode.LOG.warning.called)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_remove_unused_qos_policy_groups_with_failure(self, failed):
|
||||
|
||||
if failed:
|
||||
args = mock.Mock(side_effect=netapp_api.NaApiError)
|
||||
else:
|
||||
args = mock.Mock(return_value=fake.PASSED_FAILED_ITER_RESPONSE)
|
||||
|
||||
self.mock_object(self.client, 'send_request', args)
|
||||
self.mock_object(client_cmode.LOG, 'debug')
|
||||
|
||||
retval = self.client.remove_unused_qos_policy_groups()
|
||||
|
||||
qos_policy_group_delete_iter_args = {
|
||||
'query': {
|
||||
'qos-policy-group-info': {
|
||||
'policy-group': '%s*' % client_cmode.DELETED_PREFIX,
|
||||
}
|
||||
},
|
||||
'max-records': 3500,
|
||||
'continue-on-failure': 'true',
|
||||
'return-success-list': 'false',
|
||||
'return-failure-list': 'false',
|
||||
}
|
||||
|
||||
self.assertIsNone(retval)
|
||||
self.client.send_request.assert_called_once_with(
|
||||
'qos-policy-group-delete-iter',
|
||||
qos_policy_group_delete_iter_args, False)
|
||||
self.assertIs(failed, client_cmode.LOG.debug.called)
|
||||
|
@ -123,6 +123,7 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
||||
self.backend)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -511,3 +512,42 @@ class NetAppCDOTDataMotionSessionTestCase(test.TestCase):
|
||||
self.mock_dest_client.resume_snapmirror.assert_called_once_with(
|
||||
self.source_vserver, self.fake_src_vol_name,
|
||||
self.dest_vserver, self.fake_dest_vol_name)
|
||||
|
||||
@ddt.data((None, exception.StorageCommunicationException),
|
||||
(exception.StorageCommunicationException, None))
|
||||
@ddt.unpack
|
||||
def test_remove_qos_on_old_active_replica_unreachable_backend(self,
|
||||
side_eff_1,
|
||||
side_eff_2):
|
||||
mock_source_client = mock.Mock()
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(return_value=mock_source_client))
|
||||
self.mock_object(
|
||||
mock_source_client, 'set_qos_policy_group_for_volume',
|
||||
mock.Mock(side_effect=side_eff_1))
|
||||
self.mock_object(
|
||||
mock_source_client, 'mark_qos_policy_group_for_deletion',
|
||||
mock.Mock(side_effect=side_eff_2))
|
||||
self.mock_object(data_motion.LOG, 'exception')
|
||||
|
||||
retval = self.dm_session.remove_qos_on_old_active_replica(
|
||||
self.fake_src_share)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
(mock_source_client.set_qos_policy_group_for_volume
|
||||
.assert_called_once_with(self.fake_src_vol_name, 'none'))
|
||||
data_motion.LOG.exception.assert_called_once()
|
||||
|
||||
def test_remove_qos_on_old_active_replica(self):
|
||||
mock_source_client = mock.Mock()
|
||||
self.mock_object(data_motion, 'get_client_for_backend',
|
||||
mock.Mock(return_value=mock_source_client))
|
||||
self.mock_object(data_motion.LOG, 'exception')
|
||||
|
||||
retval = self.dm_session.remove_qos_on_old_active_replica(
|
||||
self.fake_src_share)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
(mock_source_client.set_qos_policy_group_for_volume
|
||||
.assert_called_once_with(self.fake_src_vol_name, 'none'))
|
||||
data_motion.LOG.exception.assert_not_called()
|
||||
|
@ -610,6 +610,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
share_server=fake.SHARE_SERVER)
|
||||
|
||||
mock_allocate_container.assert_called_once_with(fake.SHARE,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
mock_create_export.assert_called_once_with(fake.SHARE,
|
||||
fake.SHARE_SERVER,
|
||||
@ -641,6 +642,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock_allocate_container_from_snapshot.assert_called_once_with(
|
||||
fake.SHARE,
|
||||
fake.SNAPSHOT,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
mock_create_export.assert_called_once_with(fake.SHARE,
|
||||
fake.SHARE_SERVER,
|
||||
@ -653,14 +655,18 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
return_value=fake.SHARE_NAME))
|
||||
self.mock_object(share_utils, 'extract_host', mock.Mock(
|
||||
return_value=fake.POOL_NAME))
|
||||
self.mock_object(
|
||||
mock_get_provisioning_opts = self.mock_object(
|
||||
self.library, '_get_provisioning_options_for_share',
|
||||
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
|
||||
vserver_client = mock.Mock()
|
||||
|
||||
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
mock_get_provisioning_opts.assert_called_once_with(
|
||||
fake.EXTRA_SPEC_SHARE, fake.VSERVER1, replica=False)
|
||||
|
||||
vserver_client.create_volume.assert_called_once_with(
|
||||
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
|
||||
thin_provisioned=True, snapshot_policy='default',
|
||||
@ -680,14 +686,17 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
return_value=fake.SHARE_NAME))
|
||||
self.mock_object(share_utils, 'extract_host', mock.Mock(
|
||||
return_value=fake.POOL_NAME))
|
||||
self.mock_object(
|
||||
mock_get_provisioning_opts = self.mock_object(
|
||||
self.library, '_get_provisioning_options_for_share',
|
||||
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
|
||||
vserver_client = mock.Mock()
|
||||
|
||||
self.library._allocate_container(fake.EXTRA_SPEC_SHARE,
|
||||
self.library._allocate_container(fake.EXTRA_SPEC_SHARE, fake.VSERVER1,
|
||||
vserver_client, replica=True)
|
||||
|
||||
mock_get_provisioning_opts.assert_called_once_with(
|
||||
fake.EXTRA_SPEC_SHARE, fake.VSERVER1, replica=True)
|
||||
|
||||
vserver_client.create_volume.assert_called_once_with(
|
||||
fake.POOL_NAME, fake.SHARE_NAME, fake.SHARE['size'],
|
||||
thin_provisioned=True, snapshot_policy='default',
|
||||
@ -706,7 +715,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
|
||||
self.assertRaises(exception.InvalidHost,
|
||||
self.library._allocate_container, fake.SHARE,
|
||||
vserver_client)
|
||||
fake.VSERVER1, vserver_client)
|
||||
|
||||
self.library._get_backend_share_name.assert_called_once_with(
|
||||
fake.SHARE['id'])
|
||||
@ -775,31 +784,53 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
fake.EXTRA_SPEC_SHARE, fake.INVALID_EXTRA_SPEC_COMBO,
|
||||
list(self.library.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP))
|
||||
|
||||
def test_get_provisioning_options_for_share(self):
|
||||
@ddt.data({'extra_specs': fake.EXTRA_SPEC, 'is_replica': False},
|
||||
{'extra_specs': fake.EXTRA_SPEC_WITH_QOS, 'is_replica': True},
|
||||
{'extra_specs': fake.EXTRA_SPEC, 'is_replica': False},
|
||||
{'extra_specs': fake.EXTRA_SPEC_WITH_QOS, 'is_replica': True})
|
||||
@ddt.unpack
|
||||
def test_get_provisioning_options_for_share(self, extra_specs, is_replica):
|
||||
|
||||
qos = True if fake.QOS_EXTRA_SPEC in extra_specs else False
|
||||
mock_get_extra_specs_from_share = self.mock_object(
|
||||
share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||
mock.Mock(return_value=extra_specs))
|
||||
mock_remap_standard_boolean_extra_specs = self.mock_object(
|
||||
self.library, '_remap_standard_boolean_extra_specs',
|
||||
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||
mock.Mock(return_value=extra_specs))
|
||||
mock_check_extra_specs_validity = self.mock_object(
|
||||
self.library, '_check_extra_specs_validity')
|
||||
mock_get_provisioning_options = self.mock_object(
|
||||
self.library, '_get_provisioning_options',
|
||||
mock.Mock(return_value=fake.PROVISIONING_OPTIONS))
|
||||
mock_get_normalized_qos_specs = self.mock_object(
|
||||
self.library, '_get_normalized_qos_specs',
|
||||
mock.Mock(return_value={fake.QOS_NORMALIZED_SPEC: 3000}))
|
||||
mock_create_qos_policy_group = self.mock_object(
|
||||
self.library, '_create_qos_policy_group', mock.Mock(
|
||||
return_value=fake.QOS_POLICY_GROUP_NAME))
|
||||
|
||||
result = self.library._get_provisioning_options_for_share(
|
||||
fake.EXTRA_SPEC_SHARE)
|
||||
fake.EXTRA_SPEC_SHARE, fake.VSERVER1, replica=is_replica)
|
||||
|
||||
self.assertEqual(fake.PROVISIONING_OPTIONS, result)
|
||||
if qos and is_replica:
|
||||
expected_provisioning_opts = fake.PROVISIONING_OPTIONS
|
||||
self.assertFalse(mock_create_qos_policy_group.called)
|
||||
else:
|
||||
expected_provisioning_opts = fake.PROVISIONING_OPTIONS_WITH_QOS
|
||||
mock_create_qos_policy_group.assert_called_once_with(
|
||||
fake.EXTRA_SPEC_SHARE, fake.VSERVER1,
|
||||
{fake.QOS_NORMALIZED_SPEC: 3000})
|
||||
|
||||
self.assertEqual(expected_provisioning_opts, result)
|
||||
mock_get_extra_specs_from_share.assert_called_once_with(
|
||||
fake.EXTRA_SPEC_SHARE)
|
||||
mock_remap_standard_boolean_extra_specs.assert_called_once_with(
|
||||
fake.EXTRA_SPEC)
|
||||
extra_specs)
|
||||
mock_check_extra_specs_validity.assert_called_once_with(
|
||||
fake.EXTRA_SPEC_SHARE, fake.EXTRA_SPEC)
|
||||
mock_get_provisioning_options.assert_called_once_with(fake.EXTRA_SPEC)
|
||||
fake.EXTRA_SPEC_SHARE, extra_specs)
|
||||
mock_get_provisioning_options.assert_called_once_with(extra_specs)
|
||||
mock_get_normalized_qos_specs.assert_called_once_with(extra_specs)
|
||||
|
||||
def test_get_provisioning_options(self):
|
||||
result = self.library._get_provisioning_options(fake.EXTRA_SPEC)
|
||||
@ -879,6 +910,66 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
|
||||
self.assertEqual(fake.PROVISIONING_OPTIONS_STRING_DEFAULT, result)
|
||||
|
||||
@ddt.data({}, {'foo': 'bar'}, {'netapp:maxiops': '3000'},
|
||||
{'qos': True, 'netapp:absiops': '3000'},
|
||||
{'qos': True, 'netapp:maxiops:': '3000'})
|
||||
def test_get_normalized_qos_specs_no_qos_specs(self, extra_specs):
|
||||
if 'qos' in extra_specs:
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.library._get_normalized_qos_specs,
|
||||
extra_specs)
|
||||
else:
|
||||
self.assertDictMatch(
|
||||
{}, self.library._get_normalized_qos_specs(extra_specs))
|
||||
|
||||
@ddt.data({'qos': True, 'netapp:maxiops': '3000', 'netapp:maxbps': '9000'},
|
||||
{'qos': True, 'netapp:maxiopspergib': '1000',
|
||||
'netapp:maxiops': '1000'})
|
||||
def test_get_normalized_qos_specs_multiple_qos_specs(self, extra_specs):
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.library._get_normalized_qos_specs,
|
||||
extra_specs)
|
||||
|
||||
@ddt.data({'qos': True, 'netapp:maxIOPS': '3000'},
|
||||
{'qos': True, 'netapp:MAxBPs': '3000', 'clem': 'son'},
|
||||
{'qos': True, 'netapp:maxbps': '3000', 'tig': 'ers'},
|
||||
{'qos': True, 'netapp:MAXiopSPerGib': '3000', 'kin': 'gsof'},
|
||||
{'qos': True, 'netapp:maxiopspergib': '3000', 'coll': 'ege'},
|
||||
{'qos': True, 'netapp:maxBPSperGiB': '3000', 'foot': 'ball'})
|
||||
def test_get_normalized_qos_specs(self, extra_specs):
|
||||
expected_normalized_spec = {
|
||||
key.lower().split('netapp:')[1]: value
|
||||
for key, value in extra_specs.items() if 'netapp:' in key
|
||||
}
|
||||
|
||||
qos_specs = self.library._get_normalized_qos_specs(extra_specs)
|
||||
|
||||
self.assertDictMatch(expected_normalized_spec, qos_specs)
|
||||
self.assertEqual(1, len(qos_specs))
|
||||
|
||||
@ddt.data({'qos': {'maxiops': '3000'}, 'expected': '3000iops'},
|
||||
{'qos': {'maxbps': '3000'}, 'expected': '3000B/s'},
|
||||
{'qos': {'maxbpspergib': '3000'}, 'expected': '12000B/s'},
|
||||
{'qos': {'maxiopspergib': '3000'}, 'expected': '12000iops'})
|
||||
@ddt.unpack
|
||||
def test_get_max_throughput(self, qos, expected):
|
||||
|
||||
throughput = self.library._get_max_throughput(4, qos)
|
||||
|
||||
self.assertEqual(expected, throughput)
|
||||
|
||||
def test_create_qos_policy_group(self):
|
||||
mock_qos_policy_create = self.mock_object(
|
||||
self.library._client, 'qos_policy_group_create')
|
||||
|
||||
self.library._create_qos_policy_group(
|
||||
fake.SHARE, fake.VSERVER1, {'maxiops': '3000'})
|
||||
|
||||
expected_policy_name = 'qos_share_' + fake.SHARE['id'].replace(
|
||||
'-', '_')
|
||||
mock_qos_policy_create.assert_called_once_with(
|
||||
expected_policy_name, fake.VSERVER1, max_throughput='3000iops')
|
||||
|
||||
def test_check_if_max_files_is_valid_with_negative_integer(self):
|
||||
self.assertRaises(exception.NetAppException,
|
||||
self.library._check_if_max_files_is_valid,
|
||||
@ -898,6 +989,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertRaises(exception.InvalidHost,
|
||||
self.library._allocate_container,
|
||||
fake_share,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
def test_check_aggregate_extra_specs_validity(self):
|
||||
@ -923,9 +1015,10 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
@ddt.data(None, 'fake_location')
|
||||
def test_allocate_container_from_snapshot(self, provider_location):
|
||||
|
||||
self.mock_object(
|
||||
mock_get_provisioning_opts = self.mock_object(
|
||||
self.library, '_get_provisioning_options_for_share',
|
||||
mock.Mock(return_value=copy.deepcopy(fake.PROVISIONING_OPTIONS)))
|
||||
vserver = fake.VSERVER1
|
||||
vserver_client = mock.Mock()
|
||||
|
||||
fake_snapshot = copy.deepcopy(fake.SNAPSHOT)
|
||||
@ -933,6 +1026,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
|
||||
self.library._allocate_container_from_snapshot(fake.SHARE,
|
||||
fake_snapshot,
|
||||
vserver,
|
||||
vserver_client)
|
||||
|
||||
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
|
||||
@ -940,6 +1034,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
fake.SNAPSHOT['share_id'])
|
||||
parent_snapshot_name = self.library._get_backend_snapshot_name(
|
||||
fake.SNAPSHOT['id']) if not provider_location else 'fake_location'
|
||||
mock_get_provisioning_opts.assert_called_once_with(
|
||||
fake.SHARE, fake.VSERVER1)
|
||||
vserver_client.create_volume_clone.assert_called_once_with(
|
||||
share_name, parent_share_name, parent_snapshot_name,
|
||||
thin_provisioned=True, snapshot_policy='default',
|
||||
@ -983,10 +1079,14 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
share_server=fake.SHARE_SERVER)
|
||||
|
||||
share_name = self.library._get_backend_share_name(fake.SHARE['id'])
|
||||
qos_policy_name = self.library._get_backend_qos_policy_group_name(
|
||||
fake.SHARE['id'])
|
||||
mock_share_exists.assert_called_once_with(share_name, vserver_client)
|
||||
mock_remove_export.assert_called_once_with(fake.SHARE, vserver_client)
|
||||
mock_deallocate_container.assert_called_once_with(share_name,
|
||||
vserver_client)
|
||||
(self.library._client.mark_qos_policy_group_for_deletion
|
||||
.assert_called_once_with(qos_policy_name))
|
||||
self.assertEqual(0, lib_base.LOG.info.call_count)
|
||||
|
||||
@ddt.data(exception.InvalidInput(reason='fake_reason'),
|
||||
@ -1011,6 +1111,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertFalse(mock_share_exists.called)
|
||||
self.assertFalse(mock_remove_export.called)
|
||||
self.assertFalse(mock_deallocate_container.called)
|
||||
self.assertFalse(
|
||||
self.library._client.mark_qos_policy_group_for_deletion.called)
|
||||
self.assertEqual(1, lib_base.LOG.warning.call_count)
|
||||
|
||||
def test_delete_share_not_found(self):
|
||||
@ -1035,6 +1137,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock_share_exists.assert_called_once_with(share_name, vserver_client)
|
||||
self.assertFalse(mock_remove_export.called)
|
||||
self.assertFalse(mock_deallocate_container.called)
|
||||
self.assertFalse(
|
||||
self.library._client.mark_qos_policy_group_for_deletion.called)
|
||||
self.assertEqual(1, lib_base.LOG.info.call_count)
|
||||
|
||||
def test_deallocate_container(self):
|
||||
@ -1403,6 +1507,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
'export_locations': fake.NFS_EXPORTS
|
||||
}
|
||||
mock_manage_container.assert_called_once_with(fake.SHARE,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
mock_create_export.assert_called_once_with(fake.SHARE,
|
||||
None,
|
||||
@ -1416,9 +1521,15 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_manage_container(self):
|
||||
@ddt.data(True, False)
|
||||
def test_manage_container_with_qos(self, qos):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
qos_policy_group_name = fake.QOS_POLICY_GROUP_NAME if qos else None
|
||||
extra_specs = fake.EXTRA_SPEC_WITH_QOS if qos else fake.EXTRA_SPEC
|
||||
provisioning_opts = self.library._get_provisioning_options(extra_specs)
|
||||
if qos:
|
||||
provisioning_opts['qos_policy_group'] = fake.QOS_POLICY_GROUP_NAME
|
||||
|
||||
share_to_manage = copy.deepcopy(fake.SHARE)
|
||||
share_to_manage['export_location'] = fake.EXPORT_LOCATION
|
||||
@ -1438,15 +1549,19 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
'_validate_volume_for_manage')
|
||||
self.mock_object(share_types,
|
||||
'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||
mock.Mock(return_value=extra_specs))
|
||||
mock_check_extra_specs_validity = self.mock_object(
|
||||
self.library,
|
||||
'_check_extra_specs_validity')
|
||||
mock_check_aggregate_extra_specs_validity = self.mock_object(
|
||||
self.library,
|
||||
'_check_aggregate_extra_specs_validity')
|
||||
mock_modify_or_create_qos_policy = self.mock_object(
|
||||
self.library, '_modify_or_create_qos_for_existing_share',
|
||||
mock.Mock(return_value=qos_policy_group_name))
|
||||
|
||||
result = self.library._manage_container(share_to_manage,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
mock_get_volume_to_manage.assert_called_once_with(
|
||||
@ -1454,18 +1569,19 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock_validate_volume_for_manage.assert_called_once_with(
|
||||
fake.FLEXVOL_TO_MANAGE, vserver_client)
|
||||
mock_check_extra_specs_validity.assert_called_once_with(
|
||||
share_to_manage, fake.EXTRA_SPEC)
|
||||
share_to_manage, extra_specs)
|
||||
mock_check_aggregate_extra_specs_validity.assert_called_once_with(
|
||||
fake.POOL_NAME, fake.EXTRA_SPEC)
|
||||
fake.POOL_NAME, extra_specs)
|
||||
vserver_client.unmount_volume.assert_called_once_with(
|
||||
fake.FLEXVOL_NAME)
|
||||
vserver_client.set_volume_name.assert_called_once_with(
|
||||
fake.FLEXVOL_NAME, fake.SHARE_NAME)
|
||||
vserver_client.mount_volume.assert_called_once_with(
|
||||
fake.SHARE_NAME)
|
||||
vserver_client.manage_volume.assert_called_once_with(
|
||||
fake.POOL_NAME, fake.SHARE_NAME,
|
||||
**self.library._get_provisioning_options(fake.EXTRA_SPEC))
|
||||
vserver_client.modify_volume.assert_called_once_with(
|
||||
fake.POOL_NAME, fake.SHARE_NAME, **provisioning_opts)
|
||||
mock_modify_or_create_qos_policy.assert_called_once_with(
|
||||
share_to_manage, extra_specs, fake.VSERVER1, vserver_client)
|
||||
|
||||
original_data = {
|
||||
'original_name': fake.FLEXVOL_TO_MANAGE['name'],
|
||||
@ -1494,6 +1610,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertRaises(exception.ManageInvalidShare,
|
||||
self.library._manage_container,
|
||||
share_to_manage,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
def test_manage_container_not_found(self):
|
||||
@ -1516,6 +1633,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertRaises(exception.ManageInvalidShare,
|
||||
self.library._manage_container,
|
||||
share_to_manage,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
def test_manage_container_invalid_extra_specs(self):
|
||||
@ -1545,6 +1663,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertRaises(exception.ManageExistingShareTypeMismatch,
|
||||
self.library._manage_container,
|
||||
share_to_manage,
|
||||
fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
def test_validate_volume_for_manage(self):
|
||||
@ -1782,10 +1901,12 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock_allocate_container_from_snapshot.assert_has_calls([
|
||||
mock.call(fake.COLLATED_CGSNAPSHOT_INFO[0]['share'],
|
||||
fake.COLLATED_CGSNAPSHOT_INFO[0]['snapshot'],
|
||||
fake.VSERVER1,
|
||||
vserver_client,
|
||||
mock.ANY),
|
||||
mock.call(fake.COLLATED_CGSNAPSHOT_INFO[1]['share'],
|
||||
fake.COLLATED_CGSNAPSHOT_INFO[1]['snapshot'],
|
||||
fake.VSERVER1,
|
||||
vserver_client,
|
||||
mock.ANY),
|
||||
])
|
||||
@ -2034,6 +2155,68 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock_get_vserver.assert_called_once_with(
|
||||
share_server=fake.SHARE_SERVER)
|
||||
|
||||
def test_adjust_qos_policy_with_volume_resize_no_cluster_creds(self):
|
||||
self.library._have_cluster_creds = False
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share')
|
||||
|
||||
retval = self.library._adjust_qos_policy_with_volume_resize(
|
||||
fake.SHARE, 10, mock.Mock())
|
||||
|
||||
self.assertIsNone(retval)
|
||||
share_types.get_extra_specs_from_share.assert_not_called()
|
||||
|
||||
def test_adjust_qos_policy_with_volume_resize_no_qos_on_share(self):
|
||||
self.library._have_cluster_creds = True
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share')
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(vserver_client, 'get_volume',
|
||||
mock.Mock(return_value=fake.FLEXVOL_WITHOUT_QOS))
|
||||
|
||||
retval = self.library._adjust_qos_policy_with_volume_resize(
|
||||
fake.SHARE, 10, vserver_client)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
share_types.get_extra_specs_from_share.assert_not_called()
|
||||
|
||||
def test_adjust_qos_policy_with_volume_resize_no_size_dependent_qos(self):
|
||||
self.library._have_cluster_creds = True
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=fake.EXTRA_SPEC_WITH_QOS))
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(vserver_client, 'get_volume',
|
||||
mock.Mock(return_value=fake.FLEXVOL_WITH_QOS))
|
||||
self.mock_object(self.library, '_get_max_throughput')
|
||||
self.mock_object(self.library._client, 'qos_policy_group_modify')
|
||||
|
||||
retval = self.library._adjust_qos_policy_with_volume_resize(
|
||||
fake.SHARE, 10, vserver_client)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
share_types.get_extra_specs_from_share.assert_called_once_with(
|
||||
fake.SHARE)
|
||||
self.library._get_max_throughput.assert_not_called()
|
||||
self.library._client.qos_policy_group_modify.assert_not_called()
|
||||
|
||||
def test_adjust_qos_policy_with_volume_resize(self):
|
||||
self.library._have_cluster_creds = True
|
||||
self.mock_object(
|
||||
share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=fake.EXTRA_SPEC_WITH_SIZE_DEPENDENT_QOS))
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(vserver_client, 'get_volume',
|
||||
mock.Mock(return_value=fake.FLEXVOL_WITH_QOS))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_modify')
|
||||
|
||||
retval = self.library._adjust_qos_policy_with_volume_resize(
|
||||
fake.SHARE, 10, vserver_client)
|
||||
|
||||
expected_max_throughput = '10000B/s'
|
||||
self.assertIsNone(retval)
|
||||
share_types.get_extra_specs_from_share.assert_called_once_with(
|
||||
fake.SHARE)
|
||||
self.library._client.qos_policy_group_modify.assert_called_once_with(
|
||||
fake.QOS_POLICY_GROUP_NAME, expected_max_throughput)
|
||||
|
||||
def test_extend_share(self):
|
||||
|
||||
vserver_client = mock.Mock()
|
||||
@ -2041,6 +2224,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
vserver_client)))
|
||||
mock_adjust_qos_policy = self.mock_object(
|
||||
self.library, '_adjust_qos_policy_with_volume_resize')
|
||||
|
||||
mock_set_volume_size = self.mock_object(vserver_client,
|
||||
'set_volume_size')
|
||||
new_size = fake.SHARE['size'] * 2
|
||||
@ -2048,6 +2234,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.library.extend_share(fake.SHARE, new_size)
|
||||
|
||||
mock_set_volume_size.assert_called_once_with(fake.SHARE_NAME, new_size)
|
||||
mock_adjust_qos_policy.assert_called_once_with(
|
||||
fake.SHARE, new_size, vserver_client)
|
||||
|
||||
def test_shrink_share(self):
|
||||
|
||||
@ -2056,6 +2244,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
vserver_client)))
|
||||
mock_adjust_qos_policy = self.mock_object(
|
||||
self.library, '_adjust_qos_policy_with_volume_resize')
|
||||
mock_set_volume_size = self.mock_object(vserver_client,
|
||||
'set_volume_size')
|
||||
new_size = fake.SHARE['size'] - 1
|
||||
@ -2063,6 +2253,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.library.shrink_share(fake.SHARE, new_size)
|
||||
|
||||
mock_set_volume_size.assert_called_once_with(fake.SHARE_NAME, new_size)
|
||||
mock_adjust_qos_policy.assert_called_once_with(
|
||||
fake.SHARE, new_size, vserver_client)
|
||||
|
||||
def test_update_access(self):
|
||||
|
||||
@ -2774,6 +2966,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock.Mock(return_value=mock.Mock()))
|
||||
self.mock_object(self.library, '_create_export',
|
||||
mock.Mock(return_value='fake_export_location'))
|
||||
self.mock_object(self.library, '_handle_qos_on_replication_change')
|
||||
|
||||
replicas = self.library.promote_replica(
|
||||
None, [self.fake_replica, self.fake_replica_2],
|
||||
@ -2783,7 +2976,6 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.fake_replica, self.fake_replica, self.fake_replica_2,
|
||||
mock.ANY
|
||||
)
|
||||
|
||||
self.assertEqual(2, len(replicas))
|
||||
actual_replica_1 = list(filter(
|
||||
lambda x: x['id'] == self.fake_replica['id'], replicas))[0]
|
||||
@ -2797,6 +2989,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
actual_replica_2['export_locations'])
|
||||
self.assertEqual(constants.STATUS_ACTIVE,
|
||||
actual_replica_2['access_rules_status'])
|
||||
self.library._handle_qos_on_replication_change.assert_called_once()
|
||||
|
||||
def test_promote_replica_destination_unreachable(self):
|
||||
self.mock_object(self.library,
|
||||
@ -2806,6 +2999,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.mock_object(self.library,
|
||||
'_get_helper',
|
||||
mock.Mock(return_value=mock.Mock()))
|
||||
self.mock_object(self.library, '_handle_qos_on_replication_change')
|
||||
|
||||
self.mock_object(self.library, '_create_export',
|
||||
mock.Mock(return_value='fake_export_location'))
|
||||
self.mock_object(
|
||||
@ -2822,6 +3017,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
actual_replica['replica_state'])
|
||||
self.assertEqual(constants.STATUS_ERROR,
|
||||
actual_replica['status'])
|
||||
self.assertFalse(
|
||||
self.library._handle_qos_on_replication_change.called)
|
||||
|
||||
def test_promote_replica_more_than_two_replicas(self):
|
||||
fake_replica_3 = copy.deepcopy(self.fake_replica_2)
|
||||
@ -2831,6 +3028,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
mock.Mock())))
|
||||
self.mock_object(self.library, '_handle_qos_on_replication_change')
|
||||
self.mock_object(self.library,
|
||||
'_get_helper',
|
||||
mock.Mock(return_value=mock.Mock()))
|
||||
@ -2864,12 +3062,14 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
lambda x: x['id'] == fake_replica_3['id'], replicas))[0]
|
||||
self.assertEqual(constants.REPLICA_STATE_OUT_OF_SYNC,
|
||||
actual_replica_3['replica_state'])
|
||||
self.library._handle_qos_on_replication_change.assert_called_once()
|
||||
|
||||
def test_promote_replica_with_access_rules(self):
|
||||
self.mock_object(self.library,
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
mock.Mock())))
|
||||
self.mock_object(self.library, '_handle_qos_on_replication_change')
|
||||
mock_helper = mock.Mock()
|
||||
self.mock_object(self.library,
|
||||
'_get_helper',
|
||||
@ -2891,6 +3091,112 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock_helper.update_access.assert_called_once_with(self.fake_replica_2,
|
||||
share_name,
|
||||
[fake.SHARE_ACCESS])
|
||||
self.library._handle_qos_on_replication_change.assert_called_once()
|
||||
|
||||
@ddt.data({'extra_specs': {'netapp:snapshot_policy': 'none'},
|
||||
'have_cluster_creds': True},
|
||||
# Test Case 2 isn't possible input
|
||||
{'extra_specs': {'qos': True, 'netapp:maxiops': '3000'},
|
||||
'have_cluster_creds': False})
|
||||
@ddt.unpack
|
||||
def test_handle_qos_on_replication_change_nothing_to_handle(
|
||||
self, extra_specs, have_cluster_creds):
|
||||
|
||||
self.library._have_cluster_creds = have_cluster_creds
|
||||
self.mock_object(lib_base.LOG, 'exception')
|
||||
self.mock_object(lib_base.LOG, 'info')
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=extra_specs))
|
||||
|
||||
retval = self.library._handle_qos_on_replication_change(
|
||||
self.mock_dm_session, self.fake_replica_2, self.fake_replica,
|
||||
share_server=fake.SHARE_SERVER)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
lib_base.LOG.exception.assert_not_called()
|
||||
lib_base.LOG.info.assert_not_called()
|
||||
|
||||
def test_handle_qos_on_replication_change_exception(self):
|
||||
self.library._have_cluster_creds = True
|
||||
extra_specs = {'qos': True, fake.QOS_EXTRA_SPEC: '3000'}
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(lib_base.LOG, 'exception')
|
||||
self.mock_object(lib_base.LOG, 'info')
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=extra_specs))
|
||||
self.mock_object(self.library, '_get_vserver', mock.Mock(
|
||||
return_value=(fake.VSERVER1, vserver_client)))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_exists',
|
||||
mock.Mock(return_value=True))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_modify',
|
||||
mock.Mock(side_effect=netapp_api.NaApiError))
|
||||
|
||||
retval = self.library._handle_qos_on_replication_change(
|
||||
self.mock_dm_session, self.fake_replica_2, self.fake_replica,
|
||||
share_server=fake.SHARE_SERVER)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
(self.mock_dm_session.remove_qos_on_old_active_replica
|
||||
.assert_called_once_with(self.fake_replica))
|
||||
lib_base.LOG.exception.assert_called_once()
|
||||
lib_base.LOG.info.assert_not_called()
|
||||
vserver_client.set_qos_policy_group_for_volume.assert_not_called()
|
||||
|
||||
def test_handle_qos_on_replication_change_modify_existing_policy(self):
|
||||
self.library._have_cluster_creds = True
|
||||
extra_specs = {'qos': True, fake.QOS_EXTRA_SPEC: '3000'}
|
||||
vserver_client = mock.Mock()
|
||||
volume_name_on_backend = self.library._get_backend_share_name(
|
||||
self.fake_replica_2['id'])
|
||||
self.mock_object(lib_base.LOG, 'exception')
|
||||
self.mock_object(lib_base.LOG, 'info')
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=extra_specs))
|
||||
self.mock_object(self.library, '_get_vserver', mock.Mock(
|
||||
return_value=(fake.VSERVER1, vserver_client)))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_exists',
|
||||
mock.Mock(return_value=True))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_modify')
|
||||
self.mock_object(self.library, '_create_qos_policy_group')
|
||||
|
||||
retval = self.library._handle_qos_on_replication_change(
|
||||
self.mock_dm_session, self.fake_replica_2, self.fake_replica,
|
||||
share_server=fake.SHARE_SERVER)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
self.library._client.qos_policy_group_modify.assert_called_once_with(
|
||||
'qos_' + volume_name_on_backend, '3000iops')
|
||||
vserver_client.set_qos_policy_group_for_volume.assert_called_once_with(
|
||||
volume_name_on_backend, 'qos_' + volume_name_on_backend)
|
||||
self.library._create_qos_policy_group.assert_not_called()
|
||||
lib_base.LOG.exception.assert_not_called()
|
||||
lib_base.LOG.info.assert_called_once()
|
||||
|
||||
def test_handle_qos_on_replication_change_create_new_policy(self):
|
||||
self.library._have_cluster_creds = True
|
||||
extra_specs = {'qos': True, fake.QOS_EXTRA_SPEC: '3000'}
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(lib_base.LOG, 'exception')
|
||||
self.mock_object(lib_base.LOG, 'info')
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=extra_specs))
|
||||
self.mock_object(self.library, '_get_vserver', mock.Mock(
|
||||
return_value=(fake.VSERVER1, vserver_client)))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_exists',
|
||||
mock.Mock(return_value=False))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_modify')
|
||||
self.mock_object(self.library, '_create_qos_policy_group')
|
||||
|
||||
retval = self.library._handle_qos_on_replication_change(
|
||||
self.mock_dm_session, self.fake_replica_2, self.fake_replica,
|
||||
share_server=fake.SHARE_SERVER)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
self.library._create_qos_policy_group.assert_called_once_with(
|
||||
self.fake_replica_2, fake.VSERVER1, {'maxiops': '3000'})
|
||||
self.library._client.qos_policy_group_modify.assert_not_called()
|
||||
lib_base.LOG.exception.assert_not_called()
|
||||
lib_base.LOG.info.assert_called_once()
|
||||
|
||||
def test_convert_destination_replica_to_independent(self):
|
||||
self.mock_object(self.library,
|
||||
@ -2958,6 +3264,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
'_get_vserver',
|
||||
mock.Mock(return_value=(fake.VSERVER1,
|
||||
mock.Mock())))
|
||||
self.mock_object(self.library, '_handle_qos_on_replication_change')
|
||||
self.mock_object(self.library,
|
||||
'_get_helper',
|
||||
mock.Mock(return_value=fake_helper))
|
||||
@ -2986,6 +3293,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
actual_replica_2['export_locations'])
|
||||
self.assertEqual(constants.SHARE_INSTANCE_RULES_SYNCING,
|
||||
actual_replica_2['access_rules_status'])
|
||||
self.library._handle_qos_on_replication_change.assert_called_once()
|
||||
|
||||
def test_convert_destination_replica_to_independent_with_access_rules(
|
||||
self):
|
||||
@ -4353,9 +4661,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
mock.Mock(side_effect=vol_move_side_effects))
|
||||
self.mock_object(share_types, 'get_extra_specs_from_share',
|
||||
mock.Mock(return_value=fake.EXTRA_SPEC))
|
||||
self.mock_object(self.library, '_get_provisioning_options',
|
||||
mock.Mock(return_value=fake.PROVISIONING_OPTIONS))
|
||||
self.mock_object(vserver_client, 'manage_volume')
|
||||
self.mock_object(
|
||||
self.library, '_get_provisioning_options',
|
||||
mock.Mock(return_value=fake.PROVISIONING_OPTIONS_WITH_QOS))
|
||||
self.mock_object(
|
||||
self.library, '_modify_or_create_qos_for_existing_share',
|
||||
mock.Mock(return_value=fake.QOS_POLICY_GROUP_NAME))
|
||||
self.mock_object(vserver_client, 'modify_volume')
|
||||
|
||||
src_share = fake_share.fake_share_instance(id='source-share-instance')
|
||||
dest_share = fake_share.fake_share_instance(id='dest-share-instance')
|
||||
@ -4378,8 +4690,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.library._create_export.assert_called_once_with(
|
||||
dest_share, fake.SHARE_SERVER, fake.VSERVER1, vserver_client,
|
||||
clear_current_export_policy=False)
|
||||
vserver_client.manage_volume.assert_called_once_with(
|
||||
dest_aggr, 'new_share_name', **fake.PROVISIONING_OPTIONS)
|
||||
vserver_client.modify_volume.assert_called_once_with(
|
||||
dest_aggr, 'new_share_name', **fake.PROVISIONING_OPTIONS_WITH_QOS)
|
||||
mock_info_log.assert_called_once()
|
||||
if phase != 'completed':
|
||||
self.assertEqual(2, mock_warning_log.call_count)
|
||||
@ -4389,3 +4701,81 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertFalse(mock_warning_log.called)
|
||||
mock_debug_log.assert_called_once()
|
||||
mock_move_status_check.assert_called_once()
|
||||
|
||||
def test_modify_or_create_qos_for_existing_share_no_qos_extra_specs(self):
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(self.library, '_get_backend_qos_policy_group_name')
|
||||
self.mock_object(vserver_client, 'get_volume')
|
||||
self.mock_object(self.library, '_create_qos_policy_group')
|
||||
|
||||
retval = self.library._modify_or_create_qos_for_existing_share(
|
||||
fake.SHARE, fake.EXTRA_SPEC, fake.VSERVER1, vserver_client)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
self.library._get_backend_qos_policy_group_name.assert_not_called()
|
||||
vserver_client.get_volume.assert_not_called()
|
||||
self.library._create_qos_policy_group.assert_not_called()
|
||||
|
||||
def test_modify_or_create_qos_for_existing_share_no_existing_qos(self):
|
||||
vserver_client = mock.Mock()
|
||||
self.mock_object(self.library, '_get_backend_qos_policy_group_name')
|
||||
self.mock_object(vserver_client, 'get_volume',
|
||||
mock.Mock(return_value=fake.FLEXVOL_WITHOUT_QOS))
|
||||
self.mock_object(self.library, '_create_qos_policy_group')
|
||||
self.mock_object(self.library._client, 'qos_policy_group_modify')
|
||||
qos_policy_name = self.library._get_backend_qos_policy_group_name(
|
||||
fake.SHARE['id'])
|
||||
|
||||
retval = self.library._modify_or_create_qos_for_existing_share(
|
||||
fake.SHARE, fake.EXTRA_SPEC_WITH_QOS, fake.VSERVER1,
|
||||
vserver_client)
|
||||
|
||||
share_obj = {
|
||||
'size': 2,
|
||||
'id': fake.SHARE['id'],
|
||||
}
|
||||
self.assertEqual(qos_policy_name, retval)
|
||||
self.library._client.qos_policy_group_modify.assert_not_called()
|
||||
self.library._create_qos_policy_group.assert_called_once_with(
|
||||
share_obj, fake.VSERVER1, {'maxiops': '3000'})
|
||||
|
||||
@ddt.data(utils.annotated('volume_has_shared_qos_policy', (2, )),
|
||||
utils.annotated('volume_has_nonshared_qos_policy', (1, )))
|
||||
def test_modify_or_create_qos_for_existing_share(self, num_workloads):
|
||||
vserver_client = mock.Mock()
|
||||
num_workloads = num_workloads[0]
|
||||
qos_policy = copy.deepcopy(fake.QOS_POLICY_GROUP)
|
||||
qos_policy['num-workloads'] = num_workloads
|
||||
extra_specs = fake.EXTRA_SPEC_WITH_QOS
|
||||
self.mock_object(vserver_client, 'get_volume',
|
||||
mock.Mock(return_value=fake.FLEXVOL_WITH_QOS))
|
||||
self.mock_object(self.library._client, 'qos_policy_group_get',
|
||||
mock.Mock(return_value=qos_policy))
|
||||
mock_qos_policy_modify = self.mock_object(
|
||||
self.library._client, 'qos_policy_group_modify')
|
||||
mock_qos_policy_rename = self.mock_object(
|
||||
self.library._client, 'qos_policy_group_rename')
|
||||
mock_create_qos_policy = self.mock_object(
|
||||
self.library, '_create_qos_policy_group')
|
||||
new_qos_policy_name = self.library._get_backend_qos_policy_group_name(
|
||||
fake.SHARE['id'])
|
||||
|
||||
retval = self.library._modify_or_create_qos_for_existing_share(
|
||||
fake.SHARE, extra_specs, fake.VSERVER1, vserver_client)
|
||||
|
||||
self.assertEqual(new_qos_policy_name, retval)
|
||||
if num_workloads == 1:
|
||||
mock_create_qos_policy.assert_not_called()
|
||||
mock_qos_policy_modify.assert_called_once_with(
|
||||
fake.QOS_POLICY_GROUP_NAME, '3000iops')
|
||||
mock_qos_policy_rename.assert_called_once_with(
|
||||
fake.QOS_POLICY_GROUP_NAME, new_qos_policy_name)
|
||||
else:
|
||||
share_obj = {
|
||||
'size': 2,
|
||||
'id': fake.SHARE['id'],
|
||||
}
|
||||
mock_create_qos_policy.assert_called_once_with(
|
||||
share_obj, fake.VSERVER1, {'maxiops': '3000'})
|
||||
self.library._client.qos_policy_group_modify.assert_not_called()
|
||||
self.library._client.qos_policy_group_rename.assert_not_called()
|
||||
|
@ -165,8 +165,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
}
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_handle_housekeeping_tasks(self):
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_handle_housekeeping_tasks_with_cluster_creds(self, have_creds):
|
||||
self.library._have_cluster_creds = have_creds
|
||||
mock_vserver_client = mock.Mock()
|
||||
self.mock_object(self.library,
|
||||
'_get_api_client',
|
||||
@ -179,6 +180,9 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
|
||||
self.assertTrue(
|
||||
mock_vserver_client.prune_deleted_nfs_export_policies.called)
|
||||
self.assertTrue(mock_vserver_client.prune_deleted_snapshots.called)
|
||||
self.assertIs(
|
||||
have_creds,
|
||||
mock_vserver_client.remove_unused_qos_policy_groups.called)
|
||||
self.assertTrue(mock_super.called)
|
||||
|
||||
@ddt.data(True, False)
|
||||
|
@ -71,6 +71,10 @@ MTU = 1234
|
||||
DEFAULT_MTU = 1500
|
||||
MANILA_HOST_NAME = '%(host)s@%(backend)s#%(pool)s' % {
|
||||
'host': HOST_NAME, 'backend': BACKEND_NAME, 'pool': POOL_NAME}
|
||||
QOS_EXTRA_SPEC = 'netapp:maxiops'
|
||||
QOS_SIZE_DEPENDENT_EXTRA_SPEC = 'netapp:maxbpspergib'
|
||||
QOS_NORMALIZED_SPEC = 'maxiops'
|
||||
QOS_POLICY_GROUP_NAME = 'fake_qos_policy_group_name'
|
||||
|
||||
CLIENT_KWARGS = {
|
||||
'username': 'admin',
|
||||
@ -97,6 +101,7 @@ SHARE = {
|
||||
},
|
||||
'replica_state': constants.REPLICA_STATE_ACTIVE,
|
||||
'status': constants.STATUS_AVAILABLE,
|
||||
'share_server': None,
|
||||
}
|
||||
|
||||
FLEXVOL_TO_MANAGE = {
|
||||
@ -105,7 +110,19 @@ FLEXVOL_TO_MANAGE = {
|
||||
'name': FLEXVOL_NAME,
|
||||
'type': 'rw',
|
||||
'style': 'flex',
|
||||
'size': '1610612736', # rounds down to 1 GB
|
||||
'size': '1610612736', # rounds up to 2 GB
|
||||
}
|
||||
|
||||
FLEXVOL_WITHOUT_QOS = copy.deepcopy(FLEXVOL_TO_MANAGE)
|
||||
FLEXVOL_WITHOUT_QOS.update({'qos-policy-group-name': None})
|
||||
FLEXVOL_WITH_QOS = copy.deepcopy(FLEXVOL_TO_MANAGE)
|
||||
FLEXVOL_WITH_QOS.update({'qos-policy-group-name': QOS_POLICY_GROUP_NAME})
|
||||
|
||||
QOS_POLICY_GROUP = {
|
||||
'policy-group': QOS_POLICY_GROUP_NAME,
|
||||
'vserver': VSERVER1,
|
||||
'max-throughput': '3000iops',
|
||||
'num-workloads': 1,
|
||||
}
|
||||
|
||||
EXTRA_SPEC = {
|
||||
@ -120,6 +137,18 @@ EXTRA_SPEC = {
|
||||
'netapp_raid_type': 'raid4',
|
||||
}
|
||||
|
||||
EXTRA_SPEC_WITH_QOS = copy.deepcopy(EXTRA_SPEC)
|
||||
EXTRA_SPEC_WITH_QOS.update({
|
||||
'qos': True,
|
||||
QOS_EXTRA_SPEC: '3000',
|
||||
})
|
||||
|
||||
EXTRA_SPEC_WITH_SIZE_DEPENDENT_QOS = copy.deepcopy(EXTRA_SPEC)
|
||||
EXTRA_SPEC_WITH_SIZE_DEPENDENT_QOS.update({
|
||||
'qos': True,
|
||||
QOS_SIZE_DEPENDENT_EXTRA_SPEC: '1000',
|
||||
})
|
||||
|
||||
PROVISIONING_OPTIONS = {
|
||||
'thin_provisioned': True,
|
||||
'snapshot_policy': 'default',
|
||||
@ -130,6 +159,10 @@ PROVISIONING_OPTIONS = {
|
||||
'split': True,
|
||||
}
|
||||
|
||||
PROVISIONING_OPTIONS_WITH_QOS = copy.deepcopy(PROVISIONING_OPTIONS)
|
||||
PROVISIONING_OPTIONS_WITH_QOS.update(
|
||||
{'qos_policy_group': QOS_POLICY_GROUP_NAME})
|
||||
|
||||
PROVISIONING_OPTIONS_BOOLEAN = {
|
||||
'thin_provisioned': True,
|
||||
'dedup_enabled': False,
|
||||
@ -596,6 +629,7 @@ POOLS = [
|
||||
'snapshot_support': True,
|
||||
'create_share_from_snapshot_support': True,
|
||||
'revert_to_snapshot_support': True,
|
||||
'qos': True,
|
||||
},
|
||||
{
|
||||
'pool_name': AGGREGATES[1],
|
||||
@ -617,6 +651,7 @@ POOLS = [
|
||||
'snapshot_support': True,
|
||||
'create_share_from_snapshot_support': True,
|
||||
'revert_to_snapshot_support': True,
|
||||
'qos': True,
|
||||
},
|
||||
]
|
||||
|
||||
@ -638,6 +673,7 @@ POOLS_VSERVER_CREDS = [
|
||||
'snapshot_support': True,
|
||||
'create_share_from_snapshot_support': True,
|
||||
'revert_to_snapshot_support': True,
|
||||
'qos': False,
|
||||
},
|
||||
{
|
||||
'pool_name': AGGREGATES[1],
|
||||
@ -656,6 +692,7 @@ POOLS_VSERVER_CREDS = [
|
||||
'snapshot_support': True,
|
||||
'create_share_from_snapshot_support': True,
|
||||
'revert_to_snapshot_support': True,
|
||||
'qos': False,
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
---
|
||||
features:
|
||||
- The NetApp driver now supports Quality of Service extra specs. To create
|
||||
a share on ONTAP with qos support, set the 'qos' extra-spec in your
|
||||
share type to True and use one of 'netapp:maxiops' and 'netapp:maxbps'
|
||||
scoped extra-specs to set absolute limits. To set size based limits,
|
||||
use scoped extra-specs 'netapp:maxiopspergib' or 'netapp:maxbpspergib'.
|
||||
QoS policies on the back end are created exclusive to each manila share.
|
||||
upgrade:
|
||||
- A new configuration option 'netapp_qos_policy_group_name_template' has
|
||||
been added to allow overriding the naming of QoS policies created by the
|
||||
NetApp driver.
|
Loading…
Reference in New Issue
Block a user