"Fossies" - the Fresh Open Source Software Archive

Member "manila-8.1.4/manila/share/drivers/netapp/dataontap/client/client_cmode.py" (19 Nov 2020, 148679 Bytes) of package /linux/misc/openstack/manila-8.1.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "client_cmode.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 8.1.3_vs_8.1.4.

    1 # Copyright (c) 2014 Alex Meade.  All rights reserved.
    2 # Copyright (c) 2015 Clinton Knight.  All rights reserved.
    3 # Copyright (c) 2015 Tom Barron.  All rights reserved.
    4 # Copyright (c) 2018 Jose Porrua.  All rights reserved.
    5 #
    6 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    7 #    not use this file except in compliance with the License. You may obtain
    8 #    a copy of the License at
    9 #
   10 #         http://www.apache.org/licenses/LICENSE-2.0
   11 #
   12 #    Unless required by applicable law or agreed to in writing, software
   13 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   14 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   15 #    License for the specific language governing permissions and limitations
   16 #    under the License.
   17 
   18 
   19 import copy
   20 import hashlib
   21 import re
   22 import time
   23 
   24 from oslo_log import log
   25 from oslo_utils import strutils
   26 from oslo_utils import units
   27 import six
   28 
   29 from manila import exception
   30 from manila.i18n import _
   31 from manila.share.drivers.netapp.dataontap.client import api as netapp_api
   32 from manila.share.drivers.netapp.dataontap.client import client_base
   33 from manila.share.drivers.netapp import utils as na_utils
   34 
   35 
   36 LOG = log.getLogger(__name__)
   37 DELETED_PREFIX = 'deleted_manila_'
   38 DEFAULT_IPSPACE = 'Default'
   39 DEFAULT_MAX_PAGE_LENGTH = 50
   40 CUTOVER_ACTION_MAP = {
   41     'defer': 'defer_on_failure',
   42     'abort': 'abort_on_failure',
   43     'force': 'force',
   44     'wait': 'wait',
   45 }
   46 
   47 
   48 class NetAppCmodeClient(client_base.NetAppBaseClient):
   49 
   50     def __init__(self, **kwargs):
   51         super(NetAppCmodeClient, self).__init__(**kwargs)
   52         self.vserver = kwargs.get('vserver')
   53         self.connection.set_vserver(self.vserver)
   54 
   55         # Default values to run first api.
   56         self.connection.set_api_version(1, 15)
   57         (major, minor) = self.get_ontapi_version(cached=False)
   58         self.connection.set_api_version(major, minor)
   59         system_version = self.get_system_version(cached=False)
   60         self.connection.set_system_version(system_version)
   61 
   62         self._init_features()
   63 
   64     def _init_features(self):
   65         """Initialize cDOT feature support map."""
   66         super(NetAppCmodeClient, self)._init_features()
   67 
   68         ontapi_version = self.get_ontapi_version(cached=True)
   69         ontapi_1_20 = ontapi_version >= (1, 20)
   70         ontapi_1_2x = (1, 20) <= ontapi_version < (1, 30)
   71         ontapi_1_30 = ontapi_version >= (1, 30)
   72         ontapi_1_110 = ontapi_version >= (1, 110)
   73         ontapi_1_150 = ontapi_version >= (1, 150)
   74 
   75         self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
   76         self.features.add_feature('SYSTEM_METRICS', supported=ontapi_1_2x)
   77         self.features.add_feature('SYSTEM_CONSTITUENT_METRICS',
   78                                   supported=ontapi_1_30)
   79         self.features.add_feature('BROADCAST_DOMAINS', supported=ontapi_1_30)
   80         self.features.add_feature('IPSPACES', supported=ontapi_1_30)
   81         self.features.add_feature('SUBNETS', supported=ontapi_1_30)
   82         self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
   83         self.features.add_feature('ADVANCED_DISK_PARTITIONING',
   84                                   supported=ontapi_1_30)
   85         self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_110)
   86         self.features.add_feature('CIFS_DC_ADD_SKIP_CHECK',
   87                                   supported=ontapi_1_150)
   88 
   89     def _invoke_vserver_api(self, na_element, vserver):
   90         server = copy.copy(self.connection)
   91         server.set_vserver(vserver)
   92         result = server.invoke_successfully(na_element, True)
   93         return result
   94 
   95     def _has_records(self, api_result_element):
   96         if (not api_result_element.get_child_content('num-records') or
   97                 api_result_element.get_child_content('num-records') == '0'):
   98             return False
   99         else:
  100             return True
  101 
  102     def _get_record_count(self, api_result_element):
  103         try:
  104             return int(api_result_element.get_child_content('num-records'))
  105         except TypeError:
  106             msg = _('Missing record count for NetApp iterator API invocation.')
  107             raise exception.NetAppException(msg)
  108 
  109     def set_vserver(self, vserver):
  110         self.vserver = vserver
  111         self.connection.set_vserver(vserver)
  112 
  113     def send_iter_request(self, api_name, api_args=None,
  114                           max_page_length=DEFAULT_MAX_PAGE_LENGTH):
  115         """Invoke an iterator-style getter API."""
  116 
  117         if not api_args:
  118             api_args = {}
  119 
  120         api_args['max-records'] = max_page_length
  121 
  122         # Get first page
  123         result = self.send_request(api_name, api_args)
  124 
  125         # Most commonly, we can just return here if there is no more data
  126         next_tag = result.get_child_content('next-tag')
  127         if not next_tag:
  128             return result
  129 
  130         # Ensure pagination data is valid and prepare to store remaining pages
  131         num_records = self._get_record_count(result)
  132         attributes_list = result.get_child_by_name('attributes-list')
  133         if not attributes_list:
  134             msg = _('Missing attributes list for API %s.') % api_name
  135             raise exception.NetAppException(msg)
  136 
  137         # Get remaining pages, saving data into first page
  138         while next_tag is not None:
  139             next_api_args = copy.deepcopy(api_args)
  140             next_api_args['tag'] = next_tag
  141             next_result = self.send_request(api_name, next_api_args)
  142 
  143             next_attributes_list = next_result.get_child_by_name(
  144                 'attributes-list') or netapp_api.NaElement('none')
  145 
  146             for record in next_attributes_list.get_children():
  147                 attributes_list.add_child_elem(record)
  148 
  149             num_records += self._get_record_count(next_result)
  150             next_tag = next_result.get_child_content('next-tag')
  151 
  152         result.get_child_by_name('num-records').set_content(
  153             six.text_type(num_records))
  154         result.get_child_by_name('next-tag').set_content('')
  155         return result
  156 
  157     @na_utils.trace
  158     def create_vserver(self, vserver_name, root_volume_aggregate_name,
  159                        root_volume_name, aggregate_names, ipspace_name):
  160         """Creates new vserver and assigns aggregates."""
  161         create_args = {
  162             'vserver-name': vserver_name,
  163             'root-volume-security-style': 'unix',
  164             'root-volume-aggregate': root_volume_aggregate_name,
  165             'root-volume': root_volume_name,
  166             'name-server-switch': {
  167                 'nsswitch': 'file',
  168             },
  169         }
  170 
  171         if ipspace_name:
  172             if not self.features.IPSPACES:
  173                 msg = 'IPSpaces are not supported on this backend.'
  174                 raise exception.NetAppException(msg)
  175             else:
  176                 create_args['ipspace'] = ipspace_name
  177 
  178         self.send_request('vserver-create', create_args)
  179 
  180         aggr_list = [{'aggr-name': aggr_name} for aggr_name in aggregate_names]
  181         modify_args = {
  182             'aggr-list': aggr_list,
  183             'vserver-name': vserver_name,
  184         }
  185         self.send_request('vserver-modify', modify_args)
  186 
  187     @na_utils.trace
  188     def vserver_exists(self, vserver_name):
  189         """Checks if Vserver exists."""
  190         LOG.debug('Checking if Vserver %s exists', vserver_name)
  191 
  192         api_args = {
  193             'query': {
  194                 'vserver-info': {
  195                     'vserver-name': vserver_name,
  196                 },
  197             },
  198             'desired-attributes': {
  199                 'vserver-info': {
  200                     'vserver-name': None,
  201                 },
  202             },
  203         }
  204         result = self.send_iter_request('vserver-get-iter', api_args)
  205         return self._has_records(result)
  206 
  207     @na_utils.trace
  208     def get_vserver_root_volume_name(self, vserver_name):
  209         """Get the root volume name of the vserver."""
  210         api_args = {
  211             'query': {
  212                 'vserver-info': {
  213                     'vserver-name': vserver_name,
  214                 },
  215             },
  216             'desired-attributes': {
  217                 'vserver-info': {
  218                     'root-volume': None,
  219                 },
  220             },
  221         }
  222         vserver_info = self.send_iter_request('vserver-get-iter', api_args)
  223 
  224         try:
  225             root_volume_name = vserver_info.get_child_by_name(
  226                 'attributes-list').get_child_by_name(
  227                     'vserver-info').get_child_content('root-volume')
  228         except AttributeError:
  229             msg = _('Could not determine root volume name '
  230                     'for Vserver %s.') % vserver_name
  231             raise exception.NetAppException(msg)
  232         return root_volume_name
  233 
  234     @na_utils.trace
  235     def get_vserver_ipspace(self, vserver_name):
  236         """Get the IPspace of the vserver, or None if not supported."""
  237         if not self.features.IPSPACES:
  238             return None
  239 
  240         api_args = {
  241             'query': {
  242                 'vserver-info': {
  243                     'vserver-name': vserver_name,
  244                 },
  245             },
  246             'desired-attributes': {
  247                 'vserver-info': {
  248                     'ipspace': None,
  249                 },
  250             },
  251         }
  252         vserver_info = self.send_iter_request('vserver-get-iter', api_args)
  253 
  254         try:
  255             ipspace = vserver_info.get_child_by_name(
  256                 'attributes-list').get_child_by_name(
  257                     'vserver-info').get_child_content('ipspace')
  258         except AttributeError:
  259             msg = _('Could not determine IPspace for Vserver %s.')
  260             raise exception.NetAppException(msg % vserver_name)
  261         return ipspace
  262 
  263     @na_utils.trace
  264     def ipspace_has_data_vservers(self, ipspace_name):
  265         """Check whether an IPspace has any data Vservers assigned to it."""
  266         if not self.features.IPSPACES:
  267             return False
  268 
  269         api_args = {
  270             'query': {
  271                 'vserver-info': {
  272                     'ipspace': ipspace_name,
  273                     'vserver-type': 'data'
  274                 },
  275             },
  276             'desired-attributes': {
  277                 'vserver-info': {
  278                     'vserver-name': None,
  279                 },
  280             },
  281         }
  282         result = self.send_iter_request('vserver-get-iter', api_args)
  283         return self._has_records(result)
  284 
  285     @na_utils.trace
  286     def list_vservers(self, vserver_type='data'):
  287         """Get the names of vservers present, optionally filtered by type."""
  288         query = {
  289             'vserver-info': {
  290                 'vserver-type': vserver_type,
  291             }
  292         } if vserver_type else None
  293 
  294         api_args = {
  295             'desired-attributes': {
  296                 'vserver-info': {
  297                     'vserver-name': None,
  298                 },
  299             },
  300         }
  301         if query:
  302             api_args['query'] = query
  303 
  304         result = self.send_iter_request('vserver-get-iter', api_args)
  305         vserver_info_list = result.get_child_by_name(
  306             'attributes-list') or netapp_api.NaElement('none')
  307         return [vserver_info.get_child_content('vserver-name')
  308                 for vserver_info in vserver_info_list.get_children()]
  309 
  310     @na_utils.trace
  311     def get_vserver_volume_count(self):
  312         """Get the number of volumes present on a cluster or vserver.
  313 
  314         Call this on a vserver client to see how many volumes exist
  315         on that vserver.
  316         """
  317         api_args = {
  318             'desired-attributes': {
  319                 'volume-attributes': {
  320                     'volume-id-attributes': {
  321                         'name': None,
  322                     },
  323                 },
  324             },
  325         }
  326         volumes_data = self.send_iter_request('volume-get-iter', api_args)
  327         return self._get_record_count(volumes_data)
  328 
  329     @na_utils.trace
  330     def delete_vserver(self, vserver_name, vserver_client,
  331                        security_services=None):
  332         """Delete Vserver.
  333 
  334         Checks if Vserver exists and does not have active shares.
  335         Offlines and destroys root volumes.  Deletes Vserver.
  336         """
  337         if not self.vserver_exists(vserver_name):
  338             LOG.error("Vserver %s does not exist.", vserver_name)
  339             return
  340 
  341         root_volume_name = self.get_vserver_root_volume_name(vserver_name)
  342         volumes_count = vserver_client.get_vserver_volume_count()
  343 
  344         if volumes_count == 1:
  345             try:
  346                 vserver_client.offline_volume(root_volume_name)
  347             except netapp_api.NaApiError as e:
  348                 if e.code == netapp_api.EVOLUMEOFFLINE:
  349                     LOG.error("Volume %s is already offline.",
  350                               root_volume_name)
  351                 else:
  352                     raise
  353             vserver_client.delete_volume(root_volume_name)
  354 
  355         elif volumes_count > 1:
  356             msg = _("Cannot delete Vserver. Vserver %s has shares.")
  357             raise exception.NetAppException(msg % vserver_name)
  358 
  359         if security_services:
  360             self._terminate_vserver_services(vserver_name, vserver_client,
  361                                              security_services)
  362 
  363         self.send_request('vserver-destroy', {'vserver-name': vserver_name})
  364 
  365     @na_utils.trace
  366     def _terminate_vserver_services(self, vserver_name, vserver_client,
  367                                     security_services):
  368         for service in security_services:
  369             if service['type'] == 'active_directory':
  370                 api_args = {
  371                     'admin-password': service['password'],
  372                     'admin-username': service['user'],
  373                 }
  374                 try:
  375                     vserver_client.send_request('cifs-server-delete', api_args)
  376                 except netapp_api.NaApiError as e:
  377                     if e.code == netapp_api.EOBJECTNOTFOUND:
  378                         LOG.error('CIFS server does not exist for '
  379                                   'Vserver %s.', vserver_name)
  380                     else:
  381                         vserver_client.send_request('cifs-server-delete')
  382 
  383     @na_utils.trace
  384     def is_nve_supported(self):
  385         """Determine whether NVE is supported on this platform and version."""
  386         nodes = self.list_cluster_nodes()
  387         system_version = self.get_system_version()
  388         version = system_version.get('version')
  389         version_tuple = system_version.get('version-tuple')
  390 
  391         # NVE requires an ONTAP version >= 9.1. Also, not all platforms
  392         # support this feature. NVE is not supported if the version
  393         # includes the substring '<1no-DARE>' (no Data At Rest Encryption).
  394         if version_tuple >= (9, 1, 0) and "<1no-DARE>" not in version:
  395             if nodes is not None:
  396                 return self.get_security_key_manager_nve_support(nodes[0])
  397             else:
  398                 LOG.debug('Cluster credentials are required in order to '
  399                           'determine whether NetApp Volume Encryption is '
  400                           'supported or not on this platform.')
  401                 return False
  402         else:
  403             LOG.debug('NetApp Volume Encryption is not supported on this '
  404                       'ONTAP version: %(version)s, %(version_tuple)s. ',
  405                       {'version': version, 'version_tuple': version_tuple})
  406             return False
  407 
  408     @na_utils.trace
  409     def list_cluster_nodes(self):
  410         """Get all available cluster nodes."""
  411         api_args = {
  412             'desired-attributes': {
  413                 'node-details-info': {
  414                     'node': None,
  415                 },
  416             },
  417         }
  418         result = self.send_iter_request('system-node-get-iter', api_args)
  419         nodes_info_list = result.get_child_by_name(
  420             'attributes-list') or netapp_api.NaElement('none')
  421         return [node_info.get_child_content('node') for node_info
  422                 in nodes_info_list.get_children()]
  423 
  424     @na_utils.trace
  425     def get_security_key_manager_nve_support(self, node):
  426         """Determine whether the cluster platform supports Volume Encryption"""
  427         api_args = {'node': node}
  428         try:
  429             result = self.send_request(
  430                 'security-key-manager-volume-encryption-supported', api_args)
  431             vol_encryption_supported = result.get_child_content(
  432                 'vol-encryption-supported') or 'false'
  433         except netapp_api.NaApiError as e:
  434             LOG.debug("NVE disabled due to error code: %s - %s",
  435                       e.code, e.message)
  436             return False
  437 
  438         return strutils.bool_from_string(vol_encryption_supported)
  439 
  440     @na_utils.trace
  441     def list_node_data_ports(self, node):
  442         ports = self.get_node_data_ports(node)
  443         return [port.get('port') for port in ports]
  444 
  445     @na_utils.trace
  446     def get_node_data_ports(self, node):
  447         """Get applicable data ports on the node."""
  448         api_args = {
  449             'query': {
  450                 'net-port-info': {
  451                     'node': node,
  452                     'link-status': 'up',
  453                     'port-type': 'physical|if_group',
  454                     'role': 'data',
  455                 },
  456             },
  457             'desired-attributes': {
  458                 'net-port-info': {
  459                     'port': None,
  460                     'node': None,
  461                     'operational-speed': None,
  462                     'ifgrp-port': None,
  463                 },
  464             },
  465         }
  466         result = self.send_iter_request('net-port-get-iter', api_args)
  467         net_port_info_list = result.get_child_by_name(
  468             'attributes-list') or netapp_api.NaElement('none')
  469 
  470         ports = []
  471         for port_info in net_port_info_list.get_children():
  472 
  473             # Skip physical ports that are part of interface groups.
  474             if port_info.get_child_content('ifgrp-port'):
  475                 continue
  476 
  477             port = {
  478                 'node': port_info.get_child_content('node'),
  479                 'port': port_info.get_child_content('port'),
  480                 'speed': port_info.get_child_content('operational-speed'),
  481             }
  482             ports.append(port)
  483 
  484         return self._sort_data_ports_by_speed(ports)
  485 
  486     @na_utils.trace
  487     def _sort_data_ports_by_speed(self, ports):
  488 
  489         def sort_key(port):
  490             value = port.get('speed')
  491             if not (value and isinstance(value, six.string_types)):
  492                 return 0
  493             elif value.isdigit():
  494                 return int(value)
  495             elif value == 'auto':
  496                 return 3
  497             elif value == 'undef':
  498                 return 2
  499             else:
  500                 return 1
  501 
  502         return sorted(ports, key=sort_key, reverse=True)
  503 
  504     @na_utils.trace
  505     def list_root_aggregates(self):
  506         """Get names of all aggregates that contain node root volumes."""
  507 
  508         desired_attributes = {
  509             'aggr-attributes': {
  510                 'aggregate-name': None,
  511                 'aggr-raid-attributes': {
  512                     'has-local-root': None,
  513                     'has-partner-root': None,
  514                 },
  515             },
  516         }
  517         aggrs = self._get_aggregates(desired_attributes=desired_attributes)
  518 
  519         root_aggregates = []
  520         for aggr in aggrs:
  521             aggr_name = aggr.get_child_content('aggregate-name')
  522             aggr_raid_attrs = aggr.get_child_by_name('aggr-raid-attributes')
  523 
  524             local_root = strutils.bool_from_string(
  525                 aggr_raid_attrs.get_child_content('has-local-root'))
  526             partner_root = strutils.bool_from_string(
  527                 aggr_raid_attrs.get_child_content('has-partner-root'))
  528 
  529             if local_root or partner_root:
  530                 root_aggregates.append(aggr_name)
  531 
  532         return root_aggregates
  533 
  534     @na_utils.trace
  535     def list_non_root_aggregates(self):
  536         """Get names of all aggregates that don't contain node root volumes."""
  537 
  538         query = {
  539             'aggr-attributes': {
  540                 'aggr-raid-attributes': {
  541                     'has-local-root': 'false',
  542                     'has-partner-root': 'false',
  543                 }
  544             },
  545         }
  546         return self._list_aggregates(query=query)
  547 
  548     @na_utils.trace
  549     def _list_aggregates(self, query=None):
  550         """Get names of all aggregates."""
  551         try:
  552             api_args = {
  553                 'desired-attributes': {
  554                     'aggr-attributes': {
  555                         'aggregate-name': None,
  556                     },
  557                 },
  558             }
  559             if query:
  560                 api_args['query'] = query
  561             result = self.send_iter_request('aggr-get-iter', api_args)
  562             aggr_list = result.get_child_by_name(
  563                 'attributes-list').get_children()
  564         except AttributeError:
  565             msg = _("Could not list aggregates.")
  566             raise exception.NetAppException(msg)
  567         return [aggr.get_child_content('aggregate-name') for aggr
  568                 in aggr_list]
  569 
  570     @na_utils.trace
  571     def list_vserver_aggregates(self):
  572         """Returns a list of aggregates available to a vserver.
  573 
  574         This must be called against a Vserver LIF.
  575         """
  576         return list(self.get_vserver_aggregate_capacities().keys())
  577 
  578     @na_utils.trace
  579     def create_network_interface(self, ip, netmask, vlan, node, port,
  580                                  vserver_name, lif_name, ipspace_name, mtu):
  581         """Creates LIF on VLAN port."""
  582 
  583         home_port_name = port
  584         if vlan:
  585             self._create_vlan(node, port, vlan)
  586             home_port_name = '%(port)s-%(tag)s' % {'port': port, 'tag': vlan}
  587 
  588         if self.features.BROADCAST_DOMAINS:
  589             self._ensure_broadcast_domain_for_port(
  590                 node, home_port_name, mtu, ipspace=ipspace_name)
  591 
  592         LOG.debug('Creating LIF %(lif)s for Vserver %(vserver)s ',
  593                   {'lif': lif_name, 'vserver': vserver_name})
  594 
  595         api_args = {
  596             'address': ip,
  597             'administrative-status': 'up',
  598             'data-protocols': [
  599                 {'data-protocol': 'nfs'},
  600                 {'data-protocol': 'cifs'},
  601             ],
  602             'home-node': node,
  603             'home-port': home_port_name,
  604             'netmask': netmask,
  605             'interface-name': lif_name,
  606             'role': 'data',
  607             'vserver': vserver_name,
  608         }
  609         self.send_request('net-interface-create', api_args)
  610 
  611     @na_utils.trace
  612     def _create_vlan(self, node, port, vlan):
  613         try:
  614             api_args = {
  615                 'vlan-info': {
  616                     'parent-interface': port,
  617                     'node': node,
  618                     'vlanid': vlan,
  619                 },
  620             }
  621             self.send_request('net-vlan-create', api_args)
  622         except netapp_api.NaApiError as e:
  623             if e.code == netapp_api.EDUPLICATEENTRY:
  624                 LOG.debug('VLAN %(vlan)s already exists on port %(port)s',
  625                           {'vlan': vlan, 'port': port})
  626             else:
  627                 msg = _('Failed to create VLAN %(vlan)s on '
  628                         'port %(port)s. %(err_msg)s')
  629                 msg_args = {'vlan': vlan, 'port': port, 'err_msg': e.message}
  630                 raise exception.NetAppException(msg % msg_args)
  631 
  632     @na_utils.trace
  633     def delete_vlan(self, node, port, vlan):
  634         try:
  635             api_args = {
  636                 'vlan-info': {
  637                     'parent-interface': port,
  638                     'node': node,
  639                     'vlanid': vlan,
  640                 },
  641             }
  642             self.send_request('net-vlan-delete', api_args)
  643         except netapp_api.NaApiError as e:
  644             p = re.compile('port already has a lif bound.*', re.IGNORECASE)
  645             if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
  646                 LOG.debug('VLAN %(vlan)s on port %(port)s node %(node)s '
  647                           'still used by LIF and cannot be deleted.',
  648                           {'vlan': vlan, 'port': port, 'node': node})
  649             else:
  650                 msg = _('Failed to delete VLAN %(vlan)s on '
  651                         'port %(port)s node %(node)s: %(err_msg)s')
  652                 msg_args = {
  653                     'vlan': vlan,
  654                     'port': port,
  655                     'node': node,
  656                     'err_msg': e.message
  657                 }
  658                 raise exception.NetAppException(msg % msg_args)
  659 
  660     @na_utils.trace
  661     def create_route(self, gateway, destination=None):
  662         if not gateway:
  663             return
  664         if not destination:
  665             if ':' in gateway:
  666                 destination = '::/0'
  667             else:
  668                 destination = '0.0.0.0/0'
  669         try:
  670             api_args = {
  671                 'destination': destination,
  672                 'gateway': gateway,
  673                 'return-record': 'true',
  674             }
  675             self.send_request('net-routes-create', api_args)
  676         except netapp_api.NaApiError as e:
  677             p = re.compile('.*Duplicate route exists.*', re.IGNORECASE)
  678             if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
  679                 LOG.debug('Route to %(destination)s via gateway %(gateway)s '
  680                           'exists.',
  681                           {'destination': destination, 'gateway': gateway})
  682             else:
  683                 msg = _('Failed to create a route to %(destination)s via '
  684                         'gateway %(gateway)s: %(err_msg)s')
  685                 msg_args = {
  686                     'destination': destination,
  687                     'gateway': gateway,
  688                     'err_msg': e.message,
  689                 }
  690                 raise exception.NetAppException(msg % msg_args)
  691 
  692     @na_utils.trace
  693     def _ensure_broadcast_domain_for_port(self, node, port, mtu,
  694                                           ipspace=DEFAULT_IPSPACE):
  695         """Ensure a port is in a broadcast domain.  Create one if necessary.
  696 
  697         If the IPspace:domain pair match for the given port, which commonly
  698         happens in multi-node clusters, then there isn't anything to do.
  699         Otherwise, we can assume the IPspace is correct and extant by this
  700         point, so the remaining task is to remove the port from any domain it
  701         is already in, create the domain for the IPspace if it doesn't exist,
  702         and add the port to this domain.
  703         """
  704 
  705         # Derive the broadcast domain name from the IPspace name since they
  706         # need to be 1-1 and the default for both is the same name, 'Default'.
  707         domain = re.sub(r'ipspace', 'domain', ipspace)
  708 
  709         port_info = self._get_broadcast_domain_for_port(node, port)
  710 
  711         # Port already in desired ipspace and broadcast domain.
  712         if (port_info['ipspace'] == ipspace
  713                 and port_info['broadcast-domain'] == domain):
  714             self._modify_broadcast_domain(domain, ipspace, mtu)
  715             return
  716 
  717         # If in another broadcast domain, remove port from it.
  718         if port_info['broadcast-domain']:
  719             self._remove_port_from_broadcast_domain(
  720                 node, port, port_info['broadcast-domain'],
  721                 port_info['ipspace'])
  722 
  723         # If desired broadcast domain doesn't exist, create it.
  724         if not self._broadcast_domain_exists(domain, ipspace):
  725             self._create_broadcast_domain(domain, ipspace, mtu)
  726         else:
  727             self._modify_broadcast_domain(domain, ipspace, mtu)
  728 
  729         # Move the port into the broadcast domain where it is needed.
  730         self._add_port_to_broadcast_domain(node, port, domain, ipspace)
  731 
  732     @na_utils.trace
  733     def _get_broadcast_domain_for_port(self, node, port):
  734         """Get broadcast domain for a specific port."""
  735         api_args = {
  736             'query': {
  737                 'net-port-info': {
  738                     'node': node,
  739                     'port': port,
  740                 },
  741             },
  742             'desired-attributes': {
  743                 'net-port-info': {
  744                     'broadcast-domain': None,
  745                     'ipspace': None,
  746                 },
  747             },
  748         }
  749         result = self.send_iter_request('net-port-get-iter', api_args)
  750 
  751         net_port_info_list = result.get_child_by_name(
  752             'attributes-list') or netapp_api.NaElement('none')
  753         port_info = net_port_info_list.get_children()
  754         if not port_info:
  755             msg = _('Could not find port %(port)s on node %(node)s.')
  756             msg_args = {'port': port, 'node': node}
  757             raise exception.NetAppException(msg % msg_args)
  758 
  759         port = {
  760             'broadcast-domain':
  761             port_info[0].get_child_content('broadcast-domain'),
  762             'ipspace': port_info[0].get_child_content('ipspace')
  763         }
  764         return port
  765 
  766     @na_utils.trace
  767     def _broadcast_domain_exists(self, domain, ipspace):
  768         """Check if a broadcast domain exists."""
  769         api_args = {
  770             'query': {
  771                 'net-port-broadcast-domain-info': {
  772                     'ipspace': ipspace,
  773                     'broadcast-domain': domain,
  774                 },
  775             },
  776             'desired-attributes': {
  777                 'net-port-broadcast-domain-info': None,
  778             },
  779         }
  780         result = self.send_iter_request('net-port-broadcast-domain-get-iter',
  781                                         api_args)
  782         return self._has_records(result)
  783 
  784     @na_utils.trace
  785     def _create_broadcast_domain(self, domain, ipspace, mtu):
  786         """Create a broadcast domain."""
  787         api_args = {
  788             'ipspace': ipspace,
  789             'broadcast-domain': domain,
  790             'mtu': mtu,
  791         }
  792         self.send_request('net-port-broadcast-domain-create', api_args)
  793 
  794     @na_utils.trace
  795     def _modify_broadcast_domain(self, domain, ipspace, mtu):
  796         """Modify a broadcast domain."""
  797         api_args = {
  798             'ipspace': ipspace,
  799             'broadcast-domain': domain,
  800             'mtu': mtu,
  801         }
  802         self.send_request('net-port-broadcast-domain-modify', api_args)
  803 
  804     @na_utils.trace
  805     def _delete_broadcast_domain(self, domain, ipspace):
  806         """Delete a broadcast domain."""
  807         api_args = {
  808             'ipspace': ipspace,
  809             'broadcast-domain': domain,
  810         }
  811         self.send_request('net-port-broadcast-domain-destroy', api_args)
  812 
  813     @na_utils.trace
  814     def _delete_broadcast_domains_for_ipspace(self, ipspace_name):
  815         """Deletes all broadcast domains in an IPspace."""
  816         ipspaces = self.get_ipspaces(ipspace_name=ipspace_name)
  817         if not ipspaces:
  818             return
  819 
  820         ipspace = ipspaces[0]
  821         for broadcast_domain_name in ipspace['broadcast-domains']:
  822             self._delete_broadcast_domain(broadcast_domain_name, ipspace_name)
  823 
  824     @na_utils.trace
  825     def _add_port_to_broadcast_domain(self, node, port, domain, ipspace):
  826 
  827         qualified_port_name = ':'.join([node, port])
  828         try:
  829             api_args = {
  830                 'ipspace': ipspace,
  831                 'broadcast-domain': domain,
  832                 'ports': {
  833                     'net-qualified-port-name': qualified_port_name,
  834                 }
  835             }
  836             self.send_request('net-port-broadcast-domain-add-ports', api_args)
  837         except netapp_api.NaApiError as e:
  838             if e.code == (netapp_api.
  839                           E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN):
  840                 LOG.debug('Port %(port)s already exists in broadcast domain '
  841                           '%(domain)s', {'port': port, 'domain': domain})
  842             else:
  843                 msg = _('Failed to add port %(port)s to broadcast domain '
  844                         '%(domain)s. %(err_msg)s')
  845                 msg_args = {
  846                     'port': qualified_port_name,
  847                     'domain': domain,
  848                     'err_msg': e.message,
  849                 }
  850                 raise exception.NetAppException(msg % msg_args)
  851 
  852     @na_utils.trace
  853     def _remove_port_from_broadcast_domain(self, node, port, domain, ipspace):
  854 
  855         qualified_port_name = ':'.join([node, port])
  856         api_args = {
  857             'ipspace': ipspace,
  858             'broadcast-domain': domain,
  859             'ports': {
  860                 'net-qualified-port-name': qualified_port_name,
  861             }
  862         }
  863         self.send_request('net-port-broadcast-domain-remove-ports', api_args)
  864 
  865     @na_utils.trace
  866     def network_interface_exists(self, vserver_name, node, port, ip, netmask,
  867                                  vlan):
  868         """Checks if LIF exists."""
  869 
  870         home_port_name = (port if not vlan else
  871                           '%(port)s-%(tag)s' % {'port': port, 'tag': vlan})
  872 
  873         api_args = {
  874             'query': {
  875                 'net-interface-info': {
  876                     'address': ip,
  877                     'home-node': node,
  878                     'home-port': home_port_name,
  879                     'netmask': netmask,
  880                     'vserver': vserver_name,
  881                 },
  882             },
  883             'desired-attributes': {
  884                 'net-interface-info': {
  885                     'interface-name': None,
  886                 },
  887             },
  888         }
  889         result = self.send_iter_request('net-interface-get-iter', api_args)
  890         return self._has_records(result)
  891 
  892     @na_utils.trace
  893     def list_network_interfaces(self):
  894         """Get the names of available LIFs."""
  895         api_args = {
  896             'desired-attributes': {
  897                 'net-interface-info': {
  898                     'interface-name': None,
  899                 },
  900             },
  901         }
  902         result = self.send_iter_request('net-interface-get-iter', api_args)
  903         lif_info_list = result.get_child_by_name(
  904             'attributes-list') or netapp_api.NaElement('none')
  905         return [lif_info.get_child_content('interface-name') for lif_info
  906                 in lif_info_list.get_children()]
  907 
  908     @na_utils.trace
  909     def get_network_interfaces(self, protocols=None):
  910         """Get available LIFs."""
  911         protocols = na_utils.convert_to_list(protocols)
  912         protocols = [protocol.lower() for protocol in protocols]
  913 
  914         api_args = {
  915             'query': {
  916                 'net-interface-info': {
  917                     'data-protocols': {
  918                         'data-protocol': '|'.join(protocols),
  919                     }
  920                 }
  921             }
  922         } if protocols else None
  923 
  924         result = self.send_iter_request('net-interface-get-iter', api_args)
  925         lif_info_list = result.get_child_by_name(
  926             'attributes-list') or netapp_api.NaElement('none')
  927 
  928         interfaces = []
  929         for lif_info in lif_info_list.get_children():
  930             lif = {
  931                 'address': lif_info.get_child_content('address'),
  932                 'home-node': lif_info.get_child_content('home-node'),
  933                 'home-port': lif_info.get_child_content('home-port'),
  934                 'interface-name': lif_info.get_child_content('interface-name'),
  935                 'netmask': lif_info.get_child_content('netmask'),
  936                 'role': lif_info.get_child_content('role'),
  937                 'vserver': lif_info.get_child_content('vserver'),
  938             }
  939             interfaces.append(lif)
  940 
  941         return interfaces
  942 
  943     @na_utils.trace
  944     def get_ipspace_name_for_vlan_port(self, vlan_node, vlan_port, vlan_id):
  945         """Gets IPSpace name for specified VLAN"""
  946 
  947         if not self.features.IPSPACES:
  948             return None
  949 
  950         port = vlan_port if not vlan_id else '%(port)s-%(id)s' % {
  951             'port': vlan_port,
  952             'id': vlan_id,
  953         }
  954         api_args = {'node': vlan_node, 'port': port}
  955 
  956         try:
  957             result = self.send_request('net-port-get', api_args)
  958         except netapp_api.NaApiError as e:
  959             if e.code == netapp_api.EOBJECTNOTFOUND:
  960                 msg = _('No pre-existing port or ipspace was found for '
  961                         '%(port)s, will attempt to create one.')
  962                 msg_args = {'port': port}
  963                 LOG.debug(msg, msg_args)
  964                 return None
  965             else:
  966                 raise
  967 
  968         attributes = result.get_child_by_name('attributes')
  969         net_port_info = attributes.get_child_by_name('net-port-info')
  970         ipspace_name = net_port_info.get_child_content('ipspace')
  971 
  972         return ipspace_name
  973 
  974     @na_utils.trace
  975     def get_ipspaces(self, ipspace_name=None):
  976         """Gets one or more IPSpaces."""
  977 
  978         if not self.features.IPSPACES:
  979             return []
  980 
  981         api_args = {}
  982         if ipspace_name:
  983             api_args['query'] = {
  984                 'net-ipspaces-info': {
  985                     'ipspace': ipspace_name,
  986                 }
  987             }
  988 
  989         result = self.send_iter_request('net-ipspaces-get-iter', api_args)
  990         if not self._has_records(result):
  991             return []
  992 
  993         ipspaces = []
  994 
  995         for net_ipspaces_info in result.get_child_by_name(
  996                 'attributes-list').get_children():
  997 
  998             ipspace = {
  999                 'ports': [],
 1000                 'vservers': [],
 1001                 'broadcast-domains': [],
 1002             }
 1003 
 1004             ports = net_ipspaces_info.get_child_by_name(
 1005                 'ports') or netapp_api.NaElement('none')
 1006             for port in ports.get_children():
 1007                 ipspace['ports'].append(port.get_content())
 1008 
 1009             vservers = net_ipspaces_info.get_child_by_name(
 1010                 'vservers') or netapp_api.NaElement('none')
 1011             for vserver in vservers.get_children():
 1012                 ipspace['vservers'].append(vserver.get_content())
 1013 
 1014             broadcast_domains = net_ipspaces_info.get_child_by_name(
 1015                 'broadcast-domains') or netapp_api.NaElement('none')
 1016             for broadcast_domain in broadcast_domains.get_children():
 1017                 ipspace['broadcast-domains'].append(
 1018                     broadcast_domain.get_content())
 1019 
 1020             ipspace['ipspace'] = net_ipspaces_info.get_child_content('ipspace')
 1021             ipspace['id'] = net_ipspaces_info.get_child_content('id')
 1022             ipspace['uuid'] = net_ipspaces_info.get_child_content('uuid')
 1023 
 1024             ipspaces.append(ipspace)
 1025 
 1026         return ipspaces
 1027 
 1028     @na_utils.trace
 1029     def ipspace_exists(self, ipspace_name):
 1030         """Checks if IPspace exists."""
 1031 
 1032         if not self.features.IPSPACES:
 1033             return False
 1034 
 1035         api_args = {
 1036             'query': {
 1037                 'net-ipspaces-info': {
 1038                     'ipspace': ipspace_name,
 1039                 },
 1040             },
 1041             'desired-attributes': {
 1042                 'net-ipspaces-info': {
 1043                     'ipspace': None,
 1044                 },
 1045             },
 1046         }
 1047         result = self.send_iter_request('net-ipspaces-get-iter', api_args)
 1048         return self._has_records(result)
 1049 
 1050     @na_utils.trace
 1051     def create_ipspace(self, ipspace_name):
 1052         """Creates an IPspace."""
 1053         api_args = {'ipspace': ipspace_name}
 1054         self.send_request('net-ipspaces-create', api_args)
 1055 
 1056     @na_utils.trace
 1057     def delete_ipspace(self, ipspace_name):
 1058         """Deletes an IPspace."""
 1059 
 1060         self._delete_broadcast_domains_for_ipspace(ipspace_name)
 1061 
 1062         api_args = {'ipspace': ipspace_name}
 1063         self.send_request('net-ipspaces-destroy', api_args)
 1064 
 1065     @na_utils.trace
 1066     def add_vserver_to_ipspace(self, ipspace_name, vserver_name):
 1067         """Assigns a vserver to an IPspace."""
 1068         api_args = {'ipspace': ipspace_name, 'vserver': vserver_name}
 1069         self.send_request('net-ipspaces-assign-vserver', api_args)
 1070 
 1071     @na_utils.trace
 1072     def get_node_for_aggregate(self, aggregate_name):
 1073         """Get home node for the specified aggregate.
 1074 
 1075         This API could return None, most notably if it was sent
 1076         to a Vserver LIF, so the caller must be able to handle that case.
 1077         """
 1078 
 1079         if not aggregate_name:
 1080             return None
 1081 
 1082         desired_attributes = {
 1083             'aggr-attributes': {
 1084                 'aggregate-name': None,
 1085                 'aggr-ownership-attributes': {
 1086                     'home-name': None,
 1087                 },
 1088             },
 1089         }
 1090 
 1091         try:
 1092             aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
 1093                                          desired_attributes=desired_attributes)
 1094         except netapp_api.NaApiError as e:
 1095             if e.code == netapp_api.EAPINOTFOUND:
 1096                 return None
 1097             else:
 1098                 raise
 1099 
 1100         if len(aggrs) < 1:
 1101             return None
 1102 
 1103         aggr_ownership_attrs = aggrs[0].get_child_by_name(
 1104             'aggr-ownership-attributes') or netapp_api.NaElement('none')
 1105         return aggr_ownership_attrs.get_child_content('home-name')
 1106 
 1107     @na_utils.trace
 1108     def get_cluster_aggregate_capacities(self, aggregate_names):
 1109         """Calculates capacity of one or more aggregates.
 1110 
 1111         Returns dictionary of aggregate capacity metrics.
 1112         'size-used' is the actual space consumed on the aggregate.
 1113         'size-available' is the actual space remaining.
 1114         'size-total' is the defined total aggregate size, such that
 1115         used + available = total.
 1116         """
 1117 
 1118         if aggregate_names is not None and len(aggregate_names) == 0:
 1119             return {}
 1120 
 1121         desired_attributes = {
 1122             'aggr-attributes': {
 1123                 'aggregate-name': None,
 1124                 'aggr-space-attributes': {
 1125                     'size-available': None,
 1126                     'size-total': None,
 1127                     'size-used': None,
 1128                 },
 1129             },
 1130         }
 1131         aggrs = self._get_aggregates(aggregate_names=aggregate_names,
 1132                                      desired_attributes=desired_attributes)
 1133         aggr_space_dict = dict()
 1134         for aggr in aggrs:
 1135             aggr_name = aggr.get_child_content('aggregate-name')
 1136             aggr_space_attrs = aggr.get_child_by_name('aggr-space-attributes')
 1137 
 1138             aggr_space_dict[aggr_name] = {
 1139                 'available':
 1140                 int(aggr_space_attrs.get_child_content('size-available')),
 1141                 'total':
 1142                 int(aggr_space_attrs.get_child_content('size-total')),
 1143                 'used':
 1144                 int(aggr_space_attrs.get_child_content('size-used')),
 1145             }
 1146         return aggr_space_dict
 1147 
 1148     @na_utils.trace
 1149     def get_vserver_aggregate_capacities(self, aggregate_names=None):
 1150         """Calculates capacity of one or more aggregates for a vserver.
 1151 
 1152         Returns dictionary of aggregate capacity metrics.  This must
 1153         be called against a Vserver LIF.
 1154         """
 1155 
 1156         if aggregate_names is not None and len(aggregate_names) == 0:
 1157             return {}
 1158 
 1159         api_args = {
 1160             'desired-attributes': {
 1161                 'vserver-info': {
 1162                     'vserver-name': None,
 1163                     'vserver-aggr-info-list': {
 1164                         'vserver-aggr-info': {
 1165                             'aggr-name': None,
 1166                             'aggr-availsize': None,
 1167                         },
 1168                     },
 1169                 },
 1170             },
 1171         }
 1172         result = self.send_request('vserver-get', api_args)
 1173         attributes = result.get_child_by_name('attributes')
 1174         if not attributes:
 1175             raise exception.NetAppException('Failed to read Vserver info')
 1176 
 1177         vserver_info = attributes.get_child_by_name('vserver-info')
 1178         vserver_name = vserver_info.get_child_content('vserver-name')
 1179         vserver_aggr_info_element = vserver_info.get_child_by_name(
 1180             'vserver-aggr-info-list') or netapp_api.NaElement('none')
 1181         vserver_aggr_info_list = vserver_aggr_info_element.get_children()
 1182 
 1183         if not vserver_aggr_info_list:
 1184             LOG.warning('No aggregates assigned to Vserver %s.',
 1185                         vserver_name)
 1186 
 1187         # Return dict of key-value pair of aggr_name:aggr_size_available.
 1188         aggr_space_dict = {}
 1189 
 1190         for aggr_info in vserver_aggr_info_list:
 1191             aggr_name = aggr_info.get_child_content('aggr-name')
 1192 
 1193             if aggregate_names is None or aggr_name in aggregate_names:
 1194                 aggr_size = int(aggr_info.get_child_content('aggr-availsize'))
 1195                 aggr_space_dict[aggr_name] = {'available': aggr_size}
 1196 
 1197         LOG.debug('Found available Vserver aggregates: %s', aggr_space_dict)
 1198         return aggr_space_dict
 1199 
 1200     @na_utils.trace
 1201     def _get_aggregates(self, aggregate_names=None, desired_attributes=None):
 1202 
 1203         query = {
 1204             'aggr-attributes': {
 1205                 'aggregate-name': '|'.join(aggregate_names),
 1206             }
 1207         } if aggregate_names else None
 1208 
 1209         api_args = {}
 1210         if query:
 1211             api_args['query'] = query
 1212         if desired_attributes:
 1213             api_args['desired-attributes'] = desired_attributes
 1214 
 1215         result = self.send_iter_request('aggr-get-iter', api_args)
 1216         if not self._has_records(result):
 1217             return []
 1218         else:
 1219             return result.get_child_by_name('attributes-list').get_children()
 1220 
 1221     def get_performance_instance_uuids(self, object_name, node_name):
 1222         """Get UUIDs of performance instances for a cluster node."""
 1223 
 1224         api_args = {
 1225             'objectname': object_name,
 1226             'query': {
 1227                 'instance-info': {
 1228                     'uuid': node_name + ':*',
 1229                 }
 1230             }
 1231         }
 1232 
 1233         result = self.send_request('perf-object-instance-list-info-iter',
 1234                                    api_args)
 1235 
 1236         uuids = []
 1237 
 1238         instances = result.get_child_by_name(
 1239             'attributes-list') or netapp_api.NaElement('None')
 1240 
 1241         for instance_info in instances.get_children():
 1242             uuids.append(instance_info.get_child_content('uuid'))
 1243 
 1244         return uuids
 1245 
 1246     def get_performance_counter_info(self, object_name, counter_name):
 1247         """Gets info about one or more Data ONTAP performance counters."""
 1248 
 1249         api_args = {'objectname': object_name}
 1250         result = self.send_request('perf-object-counter-list-info', api_args)
 1251 
 1252         counters = result.get_child_by_name(
 1253             'counters') or netapp_api.NaElement('None')
 1254 
 1255         for counter in counters.get_children():
 1256 
 1257             if counter.get_child_content('name') == counter_name:
 1258 
 1259                 labels = []
 1260                 label_list = counter.get_child_by_name(
 1261                     'labels') or netapp_api.NaElement('None')
 1262                 for label in label_list.get_children():
 1263                     labels.extend(label.get_content().split(','))
 1264                 base_counter = counter.get_child_content('base-counter')
 1265 
 1266                 return {
 1267                     'name': counter_name,
 1268                     'labels': labels,
 1269                     'base-counter': base_counter,
 1270                 }
 1271         else:
 1272             raise exception.NotFound(_('Counter %s not found') % counter_name)
 1273 
 1274     def get_performance_counters(self, object_name, instance_uuids,
 1275                                  counter_names):
 1276         """Gets one or more cDOT performance counters."""
 1277 
 1278         api_args = {
 1279             'objectname': object_name,
 1280             'instance-uuids': [
 1281                 {'instance-uuid': instance_uuid}
 1282                 for instance_uuid in instance_uuids
 1283             ],
 1284             'counters': [
 1285                 {'counter': counter} for counter in counter_names
 1286             ],
 1287         }
 1288 
 1289         result = self.send_request('perf-object-get-instances', api_args)
 1290 
 1291         counter_data = []
 1292 
 1293         timestamp = result.get_child_content('timestamp')
 1294 
 1295         instances = result.get_child_by_name(
 1296             'instances') or netapp_api.NaElement('None')
 1297         for instance in instances.get_children():
 1298 
 1299             instance_name = instance.get_child_content('name')
 1300             instance_uuid = instance.get_child_content('uuid')
 1301             node_name = instance_uuid.split(':')[0]
 1302 
 1303             counters = instance.get_child_by_name(
 1304                 'counters') or netapp_api.NaElement('None')
 1305             for counter in counters.get_children():
 1306 
 1307                 counter_name = counter.get_child_content('name')
 1308                 counter_value = counter.get_child_content('value')
 1309 
 1310                 counter_data.append({
 1311                     'instance-name': instance_name,
 1312                     'instance-uuid': instance_uuid,
 1313                     'node-name': node_name,
 1314                     'timestamp': timestamp,
 1315                     counter_name: counter_value,
 1316                 })
 1317 
 1318         return counter_data
 1319 
 1320     @na_utils.trace
 1321     def setup_security_services(self, security_services, vserver_client,
 1322                                 vserver_name):
 1323         api_args = {
 1324             'name-mapping-switch': [
 1325                 {'nmswitch': 'ldap'},
 1326                 {'nmswitch': 'file'}
 1327             ],
 1328             'name-server-switch': [
 1329                 {'nsswitch': 'ldap'},
 1330                 {'nsswitch': 'file'}
 1331             ],
 1332             'vserver-name': vserver_name,
 1333         }
 1334         self.send_request('vserver-modify', api_args)
 1335 
 1336         for security_service in security_services:
 1337             if security_service['type'].lower() == 'ldap':
 1338                 vserver_client.configure_ldap(security_service)
 1339 
 1340             elif security_service['type'].lower() == 'active_directory':
 1341                 vserver_client.configure_active_directory(security_service,
 1342                                                           vserver_name)
 1343 
 1344             elif security_service['type'].lower() == 'kerberos':
 1345                 self.create_kerberos_realm(security_service)
 1346                 vserver_client.configure_kerberos(security_service,
 1347                                                   vserver_name)
 1348 
 1349             else:
 1350                 msg = _('Unsupported security service type %s for '
 1351                         'Data ONTAP driver')
 1352                 raise exception.NetAppException(msg % security_service['type'])
 1353 
 1354     @na_utils.trace
 1355     def enable_nfs(self, versions):
 1356         """Enables NFS on Vserver."""
 1357         self.send_request('nfs-enable')
 1358         self._enable_nfs_protocols(versions)
 1359         self._create_default_nfs_export_rules()
 1360 
 1361     @na_utils.trace
 1362     def _enable_nfs_protocols(self, versions):
 1363         """Set the enabled NFS protocol versions."""
 1364         nfs3 = 'true' if 'nfs3' in versions else 'false'
 1365         nfs40 = 'true' if 'nfs4.0' in versions else 'false'
 1366         nfs41 = 'true' if 'nfs4.1' in versions else 'false'
 1367 
 1368         nfs_service_modify_args = {
 1369             'is-nfsv3-enabled': nfs3,
 1370             'is-nfsv40-enabled': nfs40,
 1371             'is-nfsv41-enabled': nfs41,
 1372         }
 1373         self.send_request('nfs-service-modify', nfs_service_modify_args)
 1374 
 1375     @na_utils.trace
 1376     def _create_default_nfs_export_rules(self):
 1377         """Create the default export rule for the NFS service."""
 1378 
 1379         export_rule_create_args = {
 1380             'client-match': '0.0.0.0/0',
 1381             'policy-name': 'default',
 1382             'ro-rule': {
 1383                 'security-flavor': 'any',
 1384             },
 1385             'rw-rule': {
 1386                 'security-flavor': 'never',
 1387             },
 1388         }
 1389         self.send_request('export-rule-create', export_rule_create_args)
 1390         export_rule_create_args['client-match'] = '::/0'
 1391         self.send_request('export-rule-create', export_rule_create_args)
 1392 
 1393     @na_utils.trace
 1394     def configure_ldap(self, security_service):
 1395         """Configures LDAP on Vserver."""
 1396         config_name = hashlib.md5(six.b(security_service['id'])).hexdigest()
 1397         api_args = {
 1398             'ldap-client-config': config_name,
 1399             'servers': {
 1400                 'ip-address': security_service['server'],
 1401             },
 1402             'tcp-port': '389',
 1403             'schema': 'RFC-2307',
 1404             'bind-password': security_service['password'],
 1405         }
 1406         self.send_request('ldap-client-create', api_args)
 1407 
 1408         api_args = {'client-config': config_name, 'client-enabled': 'true'}
 1409         self.send_request('ldap-config-create', api_args)
 1410 
 1411     @na_utils.trace
 1412     def configure_active_directory(self, security_service, vserver_name):
 1413         """Configures AD on Vserver."""
 1414         self.configure_dns(security_service)
 1415         self.set_preferred_dc(security_service)
 1416 
 1417         # 'cifs-server' is CIFS Server NetBIOS Name, max length is 15.
 1418         # Should be unique within each domain (data['domain']).
 1419         # Cut to 15 char with begin and end, attempt to make valid DNS hostname
 1420         cifs_server = (vserver_name[0:8] +
 1421                        '-' +
 1422                        vserver_name[-6:]).replace('_', '-').upper()
 1423 
 1424         api_args = {
 1425             'admin-username': security_service['user'],
 1426             'admin-password': security_service['password'],
 1427             'force-account-overwrite': 'true',
 1428             'cifs-server': cifs_server,
 1429             'domain': security_service['domain'],
 1430         }
 1431 
 1432         if security_service['ou'] is not None:
 1433             api_args['organizational-unit'] = security_service['ou']
 1434 
 1435         try:
 1436             LOG.debug("Trying to setup CIFS server with data: %s", api_args)
 1437             self.send_request('cifs-server-create', api_args)
 1438         except netapp_api.NaApiError as e:
 1439             msg = _("Failed to create CIFS server entry. %s")
 1440             raise exception.NetAppException(msg % e.message)
 1441 
 1442     @na_utils.trace
 1443     def create_kerberos_realm(self, security_service):
 1444         """Creates Kerberos realm on cluster."""
 1445 
 1446         api_args = {
 1447             'admin-server-ip': security_service['server'],
 1448             'admin-server-port': '749',
 1449             'clock-skew': '5',
 1450             'comment': '',
 1451             'config-name': security_service['id'],
 1452             'kdc-ip': security_service['server'],
 1453             'kdc-port': '88',
 1454             'kdc-vendor': 'other',
 1455             'password-server-ip': security_service['server'],
 1456             'password-server-port': '464',
 1457             'realm': security_service['domain'].upper(),
 1458         }
 1459         try:
 1460             self.send_request('kerberos-realm-create', api_args)
 1461         except netapp_api.NaApiError as e:
 1462             if e.code == netapp_api.EDUPLICATEENTRY:
 1463                 LOG.debug('Kerberos realm config already exists.')
 1464             else:
 1465                 msg = _('Failed to create Kerberos realm. %s')
 1466                 raise exception.NetAppException(msg % e.message)
 1467 
 1468     @na_utils.trace
 1469     def configure_kerberos(self, security_service, vserver_name):
 1470         """Configures Kerberos for NFS on Vserver."""
 1471 
 1472         self.configure_dns(security_service)
 1473         spn = self._get_kerberos_service_principal_name(
 1474             security_service, vserver_name)
 1475 
 1476         lifs = self.list_network_interfaces()
 1477         if not lifs:
 1478             msg = _("Cannot set up Kerberos. There are no LIFs configured.")
 1479             raise exception.NetAppException(msg)
 1480 
 1481         for lif_name in lifs:
 1482             api_args = {
 1483                 'admin-password': security_service['password'],
 1484                 'admin-user-name': security_service['user'],
 1485                 'interface-name': lif_name,
 1486                 'is-kerberos-enabled': 'true',
 1487                 'service-principal-name': spn,
 1488             }
 1489             self.send_request('kerberos-config-modify', api_args)
 1490 
 1491     @na_utils.trace
 1492     def _get_kerberos_service_principal_name(self, security_service,
 1493                                              vserver_name):
 1494         return ('nfs/' + vserver_name.replace('_', '-') + '.' +
 1495                 security_service['domain'] + '@' +
 1496                 security_service['domain'].upper())
 1497 
 1498     @na_utils.trace
 1499     def configure_dns(self, security_service):
 1500         api_args = {
 1501             'domains': {
 1502                 'string': security_service['domain'],
 1503             },
 1504             'name-servers': [],
 1505             'dns-state': 'enabled',
 1506         }
 1507         for dns_ip in security_service['dns_ip'].split(','):
 1508             api_args['name-servers'].append({'ip-address': dns_ip.strip()})
 1509 
 1510         try:
 1511             self.send_request('net-dns-create', api_args)
 1512         except netapp_api.NaApiError as e:
 1513             if e.code == netapp_api.EDUPLICATEENTRY:
 1514                 LOG.error("DNS exists for Vserver.")
 1515             else:
 1516                 msg = _("Failed to configure DNS. %s")
 1517                 raise exception.NetAppException(msg % e.message)
 1518 
 1519     @na_utils.trace
 1520     def set_preferred_dc(self, security_service):
 1521         # server is optional
 1522         if not security_service['server']:
 1523             return
 1524 
 1525         api_args = {
 1526             'preferred-dc': [],
 1527             'domain': security_service['domain'],
 1528         }
 1529 
 1530         for dc_ip in security_service['server'].split(','):
 1531             api_args['preferred-dc'].append({'string': dc_ip.strip()})
 1532 
 1533         if self.features.CIFS_DC_ADD_SKIP_CHECK:
 1534             api_args['skip-config-validation'] = 'false'
 1535 
 1536         try:
 1537             self.send_request('cifs-domain-preferred-dc-add', api_args)
 1538         except netapp_api.NaApiError as e:
 1539             msg = _("Failed to set preferred DC. %s")
 1540             raise exception.NetAppException(msg % e.message)
 1541 
 1542     @na_utils.trace
 1543     def create_volume(self, aggregate_name, volume_name, size_gb,
 1544                       thin_provisioned=False, snapshot_policy=None,
 1545                       language=None, dedup_enabled=False,
 1546                       compression_enabled=False, max_files=None,
 1547                       snapshot_reserve=None, volume_type='rw',
 1548                       qos_policy_group=None,
 1549                       encrypt=False, **options):
 1550         """Creates a volume."""
 1551         api_args = {
 1552             'containing-aggr-name': aggregate_name,
 1553             'size': six.text_type(size_gb) + 'g',
 1554             'volume': volume_name,
 1555             'volume-type': volume_type,
 1556         }
 1557         if volume_type != 'dp':
 1558             api_args['junction-path'] = '/%s' % volume_name
 1559         if thin_provisioned:
 1560             api_args['space-reserve'] = 'none'
 1561         if snapshot_policy is not None:
 1562             api_args['snapshot-policy'] = snapshot_policy
 1563         if language is not None:
 1564             api_args['language-code'] = language
 1565         if snapshot_reserve is not None:
 1566             api_args['percentage-snapshot-reserve'] = six.text_type(
 1567                 snapshot_reserve)
 1568         if qos_policy_group is not None:
 1569             api_args['qos-policy-group-name'] = qos_policy_group
 1570 
 1571         if encrypt is True:
 1572             if not self.features.FLEXVOL_ENCRYPTION:
 1573                 msg = 'Flexvol encryption is not supported on this backend.'
 1574                 raise exception.NetAppException(msg)
 1575             else:
 1576                 api_args['encrypt'] = 'true'
 1577 
 1578         self.send_request('volume-create', api_args)
 1579 
 1580         self.update_volume_efficiency_attributes(volume_name,
 1581                                                  dedup_enabled,
 1582                                                  compression_enabled)
 1583         if max_files is not None:
 1584             self.set_volume_max_files(volume_name, max_files)
 1585 
 1586     @na_utils.trace
 1587     def enable_dedup(self, volume_name):
 1588         """Enable deduplication on volume."""
 1589         api_args = {'path': '/vol/%s' % volume_name}
 1590         self.send_request('sis-enable', api_args)
 1591 
 1592     @na_utils.trace
 1593     def disable_dedup(self, volume_name):
 1594         """Disable deduplication on volume."""
 1595         api_args = {'path': '/vol/%s' % volume_name}
 1596         self.send_request('sis-disable', api_args)
 1597 
 1598     @na_utils.trace
 1599     def enable_compression(self, volume_name):
 1600         """Enable compression on volume."""
 1601         api_args = {
 1602             'path': '/vol/%s' % volume_name,
 1603             'enable-compression': 'true'
 1604         }
 1605         self.send_request('sis-set-config', api_args)
 1606 
 1607     @na_utils.trace
 1608     def disable_compression(self, volume_name):
 1609         """Disable compression on volume."""
 1610         api_args = {
 1611             'path': '/vol/%s' % volume_name,
 1612             'enable-compression': 'false'
 1613         }
 1614         self.send_request('sis-set-config', api_args)
 1615 
 1616     @na_utils.trace
 1617     def get_volume_efficiency_status(self, volume_name):
 1618         """Get dedupe & compression status for a volume."""
 1619         api_args = {
 1620             'query': {
 1621                 'sis-status-info': {
 1622                     'path': '/vol/%s' % volume_name,
 1623                 },
 1624             },
 1625             'desired-attributes': {
 1626                 'sis-status-info': {
 1627                     'state': None,
 1628                     'is-compression-enabled': None,
 1629                 },
 1630             },
 1631         }
 1632         try:
 1633             result = self.send_iter_request('sis-get-iter', api_args)
 1634             attributes_list = result.get_child_by_name(
 1635                 'attributes-list') or netapp_api.NaElement('none')
 1636             sis_status_info = attributes_list.get_child_by_name(
 1637                 'sis-status-info') or netapp_api.NaElement('none')
 1638         except exception.NetAppException:
 1639             msg = _('Failed to get volume efficiency status for %s.')
 1640             LOG.error(msg, volume_name)
 1641             sis_status_info = netapp_api.NaElement('none')
 1642 
 1643         return {
 1644             'dedupe': True if 'enabled' == sis_status_info.get_child_content(
 1645                 'state') else False,
 1646             'compression': True if 'true' == sis_status_info.get_child_content(
 1647                 'is-compression-enabled') else False,
 1648         }
 1649 
 1650     @na_utils.trace
 1651     def set_volume_max_files(self, volume_name, max_files):
 1652         """Set flexvol file limit."""
 1653         api_args = {
 1654             'query': {
 1655                 'volume-attributes': {
 1656                     'volume-id-attributes': {
 1657                         'name': volume_name,
 1658                     },
 1659                 },
 1660             },
 1661             'attributes': {
 1662                 'volume-attributes': {
 1663                     'volume-inode-attributes': {
 1664                         'files-total': max_files,
 1665                     },
 1666                 },
 1667             },
 1668         }
 1669         self.send_request('volume-modify-iter', api_args)
 1670 
 1671     @na_utils.trace
 1672     def set_volume_size(self, volume_name, size_gb):
 1673         """Set volume size."""
 1674         api_args = {
 1675             'query': {
 1676                 'volume-attributes': {
 1677                     'volume-id-attributes': {
 1678                         'name': volume_name,
 1679                     },
 1680                 },
 1681             },
 1682             'attributes': {
 1683                 'volume-attributes': {
 1684                     'volume-space-attributes': {
 1685                         'size': int(size_gb) * units.Gi,
 1686                     },
 1687                 },
 1688             },
 1689         }
 1690         result = self.send_request('volume-modify-iter', api_args)
 1691         failures = result.get_child_content('num-failed')
 1692         if failures and int(failures) > 0:
 1693             failure_list = result.get_child_by_name(
 1694                 'failure-list') or netapp_api.NaElement('none')
 1695             errors = failure_list.get_children()
 1696             if errors:
 1697                 raise netapp_api.NaApiError(
 1698                     errors[0].get_child_content('error-code'),
 1699                     errors[0].get_child_content('error-message'))
 1700 
 1701     @na_utils.trace
 1702     def set_volume_snapdir_access(self, volume_name, hide_snapdir):
 1703         """Set volume snapshot directory visibility."""
 1704         api_args = {
 1705             'query': {
 1706                 'volume-attributes': {
 1707                     'volume-id-attributes': {
 1708                         'name': volume_name,
 1709                     },
 1710                 },
 1711             },
 1712             'attributes': {
 1713                 'volume-attributes': {
 1714                     'volume-snapshot-attributes': {
 1715                         'snapdir-access-enabled': six.text_type(
 1716                             not hide_snapdir).lower(),
 1717                     },
 1718                 },
 1719             },
 1720         }
 1721         result = self.send_request('volume-modify-iter', api_args)
 1722         failures = result.get_child_content('num-failed')
 1723         if failures and int(failures) > 0:
 1724             failure_list = result.get_child_by_name(
 1725                 'failure-list') or netapp_api.NaElement('none')
 1726             errors = failure_list.get_children()
 1727             if errors:
 1728                 raise netapp_api.NaApiError(
 1729                     errors[0].get_child_content('error-code'),
 1730                     errors[0].get_child_content('error-message'))
 1731 
 1732     @na_utils.trace
 1733     def set_volume_filesys_size_fixed(self,
 1734                                       volume_name, filesys_size_fixed=False):
 1735         """Set volume file system size fixed to true/false."""
 1736         api_args = {
 1737             'query': {
 1738                 'volume-attributes': {
 1739                     'volume-id-attributes': {
 1740                         'name': volume_name,
 1741                     },
 1742                 },
 1743             },
 1744             'attributes': {
 1745                 'volume-attributes': {
 1746                     'volume-space-attributes': {
 1747                         'is-filesys-size-fixed': six.text_type(
 1748                             filesys_size_fixed).lower(),
 1749                     },
 1750                 },
 1751             },
 1752         }
 1753         result = self.send_request('volume-modify-iter', api_args)
 1754         failures = result.get_child_content('num-failed')
 1755         if failures and int(failures) > 0:
 1756             failure_list = result.get_child_by_name(
 1757                 'failure-list') or netapp_api.NaElement('none')
 1758             errors = failure_list.get_children()
 1759             if errors:
 1760                 raise netapp_api.NaApiError(
 1761                     errors[0].get_child_content('error-code'),
 1762                     errors[0].get_child_content('error-message'))
 1763 
 1764     @na_utils.trace
 1765     def set_volume_security_style(self, volume_name, security_style='unix'):
 1766         """Set volume security style"""
 1767         api_args = {
 1768             'query': {
 1769                 'volume-attributes': {
 1770                     'volume-id-attributes': {
 1771                         'name': volume_name,
 1772                     },
 1773                 },
 1774             },
 1775             'attributes': {
 1776                 'volume-attributes': {
 1777                     'volume-security-attributes': {
 1778                         'style': security_style,
 1779                     },
 1780                 },
 1781             },
 1782         }
 1783         result = self.send_request('volume-modify-iter', api_args)
 1784         failures = result.get_child_content('num-failed')
 1785         if failures and int(failures) > 0:
 1786             failure_list = result.get_child_by_name(
 1787                 'failure-list') or netapp_api.NaElement('none')
 1788             errors = failure_list.get_children()
 1789             if errors:
 1790                 raise netapp_api.NaApiError(
 1791                     errors[0].get_child_content('error-code'),
 1792                     errors[0].get_child_content('error-message'))
 1793 
 1794     @na_utils.trace
 1795     def set_volume_name(self, volume_name, new_volume_name):
 1796         """Set flexvol name."""
 1797         api_args = {
 1798             'volume': volume_name,
 1799             'new-volume-name': new_volume_name,
 1800         }
 1801         self.send_request('volume-rename', api_args)
 1802 
 1803     @na_utils.trace
 1804     def rename_vserver(self, vserver_name, new_vserver_name):
 1805         """Rename a vserver."""
 1806         api_args = {
 1807             'vserver-name': vserver_name,
 1808             'new-name': new_vserver_name,
 1809         }
 1810         self.send_request('vserver-rename', api_args)
 1811 
 1812     @na_utils.trace
 1813     def modify_volume(self, aggregate_name, volume_name,
 1814                       thin_provisioned=False, snapshot_policy=None,
 1815                       language=None, dedup_enabled=False,
 1816                       compression_enabled=False, max_files=None,
 1817                       qos_policy_group=None, hide_snapdir=None,
 1818                       **options):
 1819         """Update backend volume for a share as necessary."""
 1820         api_args = {
 1821             'query': {
 1822                 'volume-attributes': {
 1823                     'volume-id-attributes': {
 1824                         'containing-aggregate-name': aggregate_name,
 1825                         'name': volume_name,
 1826                     },
 1827                 },
 1828             },
 1829             'attributes': {
 1830                 'volume-attributes': {
 1831                     'volume-inode-attributes': {},
 1832                     'volume-language-attributes': {},
 1833                     'volume-snapshot-attributes': {},
 1834                     'volume-space-attributes': {
 1835                         'space-guarantee': ('none' if thin_provisioned else
 1836                                             'volume'),
 1837                     },
 1838                 },
 1839             },
 1840         }
 1841         if language:
 1842             api_args['attributes']['volume-attributes'][
 1843                 'volume-language-attributes']['language'] = language
 1844         if max_files:
 1845             api_args['attributes']['volume-attributes'][
 1846                 'volume-inode-attributes']['files-total'] = max_files
 1847         if snapshot_policy:
 1848             api_args['attributes']['volume-attributes'][
 1849                 'volume-snapshot-attributes'][
 1850                     'snapshot-policy'] = snapshot_policy
 1851         if qos_policy_group:
 1852             api_args['attributes']['volume-attributes'][
 1853                 'volume-qos-attributes'] = {
 1854                 'policy-group-name': qos_policy_group,
 1855             }
 1856         if hide_snapdir in (True, False):
 1857             # Value of hide_snapdir needs to be inverted for ZAPI parameter
 1858             api_args['attributes']['volume-attributes'][
 1859                 'volume-snapshot-attributes'][
 1860                 'snapdir-access-enabled'] = six.text_type(
 1861                 not hide_snapdir).lower()
 1862 
 1863         self.send_request('volume-modify-iter', api_args)
 1864 
 1865         # Efficiency options must be handled separately
 1866         self.update_volume_efficiency_attributes(volume_name,
 1867                                                  dedup_enabled,
 1868                                                  compression_enabled)
 1869 
 1870     @na_utils.trace
 1871     def update_volume_efficiency_attributes(self, volume_name, dedup_enabled,
 1872                                             compression_enabled):
 1873         """Update dedupe & compression attributes to match desired values."""
 1874         efficiency_status = self.get_volume_efficiency_status(volume_name)
 1875 
 1876         # cDOT compression requires dedup to be enabled
 1877         dedup_enabled = dedup_enabled or compression_enabled
 1878 
 1879         # enable/disable dedup if needed
 1880         if dedup_enabled and not efficiency_status['dedupe']:
 1881             self.enable_dedup(volume_name)
 1882         elif not dedup_enabled and efficiency_status['dedupe']:
 1883             self.disable_dedup(volume_name)
 1884 
 1885         # enable/disable compression if needed
 1886         if compression_enabled and not efficiency_status['compression']:
 1887             self.enable_compression(volume_name)
 1888         elif not compression_enabled and efficiency_status['compression']:
 1889             self.disable_compression(volume_name)
 1890 
 1891     @na_utils.trace
 1892     def volume_exists(self, volume_name):
 1893         """Checks if volume exists."""
 1894         LOG.debug('Checking if volume %s exists', volume_name)
 1895 
 1896         api_args = {
 1897             'query': {
 1898                 'volume-attributes': {
 1899                     'volume-id-attributes': {
 1900                         'name': volume_name,
 1901                     },
 1902                 },
 1903             },
 1904             'desired-attributes': {
 1905                 'volume-attributes': {
 1906                     'volume-id-attributes': {
 1907                         'name': None,
 1908                     },
 1909                 },
 1910             },
 1911         }
 1912         result = self.send_iter_request('volume-get-iter', api_args)
 1913         return self._has_records(result)
 1914 
 1915     @na_utils.trace
 1916     def is_flexvol_encrypted(self, volume_name, vserver_name):
 1917         """Checks whether the volume is encrypted or not."""
 1918 
 1919         if not self.features.FLEXVOL_ENCRYPTION:
 1920             return False
 1921 
 1922         api_args = {
 1923             'query': {
 1924                 'volume-attributes': {
 1925                     'encrypt': 'true',
 1926                     'volume-id-attributes': {
 1927                         'name': volume_name,
 1928                         'owning-vserver-name': vserver_name,
 1929                     },
 1930                 },
 1931             },
 1932             'desired-attributes': {
 1933                 'volume-attributes': {
 1934                     'encrypt': None,
 1935                 },
 1936             },
 1937         }
 1938         result = self.send_iter_request('volume-get-iter', api_args)
 1939         if self._has_records(result):
 1940             attributes_list = result.get_child_by_name(
 1941                 'attributes-list') or netapp_api.NaElement('none')
 1942             volume_attributes = attributes_list.get_child_by_name(
 1943                 'volume-attributes') or netapp_api.NaElement('none')
 1944             encrypt = volume_attributes.get_child_content('encrypt')
 1945             if encrypt:
 1946                 return True
 1947 
 1948         return False
 1949 
 1950     @na_utils.trace
 1951     def get_aggregate_for_volume(self, volume_name):
 1952         """Get the name of the aggregate containing a volume."""
 1953 
 1954         api_args = {
 1955             'query': {
 1956                 'volume-attributes': {
 1957                     'volume-id-attributes': {
 1958                         'name': volume_name,
 1959                     },
 1960                 },
 1961             },
 1962             'desired-attributes': {
 1963                 'volume-attributes': {
 1964                     'volume-id-attributes': {
 1965                         'containing-aggregate-name': None,
 1966                         'name': None,
 1967                     },
 1968                 },
 1969             },
 1970         }
 1971         result = self.send_iter_request('volume-get-iter', api_args)
 1972 
 1973         attributes_list = result.get_child_by_name(
 1974             'attributes-list') or netapp_api.NaElement('none')
 1975         volume_attributes = attributes_list.get_child_by_name(
 1976             'volume-attributes') or netapp_api.NaElement('none')
 1977         volume_id_attributes = volume_attributes.get_child_by_name(
 1978             'volume-id-attributes') or netapp_api.NaElement('none')
 1979 
 1980         aggregate = volume_id_attributes.get_child_content(
 1981             'containing-aggregate-name')
 1982 
 1983         if not aggregate:
 1984             msg = _('Could not find aggregate for volume %s.')
 1985             raise exception.NetAppException(msg % volume_name)
 1986 
 1987         return aggregate
 1988 
 1989     @na_utils.trace
 1990     def volume_has_luns(self, volume_name):
 1991         """Checks if volume has LUNs."""
 1992         LOG.debug('Checking if volume %s has LUNs', volume_name)
 1993 
 1994         api_args = {
 1995             'query': {
 1996                 'lun-info': {
 1997                     'volume': volume_name,
 1998                 },
 1999             },
 2000             'desired-attributes': {
 2001                 'lun-info': {
 2002                     'path': None,
 2003                 },
 2004             },
 2005         }
 2006         result = self.send_iter_request('lun-get-iter', api_args)
 2007         return self._has_records(result)
 2008 
 2009     @na_utils.trace
 2010     def volume_has_junctioned_volumes(self, volume_name):
 2011         """Checks if volume has volumes mounted beneath its junction path."""
 2012         junction_path = self.get_volume_junction_path(volume_name)
 2013         if not junction_path:
 2014             return False
 2015 
 2016         api_args = {
 2017             'query': {
 2018                 'volume-attributes': {
 2019                     'volume-id-attributes': {
 2020                         'junction-path': junction_path + '/*',
 2021                     },
 2022                 },
 2023             },
 2024             'desired-attributes': {
 2025                 'volume-attributes': {
 2026                     'volume-id-attributes': {
 2027                         'name': None,
 2028                     },
 2029                 },
 2030             },
 2031         }
 2032         result = self.send_iter_request('volume-get-iter', api_args)
 2033         return self._has_records(result)
 2034 
 2035     @na_utils.trace
 2036     def get_volume(self, volume_name):
 2037         """Returns the volume with the specified name, if present."""
 2038 
 2039         api_args = {
 2040             'query': {
 2041                 'volume-attributes': {
 2042                     'volume-id-attributes': {
 2043                         'name': volume_name,
 2044                     },
 2045                 },
 2046             },
 2047             'desired-attributes': {
 2048                 'volume-attributes': {
 2049                     'volume-id-attributes': {
 2050                         'containing-aggregate-name': None,
 2051                         'junction-path': None,
 2052                         'name': None,
 2053                         'owning-vserver-name': None,
 2054                         'type': None,
 2055                         'style': None,
 2056                     },
 2057                     'volume-qos-attributes': {
 2058                         'policy-group-name': None,
 2059                     },
 2060                     'volume-space-attributes': {
 2061                         'size': None,
 2062                     },
 2063                 },
 2064             },
 2065         }
 2066         result = self.send_request('volume-get-iter', api_args)
 2067 
 2068         attributes_list = result.get_child_by_name(
 2069             'attributes-list') or netapp_api.NaElement('none')
 2070         volume_attributes_list = attributes_list.get_children()
 2071 
 2072         if not self._has_records(result):
 2073             raise exception.StorageResourceNotFound(name=volume_name)
 2074         elif len(volume_attributes_list) > 1:
 2075             msg = _('Could not find unique volume %(vol)s.')
 2076             msg_args = {'vol': volume_name}
 2077             raise exception.NetAppException(msg % msg_args)
 2078 
 2079         volume_attributes = volume_attributes_list[0]
 2080 
 2081         volume_id_attributes = volume_attributes.get_child_by_name(
 2082             'volume-id-attributes') or netapp_api.NaElement('none')
 2083         volume_qos_attributes = volume_attributes.get_child_by_name(
 2084             'volume-qos-attributes') or netapp_api.NaElement('none')
 2085         volume_space_attributes = volume_attributes.get_child_by_name(
 2086             'volume-space-attributes') or netapp_api.NaElement('none')
 2087 
 2088         volume = {
 2089             'aggregate': volume_id_attributes.get_child_content(
 2090                 'containing-aggregate-name'),
 2091             'junction-path': volume_id_attributes.get_child_content(
 2092                 'junction-path'),
 2093             'name': volume_id_attributes.get_child_content('name'),
 2094             'owning-vserver-name': volume_id_attributes.get_child_content(
 2095                 'owning-vserver-name'),
 2096             'type': volume_id_attributes.get_child_content('type'),
 2097             'style': volume_id_attributes.get_child_content('style'),
 2098             'size': volume_space_attributes.get_child_content('size'),
 2099             'qos-policy-group-name': volume_qos_attributes.get_child_content(
 2100                 'policy-group-name')
 2101         }
 2102         return volume
 2103 
 2104     @na_utils.trace
 2105     def get_volume_at_junction_path(self, junction_path):
 2106         """Returns the volume with the specified junction path, if present."""
 2107         if not junction_path:
 2108             return None
 2109 
 2110         api_args = {
 2111             'query': {
 2112                 'volume-attributes': {
 2113                     'volume-id-attributes': {
 2114                         'junction-path': junction_path,
 2115                     },
 2116                 },
 2117             },
 2118             'desired-attributes': {
 2119                 'volume-attributes': {
 2120                     'volume-id-attributes': {
 2121                         'containing-aggregate-name': None,
 2122                         'junction-path': None,
 2123                         'name': None,
 2124                         'type': None,
 2125                         'style': None,
 2126                     },
 2127                     'volume-space-attributes': {
 2128                         'size': None,
 2129                     }
 2130                 },
 2131             },
 2132         }
 2133         result = self.send_iter_request('volume-get-iter', api_args)
 2134         if not self._has_records(result):
 2135             return None
 2136 
 2137         attributes_list = result.get_child_by_name(
 2138             'attributes-list') or netapp_api.NaElement('none')
 2139         volume_attributes = attributes_list.get_child_by_name(
 2140             'volume-attributes') or netapp_api.NaElement('none')
 2141         volume_id_attributes = volume_attributes.get_child_by_name(
 2142             'volume-id-attributes') or netapp_api.NaElement('none')
 2143         volume_space_attributes = volume_attributes.get_child_by_name(
 2144             'volume-space-attributes') or netapp_api.NaElement('none')
 2145 
 2146         volume = {
 2147             'aggregate': volume_id_attributes.get_child_content(
 2148                 'containing-aggregate-name'),
 2149             'junction-path': volume_id_attributes.get_child_content(
 2150                 'junction-path'),
 2151             'name': volume_id_attributes.get_child_content('name'),
 2152             'type': volume_id_attributes.get_child_content('type'),
 2153             'style': volume_id_attributes.get_child_content('style'),
 2154             'size': volume_space_attributes.get_child_content('size'),
 2155         }
 2156         return volume
 2157 
 2158     @na_utils.trace
 2159     def get_volume_to_manage(self, aggregate_name, volume_name):
 2160         """Get flexvol to be managed by Manila."""
 2161 
 2162         api_args = {
 2163             'query': {
 2164                 'volume-attributes': {
 2165                     'volume-id-attributes': {
 2166                         'containing-aggregate-name': aggregate_name,
 2167                         'name': volume_name,
 2168                     },
 2169                 },
 2170             },
 2171             'desired-attributes': {
 2172                 'volume-attributes': {
 2173                     'volume-id-attributes': {
 2174                         'containing-aggregate-name': None,
 2175                         'junction-path': None,
 2176                         'name': None,
 2177                         'type': None,
 2178                         'style': None,
 2179                         'owning-vserver-name': None,
 2180                     },
 2181                     'volume-qos-attributes': {
 2182                         'policy-group-name': None,
 2183                     },
 2184                     'volume-space-attributes': {
 2185                         'size': None,
 2186                     },
 2187                 },
 2188             },
 2189         }
 2190         result = self.send_iter_request('volume-get-iter', api_args)
 2191         if not self._has_records(result):
 2192             return None
 2193 
 2194         attributes_list = result.get_child_by_name(
 2195             'attributes-list') or netapp_api.NaElement('none')
 2196         volume_attributes = attributes_list.get_child_by_name(
 2197             'volume-attributes') or netapp_api.NaElement('none')
 2198         volume_id_attributes = volume_attributes.get_child_by_name(
 2199             'volume-id-attributes') or netapp_api.NaElement('none')
 2200         volume_qos_attributes = volume_attributes.get_child_by_name(
 2201             'volume-qos-attributes') or netapp_api.NaElement('none')
 2202         volume_space_attributes = volume_attributes.get_child_by_name(
 2203             'volume-space-attributes') or netapp_api.NaElement('none')
 2204 
 2205         volume = {
 2206             'aggregate': volume_id_attributes.get_child_content(
 2207                 'containing-aggregate-name'),
 2208             'junction-path': volume_id_attributes.get_child_content(
 2209                 'junction-path'),
 2210             'name': volume_id_attributes.get_child_content('name'),
 2211             'type': volume_id_attributes.get_child_content('type'),
 2212             'style': volume_id_attributes.get_child_content('style'),
 2213             'owning-vserver-name': volume_id_attributes.get_child_content(
 2214                 'owning-vserver-name'),
 2215             'size': volume_space_attributes.get_child_content('size'),
 2216             'qos-policy-group-name': volume_qos_attributes.get_child_content(
 2217                 'policy-group-name')
 2218 
 2219         }
 2220         return volume
 2221 
 2222     @na_utils.trace
 2223     def create_volume_clone(self, volume_name, parent_volume_name,
 2224                             parent_snapshot_name=None, split=False,
 2225                             qos_policy_group=None, **options):
 2226         """Clones a volume."""
 2227         api_args = {
 2228             'volume': volume_name,
 2229             'parent-volume': parent_volume_name,
 2230             'parent-snapshot': parent_snapshot_name,
 2231             'junction-path': '/%s' % volume_name,
 2232         }
 2233 
 2234         if qos_policy_group is not None:
 2235             api_args['qos-policy-group-name'] = qos_policy_group
 2236 
 2237         self.send_request('volume-clone-create', api_args)
 2238 
 2239         if split:
 2240             self.split_volume_clone(volume_name)
 2241 
 2242     @na_utils.trace
 2243     def split_volume_clone(self, volume_name):
 2244         """Begins splitting a clone from its parent."""
 2245         try:
 2246             api_args = {'volume': volume_name}
 2247             self.send_request('volume-clone-split-start', api_args)
 2248         except netapp_api.NaApiError as e:
 2249             if e.code == netapp_api.EVOL_CLONE_BEING_SPLIT:
 2250                 return
 2251             raise
 2252 
 2253     @na_utils.trace
 2254     def get_clone_children_for_snapshot(self, volume_name, snapshot_name):
 2255         """Returns volumes that are keeping a snapshot locked."""
 2256 
 2257         api_args = {
 2258             'query': {
 2259                 'volume-attributes': {
 2260                     'volume-clone-attributes': {
 2261                         'volume-clone-parent-attributes': {
 2262                             'name': volume_name,
 2263                             'snapshot-name': snapshot_name,
 2264                         },
 2265                     },
 2266                 },
 2267             },
 2268             'desired-attributes': {
 2269                 'volume-attributes': {
 2270                     'volume-id-attributes': {
 2271                         'name': None,
 2272                     },
 2273                 },
 2274             },
 2275         }
 2276         result = self.send_iter_request('volume-get-iter', api_args)
 2277         if not self._has_records(result):
 2278             return []
 2279 
 2280         volume_list = []
 2281         attributes_list = result.get_child_by_name(
 2282             'attributes-list') or netapp_api.NaElement('none')
 2283 
 2284         for volume_attributes in attributes_list.get_children():
 2285 
 2286             volume_id_attributes = volume_attributes.get_child_by_name(
 2287                 'volume-id-attributes') or netapp_api.NaElement('none')
 2288 
 2289             volume_list.append({
 2290                 'name': volume_id_attributes.get_child_content('name'),
 2291             })
 2292 
 2293         return volume_list
 2294 
 2295     @na_utils.trace
 2296     def get_volume_junction_path(self, volume_name, is_style_cifs=False):
 2297         """Gets a volume junction path."""
 2298         api_args = {
 2299             'volume': volume_name,
 2300             'is-style-cifs': six.text_type(is_style_cifs).lower(),
 2301         }
 2302         result = self.send_request('volume-get-volume-path', api_args)
 2303         return result.get_child_content('junction')
 2304 
 2305     @na_utils.trace
 2306     def mount_volume(self, volume_name, junction_path=None):
 2307         """Mounts a volume on a junction path."""
 2308         api_args = {
 2309             'volume-name': volume_name,
 2310             'junction-path': (junction_path if junction_path
 2311                               else '/%s' % volume_name)
 2312         }
 2313         self.send_request('volume-mount', api_args)
 2314 
 2315     @na_utils.trace
 2316     def offline_volume(self, volume_name):
 2317         """Offlines a volume."""
 2318         try:
 2319             self.send_request('volume-offline', {'name': volume_name})
 2320         except netapp_api.NaApiError as e:
 2321             if e.code == netapp_api.EVOLUMEOFFLINE:
 2322                 return
 2323             raise
 2324 
 2325     @na_utils.trace
 2326     def _unmount_volume(self, volume_name, force=False):
 2327         """Unmounts a volume."""
 2328         api_args = {
 2329             'volume-name': volume_name,
 2330             'force': six.text_type(force).lower(),
 2331         }
 2332         try:
 2333             self.send_request('volume-unmount', api_args)
 2334         except netapp_api.NaApiError as e:
 2335             if e.code == netapp_api.EVOL_NOT_MOUNTED:
 2336                 return
 2337             raise
 2338 
 2339     @na_utils.trace
 2340     def unmount_volume(self, volume_name, force=False, wait_seconds=30):
 2341         """Unmounts a volume, retrying if a clone split is ongoing.
 2342 
 2343         NOTE(cknight): While unlikely to happen in normal operation, any client
 2344         that tries to delete volumes immediately after creating volume clones
 2345         is likely to experience failures if cDOT isn't quite ready for the
 2346         delete.  The volume unmount is the first operation in the delete
 2347         path that fails in this case, and there is no proactive check we can
 2348         use to reliably predict the failure.  And there isn't a specific error
 2349         code from volume-unmount, so we have to check for a generic error code
 2350         plus certain language in the error code.  It's ugly, but it works, and
 2351         it's better than hard-coding a fixed delay.
 2352         """
 2353 
 2354         # Do the unmount, handling split-related errors with retries.
 2355         retry_interval = 3  # seconds
 2356         for retry in range(int(wait_seconds / retry_interval)):
 2357             try:
 2358                 self._unmount_volume(volume_name, force=force)
 2359                 LOG.debug('Volume %s unmounted.', volume_name)
 2360                 return
 2361             except netapp_api.NaApiError as e:
 2362                 if e.code == netapp_api.EAPIERROR and 'job ID' in e.message:
 2363                     msg = ('Could not unmount volume %(volume)s due to '
 2364                            'ongoing volume operation: %(exception)s')
 2365                     msg_args = {'volume': volume_name, 'exception': e}
 2366                     LOG.warning(msg, msg_args)
 2367                     time.sleep(retry_interval)
 2368                     continue
 2369                 raise
 2370 
 2371         msg = _('Failed to unmount volume %(volume)s after '
 2372                 'waiting for %(wait_seconds)s seconds.')
 2373         msg_args = {'volume': volume_name, 'wait_seconds': wait_seconds}
 2374         LOG.error(msg, msg_args)
 2375         raise exception.NetAppException(msg % msg_args)
 2376 
 2377     @na_utils.trace
 2378     def delete_volume(self, volume_name):
 2379         """Deletes a volume."""
 2380         self.send_request('volume-destroy', {'name': volume_name})
 2381 
 2382     @na_utils.trace
 2383     def create_snapshot(self, volume_name, snapshot_name):
 2384         """Creates a volume snapshot."""
 2385         api_args = {'volume': volume_name, 'snapshot': snapshot_name}
 2386         self.send_request('snapshot-create', api_args)
 2387 
 2388     @na_utils.trace
 2389     def snapshot_exists(self, snapshot_name, volume_name):
 2390         """Checks if Snapshot exists for a specified volume."""
 2391         LOG.debug('Checking if snapshot %(snapshot)s exists for '
 2392                   'volume %(volume)s',
 2393                   {'snapshot': snapshot_name, 'volume': volume_name})
 2394 
 2395         """Gets a single snapshot."""
 2396         api_args = {
 2397             'query': {
 2398                 'snapshot-info': {
 2399                     'name': snapshot_name,
 2400                     'volume': volume_name,
 2401                 },
 2402             },
 2403             'desired-attributes': {
 2404                 'snapshot-info': {
 2405                     'name': None,
 2406                     'volume': None,
 2407                     'busy': None,
 2408                     'snapshot-owners-list': {
 2409                         'snapshot-owner': None,
 2410                     }
 2411                 },
 2412             },
 2413         }
 2414         result = self.send_request('snapshot-get-iter', api_args)
 2415 
 2416         error_record_list = result.get_child_by_name(
 2417             'volume-errors') or netapp_api.NaElement('none')
 2418         errors = error_record_list.get_children()
 2419 
 2420         if errors:
 2421             error = errors[0]
 2422             error_code = error.get_child_content('errno')
 2423             error_reason = error.get_child_content('reason')
 2424             msg = _('Could not read information for snapshot %(name)s. '
 2425                     'Code: %(code)s. Reason: %(reason)s')
 2426             msg_args = {
 2427                 'name': snapshot_name,
 2428                 'code': error_code,
 2429                 'reason': error_reason
 2430             }
 2431             if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
 2432                 raise exception.SnapshotUnavailable(msg % msg_args)
 2433             else:
 2434                 raise exception.NetAppException(msg % msg_args)
 2435 
 2436         return self._has_records(result)
 2437 
 2438     @na_utils.trace
 2439     def get_snapshot(self, volume_name, snapshot_name):
 2440         """Gets a single snapshot."""
 2441         api_args = {
 2442             'query': {
 2443                 'snapshot-info': {
 2444                     'name': snapshot_name,
 2445                     'volume': volume_name,
 2446                 },
 2447             },
 2448             'desired-attributes': {
 2449                 'snapshot-info': {
 2450                     'access-time': None,
 2451                     'name': None,
 2452                     'volume': None,
 2453                     'busy': None,
 2454                     'snapshot-owners-list': {
 2455                         'snapshot-owner': None,
 2456                     }
 2457                 },
 2458             },
 2459         }
 2460         result = self.send_request('snapshot-get-iter', api_args)
 2461 
 2462         error_record_list = result.get_child_by_name(
 2463             'volume-errors') or netapp_api.NaElement('none')
 2464         errors = error_record_list.get_children()
 2465 
 2466         if errors:
 2467             error = errors[0]
 2468             error_code = error.get_child_content('errno')
 2469             error_reason = error.get_child_content('reason')
 2470             msg = _('Could not read information for snapshot %(name)s. '
 2471                     'Code: %(code)s. Reason: %(reason)s')
 2472             msg_args = {
 2473                 'name': snapshot_name,
 2474                 'code': error_code,
 2475                 'reason': error_reason
 2476             }
 2477             if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
 2478                 raise exception.SnapshotUnavailable(msg % msg_args)
 2479             else:
 2480                 raise exception.NetAppException(msg % msg_args)
 2481 
 2482         attributes_list = result.get_child_by_name(
 2483             'attributes-list') or netapp_api.NaElement('none')
 2484         snapshot_info_list = attributes_list.get_children()
 2485 
 2486         if not self._has_records(result):
 2487             raise exception.SnapshotResourceNotFound(name=snapshot_name)
 2488         elif len(snapshot_info_list) > 1:
 2489             msg = _('Could not find unique snapshot %(snap)s on '
 2490                     'volume %(vol)s.')
 2491             msg_args = {'snap': snapshot_name, 'vol': volume_name}
 2492             raise exception.NetAppException(msg % msg_args)
 2493 
 2494         snapshot_info = snapshot_info_list[0]
 2495         snapshot = {
 2496             'access-time': snapshot_info.get_child_content('access-time'),
 2497             'name': snapshot_info.get_child_content('name'),
 2498             'volume': snapshot_info.get_child_content('volume'),
 2499             'busy': strutils.bool_from_string(
 2500                 snapshot_info.get_child_content('busy')),
 2501         }
 2502 
 2503         snapshot_owners_list = snapshot_info.get_child_by_name(
 2504             'snapshot-owners-list') or netapp_api.NaElement('none')
 2505         snapshot_owners = set([
 2506             snapshot_owner.get_child_content('owner')
 2507             for snapshot_owner in snapshot_owners_list.get_children()])
 2508         snapshot['owners'] = snapshot_owners
 2509 
 2510         return snapshot
 2511 
 2512     @na_utils.trace
 2513     def rename_snapshot(self, volume_name, snapshot_name, new_snapshot_name):
 2514         api_args = {
 2515             'volume': volume_name,
 2516             'current-name': snapshot_name,
 2517             'new-name': new_snapshot_name
 2518         }
 2519         self.send_request('snapshot-rename', api_args)
 2520 
 2521     @na_utils.trace
 2522     def restore_snapshot(self, volume_name, snapshot_name):
 2523         """Reverts a volume to the specified snapshot."""
 2524         api_args = {
 2525             'volume': volume_name,
 2526             'snapshot': snapshot_name,
 2527         }
 2528         self.send_request('snapshot-restore-volume', api_args)
 2529 
 2530     @na_utils.trace
 2531     def delete_snapshot(self, volume_name, snapshot_name, ignore_owners=False):
 2532         """Deletes a volume snapshot."""
 2533 
 2534         ignore_owners = ('true' if strutils.bool_from_string(ignore_owners)
 2535                          else 'false')
 2536 
 2537         api_args = {
 2538             'volume': volume_name,
 2539             'snapshot': snapshot_name,
 2540             'ignore-owners': ignore_owners,
 2541         }
 2542         self.send_request('snapshot-delete', api_args)
 2543 
 2544     @na_utils.trace
 2545     def soft_delete_snapshot(self, volume_name, snapshot_name):
 2546         """Deletes a volume snapshot, or renames it if delete fails."""
 2547         try:
 2548             self.delete_snapshot(volume_name, snapshot_name)
 2549         except netapp_api.NaApiError:
 2550             self.rename_snapshot(volume_name,
 2551                                  snapshot_name,
 2552                                  DELETED_PREFIX + snapshot_name)
 2553             msg = _('Soft-deleted snapshot %(snapshot)s on volume %(volume)s.')
 2554             msg_args = {'snapshot': snapshot_name, 'volume': volume_name}
 2555             LOG.info(msg, msg_args)
 2556 
 2557     @na_utils.trace
 2558     def prune_deleted_snapshots(self):
 2559         """Deletes non-busy snapshots that were previously soft-deleted."""
 2560 
 2561         deleted_snapshots_map = self._get_deleted_snapshots()
 2562 
 2563         for vserver in deleted_snapshots_map:
 2564             client = copy.deepcopy(self)
 2565             client.set_vserver(vserver)
 2566 
 2567             for snapshot in deleted_snapshots_map[vserver]:
 2568                 try:
 2569                     client.delete_snapshot(snapshot['volume'],
 2570                                            snapshot['name'])
 2571                 except netapp_api.NaApiError:
 2572                     msg = _('Could not delete snapshot %(snap)s on '
 2573                             'volume %(volume)s.')
 2574                     msg_args = {
 2575                         'snap': snapshot['name'],
 2576                         'volume': snapshot['volume'],
 2577                     }
 2578                     LOG.exception(msg, msg_args)
 2579 
 2580     @na_utils.trace
 2581     def _get_deleted_snapshots(self):
 2582         """Returns non-busy, soft-deleted snapshots suitable for reaping."""
 2583         api_args = {
 2584             'query': {
 2585                 'snapshot-info': {
 2586                     'name': DELETED_PREFIX + '*',
 2587                     'busy': 'false',
 2588                 },
 2589             },
 2590             'desired-attributes': {
 2591                 'snapshot-info': {
 2592                     'name': None,
 2593                     'vserver': None,
 2594                     'volume': None,
 2595                 },
 2596             },
 2597         }
 2598         result = self.send_iter_request('snapshot-get-iter', api_args)
 2599 
 2600         attributes_list = result.get_child_by_name(
 2601             'attributes-list') or netapp_api.NaElement('none')
 2602 
 2603         # Build a map of snapshots, one list of snapshots per vserver
 2604         snapshot_map = {}
 2605         for snapshot_info in attributes_list.get_children():
 2606             vserver = snapshot_info.get_child_content('vserver')
 2607             snapshot_list = snapshot_map.get(vserver, [])
 2608             snapshot_list.append({
 2609                 'name': snapshot_info.get_child_content('name'),
 2610                 'volume': snapshot_info.get_child_content('volume'),
 2611                 'vserver': vserver,
 2612             })
 2613             snapshot_map[vserver] = snapshot_list
 2614 
 2615         return snapshot_map
 2616 
 2617     @na_utils.trace
 2618     def create_cg_snapshot(self, volume_names, snapshot_name):
 2619         """Creates a consistency group snapshot of one or more flexvols."""
 2620         cg_id = self._start_cg_snapshot(volume_names, snapshot_name)
 2621         if not cg_id:
 2622             msg = _('Could not start consistency group snapshot %s.')
 2623             raise exception.NetAppException(msg % snapshot_name)
 2624         self._commit_cg_snapshot(cg_id)
 2625 
 2626     @na_utils.trace
 2627     def _start_cg_snapshot(self, volume_names, snapshot_name):
 2628         api_args = {
 2629             'snapshot': snapshot_name,
 2630             'timeout': 'relaxed',
 2631             'volumes': [
 2632                 {'volume-name': volume_name} for volume_name in volume_names
 2633             ],
 2634         }
 2635         result = self.send_request('cg-start', api_args)
 2636         return result.get_child_content('cg-id')
 2637 
 2638     @na_utils.trace
 2639     def _commit_cg_snapshot(self, cg_id):
 2640         api_args = {'cg-id': cg_id}
 2641         self.send_request('cg-commit', api_args)
 2642 
 2643     @na_utils.trace
 2644     def create_cifs_share(self, share_name):
 2645         share_path = '/%s' % share_name
 2646         api_args = {'path': share_path, 'share-name': share_name}
 2647         self.send_request('cifs-share-create', api_args)
 2648 
 2649     @na_utils.trace
 2650     def get_cifs_share_access(self, share_name):
 2651         api_args = {
 2652             'query': {
 2653                 'cifs-share-access-control': {
 2654                     'share': share_name,
 2655                 },
 2656             },
 2657             'desired-attributes': {
 2658                 'cifs-share-access-control': {
 2659                     'user-or-group': None,
 2660                     'permission': None,
 2661                 },
 2662             },
 2663         }
 2664         result = self.send_iter_request('cifs-share-access-control-get-iter',
 2665                                         api_args)
 2666 
 2667         attributes_list = result.get_child_by_name(
 2668             'attributes-list') or netapp_api.NaElement('none')
 2669 
 2670         rules = {}
 2671 
 2672         for rule in attributes_list.get_children():
 2673             user_or_group = rule.get_child_content('user-or-group')
 2674             permission = rule.get_child_content('permission')
 2675             rules[user_or_group] = permission
 2676 
 2677         return rules
 2678 
 2679     @na_utils.trace
 2680     def add_cifs_share_access(self, share_name, user_name, readonly):
 2681         api_args = {
 2682             'permission': 'read' if readonly else 'full_control',
 2683             'share': share_name,
 2684             'user-or-group': user_name,
 2685         }
 2686         self.send_request('cifs-share-access-control-create', api_args)
 2687 
 2688     @na_utils.trace
 2689     def modify_cifs_share_access(self, share_name, user_name, readonly):
 2690         api_args = {
 2691             'permission': 'read' if readonly else 'full_control',
 2692             'share': share_name,
 2693             'user-or-group': user_name,
 2694         }
 2695         self.send_request('cifs-share-access-control-modify', api_args)
 2696 
 2697     @na_utils.trace
 2698     def remove_cifs_share_access(self, share_name, user_name):
 2699         api_args = {'user-or-group': user_name, 'share': share_name}
 2700         self.send_request('cifs-share-access-control-delete', api_args)
 2701 
 2702     @na_utils.trace
 2703     def remove_cifs_share(self, share_name):
 2704         self.send_request('cifs-share-delete', {'share-name': share_name})
 2705 
 2706     @na_utils.trace
 2707     def add_nfs_export_rule(self, policy_name, client_match, readonly):
 2708         rule_indices = self._get_nfs_export_rule_indices(policy_name,
 2709                                                          client_match)
 2710         if not rule_indices:
 2711             self._add_nfs_export_rule(policy_name, client_match, readonly)
 2712         else:
 2713             # Update first rule and delete the rest
 2714             self._update_nfs_export_rule(
 2715                 policy_name, client_match, readonly, rule_indices.pop(0))
 2716             self._remove_nfs_export_rules(policy_name, rule_indices)
 2717 
 2718     @na_utils.trace
 2719     def _add_nfs_export_rule(self, policy_name, client_match, readonly):
 2720         api_args = {
 2721             'policy-name': policy_name,
 2722             'client-match': client_match,
 2723             'ro-rule': {
 2724                 'security-flavor': 'sys',
 2725             },
 2726             'rw-rule': {
 2727                 'security-flavor': 'sys' if not readonly else 'never',
 2728             },
 2729             'super-user-security': {
 2730                 'security-flavor': 'sys',
 2731             },
 2732         }
 2733         self.send_request('export-rule-create', api_args)
 2734 
 2735     @na_utils.trace
 2736     def _update_nfs_export_rule(self, policy_name, client_match, readonly,
 2737                                 rule_index):
 2738         api_args = {
 2739             'policy-name': policy_name,
 2740             'rule-index': rule_index,
 2741             'client-match': client_match,
 2742             'ro-rule': {
 2743                 'security-flavor': 'sys'
 2744             },
 2745             'rw-rule': {
 2746                 'security-flavor': 'sys' if not readonly else 'never'
 2747             },
 2748             'super-user-security': {
 2749                 'security-flavor': 'sys'
 2750             },
 2751         }
 2752         self.send_request('export-rule-modify', api_args)
 2753 
 2754     @na_utils.trace
 2755     def _get_nfs_export_rule_indices(self, policy_name, client_match):
 2756         api_args = {
 2757             'query': {
 2758                 'export-rule-info': {
 2759                     'policy-name': policy_name,
 2760                     'client-match': client_match,
 2761                 },
 2762             },
 2763             'desired-attributes': {
 2764                 'export-rule-info': {
 2765                     'vserver-name': None,
 2766                     'policy-name': None,
 2767                     'client-match': None,
 2768                     'rule-index': None,
 2769                 },
 2770             },
 2771         }
 2772         result = self.send_iter_request('export-rule-get-iter', api_args)
 2773 
 2774         attributes_list = result.get_child_by_name(
 2775             'attributes-list') or netapp_api.NaElement('none')
 2776         export_rule_info_list = attributes_list.get_children()
 2777 
 2778         rule_indices = [int(export_rule_info.get_child_content('rule-index'))
 2779                         for export_rule_info in export_rule_info_list]
 2780         rule_indices.sort()
 2781         return [six.text_type(rule_index) for rule_index in rule_indices]
 2782 
 2783     @na_utils.trace
 2784     def remove_nfs_export_rule(self, policy_name, client_match):
 2785         rule_indices = self._get_nfs_export_rule_indices(policy_name,
 2786                                                          client_match)
 2787         self._remove_nfs_export_rules(policy_name, rule_indices)
 2788 
 2789     @na_utils.trace
 2790     def _remove_nfs_export_rules(self, policy_name, rule_indices):
 2791         for rule_index in rule_indices:
 2792             api_args = {
 2793                 'policy-name': policy_name,
 2794                 'rule-index': rule_index
 2795             }
 2796             try:
 2797                 self.send_request('export-rule-destroy', api_args)
 2798             except netapp_api.NaApiError as e:
 2799                 if e.code != netapp_api.EOBJECTNOTFOUND:
 2800                     raise
 2801 
 2802     @na_utils.trace
 2803     def clear_nfs_export_policy_for_volume(self, volume_name):
 2804         self.set_nfs_export_policy_for_volume(volume_name, 'default')
 2805 
 2806     @na_utils.trace
 2807     def set_nfs_export_policy_for_volume(self, volume_name, policy_name):
 2808         api_args = {
 2809             'query': {
 2810                 'volume-attributes': {
 2811                     'volume-id-attributes': {
 2812                         'name': volume_name,
 2813                     },
 2814                 },
 2815             },
 2816             'attributes': {
 2817                 'volume-attributes': {
 2818                     'volume-export-attributes': {
 2819                         'policy': policy_name,
 2820                     },
 2821                 },
 2822             },
 2823         }
 2824         self.send_request('volume-modify-iter', api_args)
 2825 
 2826     @na_utils.trace
 2827     def set_qos_policy_group_for_volume(self, volume_name,
 2828                                         qos_policy_group_name):
 2829         api_args = {
 2830             'query': {
 2831                 'volume-attributes': {
 2832                     'volume-id-attributes': {
 2833                         'name': volume_name,
 2834                     },
 2835                 },
 2836             },
 2837             'attributes': {
 2838                 'volume-attributes': {
 2839                     'volume-qos-attributes': {
 2840                         'policy-group-name': qos_policy_group_name,
 2841                     },
 2842                 },
 2843             },
 2844         }
 2845         self.send_request('volume-modify-iter', api_args)
 2846 
 2847     @na_utils.trace
 2848     def get_nfs_export_policy_for_volume(self, volume_name):
 2849         """Get the name of the export policy for a volume."""
 2850 
 2851         api_args = {
 2852             'query': {
 2853                 'volume-attributes': {
 2854                     'volume-id-attributes': {
 2855                         'name': volume_name,
 2856                     },
 2857                 },
 2858             },
 2859             'desired-attributes': {
 2860                 'volume-attributes': {
 2861                     'volume-export-attributes': {
 2862                         'policy': None,
 2863                     },
 2864                 },
 2865             },
 2866         }
 2867         result = self.send_iter_request('volume-get-iter', api_args)
 2868 
 2869         attributes_list = result.get_child_by_name(
 2870             'attributes-list') or netapp_api.NaElement('none')
 2871         volume_attributes = attributes_list.get_child_by_name(
 2872             'volume-attributes') or netapp_api.NaElement('none')
 2873         volume_export_attributes = volume_attributes.get_child_by_name(
 2874             'volume-export-attributes') or netapp_api.NaElement('none')
 2875 
 2876         export_policy = volume_export_attributes.get_child_content('policy')
 2877 
 2878         if not export_policy:
 2879             msg = _('Could not find export policy for volume %s.')
 2880             raise exception.NetAppException(msg % volume_name)
 2881 
 2882         return export_policy
 2883 
 2884     @na_utils.trace
 2885     def create_nfs_export_policy(self, policy_name):
 2886         api_args = {'policy-name': policy_name}
 2887         try:
 2888             self.send_request('export-policy-create', api_args)
 2889         except netapp_api.NaApiError as e:
 2890             if e.code != netapp_api.EDUPLICATEENTRY:
 2891                 raise
 2892 
 2893     @na_utils.trace
 2894     def soft_delete_nfs_export_policy(self, policy_name):
 2895         try:
 2896             self.delete_nfs_export_policy(policy_name)
 2897         except netapp_api.NaApiError:
 2898             # NOTE(cknight): Policy deletion can fail if called too soon after
 2899             # removing from a flexvol.  So rename for later harvesting.
 2900             self.rename_nfs_export_policy(policy_name,
 2901                                           DELETED_PREFIX + policy_name)
 2902 
 2903     @na_utils.trace
 2904     def delete_nfs_export_policy(self, policy_name):
 2905         api_args = {'policy-name': policy_name}
 2906         try:
 2907             self.send_request('export-policy-destroy', api_args)
 2908         except netapp_api.NaApiError as e:
 2909             if e.code == netapp_api.EOBJECTNOTFOUND:
 2910                 return
 2911             raise
 2912 
 2913     @na_utils.trace
 2914     def rename_nfs_export_policy(self, policy_name, new_policy_name):
 2915         api_args = {
 2916             'policy-name': policy_name,
 2917             'new-policy-name': new_policy_name
 2918         }
 2919         self.send_request('export-policy-rename', api_args)
 2920 
 2921     @na_utils.trace
 2922     def prune_deleted_nfs_export_policies(self):
 2923         deleted_policy_map = self._get_deleted_nfs_export_policies()
 2924         for vserver in deleted_policy_map:
 2925             client = copy.deepcopy(self)
 2926             client.set_vserver(vserver)
 2927             for policy in deleted_policy_map[vserver]:
 2928                 try:
 2929                     client.delete_nfs_export_policy(policy)
 2930                 except netapp_api.NaApiError:
 2931                     LOG.debug('Could not delete export policy %s.', policy)
 2932 
 2933     @na_utils.trace
 2934     def _get_deleted_nfs_export_policies(self):
 2935         api_args = {
 2936             'query': {
 2937                 'export-policy-info': {
 2938                     'policy-name': DELETED_PREFIX + '*',
 2939                 },
 2940             },
 2941             'desired-attributes': {
 2942                 'export-policy-info': {
 2943                     'policy-name': None,
 2944                     'vserver': None,
 2945                 },
 2946             },
 2947         }
 2948         result = self.send_iter_request('export-policy-get-iter', api_args)
 2949 
 2950         attributes_list = result.get_child_by_name(
 2951             'attributes-list') or netapp_api.NaElement('none')
 2952 
 2953         policy_map = {}
 2954         for export_info in attributes_list.get_children():
 2955             vserver = export_info.get_child_content('vserver')
 2956             policies = policy_map.get(vserver, [])
 2957             policies.append(export_info.get_child_content('policy-name'))
 2958             policy_map[vserver] = policies
 2959 
 2960         return policy_map
 2961 
 2962     @na_utils.trace
 2963     def _get_ems_log_destination_vserver(self):
 2964         """Returns the best vserver destination for EMS messages."""
 2965         major, minor = self.get_ontapi_version(cached=True)
 2966 
 2967         if (major > 1) or (major == 1 and minor > 15):
 2968             # Prefer admin Vserver (requires cluster credentials).
 2969             admin_vservers = self.list_vservers(vserver_type='admin')
 2970             if admin_vservers:
 2971                 return admin_vservers[0]
 2972 
 2973             # Fall back to data Vserver.
 2974             data_vservers = self.list_vservers(vserver_type='data')
 2975             if data_vservers:
 2976                 return data_vservers[0]
 2977 
 2978         # If older API version, or no other Vservers found, use node Vserver.
 2979         node_vservers = self.list_vservers(vserver_type='node')
 2980         if node_vservers:
 2981             return node_vservers[0]
 2982 
 2983         raise exception.NotFound("No Vserver found to receive EMS messages.")
 2984 
 2985     @na_utils.trace
 2986     def send_ems_log_message(self, message_dict):
 2987         """Sends a message to the Data ONTAP EMS log."""
 2988 
 2989         # NOTE(cknight): Cannot use deepcopy on the connection context
 2990         node_client = copy.copy(self)
 2991         node_client.connection = copy.copy(self.connection)
 2992         node_client.connection.set_timeout(25)
 2993 
 2994         try:
 2995             node_client.set_vserver(self._get_ems_log_destination_vserver())
 2996             node_client.send_request('ems-autosupport-log', message_dict)
 2997             LOG.debug('EMS executed successfully.')
 2998         except netapp_api.NaApiError as e:
 2999             LOG.warning('Failed to invoke EMS. %s', e)
 3000 
 3001     @na_utils.trace
 3002     def get_aggregate(self, aggregate_name):
 3003         """Get aggregate attributes needed for the storage service catalog."""
 3004 
 3005         if not aggregate_name:
 3006             return {}
 3007 
 3008         desired_attributes = {
 3009             'aggr-attributes': {
 3010                 'aggregate-name': None,
 3011                 'aggr-raid-attributes': {
 3012                     'raid-type': None,
 3013                     'is-hybrid': None,
 3014                 },
 3015             },
 3016         }
 3017 
 3018         try:
 3019             aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
 3020                                          desired_attributes=desired_attributes)
 3021         except netapp_api.NaApiError:
 3022             msg = _('Failed to get info for aggregate %s.')
 3023             LOG.exception(msg, aggregate_name)
 3024             return {}
 3025 
 3026         if len(aggrs) < 1:
 3027             return {}
 3028 
 3029         aggr_attributes = aggrs[0]
 3030         aggr_raid_attrs = aggr_attributes.get_child_by_name(
 3031             'aggr-raid-attributes') or netapp_api.NaElement('none')
 3032 
 3033         aggregate = {
 3034             'name': aggr_attributes.get_child_content('aggregate-name'),
 3035             'raid-type': aggr_raid_attrs.get_child_content('raid-type'),
 3036             'is-hybrid': strutils.bool_from_string(
 3037                 aggr_raid_attrs.get_child_content('is-hybrid')),
 3038         }
 3039 
 3040         return aggregate
 3041 
 3042     @na_utils.trace
 3043     def get_aggregate_disk_types(self, aggregate_name):
 3044         """Get the disk type(s) of an aggregate."""
 3045 
 3046         disk_types = set()
 3047         disk_types.update(self._get_aggregate_disk_types(aggregate_name))
 3048         if self.features.ADVANCED_DISK_PARTITIONING:
 3049             disk_types.update(self._get_aggregate_disk_types(aggregate_name,
 3050                                                              shared=True))
 3051 
 3052         return list(disk_types) if disk_types else None
 3053 
 3054     @na_utils.trace
 3055     def _get_aggregate_disk_types(self, aggregate_name, shared=False):
 3056         """Get the disk type(s) of an aggregate."""
 3057 
 3058         disk_types = set()
 3059 
 3060         if shared:
 3061             disk_raid_info = {
 3062                 'disk-shared-info': {
 3063                     'aggregate-list': {
 3064                         'shared-aggregate-info': {
 3065                             'aggregate-name': aggregate_name,
 3066                         },
 3067                     },
 3068                 },
 3069             }
 3070         else:
 3071             disk_raid_info = {
 3072                 'disk-aggregate-info': {
 3073                     'aggregate-name': aggregate_name,
 3074                 },
 3075             }
 3076 
 3077         api_args = {
 3078             'query': {
 3079                 'storage-disk-info': {
 3080                     'disk-raid-info': disk_raid_info,
 3081                 },
 3082             },
 3083             'desired-attributes': {
 3084                 'storage-disk-info': {
 3085                     'disk-raid-info': {
 3086                         'effective-disk-type': None,
 3087                     },
 3088                 },
 3089             },
 3090         }
 3091 
 3092         try:
 3093             result = self.send_iter_request('storage-disk-get-iter', api_args)
 3094         except netapp_api.NaApiError:
 3095             msg = _('Failed to get disk info for aggregate %s.')
 3096             LOG.exception(msg, aggregate_name)
 3097             return disk_types
 3098 
 3099         attributes_list = result.get_child_by_name(
 3100             'attributes-list') or netapp_api.NaElement('none')
 3101 
 3102         for storage_disk_info in attributes_list.get_children():
 3103 
 3104                 disk_raid_info = storage_disk_info.get_child_by_name(
 3105                     'disk-raid-info') or netapp_api.NaElement('none')
 3106                 disk_type = disk_raid_info.get_child_content(
 3107                     'effective-disk-type')
 3108                 if disk_type:
 3109                     disk_types.add(disk_type)
 3110 
 3111         return disk_types
 3112 
 3113     @na_utils.trace
 3114     def check_for_cluster_credentials(self):
 3115         try:
 3116             self.list_cluster_nodes()
 3117             # API succeeded, so definitely a cluster management LIF
 3118             return True
 3119         except netapp_api.NaApiError as e:
 3120             if e.code == netapp_api.EAPINOTFOUND:
 3121                 LOG.debug('Not connected to cluster management LIF.')
 3122                 return False
 3123             else:
 3124                 raise
 3125 
 3126     @na_utils.trace
 3127     def create_cluster_peer(self, addresses, username=None, password=None,
 3128                             passphrase=None):
 3129         """Creates a cluster peer relationship."""
 3130 
 3131         api_args = {
 3132             'peer-addresses': [
 3133                 {'remote-inet-address': address} for address in addresses
 3134             ],
 3135         }
 3136         if username:
 3137             api_args['user-name'] = username
 3138         if password:
 3139             api_args['password'] = password
 3140         if passphrase:
 3141             api_args['passphrase'] = passphrase
 3142 
 3143         self.send_request('cluster-peer-create', api_args)
 3144 
 3145     @na_utils.trace
 3146     def get_cluster_peers(self, remote_cluster_name=None):
 3147         """Gets one or more cluster peer relationships."""
 3148 
 3149         api_args = {}
 3150         if remote_cluster_name:
 3151             api_args['query'] = {
 3152                 'cluster-peer-info': {
 3153                     'remote-cluster-name': remote_cluster_name,
 3154                 }
 3155             }
 3156 
 3157         result = self.send_iter_request('cluster-peer-get-iter', api_args)
 3158         if not self._has_records(result):
 3159             return []
 3160 
 3161         cluster_peers = []
 3162 
 3163         for cluster_peer_info in result.get_child_by_name(
 3164                 'attributes-list').get_children():
 3165 
 3166             cluster_peer = {
 3167                 'active-addresses': [],
 3168                 'peer-addresses': []
 3169             }
 3170 
 3171             active_addresses = cluster_peer_info.get_child_by_name(
 3172                 'active-addresses') or netapp_api.NaElement('none')
 3173             for address in active_addresses.get_children():
 3174                 cluster_peer['active-addresses'].append(address.get_content())
 3175 
 3176             peer_addresses = cluster_peer_info.get_child_by_name(
 3177                 'peer-addresses') or netapp_api.NaElement('none')
 3178             for address in peer_addresses.get_children():
 3179                 cluster_peer['peer-addresses'].append(address.get_content())
 3180 
 3181             cluster_peer['availability'] = cluster_peer_info.get_child_content(
 3182                 'availability')
 3183             cluster_peer['cluster-name'] = cluster_peer_info.get_child_content(
 3184                 'cluster-name')
 3185             cluster_peer['cluster-uuid'] = cluster_peer_info.get_child_content(
 3186                 'cluster-uuid')
 3187             cluster_peer['remote-cluster-name'] = (
 3188                 cluster_peer_info.get_child_content('remote-cluster-name'))
 3189             cluster_peer['serial-number'] = (
 3190                 cluster_peer_info.get_child_content('serial-number'))
 3191             cluster_peer['timeout'] = cluster_peer_info.get_child_content(
 3192                 'timeout')
 3193 
 3194             cluster_peers.append(cluster_peer)
 3195 
 3196         return cluster_peers
 3197 
 3198     @na_utils.trace
 3199     def delete_cluster_peer(self, cluster_name):
 3200         """Deletes a cluster peer relationship."""
 3201 
 3202         api_args = {'cluster-name': cluster_name}
 3203         self.send_request('cluster-peer-delete', api_args)
 3204 
 3205     @na_utils.trace
 3206     def get_cluster_peer_policy(self):
 3207         """Gets the cluster peering policy configuration."""
 3208 
 3209         if not self.features.CLUSTER_PEER_POLICY:
 3210             return {}
 3211 
 3212         result = self.send_request('cluster-peer-policy-get')
 3213 
 3214         attributes = result.get_child_by_name(
 3215             'attributes') or netapp_api.NaElement('none')
 3216         cluster_peer_policy = attributes.get_child_by_name(
 3217             'cluster-peer-policy') or netapp_api.NaElement('none')
 3218 
 3219         policy = {
 3220             'is-unauthenticated-access-permitted':
 3221             cluster_peer_policy.get_child_content(
 3222                 'is-unauthenticated-access-permitted'),
 3223             'passphrase-minimum-length':
 3224             cluster_peer_policy.get_child_content(
 3225                 'passphrase-minimum-length'),
 3226         }
 3227 
 3228         if policy['is-unauthenticated-access-permitted'] is not None:
 3229             policy['is-unauthenticated-access-permitted'] = (
 3230                 strutils.bool_from_string(
 3231                     policy['is-unauthenticated-access-permitted']))
 3232         if policy['passphrase-minimum-length'] is not None:
 3233             policy['passphrase-minimum-length'] = int(
 3234                 policy['passphrase-minimum-length'])
 3235 
 3236         return policy
 3237 
 3238     @na_utils.trace
 3239     def set_cluster_peer_policy(self, is_unauthenticated_access_permitted=None,
 3240                                 passphrase_minimum_length=None):
 3241         """Modifies the cluster peering policy configuration."""
 3242 
 3243         if not self.features.CLUSTER_PEER_POLICY:
 3244             return
 3245 
 3246         if (is_unauthenticated_access_permitted is None and
 3247                 passphrase_minimum_length is None):
 3248             return
 3249 
 3250         api_args = {}
 3251         if is_unauthenticated_access_permitted is not None:
 3252             api_args['is-unauthenticated-access-permitted'] = (
 3253                 'true' if strutils.bool_from_string(
 3254                     is_unauthenticated_access_permitted) else 'false')
 3255         if passphrase_minimum_length is not None:
 3256             api_args['passphrase-minlength'] = six.text_type(
 3257                 passphrase_minimum_length)
 3258 
 3259         self.send_request('cluster-peer-policy-modify', api_args)
 3260 
 3261     @na_utils.trace
 3262     def create_vserver_peer(self, vserver_name, peer_vserver_name):
 3263         """Creates a Vserver peer relationship for SnapMirrors."""
 3264         api_args = {
 3265             'vserver': vserver_name,
 3266             'peer-vserver': peer_vserver_name,
 3267             'applications': [
 3268                 {'vserver-peer-application': 'snapmirror'},
 3269             ],
 3270         }
 3271         self.send_request('vserver-peer-create', api_args)
 3272 
 3273     @na_utils.trace
 3274     def delete_vserver_peer(self, vserver_name, peer_vserver_name):
 3275         """Deletes a Vserver peer relationship."""
 3276 
 3277         api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
 3278         self.send_request('vserver-peer-delete', api_args)
 3279 
 3280     @na_utils.trace
 3281     def accept_vserver_peer(self, vserver_name, peer_vserver_name):
 3282         """Accepts a pending Vserver peer relationship."""
 3283 
 3284         api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
 3285         self.send_request('vserver-peer-accept', api_args)
 3286 
 3287     @na_utils.trace
 3288     def get_vserver_peers(self, vserver_name=None, peer_vserver_name=None):
 3289         """Gets one or more Vserver peer relationships."""
 3290 
 3291         api_args = None
 3292         if vserver_name or peer_vserver_name:
 3293             api_args = {'query': {'vserver-peer-info': {}}}
 3294             if vserver_name:
 3295                 api_args['query']['vserver-peer-info']['vserver'] = (
 3296                     vserver_name)
 3297             if peer_vserver_name:
 3298                 api_args['query']['vserver-peer-info']['peer-vserver'] = (
 3299                     peer_vserver_name)
 3300 
 3301         result = self.send_iter_request('vserver-peer-get-iter', api_args)
 3302         if not self._has_records(result):
 3303             return []
 3304 
 3305         vserver_peers = []
 3306 
 3307         for vserver_peer_info in result.get_child_by_name(
 3308                 'attributes-list').get_children():
 3309 
 3310             vserver_peer = {
 3311                 'vserver': vserver_peer_info.get_child_content('vserver'),
 3312                 'peer-vserver':
 3313                 vserver_peer_info.get_child_content('peer-vserver'),
 3314                 'peer-state':
 3315                 vserver_peer_info.get_child_content('peer-state'),
 3316                 'peer-cluster':
 3317                 vserver_peer_info.get_child_content('peer-cluster'),
 3318             }
 3319             vserver_peers.append(vserver_peer)
 3320 
 3321         return vserver_peers
 3322 
 3323     def _ensure_snapmirror_v2(self):
 3324         """Verify support for SnapMirror control plane v2."""
 3325         if not self.features.SNAPMIRROR_V2:
 3326             msg = _('SnapMirror features require Data ONTAP 8.2 or later.')
 3327             raise exception.NetAppException(msg)
 3328 
 3329     @na_utils.trace
 3330     def create_snapmirror(self, source_vserver, source_volume,
 3331                           destination_vserver, destination_volume,
 3332                           schedule=None, policy=None,
 3333                           relationship_type='data_protection'):
 3334         """Creates a SnapMirror relationship (cDOT 8.2 or later only)."""
 3335         self._ensure_snapmirror_v2()
 3336 
 3337         api_args = {
 3338             'source-volume': source_volume,
 3339             'source-vserver': source_vserver,
 3340             'destination-volume': destination_volume,
 3341             'destination-vserver': destination_vserver,
 3342             'relationship-type': relationship_type,
 3343         }
 3344         if schedule:
 3345             api_args['schedule'] = schedule
 3346         if policy:
 3347             api_args['policy'] = policy
 3348 
 3349         try:
 3350             self.send_request('snapmirror-create', api_args)
 3351         except netapp_api.NaApiError as e:
 3352             if e.code != netapp_api.ERELATION_EXISTS:
 3353                 raise
 3354 
 3355     @na_utils.trace
 3356     def initialize_snapmirror(self, source_vserver, source_volume,
 3357                               destination_vserver, destination_volume,
 3358                               source_snapshot=None, transfer_priority=None):
 3359         """Initializes a SnapMirror relationship (cDOT 8.2 or later only)."""
 3360         self._ensure_snapmirror_v2()
 3361 
 3362         api_args = {
 3363             'source-volume': source_volume,
 3364             'source-vserver': source_vserver,
 3365             'destination-volume': destination_volume,
 3366             'destination-vserver': destination_vserver,
 3367         }
 3368         if source_snapshot:
 3369             api_args['source-snapshot'] = source_snapshot
 3370         if transfer_priority:
 3371             api_args['transfer-priority'] = transfer_priority
 3372 
 3373         result = self.send_request('snapmirror-initialize', api_args)
 3374 
 3375         result_info = {}
 3376         result_info['operation-id'] = result.get_child_content(
 3377             'result-operation-id')
 3378         result_info['status'] = result.get_child_content('result-status')
 3379         result_info['jobid'] = result.get_child_content('result-jobid')
 3380         result_info['error-code'] = result.get_child_content(
 3381             'result-error-code')
 3382         result_info['error-message'] = result.get_child_content(
 3383             'result-error-message')
 3384 
 3385         return result_info
 3386 
 3387     @na_utils.trace
 3388     def release_snapmirror(self, source_vserver, source_volume,
 3389                            destination_vserver, destination_volume,
 3390                            relationship_info_only=False):
 3391         """Removes a SnapMirror relationship on the source endpoint."""
 3392         self._ensure_snapmirror_v2()
 3393 
 3394         api_args = {
 3395             'query': {
 3396                 'snapmirror-destination-info': {
 3397                     'source-volume': source_volume,
 3398                     'source-vserver': source_vserver,
 3399                     'destination-volume': destination_volume,
 3400                     'destination-vserver': destination_vserver,
 3401                     'relationship-info-only': ('true' if relationship_info_only
 3402                                                else 'false'),
 3403                 }
 3404             }
 3405         }
 3406         self.send_request('snapmirror-release-iter', api_args)
 3407 
 3408     @na_utils.trace
 3409     def quiesce_snapmirror(self, source_vserver, source_volume,
 3410                            destination_vserver, destination_volume):
 3411         """Disables future transfers to a SnapMirror destination."""
 3412         self._ensure_snapmirror_v2()
 3413 
 3414         api_args = {
 3415             'source-volume': source_volume,
 3416             'source-vserver': source_vserver,
 3417             'destination-volume': destination_volume,
 3418             'destination-vserver': destination_vserver,
 3419         }
 3420         self.send_request('snapmirror-quiesce', api_args)
 3421 
 3422     @na_utils.trace
 3423     def abort_snapmirror(self, source_vserver, source_volume,
 3424                          destination_vserver, destination_volume,
 3425                          clear_checkpoint=False):
 3426         """Stops ongoing transfers for a SnapMirror relationship."""
 3427         self._ensure_snapmirror_v2()
 3428 
 3429         api_args = {
 3430             'source-volume': source_volume,
 3431             'source-vserver': source_vserver,
 3432             'destination-volume': destination_volume,
 3433             'destination-vserver': destination_vserver,
 3434             'clear-checkpoint': 'true' if clear_checkpoint else 'false',
 3435         }
 3436         try:
 3437             self.send_request('snapmirror-abort', api_args)
 3438         except netapp_api.NaApiError as e:
 3439             if e.code != netapp_api.ENOTRANSFER_IN_PROGRESS:
 3440                 raise
 3441 
 3442     @na_utils.trace
 3443     def break_snapmirror(self, source_vserver, source_volume,
 3444                          destination_vserver, destination_volume):
 3445         """Breaks a data protection SnapMirror relationship."""
 3446         self._ensure_snapmirror_v2()
 3447 
 3448         api_args = {
 3449             'source-volume': source_volume,
 3450             'source-vserver': source_vserver,
 3451             'destination-volume': destination_volume,
 3452             'destination-vserver': destination_vserver,
 3453         }
 3454         self.send_request('snapmirror-break', api_args)
 3455 
 3456     @na_utils.trace
 3457     def modify_snapmirror(self, source_vserver, source_volume,
 3458                           destination_vserver, destination_volume,
 3459                           schedule=None, policy=None, tries=None,
 3460                           max_transfer_rate=None):
 3461         """Modifies a SnapMirror relationship."""
 3462         self._ensure_snapmirror_v2()
 3463 
 3464         api_args = {
 3465             'source-volume': source_volume,
 3466             'source-vserver': source_vserver,
 3467             'destination-volume': destination_volume,
 3468             'destination-vserver': destination_vserver,
 3469         }
 3470         if schedule:
 3471             api_args['schedule'] = schedule
 3472         if policy:
 3473             api_args['policy'] = policy
 3474         if tries is not None:
 3475             api_args['tries'] = tries
 3476         if max_transfer_rate is not None:
 3477             api_args['max-transfer-rate'] = max_transfer_rate
 3478 
 3479         self.send_request('snapmirror-modify', api_args)
 3480 
 3481     @na_utils.trace
 3482     def delete_snapmirror(self, source_vserver, source_volume,
 3483                           destination_vserver, destination_volume):
 3484         """Destroys a SnapMirror relationship."""
 3485         self._ensure_snapmirror_v2()
 3486 
 3487         api_args = {
 3488             'query': {
 3489                 'snapmirror-info': {
 3490                     'source-volume': source_volume,
 3491                     'source-vserver': source_vserver,
 3492                     'destination-volume': destination_volume,
 3493                     'destination-vserver': destination_vserver,
 3494                 }
 3495             }
 3496         }
 3497         self.send_request('snapmirror-destroy-iter', api_args)
 3498 
 3499     @na_utils.trace
 3500     def update_snapmirror(self, source_vserver, source_volume,
 3501                           destination_vserver, destination_volume):
 3502         """Schedules a snapmirror update."""
 3503         self._ensure_snapmirror_v2()
 3504 
 3505         api_args = {
 3506             'source-volume': source_volume,
 3507             'source-vserver': source_vserver,
 3508             'destination-volume': destination_volume,
 3509             'destination-vserver': destination_vserver,
 3510         }
 3511         try:
 3512             self.send_request('snapmirror-update', api_args)
 3513         except netapp_api.NaApiError as e:
 3514             if (e.code != netapp_api.ETRANSFER_IN_PROGRESS and
 3515                     e.code != netapp_api.EANOTHER_OP_ACTIVE):
 3516                 raise
 3517 
 3518     @na_utils.trace
 3519     def resume_snapmirror(self, source_vserver, source_volume,
 3520                           destination_vserver, destination_volume):
 3521         """Resume a SnapMirror relationship if it is quiesced."""
 3522         self._ensure_snapmirror_v2()
 3523 
 3524         api_args = {
 3525             'source-volume': source_volume,
 3526             'source-vserver': source_vserver,
 3527             'destination-volume': destination_volume,
 3528             'destination-vserver': destination_vserver,
 3529         }
 3530         try:
 3531             self.send_request('snapmirror-resume', api_args)
 3532         except netapp_api.NaApiError as e:
 3533             if e.code != netapp_api.ERELATION_NOT_QUIESCED:
 3534                 raise
 3535 
 3536     @na_utils.trace
 3537     def resync_snapmirror(self, source_vserver, source_volume,
 3538                           destination_vserver, destination_volume):
 3539         """Resync a SnapMirror relationship."""
 3540         self._ensure_snapmirror_v2()
 3541 
 3542         api_args = {
 3543             'source-volume': source_volume,
 3544             'source-vserver': source_vserver,
 3545             'destination-volume': destination_volume,
 3546             'destination-vserver': destination_vserver,
 3547         }
 3548         self.send_request('snapmirror-resync', api_args)
 3549 
 3550     @na_utils.trace
 3551     def _get_snapmirrors(self, source_vserver=None, source_volume=None,
 3552                          destination_vserver=None, destination_volume=None,
 3553                          desired_attributes=None):
 3554 
 3555         query = None
 3556         if (source_vserver or source_volume or destination_vserver or
 3557                 destination_volume):
 3558             query = {'snapmirror-info': {}}
 3559             if source_volume:
 3560                 query['snapmirror-info']['source-volume'] = source_volume
 3561             if destination_volume:
 3562                 query['snapmirror-info']['destination-volume'] = (
 3563                     destination_volume)
 3564             if source_vserver:
 3565                 query['snapmirror-info']['source-vserver'] = source_vserver
 3566             if destination_vserver:
 3567                 query['snapmirror-info']['destination-vserver'] = (
 3568                     destination_vserver)
 3569 
 3570         api_args = {}
 3571         if query:
 3572             api_args['query'] = query
 3573         if desired_attributes:
 3574             api_args['desired-attributes'] = desired_attributes
 3575 
 3576         result = self.send_iter_request('snapmirror-get-iter', api_args)
 3577         if not self._has_records(result):
 3578             return []
 3579         else:
 3580             return result.get_child_by_name('attributes-list').get_children()
 3581 
 3582     @na_utils.trace
 3583     def get_snapmirrors(self, source_vserver, source_volume,
 3584                         destination_vserver, destination_volume,
 3585                         desired_attributes=None):
 3586         """Gets one or more SnapMirror relationships.
 3587 
 3588         Either the source or destination info may be omitted.
 3589         Desired attributes should be a flat list of attribute names.
 3590         """
 3591         self._ensure_snapmirror_v2()
 3592 
 3593         if desired_attributes is not None:
 3594             desired_attributes = {
 3595                 'snapmirror-info': {attr: None for attr in desired_attributes},
 3596             }
 3597 
 3598         result = self._get_snapmirrors(
 3599             source_vserver=source_vserver,
 3600             source_volume=source_volume,
 3601             destination_vserver=destination_vserver,
 3602             destination_volume=destination_volume,
 3603             desired_attributes=desired_attributes)
 3604 
 3605         snapmirrors = []
 3606 
 3607         for snapmirror_info in result:
 3608             snapmirror = {}
 3609             for child in snapmirror_info.get_children():
 3610                 name = self._strip_xml_namespace(child.get_name())
 3611                 snapmirror[name] = child.get_content()
 3612             snapmirrors.append(snapmirror)
 3613 
 3614         return snapmirrors
 3615 
 3616     def volume_has_snapmirror_relationships(self, volume):
 3617         """Return True if snapmirror relationships exist for a given volume.
 3618 
 3619         If we have snapmirror control plane license, we can verify whether
 3620         the given volume is part of any snapmirror relationships.
 3621         """
 3622         try:
 3623             # Check if volume is a source snapmirror volume
 3624             snapmirrors = self.get_snapmirrors(
 3625                 volume['owning-vserver-name'], volume['name'], None, None)
 3626             # Check if volume is a destination snapmirror volume
 3627             if not snapmirrors:
 3628                 snapmirrors = self.get_snapmirrors(
 3629                     None, None, volume['owning-vserver-name'], volume['name'])
 3630 
 3631             has_snapmirrors = len(snapmirrors) > 0
 3632         except netapp_api.NaApiError:
 3633             msg = ("Could not determine if volume %s is part of "
 3634                    "existing snapmirror relationships.")
 3635             LOG.exception(msg, volume['name'])
 3636             has_snapmirrors = False
 3637 
 3638         return has_snapmirrors
 3639 
 3640     def list_snapmirror_snapshots(self, volume_name, newer_than=None):
 3641         """Gets SnapMirror snapshots on a volume."""
 3642         api_args = {
 3643             'query': {
 3644                 'snapshot-info': {
 3645                     'dependency': 'snapmirror',
 3646                     'volume': volume_name,
 3647                 },
 3648             },
 3649         }
 3650         if newer_than:
 3651             api_args['query']['snapshot-info'][
 3652                 'access-time'] = '>' + newer_than
 3653 
 3654         result = self.send_iter_request('snapshot-get-iter', api_args)
 3655 
 3656         attributes_list = result.get_child_by_name(
 3657             'attributes-list') or netapp_api.NaElement('none')
 3658 
 3659         return [snapshot_info.get_child_content('name')
 3660                 for snapshot_info in attributes_list.get_children()]
 3661 
 3662     @na_utils.trace
 3663     def start_volume_move(self, volume_name, vserver, destination_aggregate,
 3664                           cutover_action='wait', encrypt_destination=None):
 3665         """Moves a FlexVol across Vserver aggregates.
 3666 
 3667         Requires cluster-scoped credentials.
 3668         """
 3669         self._send_volume_move_request(
 3670             volume_name, vserver,
 3671             destination_aggregate,
 3672             cutover_action=cutover_action,
 3673             encrypt_destination=encrypt_destination)
 3674 
 3675     @na_utils.trace
 3676     def check_volume_move(self, volume_name, vserver, destination_aggregate,
 3677                           encrypt_destination=None):
 3678         """Moves a FlexVol across Vserver aggregates.
 3679 
 3680         Requires cluster-scoped credentials.
 3681         """
 3682         self._send_volume_move_request(
 3683             volume_name,
 3684             vserver,
 3685             destination_aggregate,
 3686             validation_only=True,
 3687             encrypt_destination=encrypt_destination)
 3688 
 3689     @na_utils.trace
 3690     def _send_volume_move_request(self, volume_name, vserver,
 3691                                   destination_aggregate,
 3692                                   cutover_action='wait',
 3693                                   validation_only=False,
 3694                                   encrypt_destination=None):
 3695         """Send request to check if vol move is possible, or start it.
 3696 
 3697         :param volume_name: Name of the FlexVol to be moved.
 3698         :param destination_aggregate: Name of the destination aggregate
 3699         :param cutover_action: can have one of ['force', 'defer', 'abort',
 3700             'wait']. 'force' will force a cutover despite errors (causing
 3701             possible client disruptions), 'wait' will wait for cutover to be
 3702             triggered manually. 'abort' will rollback move on errors on
 3703             cutover, 'defer' will attempt a cutover, but wait for manual
 3704             intervention in case of errors.
 3705         :param validation_only: If set to True, only validates if the volume
 3706             move is possible, does not trigger data copy.
 3707         :param encrypt_destination: If set to True, it encrypts the Flexvol
 3708             after the volume move is complete.
 3709         """
 3710         api_args = {
 3711             'source-volume': volume_name,
 3712             'vserver': vserver,
 3713             'dest-aggr': destination_aggregate,
 3714             'cutover-action': CUTOVER_ACTION_MAP[cutover_action],
 3715         }
 3716 
 3717         if self.features.FLEXVOL_ENCRYPTION and encrypt_destination:
 3718             api_args['encrypt-destination'] = 'true'
 3719         elif encrypt_destination:
 3720             msg = 'Flexvol encryption is not supported on this backend.'
 3721             raise exception.NetAppException(msg)
 3722         else:
 3723             api_args['encrypt-destination'] = 'false'
 3724 
 3725         if validation_only:
 3726             api_args['perform-validation-only'] = 'true'
 3727         self.send_request('volume-move-start', api_args)
 3728 
 3729     @na_utils.trace
 3730     def abort_volume_move(self, volume_name, vserver):
 3731         """Aborts an existing volume move operation."""
 3732         api_args = {
 3733             'source-volume': volume_name,
 3734             'vserver': vserver,
 3735         }
 3736         self.send_request('volume-move-trigger-abort', api_args)
 3737 
 3738     @na_utils.trace
 3739     def trigger_volume_move_cutover(self, volume_name, vserver, force=True):
 3740         """Triggers the cut-over for a volume in data motion."""
 3741         api_args = {
 3742             'source-volume': volume_name,
 3743             'vserver': vserver,
 3744             'force': 'true' if force else 'false',
 3745         }
 3746         self.send_request('volume-move-trigger-cutover', api_args)
 3747 
 3748     @na_utils.trace
 3749     def get_volume_move_status(self, volume_name, vserver):
 3750         """Gets the current state of a volume move operation."""
 3751         api_args = {
 3752             'query': {
 3753                 'volume-move-info': {
 3754                     'volume': volume_name,
 3755                     'vserver': vserver,
 3756                 },
 3757             },
 3758             'desired-attributes': {
 3759                 'volume-move-info': {
 3760                     'percent-complete': None,
 3761                     'estimated-completion-time': None,
 3762                     'state': None,
 3763                     'details': None,
 3764                     'cutover-action': None,
 3765                     'phase': None,
 3766                 },
 3767             },
 3768         }
 3769         result = self.send_iter_request('volume-move-get-iter', api_args)
 3770 
 3771         if not self._has_records(result):
 3772             msg = _("Volume %(vol)s in Vserver %(server)s is not part of any "
 3773                     "data motion operations.")
 3774             msg_args = {'vol': volume_name, 'server': vserver}
 3775             raise exception.NetAppException(msg % msg_args)
 3776 
 3777         attributes_list = result.get_child_by_name(
 3778             'attributes-list') or netapp_api.NaElement('none')
 3779         volume_move_info = attributes_list.get_child_by_name(
 3780             'volume-move-info') or netapp_api.NaElement('none')
 3781 
 3782         status_info = {
 3783             'percent-complete': volume_move_info.get_child_content(
 3784                 'percent-complete'),
 3785             'estimated-completion-time': volume_move_info.get_child_content(
 3786                 'estimated-completion-time'),
 3787             'state': volume_move_info.get_child_content('state'),
 3788             'details': volume_move_info.get_child_content('details'),
 3789             'cutover-action': volume_move_info.get_child_content(
 3790                 'cutover-action'),
 3791             'phase': volume_move_info.get_child_content('phase'),
 3792         }
 3793         return status_info
 3794 
 3795     @na_utils.trace
 3796     def qos_policy_group_exists(self, qos_policy_group_name):
 3797         """Checks if a QoS policy group exists."""
 3798         try:
 3799             self.qos_policy_group_get(qos_policy_group_name)
 3800         except exception.NetAppException:
 3801             return False
 3802         return True
 3803 
 3804     @na_utils.trace
 3805     def qos_policy_group_get(self, qos_policy_group_name):
 3806         """Checks if a QoS policy group exists."""
 3807         api_args = {
 3808             'query': {
 3809                 'qos-policy-group-info': {
 3810                     'policy-group': qos_policy_group_name,
 3811                 },
 3812             },
 3813             'desired-attributes': {
 3814                 'qos-policy-group-info': {
 3815                     'policy-group': None,
 3816                     'vserver': None,
 3817                     'max-throughput': None,
 3818                     'num-workloads': None
 3819                 },
 3820             },
 3821         }
 3822 
 3823         try:
 3824             result = self.send_request('qos-policy-group-get-iter',
 3825                                        api_args,
 3826                                        False)
 3827         except netapp_api.NaApiError as e:
 3828             if e.code == netapp_api.EAPINOTFOUND:
 3829                 msg = _("Configured ONTAP login user cannot retrieve "
 3830                         "QoS policies.")
 3831                 LOG.error(msg)
 3832                 raise exception.NetAppException(msg)
 3833             else:
 3834                 raise
 3835         if not self._has_records(result):
 3836             msg = _("No QoS policy group found with name %s.")
 3837             raise exception.NetAppException(msg % qos_policy_group_name)
 3838 
 3839         attributes_list = result.get_child_by_name(
 3840             'attributes-list') or netapp_api.NaElement('none')
 3841 
 3842         qos_policy_group_info = attributes_list.get_child_by_name(
 3843             'qos-policy-group-info') or netapp_api.NaElement('none')
 3844 
 3845         policy_info = {
 3846             'policy-group': qos_policy_group_info.get_child_content(
 3847                 'policy-group'),
 3848             'vserver': qos_policy_group_info.get_child_content('vserver'),
 3849             'max-throughput': qos_policy_group_info.get_child_content(
 3850                 'max-throughput'),
 3851             'num-workloads': int(qos_policy_group_info.get_child_content(
 3852                 'num-workloads')),
 3853         }
 3854         return policy_info
 3855 
 3856     @na_utils.trace
 3857     def qos_policy_group_create(self, qos_policy_group_name, vserver,
 3858                                 max_throughput=None):
 3859         """Creates a QoS policy group."""
 3860         api_args = {
 3861             'policy-group': qos_policy_group_name,
 3862             'vserver': vserver,
 3863         }
 3864         if max_throughput:
 3865             api_args['max-throughput'] = max_throughput
 3866         return self.send_request('qos-policy-group-create', api_args, False)
 3867 
 3868     @na_utils.trace
 3869     def qos_policy_group_modify(self, qos_policy_group_name, max_throughput):
 3870         """Modifies a QoS policy group."""
 3871         api_args = {
 3872             'policy-group': qos_policy_group_name,
 3873             'max-throughput': max_throughput,
 3874         }
 3875         return self.send_request('qos-policy-group-modify', api_args, False)
 3876 
 3877     @na_utils.trace
 3878     def qos_policy_group_delete(self, qos_policy_group_name):
 3879         """Attempts to delete a QoS policy group."""
 3880         api_args = {'policy-group': qos_policy_group_name}
 3881         return self.send_request('qos-policy-group-delete', api_args, False)
 3882 
 3883     @na_utils.trace
 3884     def qos_policy_group_rename(self, qos_policy_group_name, new_name):
 3885         """Renames a QoS policy group."""
 3886         if qos_policy_group_name == new_name:
 3887             return
 3888         api_args = {
 3889             'policy-group-name': qos_policy_group_name,
 3890             'new-name': new_name,
 3891         }
 3892         return self.send_request('qos-policy-group-rename', api_args, False)
 3893 
 3894     @na_utils.trace
 3895     def mark_qos_policy_group_for_deletion(self, qos_policy_group_name):
 3896         """Soft delete backing QoS policy group for a manila share."""
 3897         # NOTE(gouthamr): ONTAP deletes storage objects asynchronously. As
 3898         # long as garbage collection hasn't occurred, assigned QoS policy may
 3899         # still be tagged "in use". So, we rename the QoS policy group using a
 3900         # specific pattern and later attempt on a best effort basis to
 3901         # delete any QoS policy groups matching that pattern.
 3902 
 3903         if self.qos_policy_group_exists(qos_policy_group_name):
 3904             new_name = DELETED_PREFIX + qos_policy_group_name
 3905             try:
 3906                 self.qos_policy_group_rename(qos_policy_group_name, new_name)
 3907             except netapp_api.NaApiError as ex:
 3908                 msg = ('Rename failure in cleanup of cDOT QoS policy '
 3909                        'group %(name)s: %(ex)s')
 3910                 msg_args = {'name': qos_policy_group_name, 'ex': ex}
 3911                 LOG.warning(msg, msg_args)
 3912             # Attempt to delete any QoS policies named "deleted_manila-*".
 3913             self.remove_unused_qos_policy_groups()
 3914 
 3915     @na_utils.trace
 3916     def remove_unused_qos_policy_groups(self):
 3917         """Deletes all QoS policy groups that are marked for deletion."""
 3918         api_args = {
 3919             'query': {
 3920                 'qos-policy-group-info': {
 3921                     'policy-group': '%s*' % DELETED_PREFIX,
 3922                 }
 3923             },
 3924             'max-records': 3500,
 3925             'continue-on-failure': 'true',
 3926             'return-success-list': 'false',
 3927             'return-failure-list': 'false',
 3928         }
 3929 
 3930         try:
 3931             self.send_request('qos-policy-group-delete-iter', api_args, False)
 3932         except netapp_api.NaApiError as ex:
 3933             msg = 'Could not delete QoS policy groups. Details: %(ex)s'
 3934             msg_args = {'ex': ex}
 3935             LOG.debug(msg, msg_args)
 3936 
 3937     @na_utils.trace
 3938     def get_net_options(self):
 3939         result = self.send_request('net-options-get', None, False)
 3940         options = result.get_child_by_name('net-options')
 3941         ipv6_enabled = False
 3942         ipv6_info = options.get_child_by_name('ipv6-options-info')
 3943         if ipv6_info:
 3944             ipv6_enabled = ipv6_info.get_child_content('enabled') == 'true'
 3945         return {
 3946             'ipv6-enabled': ipv6_enabled,
 3947         }