464 lines
20 KiB
Python
464 lines
20 KiB
Python
# Copyright (c) 2012 NetApp, Inc. All rights reserved.
|
|
# Copyright (c) 2014 Ben Swartzlander. All rights reserved.
|
|
# Copyright (c) 2014 Navneet Singh. All rights reserved.
|
|
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
|
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
|
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
|
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
|
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
|
|
# Copyright (c) 2016 Mike Rooney. 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.
|
|
"""
|
|
Volume driver library for NetApp 7-mode block storage systems.
|
|
"""
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_log import versionutils
|
|
from oslo_utils import timeutils
|
|
from oslo_utils import units
|
|
import six
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _, _LW
|
|
from cinder import utils
|
|
from cinder.volume import configuration
|
|
from cinder.volume.drivers.netapp.dataontap import block_base
|
|
from cinder.volume.drivers.netapp.dataontap.client import client_7mode
|
|
from cinder.volume.drivers.netapp.dataontap.performance import perf_7mode
|
|
from cinder.volume.drivers.netapp import options as na_opts
|
|
from cinder.volume.drivers.netapp import utils as na_utils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
@six.add_metaclass(utils.TraceWrapperMetaclass)
|
|
class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary):
|
|
"""NetApp block storage library for Data ONTAP (7-mode)."""
|
|
|
|
def __init__(self, driver_name, driver_protocol, **kwargs):
|
|
super(NetAppBlockStorage7modeLibrary, self).__init__(driver_name,
|
|
driver_protocol,
|
|
**kwargs)
|
|
self.configuration.append_config_values(na_opts.netapp_7mode_opts)
|
|
self.driver_mode = '7mode'
|
|
|
|
def do_setup(self, context):
|
|
super(NetAppBlockStorage7modeLibrary, self).do_setup(context)
|
|
|
|
self.volume_list = []
|
|
|
|
self.vfiler = self.configuration.netapp_vfiler
|
|
|
|
self.zapi_client = client_7mode.Client(
|
|
self.volume_list,
|
|
transport_type=self.configuration.netapp_transport_type,
|
|
username=self.configuration.netapp_login,
|
|
password=self.configuration.netapp_password,
|
|
hostname=self.configuration.netapp_server_hostname,
|
|
port=self.configuration.netapp_server_port,
|
|
vfiler=self.vfiler)
|
|
|
|
self._do_partner_setup()
|
|
|
|
self.vol_refresh_time = None
|
|
self.vol_refresh_interval = 1800
|
|
self.vol_refresh_running = False
|
|
self.vol_refresh_voluntary = False
|
|
self.root_volume_name = self._get_root_volume_name()
|
|
self.perf_library = perf_7mode.Performance7modeLibrary(
|
|
self.zapi_client)
|
|
# This driver has been marked 'deprecated' in the Ocata release and
|
|
# can be removed in Queens.
|
|
msg = _("The 7-mode Data ONTAP driver is deprecated and will be "
|
|
"removed in a future release.")
|
|
versionutils.report_deprecated_feature(LOG, msg)
|
|
|
|
def _do_partner_setup(self):
|
|
partner_backend = self.configuration.netapp_partner_backend_name
|
|
if partner_backend:
|
|
config = configuration.Configuration(na_opts.netapp_7mode_opts,
|
|
partner_backend)
|
|
config.append_config_values(na_opts.netapp_connection_opts)
|
|
config.append_config_values(na_opts.netapp_basicauth_opts)
|
|
config.append_config_values(na_opts.netapp_transport_opts)
|
|
|
|
self.partner_zapi_client = client_7mode.Client(
|
|
None,
|
|
transport_type=config.netapp_transport_type,
|
|
username=config.netapp_login,
|
|
password=config.netapp_password,
|
|
hostname=config.netapp_server_hostname,
|
|
port=config.netapp_server_port,
|
|
vfiler=None)
|
|
|
|
def check_for_setup_error(self):
|
|
"""Check that the driver is working and can communicate."""
|
|
api_version = self.zapi_client.get_ontapi_version()
|
|
if api_version:
|
|
major, minor = api_version
|
|
if major == 1 and minor < 9:
|
|
msg = _("Unsupported Data ONTAP version."
|
|
" Data ONTAP version 7.3.1 and above is supported.")
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
msg = _("API version could not be determined.")
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
|
|
self._refresh_volume_info()
|
|
|
|
if not self.volume_list:
|
|
msg = _('No pools are available for provisioning volumes. '
|
|
'Ensure that the configuration option '
|
|
'netapp_pool_name_search_pattern is set correctly.')
|
|
raise exception.NetAppDriverException(msg)
|
|
self._add_looping_tasks()
|
|
super(NetAppBlockStorage7modeLibrary, self).check_for_setup_error()
|
|
|
|
def _add_looping_tasks(self):
|
|
"""Add tasks that need to be executed at a fixed interval."""
|
|
super(NetAppBlockStorage7modeLibrary, self)._add_looping_tasks()
|
|
|
|
def _get_volume_model_update(self, volume):
|
|
"""Provide any updates necessary for a volume being created/managed."""
|
|
|
|
def _create_lun(self, volume_name, lun_name, size,
|
|
metadata, qos_policy_group_name=None):
|
|
"""Creates a LUN, handling Data ONTAP differences as needed."""
|
|
if qos_policy_group_name is not None:
|
|
msg = _('Data ONTAP operating in 7-Mode does not support QoS '
|
|
'policy groups.')
|
|
raise exception.VolumeDriverException(msg)
|
|
self.zapi_client.create_lun(
|
|
volume_name, lun_name, size, metadata, qos_policy_group_name)
|
|
|
|
self.vol_refresh_voluntary = True
|
|
|
|
def _get_root_volume_name(self):
|
|
# switch to volume-get-root-name API when possible
|
|
vols = self.zapi_client.get_filer_volumes()
|
|
for vol in vols:
|
|
volume_name = vol.get_child_content('name')
|
|
if self._get_vol_option(volume_name, 'root') == 'true':
|
|
return volume_name
|
|
LOG.warning(_LW('Could not determine root volume name '
|
|
'on %s.'), self._get_owner())
|
|
return None
|
|
|
|
def _get_owner(self):
|
|
if self.vfiler:
|
|
owner = '%s:%s' % (self.configuration.netapp_server_hostname,
|
|
self.vfiler)
|
|
else:
|
|
owner = self.configuration.netapp_server_hostname
|
|
return owner
|
|
|
|
def _create_lun_handle(self, metadata):
|
|
"""Returns LUN handle based on filer type."""
|
|
owner = self._get_owner()
|
|
return '%s:%s' % (owner, metadata['Path'])
|
|
|
|
def _find_mapped_lun_igroup(self, path, initiator_list):
|
|
"""Find an igroup for a LUN mapped to the given initiator(s)."""
|
|
initiator_set = set(initiator_list)
|
|
|
|
result = self.zapi_client.get_lun_map(path)
|
|
initiator_groups = result.get_child_by_name('initiator-groups')
|
|
if initiator_groups:
|
|
for initiator_group_info in initiator_groups.get_children():
|
|
|
|
initiator_set_for_igroup = set()
|
|
for initiator_info in initiator_group_info.get_child_by_name(
|
|
'initiators').get_children():
|
|
initiator_set_for_igroup.add(
|
|
initiator_info.get_child_content('initiator-name'))
|
|
|
|
if initiator_set == initiator_set_for_igroup:
|
|
igroup = initiator_group_info.get_child_content(
|
|
'initiator-group-name')
|
|
lun_id = initiator_group_info.get_child_content(
|
|
'lun-id')
|
|
return igroup, lun_id
|
|
|
|
return None, None
|
|
|
|
def _has_luns_mapped_to_initiators(self, initiator_list,
|
|
include_partner=True):
|
|
"""Checks whether any LUNs are mapped to the given initiator(s)."""
|
|
if self.zapi_client.has_luns_mapped_to_initiators(initiator_list):
|
|
return True
|
|
if include_partner and self.partner_zapi_client and \
|
|
self.partner_zapi_client.has_luns_mapped_to_initiators(
|
|
initiator_list):
|
|
return True
|
|
return False
|
|
|
|
def _clone_lun(self, name, new_name, space_reserved=None,
|
|
qos_policy_group_name=None, src_block=0, dest_block=0,
|
|
block_count=0, source_snapshot=None, is_snapshot=False):
|
|
"""Clone LUN with the given handle to the new name.
|
|
|
|
:param: is_snapshot Not used, present for method signature consistency
|
|
"""
|
|
|
|
if not space_reserved:
|
|
space_reserved = self.lun_space_reservation
|
|
if qos_policy_group_name is not None:
|
|
msg = _('Data ONTAP operating in 7-Mode does not support QoS '
|
|
'policy groups.')
|
|
raise exception.VolumeDriverException(msg)
|
|
|
|
metadata = self._get_lun_attr(name, 'metadata')
|
|
path = metadata['Path']
|
|
(parent, _splitter, name) = path.rpartition('/')
|
|
clone_path = '%s/%s' % (parent, new_name)
|
|
|
|
self.zapi_client.clone_lun(path, clone_path, name, new_name,
|
|
space_reserved, src_block=src_block,
|
|
dest_block=dest_block,
|
|
block_count=block_count,
|
|
source_snapshot=source_snapshot)
|
|
|
|
self.vol_refresh_voluntary = True
|
|
luns = self.zapi_client.get_lun_by_args(path=clone_path)
|
|
cloned_lun = luns[0]
|
|
self.zapi_client.set_space_reserve(clone_path, space_reserved)
|
|
clone_meta = self._create_lun_meta(cloned_lun)
|
|
handle = self._create_lun_handle(clone_meta)
|
|
self._add_lun_to_table(
|
|
block_base.NetAppLun(handle, new_name,
|
|
cloned_lun.get_child_content('size'),
|
|
clone_meta))
|
|
|
|
def _create_lun_meta(self, lun):
|
|
"""Creates LUN metadata dictionary."""
|
|
self.zapi_client.check_is_naelement(lun)
|
|
meta_dict = {}
|
|
meta_dict['Path'] = lun.get_child_content('path')
|
|
meta_dict['Volume'] = lun.get_child_content('path').split('/')[2]
|
|
meta_dict['OsType'] = lun.get_child_content('multiprotocol-type')
|
|
meta_dict['SpaceReserved'] = lun.get_child_content(
|
|
'is-space-reservation-enabled')
|
|
meta_dict['UUID'] = lun.get_child_content('uuid')
|
|
return meta_dict
|
|
|
|
def _get_fc_target_wwpns(self, include_partner=True):
|
|
wwpns = self.zapi_client.get_fc_target_wwpns()
|
|
if include_partner and self.partner_zapi_client:
|
|
wwpns.extend(self.partner_zapi_client.get_fc_target_wwpns())
|
|
return wwpns
|
|
|
|
def _update_volume_stats(self, filter_function=None,
|
|
goodness_function=None):
|
|
"""Retrieve stats info from filer."""
|
|
|
|
# ensure we get current data
|
|
self.vol_refresh_voluntary = True
|
|
self._refresh_volume_info()
|
|
|
|
LOG.debug('Updating volume stats')
|
|
data = {}
|
|
backend_name = self.configuration.safe_get('volume_backend_name')
|
|
data['volume_backend_name'] = backend_name or self.driver_name
|
|
data['vendor_name'] = 'NetApp'
|
|
data['driver_version'] = self.VERSION
|
|
data['storage_protocol'] = self.driver_protocol
|
|
data['pools'] = self._get_pool_stats(
|
|
filter_function=filter_function,
|
|
goodness_function=goodness_function)
|
|
data['sparse_copy_volume'] = True
|
|
|
|
self.zapi_client.provide_ems(self, self.driver_name, self.app_version,
|
|
server_type=self.driver_mode)
|
|
self._stats = data
|
|
|
|
def _get_pool_stats(self, filter_function=None, goodness_function=None):
|
|
"""Retrieve pool (i.e. Data ONTAP volume) stats info from volumes."""
|
|
|
|
pools = []
|
|
self.perf_library.update_performance_cache()
|
|
|
|
for vol in self.vols:
|
|
|
|
volume_name = vol.get_child_content('name')
|
|
|
|
# omit volumes not specified in the config
|
|
if self.volume_list and volume_name not in self.volume_list:
|
|
continue
|
|
|
|
# omit root volume
|
|
if volume_name == self.root_volume_name:
|
|
continue
|
|
|
|
# ensure good volume state
|
|
state = vol.get_child_content('state')
|
|
inconsistent = vol.get_child_content('is-inconsistent')
|
|
invalid = vol.get_child_content('is-invalid')
|
|
if (state != 'online' or
|
|
inconsistent != 'false' or
|
|
invalid != 'false'):
|
|
continue
|
|
|
|
pool = dict()
|
|
pool['pool_name'] = volume_name
|
|
pool['QoS_support'] = False
|
|
pool['multiattach'] = True
|
|
pool['reserved_percentage'] = (
|
|
self.reserved_percentage)
|
|
pool['max_over_subscription_ratio'] = (
|
|
self.max_over_subscription_ratio)
|
|
|
|
# convert sizes to GB
|
|
total = float(vol.get_child_content('size-total') or 0)
|
|
total /= units.Gi
|
|
pool['total_capacity_gb'] = na_utils.round_down(total, '0.01')
|
|
|
|
free = float(vol.get_child_content('size-available') or 0)
|
|
free /= units.Gi
|
|
pool['free_capacity_gb'] = na_utils.round_down(free, '0.01')
|
|
|
|
pool['provisioned_capacity_gb'] = (round(
|
|
pool['total_capacity_gb'] - pool['free_capacity_gb'], 2))
|
|
|
|
thick = (
|
|
self.configuration.netapp_lun_space_reservation == 'enabled')
|
|
pool['thick_provisioning_support'] = thick
|
|
pool['thin_provisioning_support'] = not thick
|
|
|
|
utilization = self.perf_library.get_node_utilization()
|
|
pool['utilization'] = na_utils.round_down(utilization, '0.01')
|
|
pool['filter_function'] = filter_function
|
|
pool['goodness_function'] = goodness_function
|
|
|
|
pool['consistencygroup_support'] = True
|
|
|
|
pools.append(pool)
|
|
|
|
return pools
|
|
|
|
def _get_filtered_pools(self):
|
|
"""Return available pools filtered by a pool name search pattern."""
|
|
|
|
# Inform deprecation of legacy option.
|
|
if self.configuration.safe_get('netapp_volume_list'):
|
|
msg = _LW("The option 'netapp_volume_list' is deprecated and "
|
|
"will be removed in the future releases. Please use "
|
|
"the option 'netapp_pool_name_search_pattern' instead.")
|
|
versionutils.report_deprecated_feature(LOG, msg)
|
|
|
|
pool_regex = na_utils.get_pool_name_filter_regex(self.configuration)
|
|
|
|
filtered_pools = []
|
|
for vol in self.vols:
|
|
vol_name = vol.get_child_content('name')
|
|
if pool_regex.match(vol_name):
|
|
msg = ("Volume '%(vol_name)s' matches against regular "
|
|
"expression: %(vol_pattern)s")
|
|
LOG.debug(msg, {'vol_name': vol_name,
|
|
'vol_pattern': pool_regex.pattern})
|
|
filtered_pools.append(vol_name)
|
|
else:
|
|
msg = ("Volume '%(vol_name)s' does not match against regular "
|
|
"expression: %(vol_pattern)s")
|
|
LOG.debug(msg, {'vol_name': vol_name,
|
|
'vol_pattern': pool_regex.pattern})
|
|
|
|
return filtered_pools
|
|
|
|
def _get_lun_block_count(self, path):
|
|
"""Gets block counts for the LUN."""
|
|
bs = super(NetAppBlockStorage7modeLibrary,
|
|
self)._get_lun_block_count(path)
|
|
api_version = self.zapi_client.get_ontapi_version()
|
|
if api_version:
|
|
major = api_version[0]
|
|
minor = api_version[1]
|
|
if major == 1 and minor < 15:
|
|
bs -= 1
|
|
return bs
|
|
|
|
def _refresh_volume_info(self):
|
|
"""Saves the volume information for the filer."""
|
|
|
|
if (self.vol_refresh_time is None or self.vol_refresh_voluntary or
|
|
timeutils.is_newer_than(self.vol_refresh_time,
|
|
self.vol_refresh_interval)):
|
|
try:
|
|
job_set = na_utils.set_safe_attr(self, 'vol_refresh_running',
|
|
True)
|
|
if not job_set:
|
|
LOG.warning(_LW("Volume refresh job already running. "
|
|
"Returning..."))
|
|
return
|
|
self.vol_refresh_voluntary = False
|
|
self.vols = self.zapi_client.get_filer_volumes()
|
|
self.volume_list = self._get_filtered_pools()
|
|
self.vol_refresh_time = timeutils.utcnow()
|
|
except Exception as e:
|
|
LOG.warning(_LW("Error refreshing volume info. Message: %s"),
|
|
e)
|
|
finally:
|
|
na_utils.set_safe_attr(self, 'vol_refresh_running', False)
|
|
|
|
def delete_volume(self, volume):
|
|
"""Driver entry point for destroying existing volumes."""
|
|
super(NetAppBlockStorage7modeLibrary, self).delete_volume(volume)
|
|
self.vol_refresh_voluntary = True
|
|
LOG.debug('Deleted LUN with name %s', volume['name'])
|
|
|
|
def delete_snapshot(self, snapshot):
|
|
"""Driver entry point for deleting a snapshot."""
|
|
super(NetAppBlockStorage7modeLibrary, self).delete_snapshot(snapshot)
|
|
self.vol_refresh_voluntary = True
|
|
|
|
def _is_lun_valid_on_storage(self, lun):
|
|
"""Validate LUN specific to storage system."""
|
|
if self.volume_list:
|
|
lun_vol = lun.get_metadata_property('Volume')
|
|
if lun_vol not in self.volume_list:
|
|
return False
|
|
return True
|
|
|
|
def _check_volume_type_for_lun(self, volume, lun, existing_ref,
|
|
extra_specs):
|
|
"""Check if LUN satisfies volume type."""
|
|
if extra_specs:
|
|
legacy_policy = extra_specs.get('netapp:qos_policy_group')
|
|
if legacy_policy is not None:
|
|
raise exception.ManageExistingVolumeTypeMismatch(
|
|
reason=_("Setting LUN QoS policy group is not supported "
|
|
"on this storage family and ONTAP version."))
|
|
volume_type = na_utils.get_volume_type_from_volume(volume)
|
|
if volume_type is None:
|
|
return
|
|
spec = na_utils.get_backend_qos_spec_from_volume_type(volume_type)
|
|
if spec is not None:
|
|
raise exception.ManageExistingVolumeTypeMismatch(
|
|
reason=_("Back-end QoS specs are not supported on this "
|
|
"storage family and ONTAP version."))
|
|
|
|
def _get_preferred_target_from_list(self, target_details_list,
|
|
filter=None):
|
|
# 7-mode iSCSI LIFs migrate from controller to controller
|
|
# in failover and flap operational state in transit, so
|
|
# we don't filter these on operational state.
|
|
|
|
return (super(NetAppBlockStorage7modeLibrary, self)
|
|
._get_preferred_target_from_list(target_details_list))
|
|
|
|
def _get_backing_flexvol_names(self):
|
|
"""Returns a list of backing flexvol names."""
|
|
return self.volume_list or []
|