cinder/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py

330 lines
12 KiB
Python

# Copyright (c) 2016 Clinton Knight. All rights reserved.
# Copyright (c) 2017 Jose Porrua. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Storage service catalog (SSC) functions and classes for NetApp cDOT systems.
"""
import copy
import re
from oslo_log import log as logging
import six
from cinder import exception
from cinder.i18n import _
LOG = logging.getLogger(__name__)
# NOTE(cknight): The keys in this map are tuples that contain arguments needed
# for efficient use of the system-user-capability-get-iter cDOT API. The
# values are SSC extra specs associated with the APIs listed in the keys.
SSC_API_MAP = {
('storage.aggregate', 'show', 'aggr-options-list-info'): [
'netapp_raid_type',
],
('storage.disk', 'show', 'storage-disk-get-iter'): [
'netapp_disk_type',
],
('snapmirror', 'show', 'snapmirror-get-iter'): [
'netapp_mirrored',
],
('volume.efficiency', 'show', 'sis-get-iter'): [
'netapp_dedup',
'netapp_compression',
],
('volume', '*show', 'volume-get-iter'): [
'netapp_flexvol_encryption',
],
}
class CapabilitiesLibrary(object):
def __init__(self, protocol, vserver_name, zapi_client, configuration):
self.protocol = protocol.lower()
self.vserver_name = vserver_name
self.zapi_client = zapi_client
self.configuration = configuration
self.backend_name = self.configuration.safe_get('volume_backend_name')
self.ssc = {}
self.invalid_extra_specs = []
def check_api_permissions(self):
"""Check which APIs that support SSC functionality are available."""
inaccessible_apis = []
invalid_extra_specs = []
for api_tuple, extra_specs in SSC_API_MAP.items():
object_name, operation_name, api = api_tuple
if not self.zapi_client.check_cluster_api(object_name,
operation_name,
api):
inaccessible_apis.append(api)
invalid_extra_specs.extend(extra_specs)
if inaccessible_apis:
if 'volume-get-iter' in inaccessible_apis:
msg = _('User not permitted to query Data ONTAP volumes.')
raise exception.VolumeBackendAPIException(data=msg)
else:
LOG.warning('The configured user account does not have '
'sufficient privileges to use all needed '
'APIs. The following extra specs will fail '
'or be ignored: %s.', invalid_extra_specs)
self.invalid_extra_specs = invalid_extra_specs
def cluster_user_supported(self):
return not self.invalid_extra_specs
def get_ssc(self):
"""Get a copy of the Storage Service Catalog."""
return copy.deepcopy(self.ssc)
def get_ssc_flexvol_names(self):
"""Get the names of the FlexVols in the Storage Service Catalog."""
ssc = self.get_ssc()
return list(ssc.keys())
def get_ssc_for_flexvol(self, flexvol_name):
"""Get map of Storage Service Catalog entries for a single flexvol."""
return copy.deepcopy(self.ssc.get(flexvol_name, {}))
def get_ssc_aggregates(self):
"""Get a list of aggregates for all SSC flexvols."""
aggregates = set()
for __, flexvol_info in self.ssc.items():
if 'netapp_aggregate' in flexvol_info:
aggregates.add(flexvol_info['netapp_aggregate'])
return list(aggregates)
def is_qos_min_supported(self, pool_name):
for __, flexvol_info in self.ssc.items():
if ('netapp_qos_min_support' in flexvol_info and
'pool_name' in flexvol_info and
flexvol_info['pool_name'] == pool_name):
return flexvol_info['netapp_qos_min_support'] == 'true'
return False
def update_ssc(self, flexvol_map):
"""Periodically runs to update Storage Service Catalog data.
The self.ssc attribute is updated with the following format.
{<flexvol_name> : {<ssc_key>: <ssc_value>}}
"""
LOG.info("Updating storage service catalog information for "
"backend '%s'", self.backend_name)
ssc = {}
for flexvol_name, flexvol_info in flexvol_map.items():
ssc_volume = {}
# Add metadata passed from the driver, including pool name
ssc_volume.update(flexvol_info)
# Get volume info
ssc_volume.update(self._get_ssc_flexvol_info(flexvol_name))
ssc_volume.update(self._get_ssc_dedupe_info(flexvol_name))
ssc_volume.update(self._get_ssc_mirror_info(flexvol_name))
ssc_volume.update(self._get_ssc_encryption_info(flexvol_name))
# Get aggregate info
aggregate_name = ssc_volume.get('netapp_aggregate')
aggr_info = self._get_ssc_aggregate_info(aggregate_name)
node_name = aggr_info.pop('netapp_node_name')
ssc_volume.update(aggr_info)
ssc_volume.update(self._get_ssc_qos_min_info(node_name))
ssc[flexvol_name] = ssc_volume
self.ssc = ssc
def _update_for_failover(self, zapi_client, flexvol_map):
self.zapi_client = zapi_client
self.update_ssc(flexvol_map)
def _get_ssc_flexvol_info(self, flexvol_name):
"""Gather flexvol info and recast into SSC-style volume stats."""
volume_info = self.zapi_client.get_flexvol(flexvol_name=flexvol_name)
netapp_thick = (volume_info.get('space-guarantee-enabled') and
(volume_info.get('space-guarantee') == 'file' or
volume_info.get('space-guarantee') == 'volume'))
thick = self._get_thick_provisioning_support(netapp_thick)
return {
'netapp_thin_provisioned': six.text_type(not netapp_thick).lower(),
'thick_provisioning_support': thick,
'thin_provisioning_support': not thick,
'netapp_aggregate': volume_info.get('aggregate'),
}
def _get_thick_provisioning_support(self, netapp_thick):
"""Get standard thick/thin values for a flexvol.
The values reported for the standard thick_provisioning_support and
thin_provisioning_support flags depend on both the flexvol state as
well as protocol-specific configuration values.
"""
if self.protocol == 'nfs':
return (netapp_thick and
not self.configuration.nfs_sparsed_volumes)
else:
return (netapp_thick and
(self.configuration.netapp_lun_space_reservation ==
'enabled'))
def _get_ssc_dedupe_info(self, flexvol_name):
"""Gather dedupe info and recast into SSC-style volume stats."""
if ('netapp_dedup' in self.invalid_extra_specs or
'netapp_compression' in self.invalid_extra_specs):
dedupe = False
compression = False
else:
dedupe_info = self.zapi_client.get_flexvol_dedupe_info(
flexvol_name)
dedupe = dedupe_info.get('dedupe')
compression = dedupe_info.get('compression')
return {
'netapp_dedup': six.text_type(dedupe).lower(),
'netapp_compression': six.text_type(compression).lower(),
}
def _get_ssc_encryption_info(self, flexvol_name):
"""Gather flexvol encryption info and recast into SSC-style stats."""
encrypted = self.zapi_client.is_flexvol_encrypted(
flexvol_name, self.vserver_name)
return {'netapp_flexvol_encryption': six.text_type(encrypted).lower()}
def _get_ssc_qos_min_info(self, node_name):
"""Gather Qos minimum info and recast into SSC-style stats."""
supported = self.zapi_client.is_qos_min_supported(
self.protocol == 'nfs', node_name)
return {'netapp_qos_min_support': six.text_type(supported).lower()}
def _get_ssc_mirror_info(self, flexvol_name):
"""Gather SnapMirror info and recast into SSC-style volume stats."""
mirrored = self.zapi_client.is_flexvol_mirrored(
flexvol_name, self.vserver_name)
return {'netapp_mirrored': six.text_type(mirrored).lower()}
def _get_ssc_aggregate_info(self, aggregate_name):
"""Gather aggregate info and recast into SSC-style volume stats."""
if 'netapp_raid_type' in self.invalid_extra_specs:
raid_type = None
hybrid = None
disk_types = None
node_name = None
else:
aggregate = self.zapi_client.get_aggregate(aggregate_name)
node_name = aggregate.get('node-name')
raid_type = aggregate.get('raid-type')
hybrid = (six.text_type(aggregate.get('is-hybrid')).lower()
if 'is-hybrid' in aggregate else None)
disk_types = self.zapi_client.get_aggregate_disk_types(
aggregate_name)
return {
'netapp_raid_type': raid_type,
'netapp_hybrid_aggregate': hybrid,
'netapp_disk_type': disk_types,
'netapp_node_name': node_name,
}
def get_matching_flexvols_for_extra_specs(self, extra_specs):
"""Return a list of flexvol names that match a set of extra specs."""
extra_specs = self._modify_extra_specs_for_comparison(extra_specs)
matching_flexvols = []
for flexvol_name, flexvol_info in self.get_ssc().items():
if self._flexvol_matches_extra_specs(flexvol_info, extra_specs):
matching_flexvols.append(flexvol_name)
return matching_flexvols
def _flexvol_matches_extra_specs(self, flexvol_info, extra_specs):
"""Check whether the SSC data for a FlexVol matches extra specs.
A set of extra specs is considered a match for a FlexVol if, for each
extra spec, the value matches what is in SSC or the key is unknown to
SSC.
"""
for extra_spec_key, extra_spec_value in extra_specs.items():
if extra_spec_key in flexvol_info:
if not self._extra_spec_matches(extra_spec_value,
flexvol_info[extra_spec_key]):
return False
return True
def _extra_spec_matches(self, extra_spec_value, ssc_flexvol_value):
"""Check whether an extra spec value matches something in the SSC.
The SSC values may be scalars or lists, so the extra spec value must be
compared to the SSC value if it is a scalar, or it must be sought in
the list.
"""
if isinstance(ssc_flexvol_value, list):
return extra_spec_value in ssc_flexvol_value
else:
return extra_spec_value == ssc_flexvol_value
def _modify_extra_specs_for_comparison(self, extra_specs):
"""Adjust extra spec values for simple comparison to SSC values.
Most extra-spec key-value tuples may be directly compared. But the
boolean values that take the form '<is> True' or '<is> False' must be
modified to allow comparison with the values we keep in the SSC and
report to the scheduler.
"""
modified_extra_specs = copy.deepcopy(extra_specs)
for key, value in extra_specs.items():
if isinstance(value, six.string_types):
if re.match(r'<is>\s+True', value, re.I):
modified_extra_specs[key] = True
elif re.match(r'<is>\s+False', value, re.I):
modified_extra_specs[key] = False
return modified_extra_specs