Shared filesystem management project for OpenStack.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

4035 lines
149 KiB

# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2015 Clinton Knight. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2018 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.
import copy
import hashlib
import re
import time
from oslo_log import log
from oslo_utils import strutils
from oslo_utils import units
import six
from manila import exception
from manila.i18n import _
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
from manila.share.drivers.netapp.dataontap.client import client_base
from manila.share.drivers.netapp import utils as na_utils
LOG = log.getLogger(__name__)
DELETED_PREFIX = 'deleted_manila_'
DEFAULT_IPSPACE = 'Default'
DEFAULT_MAX_PAGE_LENGTH = 50
CUTOVER_ACTION_MAP = {
'defer': 'defer_on_failure',
'abort': 'abort_on_failure',
'force': 'force',
'wait': 'wait',
}
class NetAppCmodeClient(client_base.NetAppBaseClient):
def __init__(self, **kwargs):
super(NetAppCmodeClient, self).__init__(**kwargs)
self.vserver = kwargs.get('vserver')
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)
system_version = self.get_system_version(cached=False)
self.connection.set_system_version(system_version)
self._init_features()
def _init_features(self):
"""Initialize cDOT feature support map."""
super(NetAppCmodeClient, self)._init_features()
ontapi_version = self.get_ontapi_version(cached=True)
ontapi_1_20 = ontapi_version >= (1, 20)
ontapi_1_2x = (1, 20) <= ontapi_version < (1, 30)
ontapi_1_30 = ontapi_version >= (1, 30)
ontapi_1_110 = ontapi_version >= (1, 110)
ontapi_1_150 = ontapi_version >= (1, 150)
self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
self.features.add_feature('SYSTEM_METRICS', supported=ontapi_1_2x)
self.features.add_feature('SYSTEM_CONSTITUENT_METRICS',
supported=ontapi_1_30)
self.features.add_feature('BROADCAST_DOMAINS', supported=ontapi_1_30)
self.features.add_feature('IPSPACES', supported=ontapi_1_30)
self.features.add_feature('SUBNETS', supported=ontapi_1_30)
self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
self.features.add_feature('ADVANCED_DISK_PARTITIONING',
supported=ontapi_1_30)
self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_110)
self.features.add_feature('CIFS_DC_ADD_SKIP_CHECK',
supported=ontapi_1_150)
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):
if (not api_result_element.get_child_content('num-records') or
api_result_element.get_child_content('num-records') == '0'):
return False
else:
return True
def _get_record_count(self, api_result_element):
try:
return int(api_result_element.get_child_content('num-records'))
except TypeError:
msg = _('Missing record count for NetApp iterator API invocation.')
raise exception.NetAppException(msg)
def set_vserver(self, vserver):
self.vserver = vserver
self.connection.set_vserver(vserver)
def send_iter_request(self, api_name, api_args=None,
max_page_length=DEFAULT_MAX_PAGE_LENGTH):
"""Invoke an iterator-style getter API."""
if not api_args:
api_args = {}
api_args['max-records'] = max_page_length
# Get first page
result = self.send_request(api_name, api_args)
# Most commonly, we can just return here if there is no more data
next_tag = result.get_child_content('next-tag')
if not next_tag:
return result
# Ensure pagination data is valid and prepare to store remaining pages
num_records = self._get_record_count(result)
attributes_list = result.get_child_by_name('attributes-list')
if not attributes_list:
msg = _('Missing attributes list for API %s.') % api_name
raise exception.NetAppException(msg)
# Get remaining pages, saving data into first page
while next_tag is not None:
next_api_args = copy.deepcopy(api_args)
next_api_args['tag'] = next_tag
next_result = self.send_request(api_name, next_api_args)
next_attributes_list = next_result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
for record in next_attributes_list.get_children():
attributes_list.add_child_elem(record)
num_records += self._get_record_count(next_result)
next_tag = next_result.get_child_content('next-tag')
result.get_child_by_name('num-records').set_content(
six.text_type(num_records))
result.get_child_by_name('next-tag').set_content('')
return result
@na_utils.trace
def create_vserver(self, vserver_name, root_volume_aggregate_name,
root_volume_name, aggregate_names, ipspace_name):
"""Creates new vserver and assigns aggregates."""
create_args = {
'vserver-name': vserver_name,
'root-volume-security-style': 'unix',
'root-volume-aggregate': root_volume_aggregate_name,
'root-volume': root_volume_name,
'name-server-switch': {
'nsswitch': 'file',
},
}
if ipspace_name:
if not self.features.IPSPACES:
msg = 'IPSpaces are not supported on this backend.'
raise exception.NetAppException(msg)
else:
create_args['ipspace'] = ipspace_name
self.send_request('vserver-create', create_args)
aggr_list = [{'aggr-name': aggr_name} for aggr_name in aggregate_names]
modify_args = {
'aggr-list': aggr_list,
'vserver-name': vserver_name,
}
self.send_request('vserver-modify', modify_args)
@na_utils.trace
def vserver_exists(self, vserver_name):
"""Checks if Vserver exists."""
LOG.debug('Checking if Vserver %s exists', vserver_name)
api_args = {
'query': {
'vserver-info': {
'vserver-name': vserver_name,
},
},
'desired-attributes': {
'vserver-info': {
'vserver-name': None,
},
},
}
result = self.send_iter_request('vserver-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def get_vserver_root_volume_name(self, vserver_name):
"""Get the root volume name of the vserver."""
api_args = {
'query': {
'vserver-info': {
'vserver-name': vserver_name,
},
},
'desired-attributes': {
'vserver-info': {
'root-volume': None,
},
},
}
vserver_info = self.send_iter_request('vserver-get-iter', api_args)
try:
root_volume_name = vserver_info.get_child_by_name(
'attributes-list').get_child_by_name(
'vserver-info').get_child_content('root-volume')
except AttributeError:
msg = _('Could not determine root volume name '
'for Vserver %s.') % vserver_name
raise exception.NetAppException(msg)
return root_volume_name
@na_utils.trace
def get_vserver_ipspace(self, vserver_name):
"""Get the IPspace of the vserver, or None if not supported."""
if not self.features.IPSPACES:
return None
api_args = {
'query': {
'vserver-info': {
'vserver-name': vserver_name,
},
},
'desired-attributes': {
'vserver-info': {
'ipspace': None,
},
},
}
vserver_info = self.send_iter_request('vserver-get-iter', api_args)
try:
ipspace = vserver_info.get_child_by_name(
'attributes-list').get_child_by_name(
'vserver-info').get_child_content('ipspace')
except AttributeError:
msg = _('Could not determine IPspace for Vserver %s.')
raise exception.NetAppException(msg % vserver_name)
return ipspace
@na_utils.trace
def ipspace_has_data_vservers(self, ipspace_name):
"""Check whether an IPspace has any data Vservers assigned to it."""
if not self.features.IPSPACES:
return False
api_args = {
'query': {
'vserver-info': {
'ipspace': ipspace_name,
'vserver-type': 'data'
},
},
'desired-attributes': {
'vserver-info': {
'vserver-name': None,
},
},
}
result = self.send_iter_request('vserver-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def list_vservers(self, vserver_type='data'):
"""Get the names of vservers present, optionally filtered by type."""
query = {
'vserver-info': {
'vserver-type': vserver_type,
}
} if vserver_type else None
api_args = {
'desired-attributes': {
'vserver-info': {
'vserver-name': None,
},
},
}
if query:
api_args['query'] = query
result = self.send_iter_request('vserver-get-iter', api_args)
vserver_info_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
return [vserver_info.get_child_content('vserver-name')
for vserver_info in vserver_info_list.get_children()]
@na_utils.trace
def get_vserver_volume_count(self):
"""Get the number of volumes present on a cluster or vserver.
Call this on a vserver client to see how many volumes exist
on that vserver.
"""
api_args = {
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'name': None,
},
},
},
}
volumes_data = self.send_iter_request('volume-get-iter', api_args)
return self._get_record_count(volumes_data)
@na_utils.trace
def delete_vserver(self, vserver_name, vserver_client,
security_services=None):
"""Delete Vserver.
Checks if Vserver exists and does not have active shares.
Offlines and destroys root volumes. Deletes Vserver.
"""
if not self.vserver_exists(vserver_name):
LOG.error("Vserver %s does not exist.", vserver_name)
return
root_volume_name = self.get_vserver_root_volume_name(vserver_name)
volumes_count = vserver_client.get_vserver_volume_count()
if volumes_count == 1:
try:
vserver_client.offline_volume(root_volume_name)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EVOLUMEOFFLINE:
LOG.error("Volume %s is already offline.",
root_volume_name)
else:
raise
vserver_client.delete_volume(root_volume_name)
elif volumes_count > 1:
msg = _("Cannot delete Vserver. Vserver %s has shares.")
raise exception.NetAppException(msg % vserver_name)
if security_services:
self._terminate_vserver_services(vserver_name, vserver_client,
security_services)
self.send_request('vserver-destroy', {'vserver-name': vserver_name})
@na_utils.trace
def _terminate_vserver_services(self, vserver_name, vserver_client,
security_services):
for service in security_services:
if service['type'] == 'active_directory':
api_args = {
'admin-password': service['password'],
'admin-username': service['user'],
}
try:
vserver_client.send_request('cifs-server-delete', api_args)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EOBJECTNOTFOUND:
LOG.error('CIFS server does not exist for '
'Vserver %s.', vserver_name)
else:
vserver_client.send_request('cifs-server-delete')
@na_utils.trace
def is_nve_supported(self):
"""Determine whether NVE is supported on this platform and version."""
nodes = self.list_cluster_nodes()
system_version = self.get_system_version()
version = system_version.get('version')
version_tuple = system_version.get('version-tuple')
# NVE requires an ONTAP version >= 9.1. Also, not all platforms
# support this feature. NVE is not supported if the version
# includes the substring '<1no-DARE>' (no Data At Rest Encryption).
if version_tuple >= (9, 1, 0) and "<1no-DARE>" not in version:
if nodes is not None:
return self.get_security_key_manager_nve_support(nodes[0])
else:
LOG.debug('Cluster credentials are required in order to '
'determine whether NetApp Volume Encryption is '
'supported or not on this platform.')
return False
else:
LOG.debug('NetApp Volume Encryption is not supported on this '
'ONTAP version: %(version)s, %(version_tuple)s. ',
{'version': version, 'version_tuple': version_tuple})
return False
@na_utils.trace
def list_cluster_nodes(self):
"""Get all available cluster nodes."""
api_args = {
'desired-attributes': {
'node-details-info': {
'node': None,
},
},
}
result = self.send_iter_request('system-node-get-iter', api_args)
nodes_info_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
return [node_info.get_child_content('node') for node_info
in nodes_info_list.get_children()]
@na_utils.trace
def get_security_key_manager_nve_support(self, node):
"""Determine whether the cluster platform supports Volume Encryption"""
api_args = {'node': node}
try:
result = self.send_request(
'security-key-manager-volume-encryption-supported', api_args)
vol_encryption_supported = result.get_child_content(
'vol-encryption-supported') or 'false'
except netapp_api.NaApiError as e:
LOG.debug("NVE disabled due to error code: %s - %s",
e.code, e.message)
return False
return strutils.bool_from_string(vol_encryption_supported)
@na_utils.trace
def list_node_data_ports(self, node):
ports = self.get_node_data_ports(node)
return [port.get('port') for port in ports]
@na_utils.trace
def get_node_data_ports(self, node):
"""Get applicable data ports on the node."""
api_args = {
'query': {
'net-port-info': {
'node': node,
'link-status': 'up',
'port-type': 'physical|if_group',
'role': 'data',
},
},
'desired-attributes': {
'net-port-info': {
'port': None,
'node': None,
'operational-speed': None,
'ifgrp-port': None,
},
},
}
result = self.send_iter_request('net-port-get-iter', api_args)
net_port_info_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
ports = []
for port_info in net_port_info_list.get_children():
# Skip physical ports that are part of interface groups.
if port_info.get_child_content('ifgrp-port'):
continue
port = {
'node': port_info.get_child_content('node'),
'port': port_info.get_child_content('port'),
'speed': port_info.get_child_content('operational-speed'),
}
ports.append(port)
return self._sort_data_ports_by_speed(ports)
@na_utils.trace
def _sort_data_ports_by_speed(self, ports):
def sort_key(port):
value = port.get('speed')
if not (value and isinstance(value, six.string_types)):
return 0
elif value.isdigit():
return int(value)
elif value == 'auto':
return 3
elif value == 'undef':
return 2
else:
return 1
return sorted(ports, key=sort_key, reverse=True)
@na_utils.trace
def list_root_aggregates(self):
"""Get names of all aggregates that contain node root volumes."""
desired_attributes = {
'aggr-attributes': {
'aggregate-name': None,
'aggr-raid-attributes': {
'has-local-root': None,
'has-partner-root': None,
},
},
}
aggrs = self._get_aggregates(desired_attributes=desired_attributes)
root_aggregates = []
for aggr in aggrs:
aggr_name = aggr.get_child_content('aggregate-name')
aggr_raid_attrs = aggr.get_child_by_name('aggr-raid-attributes')
local_root = strutils.bool_from_string(
aggr_raid_attrs.get_child_content('has-local-root'))
partner_root = strutils.bool_from_string(
aggr_raid_attrs.get_child_content('has-partner-root'))
if local_root or partner_root:
root_aggregates.append(aggr_name)
return root_aggregates
@na_utils.trace
def list_non_root_aggregates(self):
"""Get names of all aggregates that don't contain node root volumes."""
query = {
'aggr-attributes': {
'aggr-raid-attributes': {
'has-local-root': 'false',
'has-partner-root': 'false',
}
},
}
return self._list_aggregates(query=query)
@na_utils.trace
def _list_aggregates(self, query=None):
"""Get names of all aggregates."""
try:
api_args = {
'desired-attributes': {
'aggr-attributes': {
'aggregate-name': None,
},
},
}
if query:
api_args['query'] = query
result = self.send_iter_request('aggr-get-iter', api_args)
aggr_list = result.get_child_by_name(
'attributes-list').get_children()
except AttributeError:
msg = _("Could not list aggregates.")
raise exception.NetAppException(msg)
return [aggr.get_child_content('aggregate-name') for aggr
in aggr_list]
@na_utils.trace
def list_vserver_aggregates(self):
"""Returns a list of aggregates available to a vserver.
This must be called against a Vserver LIF.
"""
return list(self.get_vserver_aggregate_capacities().keys())
@na_utils.trace
def create_network_interface(self, ip, netmask, vlan, node, port,
vserver_name, lif_name, ipspace_name, mtu):
"""Creates LIF on VLAN port."""
home_port_name = port
if vlan:
self._create_vlan(node, port, vlan)
home_port_name = '%(port)s-%(tag)s' % {'port': port, 'tag': vlan}
if self.features.BROADCAST_DOMAINS:
self._ensure_broadcast_domain_for_port(
node, home_port_name, mtu, ipspace=ipspace_name)
LOG.debug('Creating LIF %(lif)s for Vserver %(vserver)s ',
{'lif': lif_name, 'vserver': vserver_name})
api_args = {
'address': ip,
'administrative-status': 'up',
'data-protocols': [
{'data-protocol': 'nfs'},
{'data-protocol': 'cifs'},
],
'home-node': node,
'home-port': home_port_name,
'netmask': netmask,
'interface-name': lif_name,
'role': 'data',
'vserver': vserver_name,
}
self.send_request('net-interface-create', api_args)
@na_utils.trace
def _create_vlan(self, node, port, vlan):
try:
api_args = {
'vlan-info': {
'parent-interface': port,
'node': node,
'vlanid': vlan,
},
}
self.send_request('net-vlan-create', api_args)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EDUPLICATEENTRY:
LOG.debug('VLAN %(vlan)s already exists on port %(port)s',
{'vlan': vlan, 'port': port})
else:
msg = _('Failed to create VLAN %(vlan)s on '
'port %(port)s. %(err_msg)s')
msg_args = {'vlan': vlan, 'port': port, 'err_msg': e.message}
raise exception.NetAppException(msg % msg_args)
@na_utils.trace
def delete_vlan(self, node, port, vlan):
try:
api_args = {
'vlan-info': {
'parent-interface': port,
'node': node,
'vlanid': vlan,
},
}
self.send_request('net-vlan-delete', api_args)
except netapp_api.NaApiError as e:
p = re.compile('port already has a lif bound.*', re.IGNORECASE)
if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
LOG.debug('VLAN %(vlan)s on port %(port)s node %(node)s '
'still used by LIF and cannot be deleted.',
{'vlan': vlan, 'port': port, 'node': node})
else:
msg = _('Failed to delete VLAN %(vlan)s on '
'port %(port)s node %(node)s: %(err_msg)s')
msg_args = {
'vlan': vlan,
'port': port,
'node': node,
'err_msg': e.message
}
raise exception.NetAppException(msg % msg_args)
@na_utils.trace
def create_route(self, gateway, destination=None):
if not gateway:
return
if not destination:
if ':' in gateway:
destination = '::/0'
else:
destination = '0.0.0.0/0'
try:
api_args = {
'destination': destination,
'gateway': gateway,
'return-record': 'true',
}
self.send_request('net-routes-create', api_args)
except netapp_api.NaApiError as e:
p = re.compile('.*Duplicate route exists.*', re.IGNORECASE)
if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
LOG.debug('Route to %(destination)s via gateway %(gateway)s '
'exists.',
{'destination': destination, 'gateway': gateway})
else:
msg = _('Failed to create a route to %(destination)s via '
'gateway %(gateway)s: %(err_msg)s')
msg_args = {
'destination': destination,
'gateway': gateway,
'err_msg': e.message,
}
raise exception.NetAppException(msg % msg_args)
@na_utils.trace
def _ensure_broadcast_domain_for_port(self, node, port, mtu,
ipspace=DEFAULT_IPSPACE):
"""Ensure a port is in a broadcast domain. Create one if necessary.
If the IPspace:domain pair match for the given port, which commonly
happens in multi-node clusters, then there isn't anything to do.
Otherwise, we can assume the IPspace is correct and extant by this
point, so the remaining task is to remove the port from any domain it
is already in, create the domain for the IPspace if it doesn't exist,
and add the port to this domain.
"""
# Derive the broadcast domain name from the IPspace name since they
# need to be 1-1 and the default for both is the same name, 'Default'.
domain = re.sub(r'ipspace', 'domain', ipspace)
port_info = self._get_broadcast_domain_for_port(node, port)
# Port already in desired ipspace and broadcast domain.
if (port_info['ipspace'] == ipspace
and port_info['broadcast-domain'] == domain):
self._modify_broadcast_domain(domain, ipspace, mtu)
return
# If in another broadcast domain, remove port from it.
if port_info['broadcast-domain']:
self._remove_port_from_broadcast_domain(
node, port, port_info['broadcast-domain'],
port_info['ipspace'])
# If desired broadcast domain doesn't exist, create it.
if not self._broadcast_domain_exists(domain, ipspace):
self._create_broadcast_domain(domain, ipspace, mtu)
else:
self._modify_broadcast_domain(domain, ipspace, mtu)
# Move the port into the broadcast domain where it is needed.
self._add_port_to_broadcast_domain(node, port, domain, ipspace)
@na_utils.trace
def _get_broadcast_domain_for_port(self, node, port):
"""Get broadcast domain for a specific port."""
api_args = {
'query': {
'net-port-info': {
'node': node,
'port': port,
},
},
'desired-attributes': {
'net-port-info': {
'broadcast-domain': None,
'ipspace': None,
},
},
}
result = self.send_iter_request('net-port-get-iter', api_args)
net_port_info_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
port_info = net_port_info_list.get_children()
if not port_info:
msg = _('Could not find port %(port)s on node %(node)s.')
msg_args = {'port': port, 'node': node}
raise exception.NetAppException(msg % msg_args)
port = {
'broadcast-domain':
port_info[0].get_child_content('broadcast-domain'),
'ipspace': port_info[0].get_child_content('ipspace')
}
return port
@na_utils.trace
def _broadcast_domain_exists(self, domain, ipspace):
"""Check if a broadcast domain exists."""
api_args = {
'query': {
'net-port-broadcast-domain-info': {
'ipspace': ipspace,
'broadcast-domain': domain,
},
},
'desired-attributes': {
'net-port-broadcast-domain-info': None,
},
}
result = self.send_iter_request('net-port-broadcast-domain-get-iter',
api_args)
return self._has_records(result)
@na_utils.trace
def _create_broadcast_domain(self, domain, ipspace, mtu):
"""Create a broadcast domain."""
api_args = {
'ipspace': ipspace,
'broadcast-domain': domain,
'mtu': mtu,
}
self.send_request('net-port-broadcast-domain-create', api_args)
@na_utils.trace
def _modify_broadcast_domain(self, domain, ipspace, mtu):
"""Modify a broadcast domain."""
api_args = {
'ipspace': ipspace,
'broadcast-domain': domain,
'mtu': mtu,
}
self.send_request('net-port-broadcast-domain-modify', api_args)
@na_utils.trace
def _delete_broadcast_domain(self, domain, ipspace):
"""Delete a broadcast domain."""
api_args = {
'ipspace': ipspace,
'broadcast-domain': domain,
}
self.send_request('net-port-broadcast-domain-destroy', api_args)
@na_utils.trace
def _delete_broadcast_domains_for_ipspace(self, ipspace_name):
"""Deletes all broadcast domains in an IPspace."""
ipspaces = self.get_ipspaces(ipspace_name=ipspace_name)
if not ipspaces:
return
ipspace = ipspaces[0]
for broadcast_domain_name in ipspace['broadcast-domains']:
self._delete_broadcast_domain(broadcast_domain_name, ipspace_name)
@na_utils.trace
def _add_port_to_broadcast_domain(self, node, port, domain, ipspace):
qualified_port_name = ':'.join([node, port])
try:
api_args = {
'ipspace': ipspace,
'broadcast-domain': domain,
'ports': {
'net-qualified-port-name': qualified_port_name,
}
}
self.send_request('net-port-broadcast-domain-add-ports', api_args)
except netapp_api.NaApiError as e:
if e.code == (netapp_api.
E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN):
LOG.debug('Port %(port)s already exists in broadcast domain '
'%(domain)s', {'port': port, 'domain': domain})
else:
msg = _('Failed to add port %(port)s to broadcast domain '
'%(domain)s. %(err_msg)s')
msg_args = {
'port': qualified_port_name,
'domain': domain,
'err_msg': e.message,
}
raise exception.NetAppException(msg % msg_args)
@na_utils.trace
def _remove_port_from_broadcast_domain(self, node, port, domain, ipspace):
qualified_port_name = ':'.join([node, port])
api_args = {
'ipspace': ipspace,
'broadcast-domain': domain,
'ports': {
'net-qualified-port-name': qualified_port_name,
}
}
self.send_request('net-port-broadcast-domain-remove-ports', api_args)
@na_utils.trace
def network_interface_exists(self, vserver_name, node, port, ip, netmask,
vlan):
"""Checks if LIF exists."""
home_port_name = (port if not vlan else
'%(port)s-%(tag)s' % {'port': port, 'tag': vlan})
api_args = {
'query': {
'net-interface-info': {
'address': ip,
'home-node': node,
'home-port': home_port_name,
'netmask': netmask,
'vserver': vserver_name,
},
},
'desired-attributes': {
'net-interface-info': {
'interface-name': None,
},
},
}
result = self.send_iter_request('net-interface-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def list_network_interfaces(self):
"""Get the names of available LIFs."""
api_args = {
'desired-attributes': {
'net-interface-info': {
'interface-name': None,
},
},
}
result = self.send_iter_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('interface-name') for lif_info
in lif_info_list.get_children()]
@na_utils.trace
def get_network_interfaces(self, protocols=None):
"""Get available LIFs."""
protocols = na_utils.convert_to_list(protocols)
protocols = [protocol.lower() for protocol in protocols]
api_args = {
'query': {
'net-interface-info': {
'data-protocols': {
'data-protocol': '|'.join(protocols),
}
}
}
} if protocols else None
result = self.send_iter_request('net-interface-get-iter', api_args)
lif_info_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
interfaces = []
for lif_info in lif_info_list.get_children():
lif = {
'address': lif_info.get_child_content('address'),
'home-node': lif_info.get_child_content('home-node'),
'home-port': lif_info.get_child_content('home-port'),
'interface-name': lif_info.get_child_content('interface-name'),
'netmask': lif_info.get_child_content('netmask'),
'role': lif_info.get_child_content('role'),
'vserver': lif_info.get_child_content('vserver'),
}
interfaces.append(lif)
return interfaces
@na_utils.trace
def get_ipspace_name_for_vlan_port(self, vlan_node, vlan_port, vlan_id):
"""Gets IPSpace name for specified VLAN"""
if not self.features.IPSPACES:
return None
port = vlan_port if not vlan_id else '%(port)s-%(id)s' % {
'port': vlan_port,
'id': vlan_id,
}
api_args = {'node': vlan_node, 'port': port}
try:
result = self.send_request('net-port-get', api_args)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EOBJECTNOTFOUND:
msg = _('No pre-existing port or ipspace was found for '
'%(port)s, will attempt to create one.')
msg_args = {'port': port}
LOG.debug(msg, msg_args)
return None
else:
raise
attributes = result.get_child_by_name('attributes')
net_port_info = attributes.get_child_by_name('net-port-info')
ipspace_name = net_port_info.get_child_content('ipspace')
return ipspace_name
@na_utils.trace
def get_ipspaces(self, ipspace_name=None):
"""Gets one or more IPSpaces."""
if not self.features.IPSPACES:
return []
api_args = {}
if ipspace_name:
api_args['query'] = {
'net-ipspaces-info': {
'ipspace': ipspace_name,
}
}
result = self.send_iter_request('net-ipspaces-get-iter', api_args)
if not self._has_records(result):
return []
ipspaces = []
for net_ipspaces_info in result.get_child_by_name(
'attributes-list').get_children():
ipspace = {
'ports': [],
'vservers': [],
'broadcast-domains': [],
}
ports = net_ipspaces_info.get_child_by_name(
'ports') or netapp_api.NaElement('none')
for port in ports.get_children():
ipspace['ports'].append(port.get_content())
vservers = net_ipspaces_info.get_child_by_name(
'vservers') or netapp_api.NaElement('none')
for vserver in vservers.get_children():
ipspace['vservers'].append(vserver.get_content())
broadcast_domains = net_ipspaces_info.get_child_by_name(
'broadcast-domains') or netapp_api.NaElement('none')
for broadcast_domain in broadcast_domains.get_children():
ipspace['broadcast-domains'].append(
broadcast_domain.get_content())
ipspace['ipspace'] = net_ipspaces_info.get_child_content('ipspace')
ipspace['id'] = net_ipspaces_info.get_child_content('id')
ipspace['uuid'] = net_ipspaces_info.get_child_content('uuid')
ipspaces.append(ipspace)
return ipspaces
@na_utils.trace
def ipspace_exists(self, ipspace_name):
"""Checks if IPspace exists."""
if not self.features.IPSPACES:
return False
api_args = {
'query': {
'net-ipspaces-info': {
'ipspace': ipspace_name,
},
},
'desired-attributes': {
'net-ipspaces-info': {
'ipspace': None,
},
},
}
result = self.send_iter_request('net-ipspaces-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def create_ipspace(self, ipspace_name):
"""Creates an IPspace."""
api_args = {'ipspace': ipspace_name}
self.send_request('net-ipspaces-create', api_args)
@na_utils.trace
def delete_ipspace(self, ipspace_name):
"""Deletes an IPspace."""
self._delete_broadcast_domains_for_ipspace(ipspace_name)
api_args = {'ipspace': ipspace_name}
self.send_request('net-ipspaces-destroy', api_args)
@na_utils.trace
def add_vserver_to_ipspace(self, ipspace_name, vserver_name):
"""Assigns a vserver to an IPspace."""
api_args = {'ipspace': ipspace_name, 'vserver': vserver_name}
self.send_request('net-ipspaces-assign-vserver', api_args)
@na_utils.trace
def get_node_for_aggregate(self, aggregate_name):
"""Get home node for the specified aggregate.
This API could return None, most notably if it was sent
to a Vserver LIF, so the caller must be able to handle that case.
"""
if not aggregate_name:
return None
desired_attributes = {
'aggr-attributes': {
'aggregate-name': None,
'aggr-ownership-attributes': {
'home-name': None,
},
},
}
try:
aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
desired_attributes=desired_attributes)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EAPINOTFOUND:
return None
else:
raise
if len(aggrs) < 1:
return None
aggr_ownership_attrs = aggrs[0].get_child_by_name(
'aggr-ownership-attributes') or netapp_api.NaElement('none')
return aggr_ownership_attrs.get_child_content('home-name')
@na_utils.trace
def get_cluster_aggregate_capacities(self, aggregate_names):
"""Calculates capacity of one or more aggregates.
Returns dictionary of aggregate capacity metrics.
'size-used' is the actual space consumed on the aggregate.
'size-available' is the actual space remaining.
'size-total' is the defined total aggregate size, such that
used + available = total.
"""
if aggregate_names is not None and len(aggregate_names) == 0:
return {}
desired_attributes = {
'aggr-attributes': {
'aggregate-name': None,
'aggr-space-attributes': {
'size-available': None,
'size-total': None,
'size-used': None,
},
},
}
aggrs = self._get_aggregates(aggregate_names=aggregate_names,
desired_attributes=desired_attributes)
aggr_space_dict = dict()
for aggr in aggrs:
aggr_name = aggr.get_child_content('aggregate-name')
aggr_space_attrs = aggr.get_child_by_name('aggr-space-attributes')
aggr_space_dict[aggr_name] = {
'available':
int(aggr_space_attrs.get_child_content('size-available')),
'total':
int(aggr_space_attrs.get_child_content('size-total')),
'used':
int(aggr_space_attrs.get_child_content('size-used')),
}
return aggr_space_dict
@na_utils.trace
def get_vserver_aggregate_capacities(self, aggregate_names=None):
"""Calculates capacity of one or more aggregates for a vserver.
Returns dictionary of aggregate capacity metrics. This must
be called against a Vserver LIF.
"""
if aggregate_names is not None and len(aggregate_names) == 0:
return {}
api_args = {
'desired-attributes': {
'vserver-info': {
'vserver-name': None,
'vserver-aggr-info-list': {
'vserver-aggr-info': {
'aggr-name': None,
'aggr-availsize': None,
},
},
},
},
}
result = self.send_request('vserver-get', api_args)
attributes = result.get_child_by_name('attributes')
if not attributes:
raise exception.NetAppException('Failed to read Vserver info')
vserver_info = attributes.get_child_by_name('vserver-info')
vserver_name = vserver_info.get_child_content('vserver-name')
vserver_aggr_info_element = vserver_info.get_child_by_name(
'vserver-aggr-info-list') or netapp_api.NaElement('none')
vserver_aggr_info_list = vserver_aggr_info_element.get_children()
if not vserver_aggr_info_list:
LOG.warning('No aggregates assigned to Vserver %s.',
vserver_name)
# Return dict of key-value pair of aggr_name:aggr_size_available.
aggr_space_dict = {}
for aggr_info in vserver_aggr_info_list:
aggr_name = aggr_info.get_child_content('aggr-name')
if aggregate_names is None or aggr_name in aggregate_names:
aggr_size = int(aggr_info.get_child_content('aggr-availsize'))
aggr_space_dict[aggr_name] = {'available': aggr_size}
LOG.debug('Found available Vserver aggregates: %s', aggr_space_dict)
return aggr_space_dict
@na_utils.trace
def _get_aggregates(self, aggregate_names=None, desired_attributes=None):
query = {
'aggr-attributes': {
'aggregate-name': '|'.join(aggregate_names),
}
} if aggregate_names else None
api_args = {}
if query:
api_args['query'] = query
if desired_attributes:
api_args['desired-attributes'] = desired_attributes
result = self.send_iter_request('aggr-get-iter', api_args)
if not self._has_records(result):
return []
else:
return result.get_child_by_name('attributes-list').get_children()
def get_performance_instance_uuids(self, object_name, node_name):
"""Get UUIDs of performance instances for a cluster node."""
api_args = {
'objectname': object_name,
'query': {
'instance-info': {
'uuid': node_name + ':*',
}
}
}
result = self.send_request('perf-object-instance-list-info-iter',
api_args)
uuids = []
instances = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('None')
for instance_info in instances.get_children():
uuids.append(instance_info.get_child_content('uuid'))
return uuids
def get_performance_counter_info(self, object_name, counter_name):
"""Gets info about one or more Data ONTAP performance counters."""
api_args = {'objectname': object_name}
result = self.send_request('perf-object-counter-list-info', api_args)
counters = result.get_child_by_name(
'counters') or netapp_api.NaElement('None')
for counter in counters.get_children():
if counter.get_child_content('name') == counter_name:
labels = []
label_list = counter.get_child_by_name(
'labels') or netapp_api.NaElement('None')
for label in label_list.get_children():
labels.extend(label.get_content().split(','))
base_counter = counter.get_child_content('base-counter')
return {
'name': counter_name,
'labels': labels,
'base-counter': base_counter,
}
else:
raise exception.NotFound(_('Counter %s not found') % counter_name)
def get_performance_counters(self, object_name, instance_uuids,
counter_names):
"""Gets one or more cDOT performance counters."""
api_args = {
'objectname': object_name,
'instance-uuids': [
{'instance-uuid': instance_uuid}
for instance_uuid in instance_uuids
],
'counters': [
{'counter': counter} for counter in counter_names
],
}
result = self.send_request('perf-object-get-instances', api_args)
counter_data = []
timestamp = result.get_child_content('timestamp')
instances = result.get_child_by_name(
'instances') or netapp_api.NaElement('None')
for instance in instances.get_children():
instance_name = instance.get_child_content('name')
instance_uuid = instance.get_child_content('uuid')
node_name = instance_uuid.split(':')[0]
counters = instance.get_child_by_name(
'counters') or netapp_api.NaElement('None')
for counter in counters.get_children():
counter_name = counter.get_child_content('name')
counter_value = counter.get_child_content('value')
counter_data.append({
'instance-name': instance_name,
'instance-uuid': instance_uuid,
'node-name': node_name,
'timestamp': timestamp,
counter_name: counter_value,
})
return counter_data
@na_utils.trace
def setup_security_services(self, security_services, vserver_client,
vserver_name):
api_args = {
'name-mapping-switch': [
{'nmswitch': 'ldap'},
{'nmswitch': 'file'}
],
'name-server-switch': [
{'nsswitch': 'ldap'},
{'nsswitch': 'file'}
],
'vserver-name': vserver_name,
}
self.send_request('vserver-modify', api_args)
for security_service in security_services:
if security_service['type'].lower() == 'ldap':
vserver_client.configure_ldap(security_service)
elif security_service['type'].lower() == 'active_directory':
vserver_client.configure_active_directory(security_service,
vserver_name)
elif security_service['type'].lower() == 'kerberos':
self.create_kerberos_realm(security_service)
vserver_client.configure_kerberos(security_service,
vserver_name)
else:
msg = _('Unsupported security service type %s for '
'Data ONTAP driver')
raise exception.NetAppException(msg % security_service['type'])
@na_utils.trace
def enable_nfs(self, versions):
"""Enables NFS on Vserver."""
self.send_request('nfs-enable')
self._enable_nfs_protocols(versions)
self._create_default_nfs_export_rules()
@na_utils.trace
def _enable_nfs_protocols(self, versions):
"""Set the enabled NFS protocol versions."""
nfs3 = 'true' if 'nfs3' in versions else 'false'
nfs40 = 'true' if 'nfs4.0' in versions else 'false'
nfs41 = 'true' if 'nfs4.1' in versions else 'false'
nfs_service_modify_args = {
'is-nfsv3-enabled': nfs3,
'is-nfsv40-enabled': nfs40,
'is-nfsv41-enabled': nfs41,
}
self.send_request('nfs-service-modify', nfs_service_modify_args)
@na_utils.trace
def _create_default_nfs_export_rules(self):
"""Create the default export rule for the NFS service."""
export_rule_create_args = {
'client-match': '0.0.0.0/0',
'policy-name': 'default',
'ro-rule': {
'security-flavor': 'any',
},
'rw-rule': {
'security-flavor': 'never',
},
}
self.send_request('export-rule-create', export_rule_create_args)
export_rule_create_args['client-match'] = '::/0'
self.send_request('export-rule-create', export_rule_create_args)
@na_utils.trace
def configure_ldap(self, security_service):
"""Configures LDAP on Vserver."""
config_name = hashlib.md5(six.b(security_service['id'])).hexdigest()
api_args = {
'ldap-client-config': config_name,
'servers': {
'ip-address': security_service['server'],
},
'tcp-port': '389',
'schema': 'RFC-2307',
'bind-password': security_service['password'],
}
self.send_request('ldap-client-create', api_args)
api_args = {'client-config': config_name, 'client-enabled': 'true'}
self.send_request('ldap-config-create', api_args)
@na_utils.trace
def configure_active_directory(self, security_service, vserver_name):
"""Configures AD on Vserver."""
self.configure_dns(security_service)
self.set_preferred_dc(security_service)
# 'cifs-server' is CIFS Server NetBIOS Name, max length is 15.
# Should be unique within each domain (data['domain']).
# Cut to 15 char with begin and end, attempt to make valid DNS hostname
cifs_server = (vserver_name[0:8] +
'-' +
vserver_name[-6:]).replace('_', '-').upper()
api_args = {
'admin-username': security_service['user'],
'admin-password': security_service['password'],
'force-account-overwrite': 'true',
'cifs-server': cifs_server,
'domain': security_service['domain'],
}
if security_service['ou'] is not None:
api_args['organizational-unit'] = security_service['ou']
try:
LOG.debug("Trying to setup CIFS server with data: %s", api_args)
self.send_request('cifs-server-create', api_args)
except netapp_api.NaApiError as e:
msg = _("Failed to create CIFS server entry. %s")
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def create_kerberos_realm(self, security_service):
"""Creates Kerberos realm on cluster."""
api_args = {
'admin-server-ip': security_service['server'],
'admin-server-port': '749',
'clock-skew': '5',
'comment': '',
'config-name': security_service['id'],
'kdc-ip': security_service['server'],
'kdc-port': '88',
'kdc-vendor': 'other',
'password-server-ip': security_service['server'],
'password-server-port': '464',
'realm': security_service['domain'].upper(),
}
try:
self.send_request('kerberos-realm-create', api_args)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EDUPLICATEENTRY:
LOG.debug('Kerberos realm config already exists.')
else:
msg = _('Failed to create Kerberos realm. %s')
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def configure_kerberos(self, security_service, vserver_name):
"""Configures Kerberos for NFS on Vserver."""
self.configure_dns(security_service)
spn = self._get_kerberos_service_principal_name(
security_service, vserver_name)
lifs = self.list_network_interfaces()
if not lifs:
msg = _("Cannot set up Kerberos. There are no LIFs configured.")
raise exception.NetAppException(msg)
for lif_name in lifs:
api_args = {
'admin-password': security_service['password'],
'admin-user-name': security_service['user'],
'interface-name': lif_name,
'is-kerberos-enabled': 'true',
'service-principal-name': spn,
}
self.send_request('kerberos-config-modify', api_args)
@na_utils.trace
def _get_kerberos_service_principal_name(self, security_service,
vserver_name):
return ('nfs/' + vserver_name.replace('_', '-') + '.' +
security_service['domain'] + '@' +
security_service['domain'].upper())
@na_utils.trace
def configure_dns(self, security_service):
api_args = {
'domains': {
'string': security_service['domain'],
},
'name-servers': [],
'dns-state': 'enabled',
}
for dns_ip in security_service['dns_ip'].split(','):
api_args['name-servers'].append({'ip-address': dns_ip.strip()})
try:
self.send_request('net-dns-create', api_args)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EDUPLICATEENTRY:
LOG.error("DNS exists for Vserver.")
else:
msg = _("Failed to configure DNS. %s")
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def set_preferred_dc(self, security_service):
# server is optional
if not security_service['server']:
return
api_args = {
'preferred-dc': [],
'domain': security_service['domain'],
}
for dc_ip in security_service['server'].split(','):
api_args['preferred-dc'].append({'string': dc_ip.strip()})
if self.features.CIFS_DC_ADD_SKIP_CHECK:
api_args['skip-config-validation'] = 'false'
try:
self.send_request('cifs-domain-preferred-dc-add', api_args)
except netapp_api.NaApiError as e:
msg = _("Failed to set preferred DC. %s")
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def create_volume(self, aggregate_name, volume_name, size_gb,
thin_provisioned=False, snapshot_policy=None,
language=None, dedup_enabled=False,
compression_enabled=False, max_files=None,
snapshot_reserve=None, volume_type='rw',
qos_policy_group=None,
encrypt=False, **options):
"""Creates a volume."""
api_args = {
'containing-aggr-name': aggregate_name,
'size': six.text_type(size_gb) + 'g',
'volume': volume_name,
'volume-type': volume_type,
}
if volume_type != 'dp':
api_args['junction-path'] = '/%s' % volume_name
if thin_provisioned:
api_args['space-reserve'] = 'none'
if snapshot_policy is not None:
api_args['snapshot-policy'] = snapshot_policy
if language is not None:
api_args['language-code'] = language
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
if encrypt is True:
if not self.features.FLEXVOL_ENCRYPTION:
msg = 'Flexvol encryption is not supported on this backend.'
raise exception.NetAppException(msg)
else:
api_args['encrypt'] = 'true'
self.send_request('volume-create', api_args)
self.update_volume_efficiency_attributes(volume_name,
dedup_enabled,
compression_enabled)
if max_files is not None:
self.set_volume_max_files(volume_name, max_files)
@na_utils.trace
def enable_dedup(self, volume_name):
"""Enable deduplication on volume."""
api_args = {'path': '/vol/%s' % volume_name}
self.send_request('sis-enable', api_args)
@na_utils.trace
def disable_dedup(self, volume_name):
"""Disable deduplication on volume."""
api_args = {'path': '/vol/%s' % volume_name}
self.send_request('sis-disable', api_args)
@na_utils.trace
def enable_compression(self, volume_name):
"""Enable compression on volume."""
api_args = {
'path': '/vol/%s' % volume_name,
'enable-compression': 'true'
}
self.send_request('sis-set-config', api_args)
@na_utils.trace
def disable_compression(self, volume_name):
"""Disable compression on volume."""
api_args = {
'path': '/vol/%s' % volume_name,
'enable-compression': 'false'
}
self.send_request('sis-set-config', api_args)
@na_utils.trace
def get_volume_efficiency_status(self, volume_name):
"""Get dedupe & compression status for a volume."""
api_args = {
'query': {
'sis-status-info': {
'path': '/vol/%s' % volume_name,
},
},
'desired-attributes': {
'sis-status-info': {
'state': None,
'is-compression-enabled': None,
},
},
}
try:
result = self.send_iter_request('sis-get-iter', api_args)
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
sis_status_info = attributes_list.get_child_by_name(
'sis-status-info') or netapp_api.NaElement('none')
except exception.NetAppException:
msg = _('Failed to get volume efficiency status for %s.')
LOG.error(msg, volume_name)
sis_status_info = netapp_api.NaElement('none')
return {
'dedupe': True if 'enabled' == sis_status_info.get_child_content(
'state') else False,
'compression': True if 'true' == sis_status_info.get_child_content(
'is-compression-enabled') else False,
}
@na_utils.trace
def set_volume_max_files(self, volume_name, max_files):
"""Set flexvol file limit."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-inode-attributes': {
'files-total': max_files,
},
},
},
}
self.send_request('volume-modify-iter', api_args)
@na_utils.trace
def set_volume_size(self, volume_name, size_gb):
"""Set volume size."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-space-attributes': {
'size': int(size_gb) * units.Gi,
},
},
},
}
result = self.send_request('volume-modify-iter', api_args)
failures = result.get_child_content('num-failed')
if failures and int(failures) > 0:
failure_list = result.get_child_by_name(
'failure-list') or netapp_api.NaElement('none')
errors = failure_list.get_children()
if errors:
raise netapp_api.NaApiError(
errors[0].get_child_content('error-code'),
errors[0].get_child_content('error-message'))
@na_utils.trace
def set_volume_snapdir_access(self, volume_name, hide_snapdir):
"""Set volume snapshot directory visibility."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-snapshot-attributes': {
'snapdir-access-enabled': six.text_type(
not hide_snapdir).lower(),
},
},
},
}
result = self.send_request('volume-modify-iter', api_args)
failures = result.get_child_content('num-failed')
if failures and int(failures) > 0:
failure_list = result.get_child_by_name(
'failure-list') or netapp_api.NaElement('none')
errors = failure_list.get_children()
if errors:
raise netapp_api.NaApiError(
errors[0].get_child_content('error-code'),
errors[0].get_child_content('error-message'))
@na_utils.trace
def set_volume_filesys_size_fixed(self,
volume_name, filesys_size_fixed=False):
"""Set volume file system size fixed to true/false."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-space-attributes': {
'is-filesys-size-fixed': six.text_type(
filesys_size_fixed).lower(),
},
},
},
}
result = self.send_request('volume-modify-iter', api_args)
failures = result.get_child_content('num-failed')
if failures and int(failures) > 0:
failure_list = result.get_child_by_name(
'failure-list') or netapp_api.NaElement('none')
errors = failure_list.get_children()
if errors:
raise netapp_api.NaApiError(
errors[0].get_child_content('error-code'),
errors[0].get_child_content('error-message'))
@na_utils.trace
def set_volume_security_style(self, volume_name, security_style='unix'):
"""Set volume security style"""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-security-attributes': {
'style': security_style,
},
},
},
}
result = self.send_request('volume-modify-iter', api_args)
failures = result.get_child_content('num-failed')
if failures and int(failures) > 0:
failure_list = result.get_child_by_name(
'failure-list') or netapp_api.NaElement('none')
errors = failure_list.get_children()
if errors:
raise netapp_api.NaApiError(
errors[0].get_child_content('error-code'),
errors[0].get_child_content('error-message'))
@na_utils.trace
def set_volume_name(self, volume_name, new_volume_name):
"""Set flexvol name."""
api_args = {
'volume': volume_name,
'new-volume-name': new_volume_name,
}
self.send_request('volume-rename', api_args)
@na_utils.trace
def rename_vserver(self, vserver_name, new_vserver_name):
"""Rename a vserver."""
api_args = {
'vserver-name': vserver_name,
'new-name': new_vserver_name,
}
self.send_request('vserver-rename', api_args)
@na_utils.trace
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,
qos_policy_group=None, hide_snapdir=None,
**options):
"""Update backend volume for a share as necessary."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': aggregate_name,
'name': volume_name,
},
},
},
'attributes': {
'volume-attributes': {
'volume-inode-attributes': {},
'volume-language-attributes': {},
'volume-snapshot-attributes': {},
'volume-space-attributes': {
'space-guarantee': ('none' if thin_provisioned else
'volume'),
},
},
},
}
if language:
api_args['attributes']['volume-attributes'][
'volume-language-attributes']['language'] = language
if max_files:
api_args['attributes']['volume-attributes'][
'volume-inode-attributes']['files-total'] = max_files
if snapshot_policy:
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,
}
if hide_snapdir in (True, False):
# Value of hide_snapdir needs to be inverted for ZAPI parameter
api_args['attributes']['volume-attributes'][
'volume-snapshot-attributes'][
'snapdir-access-enabled'] = six.text_type(
not hide_snapdir).lower()
self.send_request('volume-modify-iter', api_args)
# Efficiency options must be handled separately
self.update_volume_efficiency_attributes(volume_name,
dedup_enabled,
compression_enabled)
@na_utils.trace
def update_volume_efficiency_attributes(self, volume_name, dedup_enabled,
compression_enabled):
"""Update dedupe & compression attributes to match desired values."""
efficiency_status = self.get_volume_efficiency_status(volume_name)
# cDOT compression requires dedup to be enabled
dedup_enabled = dedup_enabled or compression_enabled
# enable/disable dedup if needed
if dedup_enabled and not efficiency_status['dedupe']:
self.enable_dedup(volume_name)
elif not dedup_enabled and efficiency_status['dedupe']:
self.disable_dedup(volume_name)
# enable/disable compression if needed
if compression_enabled and not efficiency_status['compression']:
self.enable_compression(volume_name)
elif not compression_enabled and efficiency_status['compression']:
self.disable_compression(volume_name)
@na_utils.trace
def volume_exists(self, volume_name):
"""Checks if volume exists."""
LOG.debug('Checking if volume %s exists', volume_name)
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'name': None,
},
},
},
}
result = self.send_iter_request('volume-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def is_flexvol_encrypted(self, volume_name, vserver_name):
"""Checks whether the volume is encrypted or not."""
if not self.features.FLEXVOL_ENCRYPTION:
return False
api_args = {
'query': {
'volume-attributes': {
'encrypt': 'true',
'volume-id-attributes': {
'name': volume_name,
'owning-vserver-name': vserver_name,
},
},
},
'desired-attributes': {
'volume-attributes': {
'encrypt': None,
},
},
}
result = self.send_iter_request('volume-get-iter', api_args)
if self._has_records(result):
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes = attributes_list.get_child_by_name(
'volume-attributes') or netapp_api.NaElement('none')
encrypt = volume_attributes.get_child_content('encrypt')
if encrypt:
return True
return False
@na_utils.trace
def get_aggregate_for_volume(self, volume_name):
"""Get the name of the aggregate containing a volume."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_name,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': None,
'name': None,
},
},
},
}
result = self.send_iter_request('volume-get-iter', api_args)
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes = attributes_list.get_child_by_name(
'volume-attributes') or netapp_api.NaElement('none')
volume_id_attributes = volume_attributes.get_child_by_name(
'volume-id-attributes') or netapp_api.NaElement('none')
aggregate = volume_id_attributes.get_child_content(
'containing-aggregate-name')
if not aggregate:
msg = _('Could not find aggregate for volume %s.')
raise exception.NetAppException(msg % volume_name)
return aggregate
@na_utils.trace
def volume_has_luns(self, volume_name):
"""Checks if volume has LUNs."""
LOG.debug('Checking if volume %s has LUNs', volume_name)
api_args = {
'query': {
'lun-info': {
'volume': volume_name,
},
},
'desired-attributes': {
'lun-info': {
'path': None,
},
},
}
result = self.send_iter_request('lun-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def volume_has_junctioned_volumes(self, volume_name):
"""Checks if volume has volumes mounted beneath its junction path."""
junction_path = self.get_volume_junction_path(volume_name)
if not junction_path:
return False
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'junction-path': junction_path + '/*',
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'name': None,
},
},
},
}
result = self.send_iter_request('volume-get-iter', api_args)
return self._has_records(result)
@na_utils.trace
def get_volume(self, volume_name):
"""Returns the volume with the specified name, if present."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'name': volume_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-qos-attributes': {
'policy-group-name': None,
},
'volume-space-attributes': {
'size': None,
},
},
},
}
result = self.send_request('volume-get-iter', api_args)
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes_list = attributes_list.get_children()
if not self._has_records(result):
raise exception.StorageResourceNotFound(name=volume_name)
elif len(volume_attributes_list) > 1:
msg = _('Could not find unique volume %(vol)s.')
msg_args = {'vol': volume_name}
raise exception.NetAppException(msg % msg_args)
volume_attributes = volume_attributes_list[0]
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')
volume = {
'aggregate': volume_id_attributes.get_child_content(
'containing-aggregate-name'),
'junction-path': volume_id_attributes.get_child_content(
'junction-path'),
'name': volume_id_attributes.get_child_content('name'),
'owning-vserver-name': volume_id_attributes.get_child_content(
'owning-vserver-name'),
'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
@na_utils.trace
def get_volume_at_junction_path(self, junction_path):
"""Returns the volume with the specified junction path, if present."""
if not junction_path:
return None
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'junction-path': junction_path,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
'type': None,
'style': None,
},
'volume-space-attributes': {
'size': None,
}
},
},
}
result = self.send_iter_request('volume-get-iter', api_args)
if not self._has_records(result):
return None
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes = attributes_list.get_child_by_name(
'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_space_attributes = volume_attributes.get_child_by_name(
'volume-space-attributes') or netapp_api.NaElement('none')
volume = {
'aggregate': volume_id_attributes.get_child_content(
'containing-aggregate-name'),
'junction-path': volume_id_attributes.get_child_content(
'junction-path'),
'name': volume_id_attributes.get_child_content('name'),
'type': volume_id_attributes.get_child_content('type'),
'style': volume_id_attributes.get_child_content('style'),
'size': volume_space_attributes.get_child_content('size'),
}
return volume
@na_utils.trace
def get_volume_to_manage(self, aggregate_name, volume_name):
"""Get flexvol to be managed by Manila."""
api_args = {
'query': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': aggregate_name,
'name': volume_name,
},
},
},
'desired-attributes': {
'volume-attributes': {
'volume-id-attributes': {
'containing-aggregate-name': None,
'junction-path': None,
'name': None,
'type': None,
'style': None,
'owning-vserver-name': None,
},
'volume-qos-attributes': {
'policy-group-name': None,
},
'volume-space-attributes': {
'size': None,
},
},
},
}
result = self.send_iter_request('volume-get-iter', api_args)
if not self._has_records(result):
return None
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
volume_attributes = attributes_list.get_child_by_name(
'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')
volume = {
'aggregate': volume_id_attributes.get_child_content(
'containing-aggregate-name'),
'junction-path': volume_id_attributes.get_child_content(
'junction-path'),
'name': volume_id_attributes.get_child_content('name'),
'type': volume_id_attributes.get_child_content('type'),
'style': volume_id_attributes.get_child_content('style'),
'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,
qos_policy_group=None, **options):
"""Clones a volume."""
api_args = {
'volume': volume_name,
'parent-volume': parent_volume_name,
'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:
self.split_volume_clone(volume_name)
@na_utils.trace
def split_volume_clone(self, volume_name):
"""Begins splitting a clone from its parent."""
try:
api_args = {'volume': volume_name}
self.send_request('volume-clone-split-start', api_args)
except netapp_api.NaApiError as e:
if e.code == netapp_api.EVOL_CLONE_BEING_SPLIT:
return
raise
@na_utils.trace
def check_volume_clone_split_completed(self, volume_name):
"""Check if volume clone split operation already finished"""
return self.get_volume_clone_parent_snaphot(volume_name) is None
@na_utils.trace
def get_volume_clone_parent_snaphot(self, volume_name):
"""Gets volume's clone parent.