672 lines
28 KiB
Python
672 lines
28 KiB
Python
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
|
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
|
# Copyright (c) 2015 Tom Barron. 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.
|
|
|
|
|
|
import copy
|
|
import math
|
|
|
|
from oslo_log import log as logging
|
|
import six
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _, _LW
|
|
from cinder import utils
|
|
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
|
|
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
|
from cinder.volume.drivers.netapp import utils as na_utils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
DELETED_PREFIX = 'deleted_cinder_'
|
|
|
|
|
|
@six.add_metaclass(utils.TraceWrapperMetaclass)
|
|
class Client(client_base.Client):
|
|
|
|
def __init__(self, **kwargs):
|
|
super(Client, self).__init__(**kwargs)
|
|
self.vserver = kwargs.get('vserver', None)
|
|
self.connection.set_vserver(self.vserver)
|
|
|
|
# Default values to run first api
|
|
self.connection.set_api_version(1, 15)
|
|
(major, minor) = self.get_ontapi_version(cached=False)
|
|
self.connection.set_api_version(major, minor)
|
|
self._init_features()
|
|
|
|
def _init_features(self):
|
|
super(Client, self)._init_features()
|
|
|
|
ontapi_version = self.get_ontapi_version() # major, minor
|
|
|
|
ontapi_1_30 = ontapi_version >= (1, 30)
|
|
self.features.add_feature('FAST_CLONE_DELETE', supported=ontapi_1_30)
|
|
|
|
def _invoke_vserver_api(self, na_element, vserver):
|
|
server = copy.copy(self.connection)
|
|
server.set_vserver(vserver)
|
|
result = server.invoke_successfully(na_element, True)
|
|
return result
|
|
|
|
def _has_records(self, api_result_element):
|
|
num_records = api_result_element.get_child_content('num-records')
|
|
return bool(num_records and '0' != num_records)
|
|
|
|
def set_vserver(self, vserver):
|
|
self.connection.set_vserver(vserver)
|
|
|
|
def get_iscsi_target_details(self):
|
|
"""Gets the iSCSI target portal details."""
|
|
iscsi_if_iter = netapp_api.NaElement('iscsi-interface-get-iter')
|
|
result = self.connection.invoke_successfully(iscsi_if_iter, True)
|
|
tgt_list = []
|
|
num_records = result.get_child_content('num-records')
|
|
if num_records and int(num_records) >= 1:
|
|
attr_list = result.get_child_by_name('attributes-list')
|
|
iscsi_if_list = attr_list.get_children()
|
|
for iscsi_if in iscsi_if_list:
|
|
d = dict()
|
|
d['address'] = iscsi_if.get_child_content('ip-address')
|
|
d['port'] = iscsi_if.get_child_content('ip-port')
|
|
d['tpgroup-tag'] = iscsi_if.get_child_content('tpgroup-tag')
|
|
d['interface-enabled'] = iscsi_if.get_child_content(
|
|
'is-interface-enabled')
|
|
tgt_list.append(d)
|
|
return tgt_list
|
|
|
|
def get_fc_target_wwpns(self):
|
|
"""Gets the FC target details."""
|
|
wwpns = []
|
|
port_name_list_api = netapp_api.NaElement('fcp-port-name-get-iter')
|
|
port_name_list_api.add_new_child('max-records', '100')
|
|
result = self.connection.invoke_successfully(port_name_list_api, True)
|
|
num_records = result.get_child_content('num-records')
|
|
if num_records and int(num_records) >= 1:
|
|
for port_name_info in result.get_child_by_name(
|
|
'attributes-list').get_children():
|
|
|
|
if port_name_info.get_child_content('is-used') != 'true':
|
|
continue
|
|
|
|
wwpn = port_name_info.get_child_content('port-name').lower()
|
|
wwpns.append(wwpn)
|
|
|
|
return wwpns
|
|
|
|
def get_iscsi_service_details(self):
|
|
"""Returns iscsi iqn."""
|
|
iscsi_service_iter = netapp_api.NaElement('iscsi-service-get-iter')
|
|
result = self.connection.invoke_successfully(iscsi_service_iter, True)
|
|
if result.get_child_content('num-records') and\
|
|
int(result.get_child_content('num-records')) >= 1:
|
|
attr_list = result.get_child_by_name('attributes-list')
|
|
iscsi_service = attr_list.get_child_by_name('iscsi-service-info')
|
|
return iscsi_service.get_child_content('node-name')
|
|
LOG.debug('No iSCSI service found for vserver %s', self.vserver)
|
|
return None
|
|
|
|
def get_lun_list(self):
|
|
"""Gets the list of LUNs on filer.
|
|
|
|
Gets the LUNs from cluster with vserver.
|
|
"""
|
|
|
|
luns = []
|
|
tag = None
|
|
while True:
|
|
api = netapp_api.NaElement('lun-get-iter')
|
|
api.add_new_child('max-records', '100')
|
|
if tag:
|
|
api.add_new_child('tag', tag, True)
|
|
lun_info = netapp_api.NaElement('lun-info')
|
|
lun_info.add_new_child('vserver', self.vserver)
|
|
query = netapp_api.NaElement('query')
|
|
query.add_child_elem(lun_info)
|
|
api.add_child_elem(query)
|
|
result = self.connection.invoke_successfully(api, True)
|
|
if result.get_child_by_name('num-records') and\
|
|
int(result.get_child_content('num-records')) >= 1:
|
|
attr_list = result.get_child_by_name('attributes-list')
|
|
luns.extend(attr_list.get_children())
|
|
tag = result.get_child_content('next-tag')
|
|
if tag is None:
|
|
break
|
|
return luns
|
|
|
|
def get_lun_map(self, path):
|
|
"""Gets the LUN map by LUN path."""
|
|
tag = None
|
|
map_list = []
|
|
while True:
|
|
lun_map_iter = netapp_api.NaElement('lun-map-get-iter')
|
|
lun_map_iter.add_new_child('max-records', '100')
|
|
if tag:
|
|
lun_map_iter.add_new_child('tag', tag, True)
|
|
query = netapp_api.NaElement('query')
|
|
lun_map_iter.add_child_elem(query)
|
|
query.add_node_with_children('lun-map-info', **{'path': path})
|
|
result = self.connection.invoke_successfully(lun_map_iter, True)
|
|
tag = result.get_child_content('next-tag')
|
|
if result.get_child_content('num-records') and \
|
|
int(result.get_child_content('num-records')) >= 1:
|
|
attr_list = result.get_child_by_name('attributes-list')
|
|
lun_maps = attr_list.get_children()
|
|
for lun_map in lun_maps:
|
|
lun_m = dict()
|
|
lun_m['initiator-group'] = lun_map.get_child_content(
|
|
'initiator-group')
|
|
lun_m['lun-id'] = lun_map.get_child_content('lun-id')
|
|
lun_m['vserver'] = lun_map.get_child_content('vserver')
|
|
map_list.append(lun_m)
|
|
if tag is None:
|
|
break
|
|
return map_list
|
|
|
|
def _get_igroup_by_initiator_query(self, initiator, tag):
|
|
igroup_get_iter = netapp_api.NaElement('igroup-get-iter')
|
|
igroup_get_iter.add_new_child('max-records', '100')
|
|
if tag:
|
|
igroup_get_iter.add_new_child('tag', tag, True)
|
|
|
|
query = netapp_api.NaElement('query')
|
|
igroup_info = netapp_api.NaElement('initiator-group-info')
|
|
query.add_child_elem(igroup_info)
|
|
igroup_info.add_new_child('vserver', self.vserver)
|
|
initiators = netapp_api.NaElement('initiators')
|
|
igroup_info.add_child_elem(initiators)
|
|
igroup_get_iter.add_child_elem(query)
|
|
initiators.add_node_with_children(
|
|
'initiator-info', **{'initiator-name': initiator})
|
|
|
|
# limit results to just the attributes of interest
|
|
desired_attrs = netapp_api.NaElement('desired-attributes')
|
|
desired_igroup_info = netapp_api.NaElement('initiator-group-info')
|
|
desired_igroup_info.add_node_with_children(
|
|
'initiators', **{'initiator-info': None})
|
|
desired_igroup_info.add_new_child('vserver', None)
|
|
desired_igroup_info.add_new_child('initiator-group-name', None)
|
|
desired_igroup_info.add_new_child('initiator-group-type', None)
|
|
desired_igroup_info.add_new_child('initiator-group-os-type', None)
|
|
desired_attrs.add_child_elem(desired_igroup_info)
|
|
igroup_get_iter.add_child_elem(desired_attrs)
|
|
|
|
return igroup_get_iter
|
|
|
|
def get_igroup_by_initiators(self, initiator_list):
|
|
"""Get igroups exactly matching a set of initiators."""
|
|
tag = None
|
|
igroup_list = []
|
|
if not initiator_list:
|
|
return igroup_list
|
|
|
|
initiator_set = set(initiator_list)
|
|
|
|
while True:
|
|
# C-mode getter APIs can't do an 'and' query, so match the first
|
|
# initiator (which will greatly narrow the search results) and
|
|
# filter the rest in this method.
|
|
query = self._get_igroup_by_initiator_query(initiator_list[0], tag)
|
|
result = self.connection.invoke_successfully(query, True)
|
|
|
|
tag = result.get_child_content('next-tag')
|
|
num_records = result.get_child_content('num-records')
|
|
if num_records and int(num_records) >= 1:
|
|
|
|
for igroup_info in result.get_child_by_name(
|
|
'attributes-list').get_children():
|
|
|
|
initiator_set_for_igroup = set()
|
|
for initiator_info in igroup_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-os-type':
|
|
igroup_info.get_child_content(
|
|
'initiator-group-os-type'),
|
|
'initiator-group-type':
|
|
igroup_info.get_child_content(
|
|
'initiator-group-type'),
|
|
'initiator-group-name':
|
|
igroup_info.get_child_content(
|
|
'initiator-group-name')}
|
|
igroup_list.append(igroup)
|
|
|
|
if tag is None:
|
|
break
|
|
|
|
return igroup_list
|
|
|
|
def clone_lun(self, volume, name, new_name, space_reserved='true',
|
|
qos_policy_group_name=None, src_block=0, dest_block=0,
|
|
block_count=0):
|
|
# zAPI can only handle 2^24 blocks per range
|
|
bc_limit = 2 ** 24 # 8GB
|
|
# zAPI can only handle 32 block ranges per call
|
|
br_limit = 32
|
|
z_limit = br_limit * bc_limit # 256 GB
|
|
z_calls = int(math.ceil(block_count / float(z_limit)))
|
|
zbc = block_count
|
|
if z_calls == 0:
|
|
z_calls = 1
|
|
for _call in range(0, z_calls):
|
|
if zbc > z_limit:
|
|
block_count = z_limit
|
|
zbc -= z_limit
|
|
else:
|
|
block_count = zbc
|
|
clone_create = netapp_api.NaElement.create_node_with_children(
|
|
'clone-create',
|
|
**{'volume': volume, 'source-path': name,
|
|
'destination-path': new_name,
|
|
'space-reserve': space_reserved})
|
|
if qos_policy_group_name is not None:
|
|
clone_create.add_new_child('qos-policy-group-name',
|
|
qos_policy_group_name)
|
|
if block_count > 0:
|
|
block_ranges = netapp_api.NaElement("block-ranges")
|
|
segments = int(math.ceil(block_count / float(bc_limit)))
|
|
bc = block_count
|
|
for _segment in range(0, segments):
|
|
if bc > bc_limit:
|
|
block_count = bc_limit
|
|
bc -= bc_limit
|
|
else:
|
|
block_count = bc
|
|
block_range =\
|
|
netapp_api.NaElement.create_node_with_children(
|
|
'block-range',
|
|
**{'source-block-number':
|
|
six.text_type(src_block),
|
|
'destination-block-number':
|
|
six.text_type(dest_block),
|
|
'block-count':
|
|
six.text_type(block_count)})
|
|
block_ranges.add_child_elem(block_range)
|
|
src_block += int(block_count)
|
|
dest_block += int(block_count)
|
|
clone_create.add_child_elem(block_ranges)
|
|
self.connection.invoke_successfully(clone_create, True)
|
|
|
|
def get_lun_by_args(self, **args):
|
|
"""Retrieves LUN with specified args."""
|
|
lun_iter = netapp_api.NaElement('lun-get-iter')
|
|
lun_iter.add_new_child('max-records', '100')
|
|
query = netapp_api.NaElement('query')
|
|
lun_iter.add_child_elem(query)
|
|
query.add_node_with_children('lun-info', **args)
|
|
luns = self.connection.invoke_successfully(lun_iter, True)
|
|
attr_list = luns.get_child_by_name('attributes-list')
|
|
if not attr_list:
|
|
return []
|
|
return attr_list.get_children()
|
|
|
|
def file_assign_qos(self, flex_vol, qos_policy_group_name, file_path):
|
|
"""Assigns the named QoS policy-group to a file."""
|
|
api_args = {
|
|
'volume': flex_vol,
|
|
'qos-policy-group-name': qos_policy_group_name,
|
|
'file': file_path,
|
|
'vserver': self.vserver,
|
|
}
|
|
return self.send_request('file-assign-qos', api_args, False)
|
|
|
|
def provision_qos_policy_group(self, qos_policy_group_info):
|
|
"""Create QOS policy group on the backend if appropriate."""
|
|
if qos_policy_group_info is None:
|
|
return
|
|
|
|
# Legacy QOS uses externally provisioned QOS policy group,
|
|
# so we don't need to create one on the backend.
|
|
legacy = qos_policy_group_info.get('legacy')
|
|
if legacy is not None:
|
|
return
|
|
|
|
spec = qos_policy_group_info.get('spec')
|
|
if spec is not None:
|
|
if not self.qos_policy_group_exists(spec['policy_name']):
|
|
self.qos_policy_group_create(spec['policy_name'],
|
|
spec['max_throughput'])
|
|
else:
|
|
self.qos_policy_group_modify(spec['policy_name'],
|
|
spec['max_throughput'])
|
|
|
|
def qos_policy_group_exists(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,
|
|
},
|
|
},
|
|
}
|
|
result = self.send_request('qos-policy-group-get-iter',
|
|
api_args,
|
|
False)
|
|
return self._has_records(result)
|
|
|
|
def qos_policy_group_create(self, qos_policy_group_name, max_throughput):
|
|
"""Creates a QOS policy group."""
|
|
api_args = {
|
|
'policy-group': qos_policy_group_name,
|
|
'max-throughput': max_throughput,
|
|
'vserver': self.vserver,
|
|
}
|
|
return self.send_request('qos-policy-group-create', api_args, False)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
def mark_qos_policy_group_for_deletion(self, qos_policy_group_info):
|
|
"""Do (soft) delete of backing QOS policy group for a cinder volume."""
|
|
if qos_policy_group_info is None:
|
|
return
|
|
|
|
spec = qos_policy_group_info.get('spec')
|
|
|
|
# For cDOT we want to delete the QoS policy group that we created for
|
|
# this cinder volume. Because the QoS policy may still be "in use"
|
|
# after the zapi call to delete the volume itself returns successfully,
|
|
# we instead 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 spec is not None:
|
|
current_name = spec['policy_name']
|
|
new_name = DELETED_PREFIX + current_name
|
|
try:
|
|
self.qos_policy_group_rename(current_name, new_name)
|
|
except netapp_api.NaApiError as ex:
|
|
msg = _LW('Rename failure in cleanup of cDOT QOS policy group '
|
|
'%(name)s: %(ex)s')
|
|
LOG.warning(msg, {'name': current_name, 'ex': ex})
|
|
|
|
# Attempt to delete any QoS policies named "delete-openstack-*".
|
|
self.remove_unused_qos_policy_groups()
|
|
|
|
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,
|
|
'vserver': self.vserver,
|
|
}
|
|
},
|
|
'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)
|
|
|
|
def set_lun_qos_policy_group(self, path, qos_policy_group):
|
|
"""Sets qos_policy_group on a LUN."""
|
|
api_args = {
|
|
'path': path,
|
|
'qos-policy-group': qos_policy_group,
|
|
}
|
|
return self.send_request('lun-set-qos-policy-group', api_args)
|
|
|
|
def get_if_info_by_ip(self, ip):
|
|
"""Gets the network interface info by ip."""
|
|
net_if_iter = netapp_api.NaElement('net-interface-get-iter')
|
|
net_if_iter.add_new_child('max-records', '10')
|
|
query = netapp_api.NaElement('query')
|
|
net_if_iter.add_child_elem(query)
|
|
query.add_node_with_children(
|
|
'net-interface-info',
|
|
**{'address': na_utils.resolve_hostname(ip)})
|
|
result = self.connection.invoke_successfully(net_if_iter, True)
|
|
num_records = result.get_child_content('num-records')
|
|
if num_records and int(num_records) >= 1:
|
|
attr_list = result.get_child_by_name('attributes-list')
|
|
return attr_list.get_children()
|
|
raise exception.NotFound(
|
|
_('No interface found on cluster for ip %s') % ip)
|
|
|
|
def get_vol_by_junc_vserver(self, vserver, junction):
|
|
"""Gets the volume by junction path and vserver."""
|
|
vol_iter = netapp_api.NaElement('volume-get-iter')
|
|
vol_iter.add_new_child('max-records', '10')
|
|
query = netapp_api.NaElement('query')
|
|
vol_iter.add_child_elem(query)
|
|
vol_attrs = netapp_api.NaElement('volume-attributes')
|
|
query.add_child_elem(vol_attrs)
|
|
vol_attrs.add_node_with_children(
|
|
'volume-id-attributes',
|
|
**{'junction-path': junction,
|
|
'owning-vserver-name': vserver})
|
|
des_attrs = netapp_api.NaElement('desired-attributes')
|
|
des_attrs.add_node_with_children('volume-attributes',
|
|
**{'volume-id-attributes': None})
|
|
vol_iter.add_child_elem(des_attrs)
|
|
result = self._invoke_vserver_api(vol_iter, vserver)
|
|
num_records = result.get_child_content('num-records')
|
|
if num_records and int(num_records) >= 1:
|
|
attr_list = result.get_child_by_name('attributes-list')
|
|
vols = attr_list.get_children()
|
|
vol_id = vols[0].get_child_by_name('volume-id-attributes')
|
|
return vol_id.get_child_content('name')
|
|
msg_fmt = {'vserver': vserver, 'junction': junction}
|
|
raise exception.NotFound(_("No volume on cluster with vserver "
|
|
"%(vserver)s and junction path "
|
|
"%(junction)s ") % msg_fmt)
|
|
|
|
def clone_file(self, flex_vol, src_path, dest_path, vserver,
|
|
dest_exists=False):
|
|
"""Clones file on vserver."""
|
|
LOG.debug("Cloning with params volume %(volume)s, src %(src_path)s, "
|
|
"dest %(dest_path)s, vserver %(vserver)s",
|
|
{'volume': flex_vol, 'src_path': src_path,
|
|
'dest_path': dest_path, 'vserver': vserver})
|
|
clone_create = netapp_api.NaElement.create_node_with_children(
|
|
'clone-create',
|
|
**{'volume': flex_vol, 'source-path': src_path,
|
|
'destination-path': dest_path})
|
|
major, minor = self.connection.get_api_version()
|
|
if major == 1 and minor >= 20 and dest_exists:
|
|
clone_create.add_new_child('destination-exists', 'true')
|
|
self._invoke_vserver_api(clone_create, vserver)
|
|
|
|
def get_file_usage(self, path, vserver):
|
|
"""Gets the file unique bytes."""
|
|
LOG.debug('Getting file usage for %s', path)
|
|
file_use = netapp_api.NaElement.create_node_with_children(
|
|
'file-usage-get', **{'path': path})
|
|
res = self._invoke_vserver_api(file_use, vserver)
|
|
unique_bytes = res.get_child_content('unique-bytes')
|
|
LOG.debug('file-usage for path %(path)s is %(bytes)s',
|
|
{'path': path, 'bytes': unique_bytes})
|
|
return unique_bytes
|
|
|
|
def get_vserver_ips(self, vserver):
|
|
"""Get ips for the vserver."""
|
|
result = netapp_api.invoke_api(
|
|
self.connection, api_name='net-interface-get-iter',
|
|
is_iter=True, tunnel=vserver)
|
|
if_list = []
|
|
for res in result:
|
|
records = res.get_child_content('num-records')
|
|
if records > 0:
|
|
attr_list = res['attributes-list']
|
|
ifs = attr_list.get_children()
|
|
if_list.extend(ifs)
|
|
return if_list
|
|
|
|
def check_apis_on_cluster(self, api_list=None):
|
|
"""Checks API availability and permissions on cluster.
|
|
|
|
Checks API availability and permissions for executing user.
|
|
Returns a list of failed apis.
|
|
"""
|
|
api_list = api_list or []
|
|
failed_apis = []
|
|
if api_list:
|
|
api_version = self.connection.get_api_version()
|
|
if api_version:
|
|
major, minor = api_version
|
|
if major == 1 and minor < 20:
|
|
for api_name in api_list:
|
|
na_el = netapp_api.NaElement(api_name)
|
|
try:
|
|
self.connection.invoke_successfully(na_el)
|
|
except Exception as e:
|
|
if isinstance(e, netapp_api.NaApiError):
|
|
if (e.code == netapp_api.NaErrors
|
|
['API_NOT_FOUND'].code or
|
|
e.code == netapp_api.NaErrors
|
|
['INSUFFICIENT_PRIVS'].code):
|
|
failed_apis.append(api_name)
|
|
elif major == 1 and minor >= 20:
|
|
failed_apis = copy.copy(api_list)
|
|
result = netapp_api.invoke_api(
|
|
self.connection,
|
|
api_name='system-user-capability-get-iter',
|
|
api_family='cm',
|
|
additional_elems=None,
|
|
is_iter=True)
|
|
for res in result:
|
|
attr_list = res.get_child_by_name('attributes-list')
|
|
if attr_list:
|
|
capabilities = attr_list.get_children()
|
|
for capability in capabilities:
|
|
op_list = capability.get_child_by_name(
|
|
'operation-list')
|
|
if op_list:
|
|
ops = op_list.get_children()
|
|
for op in ops:
|
|
apis = op.get_child_content(
|
|
'api-name')
|
|
if apis:
|
|
api_list = apis.split(',')
|
|
for api_name in api_list:
|
|
if (api_name and
|
|
api_name.strip()
|
|
in failed_apis):
|
|
failed_apis.remove(
|
|
api_name)
|
|
else:
|
|
continue
|
|
else:
|
|
msg = _("Unsupported Clustered Data ONTAP version.")
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
else:
|
|
msg = _("Data ONTAP API version could not be determined.")
|
|
raise exception.VolumeBackendAPIException(data=msg)
|
|
return failed_apis
|
|
|
|
def get_operational_network_interface_addresses(self):
|
|
"""Gets the IP addresses of operational LIFs on the vserver."""
|
|
|
|
api_args = {
|
|
'query': {
|
|
'net-interface-info': {
|
|
'operational-status': 'up'
|
|
}
|
|
},
|
|
'desired-attributes': {
|
|
'net-interface-info': {
|
|
'address': None,
|
|
}
|
|
}
|
|
}
|
|
result = self.send_request('net-interface-get-iter', api_args)
|
|
|
|
lif_info_list = result.get_child_by_name(
|
|
'attributes-list') or netapp_api.NaElement('none')
|
|
|
|
return [lif_info.get_child_content('address') for lif_info in
|
|
lif_info_list.get_children()]
|
|
|
|
def get_flexvol_capacity(self, flexvol_path):
|
|
"""Gets total capacity and free capacity, in bytes, of the flexvol."""
|
|
|
|
api_args = {
|
|
'query': {
|
|
'volume-attributes': {
|
|
'volume-id-attributes': {
|
|
'junction-path': flexvol_path
|
|
}
|
|
}
|
|
},
|
|
'desired-attributes': {
|
|
'volume-attributes': {
|
|
'volume-space-attributes': {
|
|
'size-available': None,
|
|
'size-total': None,
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
result = self.send_request('volume-get-iter', api_args)
|
|
|
|
attributes_list = result.get_child_by_name('attributes-list')
|
|
volume_attributes = attributes_list.get_child_by_name(
|
|
'volume-attributes')
|
|
volume_space_attributes = volume_attributes.get_child_by_name(
|
|
'volume-space-attributes')
|
|
|
|
size_available = float(
|
|
volume_space_attributes.get_child_content('size-available'))
|
|
size_total = float(
|
|
volume_space_attributes.get_child_content('size-total'))
|
|
|
|
return size_total, size_available
|
|
|
|
@utils.trace_method
|
|
def delete_file(self, path_to_file):
|
|
"""Delete file at path."""
|
|
|
|
api_args = {
|
|
'path': path_to_file,
|
|
}
|
|
# Use fast clone deletion engine if it is supported.
|
|
if self.features.FAST_CLONE_DELETE:
|
|
api_args['is-clone-file'] = 'true'
|
|
self.send_request('file-delete-file', api_args, True)
|