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

341 lines
13 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
LOG = logging.getLogger(__name__)
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):
self.invalid_extra_specs = self.zapi_client.check_api_permissions()
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:
aggr = flexvol_info['netapp_aggregate']
if isinstance(aggr, list):
aggregates.update(aggr)
else:
aggregates.add(aggr)
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')
is_flexgroup = isinstance(aggregate_name, list)
aggr_info = self._get_ssc_aggregate_info(
aggregate_name, is_flexgroup=is_flexgroup)
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)
is_flexgroup = volume_info.get('style-extended') == 'flexgroup'
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'),
'netapp_is_flexgroup': six.text_type(is_flexgroup).lower(),
}
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 = True
is_nfs = self.protocol == 'nfs'
if isinstance(node_name, list):
# NOTE(felipe_rodrigues): it cannot choose which node the volume
# is created, so the pool must have all nodes as QoS min supported
# for enabling this feature.
for n_name in node_name:
if not self.zapi_client.is_qos_min_supported(is_nfs, n_name):
supported = False
break
else:
supported = self.zapi_client.is_qos_min_supported(is_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, is_flexgroup=False):
"""Gather aggregate info and recast into SSC-style volume stats.
:param aggregate_name: a list of aggregate names for FlexGroup or
a single aggregate name for FlexVol
:param is_flexgroup: bool informing the type of aggregate_name param
"""
if 'netapp_raid_type' in self.invalid_extra_specs:
raid_type = None
hybrid = None
disk_types = None
node_name = None
elif is_flexgroup:
raid_type = set()
hybrid = set()
disk_types = set()
node_name = set()
for aggr in aggregate_name:
aggregate = self.zapi_client.get_aggregate(aggr)
node_name.add(aggregate.get('node-name'))
raid_type.add(aggregate.get('raid-type'))
hybrid.add((six.text_type(
aggregate.get('is-hybrid')).lower()
if 'is-hybrid' in aggregate else None))
disks = set(self.zapi_client.get_aggregate_disk_types(aggr))
disk_types = disk_types.union(disks)
node_name = list(node_name)
raid_type = list(raid_type)
hybrid = list(hybrid)
disk_types = list(disk_types)
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
def is_flexgroup(self, pool_name):
for __, flexvol_info in self.ssc.items():
if ('netapp_is_flexgroup' in flexvol_info and
'pool_name' in flexvol_info and
flexvol_info['pool_name'] == pool_name):
return flexvol_info['netapp_is_flexgroup'] == 'true'
return False
def contains_flexgroup_pool(self):
for __, flexvol_info in self.ssc.items():
if ('netapp_is_flexgroup' in flexvol_info and
flexvol_info['netapp_is_flexgroup'] == 'true'):
return True
return False