"Fossies" - the Fresh Open Source Software Archive

Member "manila-11.0.1/manila/share/drivers/netapp/dataontap/client/client_cmode.py" (1 Feb 2021, 179517 Bytes) of package /linux/misc/openstack/manila-11.0.1.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: 11.0.0_vs_11.0.1.

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