Coverage for manila/share/drivers/netapp/dataontap/client/client_cmode.py: 88%
3117 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
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.
19import copy
20import hashlib
21import re
22import time
24from oslo_log import log
25from oslo_utils import excutils
26from oslo_utils import netutils
27from oslo_utils import strutils
28from oslo_utils import units
29from oslo_utils import uuidutils
31from manila import exception
32from manila.i18n import _
33from manila.share.drivers.netapp.dataontap.client import api as netapp_api
34from manila.share.drivers.netapp.dataontap.client import client_base
35from manila.share.drivers.netapp import utils as na_utils
36from manila import utils as manila_utils
39LOG = log.getLogger(__name__)
40DELETED_PREFIX = 'deleted_manila_'
41DEFAULT_IPSPACE = 'Default'
42IPSPACE_PREFIX = 'ipspace_'
43CLUSTER_IPSPACES = ('Cluster', DEFAULT_IPSPACE)
44DEFAULT_BROADCAST_DOMAIN = 'Default'
45BROADCAST_DOMAIN_PREFIX = 'domain_'
46DEFAULT_MAX_PAGE_LENGTH = 50
47CUTOVER_ACTION_MAP = {
48 'defer': 'defer_on_failure',
49 'abort': 'abort_on_failure',
50 'force': 'force',
51 'wait': 'wait',
52}
55class NetAppCmodeClient(client_base.NetAppBaseClient):
57 def __init__(self, **kwargs):
58 super(NetAppCmodeClient, self).__init__(**kwargs)
59 self.vserver = kwargs.get('vserver')
60 self.connection.set_vserver(self.vserver)
62 # Default values to run first api.
63 self.connection.set_api_version(1, 15)
64 (major, minor) = self.get_ontapi_version(cached=False)
65 self.connection.set_api_version(major, minor)
66 system_version = self.get_system_version(cached=False)
67 self.connection.set_system_version(system_version)
69 self._init_features()
71 def _init_features(self):
72 """Initialize cDOT feature support map."""
73 super(NetAppCmodeClient, self)._init_features()
75 ontapi_version = self.get_ontapi_version(cached=True)
76 ontapi_1_20 = ontapi_version >= (1, 20)
77 ontapi_1_2x = (1, 20) <= ontapi_version < (1, 30)
78 ontapi_1_30 = ontapi_version >= (1, 30)
79 ontapi_1_100 = ontapi_version >= (1, 100)
80 ontapi_1_110 = ontapi_version >= (1, 110)
81 ontapi_1_120 = ontapi_version >= (1, 120)
82 ontapi_1_140 = ontapi_version >= (1, 140)
83 ontapi_1_150 = ontapi_version >= (1, 150)
84 ontapi_1_180 = ontapi_version >= (1, 180)
85 ontapi_1_191 = ontapi_version >= (1, 191)
86 ontap_9_10 = self.get_system_version()['version-tuple'] >= (9, 10, 0)
87 ontap_9_10_1 = self.get_system_version()['version-tuple'] >= (9, 10, 1)
88 ontap_9_11_1 = self.get_system_version()['version-tuple'] >= (9, 11, 1)
89 ontap_9_12_1 = self.get_system_version()['version-tuple'] >= (9, 12, 1)
91 self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
92 self.features.add_feature('SYSTEM_METRICS', supported=ontapi_1_2x)
93 self.features.add_feature('SYSTEM_CONSTITUENT_METRICS',
94 supported=ontapi_1_30)
95 self.features.add_feature('BROADCAST_DOMAINS', supported=ontapi_1_30)
96 self.features.add_feature('IPSPACES', supported=ontapi_1_30)
97 self.features.add_feature('SUBNETS', supported=ontapi_1_30)
98 self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
99 self.features.add_feature('ADVANCED_DISK_PARTITIONING',
100 supported=ontapi_1_30)
101 self.features.add_feature('KERBEROS_VSERVER', supported=ontapi_1_30)
102 self.features.add_feature('FLEXVOL_ENCRYPTION', supported=ontapi_1_110)
103 self.features.add_feature('SVM_DR', supported=ontapi_1_140)
104 self.features.add_feature('ADAPTIVE_QOS', supported=ontapi_1_140)
105 self.features.add_feature('TRANSFER_LIMIT_NFS_CONFIG',
106 supported=ontapi_1_140)
107 self.features.add_feature('CIFS_DC_ADD_SKIP_CHECK',
108 supported=ontapi_1_150)
109 self.features.add_feature('LDAP_LDAP_SERVERS',
110 supported=ontapi_1_120)
111 self.features.add_feature('FLEXGROUP', supported=ontapi_1_180)
112 self.features.add_feature('FLEXGROUP_FAN_OUT', supported=ontapi_1_191)
113 self.features.add_feature('SVM_MIGRATE', supported=ontap_9_10)
114 self.features.add_feature('SNAPLOCK', supported=ontapi_1_100)
115 self.features.add_feature('UNIFIED_AGGR', supported=ontap_9_10_1)
116 self.features.add_feature('DELETE_RETENTION_HOURS',
117 supported=ontap_9_11_1)
118 self.features.add_feature('AES_ENCRYPTION_TYPES',
119 supported=ontap_9_12_1)
121 def _invoke_vserver_api(self, na_element, vserver):
122 server = copy.copy(self.connection)
123 server.set_vserver(vserver)
124 result = server.invoke_successfully(na_element, True)
125 return result
127 def _has_records(self, api_result_element):
128 if (not api_result_element.get_child_content('num-records') or
129 api_result_element.get_child_content('num-records') == '0'):
130 return False
131 else:
132 return True
134 def _get_record_count(self, api_result_element):
135 try:
136 return int(api_result_element.get_child_content('num-records'))
137 except TypeError:
138 msg = _('Missing record count for NetApp iterator API invocation.')
139 raise exception.NetAppException(msg)
141 def set_vserver(self, vserver):
142 self.vserver = vserver
143 self.connection.set_vserver(vserver)
145 def send_iter_request(self, api_name, api_args=None,
146 max_page_length=DEFAULT_MAX_PAGE_LENGTH,
147 enable_tunneling=True):
148 """Invoke an iterator-style getter API."""
150 if not api_args:
151 api_args = {}
153 api_args['max-records'] = max_page_length
155 # Get first page
156 result = self.send_request(api_name, api_args,
157 enable_tunneling=enable_tunneling)
159 # Most commonly, we can just return here if there is no more data
160 next_tag = result.get_child_content('next-tag')
161 if not next_tag:
162 return result
164 # Ensure pagination data is valid and prepare to store remaining pages
165 num_records = self._get_record_count(result)
166 attributes_list = result.get_child_by_name('attributes-list')
167 if not attributes_list:
168 msg = _('Missing attributes list for API %s.') % api_name
169 raise exception.NetAppException(msg)
171 # Get remaining pages, saving data into first page
172 while next_tag is not None:
173 next_api_args = copy.deepcopy(api_args)
174 next_api_args['tag'] = next_tag
175 next_result = self.send_request(api_name, next_api_args,
176 enable_tunneling=enable_tunneling)
178 next_attributes_list = next_result.get_child_by_name(
179 'attributes-list') or netapp_api.NaElement('none')
181 for record in next_attributes_list.get_children():
182 attributes_list.add_child_elem(record)
184 num_records += self._get_record_count(next_result)
185 next_tag = next_result.get_child_content('next-tag')
187 result.get_child_by_name('num-records').set_content(
188 str(num_records))
189 result.get_child_by_name('next-tag').set_content('')
190 return result
192 @na_utils.trace
193 def create_vserver(self, vserver_name, root_volume_aggregate_name,
194 root_volume_name, aggregate_names, ipspace_name,
195 security_cert_expire_days, delete_retention_hours,
196 logical_space_reporting):
197 """Creates new vserver and assigns aggregates."""
198 self._create_vserver(
199 vserver_name, aggregate_names, ipspace_name,
200 delete_retention_hours,
201 root_volume_name=root_volume_name,
202 root_volume_aggregate_name=root_volume_aggregate_name,
203 root_volume_security_style='unix',
204 name_server_switch='file',
205 logical_space_reporting=logical_space_reporting)
206 self._modify_security_cert(vserver_name, security_cert_expire_days)
208 @na_utils.trace
209 def create_vserver_dp_destination(self, vserver_name, aggregate_names,
210 ipspace_name, delete_retention_hours,
211 logical_space_reporting):
212 """Creates new 'dp_destination' vserver and assigns aggregates."""
213 self._create_vserver(
214 vserver_name, aggregate_names, ipspace_name,
215 delete_retention_hours, subtype='dp_destination',
216 logical_space_reporting=logical_space_reporting)
218 @na_utils.trace
219 def _create_vserver(self, vserver_name, aggregate_names, ipspace_name,
220 delete_retention_hours,
221 root_volume_name=None, root_volume_aggregate_name=None,
222 root_volume_security_style=None,
223 name_server_switch=None, subtype=None,
224 logical_space_reporting=False):
225 """Creates new vserver and assigns aggregates."""
226 create_args = {
227 'vserver-name': vserver_name,
228 }
229 if root_volume_name:
230 create_args['root-volume'] = root_volume_name
231 if root_volume_aggregate_name:
232 create_args['root-volume-aggregate'] = root_volume_aggregate_name
233 if root_volume_security_style:
234 create_args['root-volume-security-style'] = (
235 root_volume_security_style)
236 if name_server_switch:
237 create_args['name-server-switch'] = {
238 'nsswitch': name_server_switch}
239 if subtype:
240 create_args['vserver-subtype'] = subtype
242 if ipspace_name:
243 if not self.features.IPSPACES:
244 msg = 'IPSpaces are not supported on this backend.'
245 raise exception.NetAppException(msg)
246 else:
247 create_args['ipspace'] = ipspace_name
249 create_args['is-space-reporting-logical'] = (
250 'true' if logical_space_reporting else 'false')
251 create_args['is-space-enforcement-logical'] = (
252 'true' if logical_space_reporting else 'false')
254 LOG.debug('Creating Vserver %(vserver)s with create args '
255 '%(args)s', {'vserver': vserver_name, 'args': create_args})
257 self.send_request('vserver-create', create_args)
259 aggr_list = [{'aggr-name': aggr_name} for aggr_name in aggregate_names]
260 modify_args = {
261 'aggr-list': aggr_list,
262 'vserver-name': vserver_name,
263 }
264 if self.features.DELETE_RETENTION_HOURS: 264 ↛ 268line 264 didn't jump to line 268 because the condition on line 264 was always true
265 modify_args.update(
266 {'volume-delete-retention-hours': str(delete_retention_hours)})
268 self.send_request('vserver-modify', modify_args)
270 @na_utils.trace
271 def _modify_security_cert(self, vserver_name, security_cert_expire_days):
272 """Create new security certificate with given expire days."""
274 # Do not modify security certificate if specified expire days are
275 # equal to default security certificate expire days i.e. 365.
276 if security_cert_expire_days == 365: 276 ↛ 277line 276 didn't jump to line 277 because the condition on line 276 was never true
277 return
279 api_args = {
280 'query': {
281 'certificate-info': {
282 'vserver': vserver_name,
283 'common-name': vserver_name,
284 'certificate-authority': vserver_name,
285 'type': 'server',
286 },
287 },
288 'desired-attributes': {
289 'certificate-info': {
290 'serial-number': None,
291 },
292 },
293 }
294 result = self.send_iter_request('security-certificate-get-iter',
295 api_args)
296 try:
297 old_certificate_info_list = result.get_child_by_name(
298 'attributes-list')
299 except AttributeError:
300 LOG.warning('Could not retrieve certificate-info for vserver '
301 '%(server)s.', {'server': vserver_name})
302 return
304 old_serial_nums = []
305 for certificate_info in old_certificate_info_list.get_children():
306 serial_num = certificate_info.get_child_content('serial-number')
307 old_serial_nums.append(serial_num)
309 try:
310 create_args = {
311 'vserver': vserver_name,
312 'common-name': vserver_name,
313 'type': 'server',
314 'expire-days': security_cert_expire_days,
315 }
316 self.send_request('security-certificate-create', create_args)
317 except netapp_api.NaApiError as e:
318 LOG.warning("Failed to create new security certificate: %s - %s",
319 e.code, e.message)
320 return
322 api_args = {
323 'query': {
324 'certificate-info': {
325 'vserver': vserver_name,
326 'common-name': vserver_name,
327 'certificate-authority': vserver_name,
328 'type': 'server',
329 },
330 },
331 'desired-attributes': {
332 'certificate-info': {
333 'serial-number': None,
334 },
335 },
336 }
338 result = self.send_iter_request('security-certificate-get-iter',
339 api_args)
340 try:
341 new_certificate_info_list = result.get_child_by_name(
342 'attributes-list')
343 except AttributeError:
344 LOG.warning('Could not retrieve certificate-info for vserver '
345 '%(server)s.', {'server': vserver_name})
346 return
348 for certificate_info in new_certificate_info_list.get_children():
349 serial_num = certificate_info.get_child_content('serial-number')
350 if serial_num not in old_serial_nums: 350 ↛ 351line 350 didn't jump to line 351 because the condition on line 350 was never true
351 try:
352 ssl_modify_args = {
353 'certificate-authority': vserver_name,
354 'common-name': vserver_name,
355 'certificate-serial-number': serial_num,
356 'vserver': vserver_name,
357 'client-authentication-enabled': 'false',
358 'server-authentication-enabled': 'true',
359 }
360 self.send_request('security-ssl-modify', ssl_modify_args)
361 except netapp_api.NaApiError as e:
362 LOG.debug('Failed to modify SSL for security certificate '
363 'with serial number %s: %s - %s', serial_num,
364 e.code, e.message)
366 # Delete all old security certificates
367 for certificate_info in old_certificate_info_list.get_children():
368 serial_num = certificate_info.get_child_content('serial-number')
369 delete_args = {
370 'certificate-authority': vserver_name,
371 'common-name': vserver_name,
372 'serial-number': serial_num,
373 'type': 'server',
374 'vserver': vserver_name,
375 }
376 try:
377 self.send_request('security-certificate-delete', delete_args)
378 except netapp_api.NaApiError as e:
379 LOG.warning('Failed to delete security certificate with '
380 'serial number %s: %s - %s', serial_num, e.code,
381 e.message)
383 @na_utils.trace
384 def get_vserver_info(self, vserver_name):
385 """Retrieves Vserver info."""
386 LOG.debug('Retrieving Vserver %s information.', vserver_name)
388 api_args = {
389 'query': {
390 'vserver-info': {
391 'vserver-name': vserver_name,
392 },
393 },
394 'desired-attributes': {
395 'vserver-info': {
396 'vserver-name': None,
397 'vserver-subtype': None,
398 'state': None,
399 'operational-state': None,
400 },
401 },
402 }
403 result = self.send_iter_request('vserver-get-iter', api_args)
404 if not self._has_records(result):
405 return
406 try:
407 vserver_info = result.get_child_by_name(
408 'attributes-list').get_child_by_name(
409 'vserver-info')
410 vserver_subtype = vserver_info.get_child_content(
411 'vserver-subtype')
412 vserver_op_state = vserver_info.get_child_content(
413 'operational-state')
414 vserver_state = vserver_info.get_child_content('state')
415 except AttributeError:
416 msg = _('Could not retrieve vserver-info for %s.') % vserver_name
417 raise exception.NetAppException(msg)
419 vserver_info = {
420 'name': vserver_name,
421 'subtype': vserver_subtype,
422 'operational_state': vserver_op_state,
423 'state': vserver_state,
424 }
425 return vserver_info
427 @na_utils.trace
428 def vserver_exists(self, vserver_name):
429 """Checks if Vserver exists."""
430 LOG.debug('Checking if Vserver %s exists', vserver_name)
432 api_args = {
433 'query': {
434 'vserver-info': {
435 'vserver-name': vserver_name,
436 },
437 },
438 'desired-attributes': {
439 'vserver-info': {
440 'vserver-name': None,
441 },
442 },
443 }
444 try:
445 result = self.send_iter_request('vserver-get-iter', api_args,
446 enable_tunneling=False)
447 except netapp_api.NaApiError as e:
448 if e.code == netapp_api.EVSERVERNOTFOUND:
449 return False
450 else:
451 raise
452 return self._has_records(result)
454 @na_utils.trace
455 def get_vserver_root_volume_name(self, vserver_name):
456 """Get the root volume name of the vserver."""
457 api_args = {
458 'query': {
459 'vserver-info': {
460 'vserver-name': vserver_name,
461 },
462 },
463 'desired-attributes': {
464 'vserver-info': {
465 'root-volume': None,
466 },
467 },
468 }
469 vserver_info = self.send_iter_request('vserver-get-iter', api_args)
471 try:
472 root_volume_name = vserver_info.get_child_by_name(
473 'attributes-list').get_child_by_name(
474 'vserver-info').get_child_content('root-volume')
475 except AttributeError:
476 msg = _('Could not determine root volume name '
477 'for Vserver %s.') % vserver_name
478 raise exception.NetAppException(msg)
479 return root_volume_name
481 @na_utils.trace
482 def get_vserver_ipspace(self, vserver_name):
483 """Get the IPspace of the vserver, or None if not supported."""
484 if not self.features.IPSPACES:
485 return None
487 api_args = {
488 'query': {
489 'vserver-info': {
490 'vserver-name': vserver_name,
491 },
492 },
493 'desired-attributes': {
494 'vserver-info': {
495 'ipspace': None,
496 },
497 },
498 }
499 vserver_info = self.send_iter_request('vserver-get-iter', api_args)
501 try:
502 ipspace = vserver_info.get_child_by_name(
503 'attributes-list').get_child_by_name(
504 'vserver-info').get_child_content('ipspace')
505 except AttributeError:
506 msg = _('Could not determine IPspace for Vserver %s.')
507 raise exception.NetAppException(msg % vserver_name)
508 return ipspace
510 @na_utils.trace
511 def ipspace_has_data_vservers(self, ipspace_name):
512 """Check whether an IPspace has any data Vservers assigned to it."""
513 if not self.features.IPSPACES:
514 return False
516 api_args = {
517 'query': {
518 'vserver-info': {
519 'ipspace': ipspace_name,
520 'vserver-type': 'data'
521 },
522 },
523 'desired-attributes': {
524 'vserver-info': {
525 'vserver-name': None,
526 },
527 },
528 }
529 result = self.send_iter_request('vserver-get-iter', api_args)
530 return self._has_records(result)
532 @na_utils.trace
533 def list_vservers(self, vserver_type='data'):
534 """Get the names of vservers present, optionally filtered by type."""
535 query = {
536 'vserver-info': {
537 'vserver-type': vserver_type,
538 }
539 } if vserver_type else None
541 api_args = {
542 'desired-attributes': {
543 'vserver-info': {
544 'vserver-name': None,
545 },
546 },
547 }
548 if query: 548 ↛ 551line 548 didn't jump to line 551 because the condition on line 548 was always true
549 api_args['query'] = query
551 result = self.send_iter_request('vserver-get-iter', api_args)
552 vserver_info_list = result.get_child_by_name(
553 'attributes-list') or netapp_api.NaElement('none')
554 return [vserver_info.get_child_content('vserver-name')
555 for vserver_info in vserver_info_list.get_children()]
557 @na_utils.trace
558 def get_vserver_volume_count(self):
559 """Get the number of volumes present on a cluster or vserver.
561 Call this on a vserver client to see how many volumes exist
562 on that vserver.
563 """
564 api_args = {
565 'desired-attributes': {
566 'volume-attributes': {
567 'volume-id-attributes': {
568 'name': None,
569 },
570 },
571 },
572 }
573 volumes_data = self.send_iter_request('volume-get-iter', api_args)
574 return self._get_record_count(volumes_data)
576 @na_utils.trace
577 def delete_vserver(self, vserver_name, vserver_client,
578 security_services=None):
579 """Deletes a Vserver.
581 Checks if Vserver exists and does not have active shares.
582 Offlines and destroys root volumes. Deletes Vserver.
583 """
584 vserver_info = self.get_vserver_info(vserver_name)
585 if vserver_info is None:
586 LOG.error("Vserver %s does not exist.", vserver_name)
587 return
589 is_dp_destination = vserver_info.get('subtype') == 'dp_destination'
590 root_volume_name = self.get_vserver_root_volume_name(vserver_name)
591 volumes_count = vserver_client.get_vserver_volume_count()
593 # NOTE(dviroel): 'dp_destination' vservers don't allow to delete its
594 # root volume. We can just call vserver-destroy directly.
595 if volumes_count == 1 and not is_dp_destination:
596 try:
597 vserver_client.offline_volume(root_volume_name)
598 except netapp_api.NaApiError as e:
599 if e.code == netapp_api.EVOLUMEOFFLINE:
600 LOG.error("Volume %s is already offline.",
601 root_volume_name)
602 else:
603 raise
604 vserver_client.delete_volume(root_volume_name)
606 elif volumes_count > 1:
607 msg = _("Cannot delete Vserver. Vserver %s has shares.")
608 raise exception.NetAppException(msg % vserver_name)
610 if security_services and not is_dp_destination:
611 self._terminate_vserver_services(vserver_name, vserver_client,
612 security_services)
614 self.send_request('vserver-destroy', {'vserver-name': vserver_name})
616 @na_utils.trace
617 def _terminate_vserver_services(self, vserver_name, vserver_client,
618 security_services):
619 for service in security_services:
620 if service['type'].lower() == 'active_directory': 620 ↛ 639line 620 didn't jump to line 639 because the condition on line 620 was always true
621 api_args = {
622 'admin-password': service['password'],
623 'admin-username': service['user'],
624 }
625 try:
626 vserver_client.send_request('cifs-server-delete', api_args)
627 except netapp_api.NaApiError as e:
628 if e.code == netapp_api.EOBJECTNOTFOUND:
629 LOG.error('CIFS server does not exist for '
630 'Vserver %s.', vserver_name)
631 else:
632 LOG.debug('Retrying CIFS server delete with force flag'
633 ' for Vserver %s.', vserver_name)
634 api_args = {
635 'force-account-delete': 'true'
636 }
637 vserver_client.send_request('cifs-server-delete',
638 api_args)
639 elif service['type'].lower() == 'kerberos':
640 vserver_client.disable_kerberos(service)
642 @na_utils.trace
643 def is_nve_supported(self):
644 """Determine whether NVE is supported on this platform and version."""
645 nodes = self.list_cluster_nodes()
646 system_version = self.get_system_version()
647 version = system_version.get('version')
648 version_tuple = system_version.get('version-tuple')
650 # NVE requires an ONTAP version >= 9.1. Also, not all platforms
651 # support this feature. NVE is not supported if the version
652 # includes the substring '<1no-DARE>' (no Data At Rest Encryption).
653 if version_tuple >= (9, 1, 0) and "<1no-DARE>" not in version:
654 if nodes is not None: 654 ↛ 657line 654 didn't jump to line 657 because the condition on line 654 was always true
655 return self.get_security_key_manager_nve_support(nodes[0])
656 else:
657 LOG.debug('Cluster credentials are required in order to '
658 'determine whether NetApp Volume Encryption is '
659 'supported or not on this platform.')
660 return False
661 else:
662 LOG.debug('NetApp Volume Encryption is not supported on this '
663 'ONTAP version: %(version)s, %(version_tuple)s. ',
664 {'version': version, 'version_tuple': version_tuple})
665 return False
667 @na_utils.trace
668 def list_cluster_nodes(self):
669 """Get all available cluster nodes."""
670 api_args = {
671 'desired-attributes': {
672 'node-details-info': {
673 'node': None,
674 },
675 },
676 }
677 result = self.send_iter_request('system-node-get-iter', api_args)
678 nodes_info_list = result.get_child_by_name(
679 'attributes-list') or netapp_api.NaElement('none')
680 return [node_info.get_child_content('node') for node_info
681 in nodes_info_list.get_children()]
683 @na_utils.trace
684 def get_security_key_manager_nve_support(self, node):
685 """Determine whether the cluster platform supports Volume Encryption"""
686 api_args = {'node': node}
687 try:
688 result = self.send_request(
689 'security-key-manager-volume-encryption-supported', api_args)
690 vol_encryption_supported = result.get_child_content(
691 'vol-encryption-supported') or 'false'
692 except netapp_api.NaApiError as e:
693 LOG.debug("NVE disabled due to error code: %s - %s",
694 e.code, e.message)
695 return False
697 return strutils.bool_from_string(vol_encryption_supported)
699 @na_utils.trace
700 def list_node_data_ports(self, node):
701 ports = self.get_node_data_ports(node)
702 return [port.get('port') for port in ports]
704 @na_utils.trace
705 def get_node_data_ports(self, node):
706 """Get applicable data ports on the node."""
707 api_args = {
708 'query': {
709 'net-port-info': {
710 'node': node,
711 'link-status': 'up',
712 'port-type': 'physical|if_group',
713 'role': 'data',
714 },
715 },
716 'desired-attributes': {
717 'net-port-info': {
718 'port': None,
719 'node': None,
720 'operational-speed': None,
721 'ifgrp-port': None,
722 },
723 },
724 }
725 result = self.send_iter_request('net-port-get-iter', api_args)
726 net_port_info_list = result.get_child_by_name(
727 'attributes-list') or netapp_api.NaElement('none')
729 ports = []
730 for port_info in net_port_info_list.get_children():
732 # Skip physical ports that are part of interface groups.
733 if port_info.get_child_content('ifgrp-port'): 733 ↛ 734line 733 didn't jump to line 734 because the condition on line 733 was never true
734 continue
736 port = {
737 'node': port_info.get_child_content('node'),
738 'port': port_info.get_child_content('port'),
739 'speed': port_info.get_child_content('operational-speed'),
740 }
741 ports.append(port)
743 return self._sort_data_ports_by_speed(ports)
745 @na_utils.trace
746 def _sort_data_ports_by_speed(self, ports):
748 def sort_key(port):
749 value = port.get('speed')
750 if not (value and isinstance(value, str)):
751 return 0
752 elif value.isdigit():
753 return int(value)
754 elif value == 'auto':
755 return 3
756 elif value == 'undef': 756 ↛ 759line 756 didn't jump to line 759 because the condition on line 756 was always true
757 return 2
758 else:
759 return 1
761 return sorted(ports, key=sort_key, reverse=True)
763 @na_utils.trace
764 def list_root_aggregates(self):
765 """Get names of all aggregates that contain node root volumes."""
767 desired_attributes = {
768 'aggr-attributes': {
769 'aggregate-name': None,
770 'aggr-raid-attributes': {
771 'has-local-root': None,
772 'has-partner-root': None,
773 },
774 },
775 }
776 aggrs = self._get_aggregates(desired_attributes=desired_attributes)
778 root_aggregates = []
779 for aggr in aggrs:
780 aggr_name = aggr.get_child_content('aggregate-name')
781 aggr_raid_attrs = aggr.get_child_by_name('aggr-raid-attributes')
783 local_root = strutils.bool_from_string(
784 aggr_raid_attrs.get_child_content('has-local-root'))
785 partner_root = strutils.bool_from_string(
786 aggr_raid_attrs.get_child_content('has-partner-root'))
788 if local_root or partner_root:
789 root_aggregates.append(aggr_name)
791 return root_aggregates
793 @na_utils.trace
794 def list_non_root_aggregates(self):
795 """Get names of all aggregates that don't contain node root volumes."""
797 query = {
798 'aggr-attributes': {
799 'aggr-raid-attributes': {
800 'has-local-root': 'false',
801 'has-partner-root': 'false',
802 }
803 },
804 }
805 return self._list_aggregates(query=query)
807 @na_utils.trace
808 def _list_aggregates(self, query=None):
809 """Get names of all aggregates."""
810 try:
811 api_args = {
812 'desired-attributes': {
813 'aggr-attributes': {
814 'aggregate-name': None,
815 },
816 },
817 }
818 if query:
819 api_args['query'] = query
820 result = self.send_iter_request('aggr-get-iter', api_args)
821 aggr_list = result.get_child_by_name(
822 'attributes-list').get_children()
823 except AttributeError:
824 msg = _("Could not list aggregates.")
825 raise exception.NetAppException(msg)
826 return [aggr.get_child_content('aggregate-name') for aggr
827 in aggr_list]
829 @na_utils.trace
830 def list_vserver_aggregates(self):
831 """Returns a list of aggregates available to a vserver.
833 This must be called against a Vserver LIF.
834 """
835 return list(self.get_vserver_aggregate_capacities().keys())
837 @na_utils.trace
838 def create_port_and_broadcast_domain(self, node, port, vlan, mtu, ipspace):
839 home_port_name = port
840 if vlan:
841 self._create_vlan(node, port, vlan)
842 home_port_name = '%(port)s-%(tag)s' % {'port': port, 'tag': vlan}
844 if self.features.BROADCAST_DOMAINS:
845 self._ensure_broadcast_domain_for_port(
846 node, home_port_name, mtu, ipspace=ipspace)
848 return home_port_name
850 @na_utils.trace
851 def create_network_interface(self, ip, netmask, node, port,
852 vserver_name, lif_name):
853 """Creates LIF on VLAN port."""
854 LOG.debug('Creating LIF %(lif)s for Vserver %(vserver)s '
855 'node/port %(node)s:%(port)s.',
856 {'lif': lif_name, 'vserver': vserver_name, 'node': node,
857 'port': port})
859 api_args = {
860 'address': ip,
861 'administrative-status': 'up',
862 'data-protocols': [
863 {'data-protocol': 'nfs'},
864 {'data-protocol': 'cifs'},
865 ],
866 'home-node': node,
867 'home-port': port,
868 'netmask': netmask,
869 'interface-name': lif_name,
870 'role': 'data',
871 'vserver': vserver_name,
872 }
873 self.send_request('net-interface-create', api_args)
875 @na_utils.trace
876 def _create_vlan(self, node, port, vlan):
877 try:
878 api_args = {
879 'vlan-info': {
880 'parent-interface': port,
881 'node': node,
882 'vlanid': vlan,
883 },
884 }
885 self.send_request('net-vlan-create', api_args)
886 except netapp_api.NaApiError as e:
887 if e.code == netapp_api.EDUPLICATEENTRY:
888 LOG.debug('VLAN %(vlan)s already exists on port %(port)s',
889 {'vlan': vlan, 'port': port})
890 else:
891 msg = _('Failed to create VLAN %(vlan)s on '
892 'port %(port)s. %(err_msg)s')
893 msg_args = {'vlan': vlan, 'port': port, 'err_msg': e.message}
894 raise exception.NetAppException(msg % msg_args)
896 @na_utils.trace
897 def delete_vlan(self, node, port, vlan):
898 try:
899 api_args = {
900 'vlan-info': {
901 'parent-interface': port,
902 'node': node,
903 'vlanid': vlan,
904 },
905 }
906 self.send_request('net-vlan-delete', api_args)
907 except netapp_api.NaApiError as e:
908 p = re.compile('port already has a lif bound.*', re.IGNORECASE)
909 if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
910 LOG.debug('VLAN %(vlan)s on port %(port)s node %(node)s '
911 'still used by LIF and cannot be deleted.',
912 {'vlan': vlan, 'port': port, 'node': node})
913 else:
914 msg = _('Failed to delete VLAN %(vlan)s on '
915 'port %(port)s node %(node)s: %(err_msg)s')
916 msg_args = {
917 'vlan': vlan,
918 'port': port,
919 'node': node,
920 'err_msg': e.message
921 }
922 raise exception.NetAppException(msg % msg_args)
924 @na_utils.trace
925 def get_degraded_ports(self, broadcast_domains, ipspace):
926 """Get degraded ports for broadcast domains and an ipspace."""
928 valid_domains = self._get_valid_broadcast_domains(broadcast_domains)
930 api_args = {
931 'query': {
932 'net-port-info': {
933 'broadcast-domain': '|'.join(valid_domains),
934 'health-degraded-reasons': {
935 'netport-degraded-reason': 'l2_reachability'
936 },
937 'health-status': 'degraded',
938 'ipspace': ipspace,
939 'port-type': 'vlan',
940 },
941 },
942 'desired-attributes': {
943 'net-port-info': {
944 'port': None,
945 'node': None,
946 },
947 },
948 }
950 result = self.send_iter_request('net-port-get-iter', api_args)
951 net_port_info_list = result.get_child_by_name(
952 'attributes-list') or netapp_api.NaElement('none')
954 ports = []
955 for port_info in net_port_info_list.get_children():
956 # making it a net-qualified-port-name
957 # compatible with ports result from net-ipspaces-get-iter
958 ports.append(f"{port_info.get_child_content('node')}:"
959 f"{port_info.get_child_content('port')}")
961 return ports
963 @na_utils.trace
964 def _get_valid_broadcast_domains(_self, broadcast_domains):
965 valid_domains = []
966 for broadcast_domain in broadcast_domains:
967 if (
968 broadcast_domain == 'OpenStack'
969 or broadcast_domain == DEFAULT_BROADCAST_DOMAIN
970 or broadcast_domain.startswith(BROADCAST_DOMAIN_PREFIX)
971 ):
972 valid_domains.append(broadcast_domain)
973 return valid_domains
975 @na_utils.trace
976 def create_route(self, gateway, destination=None):
977 if not gateway:
978 return
979 if not destination:
980 if netutils.is_valid_ipv6(gateway):
981 destination = '::/0'
982 else:
983 destination = '0.0.0.0/0'
984 try:
985 api_args = {
986 'destination': destination,
987 'gateway': gateway,
988 'return-record': 'true',
989 }
990 self.send_request('net-routes-create', api_args)
991 except netapp_api.NaApiError as e:
992 p = re.compile('.*Duplicate route exists.*', re.IGNORECASE)
993 if (e.code == netapp_api.EAPIERROR and re.match(p, e.message)):
994 LOG.debug('Route to %(destination)s via gateway %(gateway)s '
995 'exists.',
996 {'destination': destination, 'gateway': gateway})
997 else:
998 msg = _('Failed to create a route to %(destination)s via '
999 'gateway %(gateway)s: %(err_msg)s')
1000 msg_args = {
1001 'destination': destination,
1002 'gateway': gateway,
1003 'err_msg': e.message,
1004 }
1005 raise exception.NetAppException(msg % msg_args)
1007 @na_utils.trace
1008 def _ensure_broadcast_domain_for_port(self, node, port, mtu,
1009 ipspace=DEFAULT_IPSPACE):
1010 """Ensure a port is in a broadcast domain. Create one if necessary.
1012 If the IPspace:domain pair match for the given port, which commonly
1013 happens in multi-node clusters, then there isn't anything to do.
1014 Otherwise, we can assume the IPspace is correct and extant by this
1015 point, so the remaining task is to remove the port from any domain it
1016 is already in, create the domain for the IPspace if it doesn't exist,
1017 and add the port to this domain.
1018 """
1020 # Derive the broadcast domain name from the IPspace name since they
1021 # need to be 1-1 and the default for both is the same name, 'Default'.
1022 domain = re.sub(IPSPACE_PREFIX, BROADCAST_DOMAIN_PREFIX, ipspace)
1024 port_info = self._get_broadcast_domain_for_port(node, port)
1026 # Port already in desired ipspace and broadcast domain.
1027 if (port_info['ipspace'] == ipspace
1028 and port_info['broadcast-domain'] == domain):
1029 self._modify_broadcast_domain(domain, ipspace, mtu)
1030 return
1032 # If in another broadcast domain, remove port from it.
1033 if port_info['broadcast-domain']:
1034 self._remove_port_from_broadcast_domain(
1035 node, port, port_info['broadcast-domain'],
1036 port_info['ipspace'])
1038 # If desired broadcast domain doesn't exist, create it.
1039 if not self._broadcast_domain_exists(domain, ipspace):
1040 self._create_broadcast_domain(domain, ipspace, mtu)
1041 else:
1042 self._modify_broadcast_domain(domain, ipspace, mtu)
1044 # Move the port into the broadcast domain where it is needed.
1045 self._add_port_to_broadcast_domain(node, port, domain, ipspace)
1047 @na_utils.trace
1048 def _get_broadcast_domain_for_port(self, node, port):
1049 """Get broadcast domain for a specific port."""
1050 api_args = {
1051 'query': {
1052 'net-port-info': {
1053 'node': node,
1054 'port': port,
1055 },
1056 },
1057 'desired-attributes': {
1058 'net-port-info': {
1059 'broadcast-domain': None,
1060 'ipspace': None,
1061 },
1062 },
1063 }
1064 result = self.send_iter_request('net-port-get-iter', api_args)
1066 net_port_info_list = result.get_child_by_name(
1067 'attributes-list') or netapp_api.NaElement('none')
1068 port_info = net_port_info_list.get_children()
1069 if not port_info:
1070 msg = _('Could not find port %(port)s on node %(node)s.')
1071 msg_args = {'port': port, 'node': node}
1072 raise exception.NetAppException(msg % msg_args)
1074 port = {
1075 'broadcast-domain':
1076 port_info[0].get_child_content('broadcast-domain'),
1077 'ipspace': port_info[0].get_child_content('ipspace')
1078 }
1079 return port
1081 @na_utils.trace
1082 def _broadcast_domain_exists(self, domain, ipspace):
1083 """Check if a broadcast domain exists."""
1084 api_args = {
1085 'query': {
1086 'net-port-broadcast-domain-info': {
1087 'ipspace': ipspace,
1088 'broadcast-domain': domain,
1089 },
1090 },
1091 'desired-attributes': {
1092 'net-port-broadcast-domain-info': None,
1093 },
1094 }
1095 result = self.send_iter_request('net-port-broadcast-domain-get-iter',
1096 api_args)
1097 return self._has_records(result)
1099 @na_utils.trace
1100 def _create_broadcast_domain(self, domain, ipspace, mtu):
1101 """Create a broadcast domain."""
1102 api_args = {
1103 'ipspace': ipspace,
1104 'broadcast-domain': domain,
1105 'mtu': mtu,
1106 }
1107 self.send_request('net-port-broadcast-domain-create', api_args)
1109 @na_utils.trace
1110 def _modify_broadcast_domain(self, domain, ipspace, mtu):
1111 """Modify a broadcast domain."""
1112 api_args = {
1113 'ipspace': ipspace,
1114 'broadcast-domain': domain,
1115 'mtu': mtu,
1116 }
1117 self.send_request('net-port-broadcast-domain-modify', api_args)
1119 @na_utils.trace
1120 def _delete_broadcast_domain(self, domain, ipspace):
1121 """Delete a broadcast domain."""
1122 api_args = {
1123 'ipspace': ipspace,
1124 'broadcast-domain': domain,
1125 }
1126 self.send_request('net-port-broadcast-domain-destroy', api_args)
1128 @na_utils.trace
1129 def _delete_broadcast_domains_for_ipspace(self, ipspace_name):
1130 """Deletes all broadcast domains in an IPspace."""
1131 ipspaces = self.get_ipspaces(ipspace_name=ipspace_name)
1132 if not ipspaces:
1133 return
1135 ipspace = ipspaces[0]
1136 for broadcast_domain_name in ipspace['broadcast-domains']:
1137 self._delete_broadcast_domain(broadcast_domain_name, ipspace_name)
1139 @na_utils.trace
1140 def _add_port_to_broadcast_domain(self, node, port, domain, ipspace):
1142 qualified_port_name = ':'.join([node, port])
1143 try:
1144 api_args = {
1145 'ipspace': ipspace,
1146 'broadcast-domain': domain,
1147 'ports': {
1148 'net-qualified-port-name': qualified_port_name,
1149 }
1150 }
1151 self.send_request('net-port-broadcast-domain-add-ports', api_args)
1152 except netapp_api.NaApiError as e:
1153 if e.code == (netapp_api.
1154 E_VIFMGR_PORT_ALREADY_ASSIGNED_TO_BROADCAST_DOMAIN):
1155 LOG.debug('Port %(port)s already exists in broadcast domain '
1156 '%(domain)s', {'port': port, 'domain': domain})
1157 else:
1158 msg = _('Failed to add port %(port)s to broadcast domain '
1159 '%(domain)s. %(err_msg)s')
1160 msg_args = {
1161 'port': qualified_port_name,
1162 'domain': domain,
1163 'err_msg': e.message,
1164 }
1165 raise exception.NetAppException(msg % msg_args)
1167 @na_utils.trace
1168 def _remove_port_from_broadcast_domain(self, node, port, domain, ipspace):
1170 qualified_port_name = ':'.join([node, port])
1171 api_args = {
1172 'ipspace': ipspace,
1173 'broadcast-domain': domain,
1174 'ports': {
1175 'net-qualified-port-name': qualified_port_name,
1176 }
1177 }
1178 self.send_request('net-port-broadcast-domain-remove-ports', api_args)
1180 @na_utils.trace
1181 def network_interface_exists(self, vserver_name, node, port, ip, netmask,
1182 vlan=None, home_port=None):
1183 """Checks if LIF exists."""
1184 if not home_port: 1184 ↛ 1187line 1184 didn't jump to line 1187 because the condition on line 1184 was always true
1185 home_port = port if not vlan else f'{port}-{vlan}'
1187 api_args = {
1188 'query': {
1189 'net-interface-info': {
1190 'address': ip,
1191 'home-node': node,
1192 'home-port': home_port,
1193 'netmask': netmask,
1194 'vserver': vserver_name,
1195 },
1196 },
1197 'desired-attributes': {
1198 'net-interface-info': {
1199 'interface-name': None,
1200 },
1201 },
1202 }
1203 result = self.send_iter_request('net-interface-get-iter', api_args)
1204 return self._has_records(result)
1206 @na_utils.trace
1207 def list_network_interfaces(self):
1208 """Get the names of available LIFs."""
1209 api_args = {
1210 'desired-attributes': {
1211 'net-interface-info': {
1212 'interface-name': None,
1213 },
1214 },
1215 }
1216 result = self.send_iter_request('net-interface-get-iter', api_args)
1217 lif_info_list = result.get_child_by_name(
1218 'attributes-list') or netapp_api.NaElement('none')
1219 return [lif_info.get_child_content('interface-name') for lif_info
1220 in lif_info_list.get_children()]
1222 @na_utils.trace
1223 def get_network_interfaces(self, protocols=None):
1224 """Get available LIFs."""
1225 protocols = na_utils.convert_to_list(protocols)
1226 protocols = [protocol.lower() for protocol in protocols]
1228 api_args = {
1229 'query': {
1230 'net-interface-info': {
1231 'data-protocols': {
1232 'data-protocol': '|'.join(protocols),
1233 }
1234 }
1235 }
1236 } if protocols else None
1238 result = self.send_iter_request('net-interface-get-iter', api_args)
1239 lif_info_list = result.get_child_by_name(
1240 'attributes-list') or netapp_api.NaElement('none')
1242 interfaces = []
1243 for lif_info in lif_info_list.get_children():
1244 lif = {
1245 'administrative-status': lif_info.get_child_content(
1246 'administrative-status'),
1247 'address': lif_info.get_child_content('address'),
1248 'home-node': lif_info.get_child_content('home-node'),
1249 'home-port': lif_info.get_child_content('home-port'),
1250 'interface-name': lif_info.get_child_content('interface-name'),
1251 'netmask': lif_info.get_child_content('netmask'),
1252 'role': lif_info.get_child_content('role'),
1253 'vserver': lif_info.get_child_content('vserver'),
1254 }
1255 interfaces.append(lif)
1257 return interfaces
1259 @na_utils.trace
1260 def disable_network_interface(self, vserver_name, interface_name):
1261 api_args = {
1262 'administrative-status': 'down',
1263 'interface-name': interface_name,
1264 'vserver': vserver_name,
1265 }
1266 self.send_request('net-interface-modify', api_args)
1268 @na_utils.trace
1269 def delete_network_interface(self, vserver_name, interface_name):
1270 self.disable_network_interface(vserver_name, interface_name)
1271 api_args = {
1272 'interface-name': interface_name,
1273 'vserver': vserver_name
1274 }
1275 self.send_request('net-interface-delete', api_args)
1277 @na_utils.trace
1278 def get_ipspace_name_for_vlan_port(self, vlan_node, vlan_port, vlan_id):
1279 """Gets IPSpace name for specified VLAN"""
1281 if not self.features.IPSPACES:
1282 return None
1284 port = vlan_port if not vlan_id else '%(port)s-%(id)s' % {
1285 'port': vlan_port,
1286 'id': vlan_id,
1287 }
1288 api_args = {'node': vlan_node, 'port': port}
1290 try:
1291 result = self.send_request('net-port-get', api_args)
1292 except netapp_api.NaApiError as e:
1293 if e.code == netapp_api.EOBJECTNOTFOUND:
1294 msg = _('No pre-existing port or ipspace was found for '
1295 '%(port)s, will attempt to create one.')
1296 msg_args = {'port': port}
1297 LOG.debug(msg, msg_args)
1298 return None
1299 else:
1300 raise
1302 attributes = result.get_child_by_name('attributes')
1303 net_port_info = attributes.get_child_by_name('net-port-info')
1304 ipspace_name = net_port_info.get_child_content('ipspace')
1306 return ipspace_name
1308 @na_utils.trace
1309 def get_ipspaces(self, ipspace_name=None, vserver_name=None):
1310 """Gets one or more IPSpaces.
1312 parameters ipspace_name and vserver_name are mutually exclusive
1313 """
1314 if ipspace_name and vserver_name: 1314 ↛ 1315line 1314 didn't jump to line 1315 because the condition on line 1314 was never true
1315 msg = ('The parameters "ipspace_name" and "vserver_name" cannot '
1316 'both be used at the same time.')
1317 raise exception.InvalidInput(reason=msg)
1319 if not self.features.IPSPACES:
1320 return []
1322 api_args = {}
1323 if ipspace_name:
1324 api_args['query'] = {
1325 'net-ipspaces-info': {
1326 'ipspace': ipspace_name,
1327 }
1328 }
1329 elif vserver_name: 1329 ↛ 1330line 1329 didn't jump to line 1330 because the condition on line 1329 was never true
1330 api_args['query'] = {
1331 'net-ipspaces-info': {
1332 'vservers': {
1333 'vserver_name': vserver_name,
1334 }
1335 }
1336 }
1338 result = self.send_iter_request('net-ipspaces-get-iter', api_args)
1339 if not self._has_records(result):
1340 return []
1342 ipspaces = []
1344 for net_ipspaces_info in result.get_child_by_name(
1345 'attributes-list').get_children():
1347 ipspace = {
1348 'ports': [],
1349 'vservers': [],
1350 'broadcast-domains': [],
1351 }
1353 ports = net_ipspaces_info.get_child_by_name(
1354 'ports') or netapp_api.NaElement('none')
1355 for port in ports.get_children():
1356 ipspace['ports'].append(port.get_content())
1358 vservers = net_ipspaces_info.get_child_by_name(
1359 'vservers') or netapp_api.NaElement('none')
1360 for vserver in vservers.get_children():
1361 ipspace['vservers'].append(vserver.get_content())
1363 broadcast_domains = net_ipspaces_info.get_child_by_name(
1364 'broadcast-domains') or netapp_api.NaElement('none')
1365 for broadcast_domain in broadcast_domains.get_children():
1366 ipspace['broadcast-domains'].append(
1367 broadcast_domain.get_content())
1369 ipspace['ipspace'] = net_ipspaces_info.get_child_content('ipspace')
1370 ipspace['id'] = net_ipspaces_info.get_child_content('id')
1371 ipspace['uuid'] = net_ipspaces_info.get_child_content('uuid')
1373 ipspaces.append(ipspace)
1375 return ipspaces
1377 @na_utils.trace
1378 def ipspace_exists(self, ipspace_name):
1379 """Checks if IPspace exists."""
1381 if not self.features.IPSPACES:
1382 return False
1384 api_args = {
1385 'query': {
1386 'net-ipspaces-info': {
1387 'ipspace': ipspace_name,
1388 },
1389 },
1390 'desired-attributes': {
1391 'net-ipspaces-info': {
1392 'ipspace': None,
1393 },
1394 },
1395 }
1396 result = self.send_iter_request('net-ipspaces-get-iter', api_args)
1397 return self._has_records(result)
1399 @na_utils.trace
1400 def create_ipspace(self, ipspace_name):
1401 """Creates an IPspace."""
1402 api_args = {'ipspace': ipspace_name}
1403 self.send_request('net-ipspaces-create', api_args)
1405 @na_utils.trace
1406 def delete_ipspace(self, ipspace_name):
1407 """Deletes an IPspace.
1409 Returns:
1410 True if ipspace was deleted,
1411 False if validation or error prevented deletion
1412 """
1413 if not self.features.IPSPACES: 1413 ↛ 1414line 1413 didn't jump to line 1414 because the condition on line 1413 was never true
1414 return False
1416 if not ipspace_name: 1416 ↛ 1417line 1416 didn't jump to line 1417 because the condition on line 1416 was never true
1417 return False
1419 if ( 1419 ↛ 1423line 1419 didn't jump to line 1423 because the condition on line 1419 was never true
1420 ipspace_name in CLUSTER_IPSPACES
1421 or self.ipspace_has_data_vservers(ipspace_name)
1422 ):
1423 LOG.debug('IPspace %(ipspace)s not deleted: still in use.',
1424 {'ipspace': ipspace_name})
1425 return False
1427 try:
1428 self._delete_broadcast_domains_for_ipspace(ipspace_name)
1429 except netapp_api.NaApiError as e:
1430 msg = _('Broadcast Domains of IPspace %s not deleted. '
1431 'Reason: %s') % (ipspace_name, e)
1432 LOG.warning(msg)
1433 return False
1435 api_args = {'ipspace': ipspace_name}
1436 try:
1437 self.send_request('net-ipspaces-destroy', api_args)
1438 except netapp_api.NaApiError as e:
1439 msg = _('IPspace %s not deleted. Reason: %s') % (ipspace_name, e)
1440 LOG.warning(msg)
1441 return False
1443 return True
1445 @na_utils.trace
1446 def add_vserver_to_ipspace(self, ipspace_name, vserver_name):
1447 """Assigns a vserver to an IPspace."""
1448 api_args = {'ipspace': ipspace_name, 'vserver': vserver_name}
1449 self.send_request('net-ipspaces-assign-vserver', api_args)
1451 @na_utils.trace
1452 def get_node_for_aggregate(self, aggregate_name):
1453 """Get home node for the specified aggregate.
1455 This API could return None, most notably if it was sent
1456 to a Vserver LIF, so the caller must be able to handle that case.
1457 """
1459 if not aggregate_name:
1460 return None
1462 desired_attributes = {
1463 'aggr-attributes': {
1464 'aggregate-name': None,
1465 'aggr-ownership-attributes': {
1466 'home-name': None,
1467 },
1468 },
1469 }
1471 try:
1472 aggrs = self._get_aggregates(aggregate_names=[aggregate_name],
1473 desired_attributes=desired_attributes)
1474 except netapp_api.NaApiError as e:
1475 if e.code == netapp_api.EAPINOTFOUND:
1476 return None
1477 else:
1478 raise
1480 if len(aggrs) < 1:
1481 return None
1483 aggr_ownership_attrs = aggrs[0].get_child_by_name(
1484 'aggr-ownership-attributes') or netapp_api.NaElement('none')
1485 return aggr_ownership_attrs.get_child_content('home-name')
1487 @na_utils.trace
1488 def get_cluster_aggregate_capacities(self, aggregate_names):
1489 """Calculates capacity of one or more aggregates.
1491 Returns dictionary of aggregate capacity metrics.
1492 'size-used' is the actual space consumed on the aggregate.
1493 'size-available' is the actual space remaining.
1494 'size-total' is the defined total aggregate size, such that
1495 used + available = total.
1496 """
1498 if aggregate_names is not None and len(aggregate_names) == 0:
1499 return {}
1501 desired_attributes = {
1502 'aggr-attributes': {
1503 'aggregate-name': None,
1504 'aggr-space-attributes': {
1505 'size-available': None,
1506 'size-total': None,
1507 'size-used': None,
1508 },
1509 },
1510 }
1511 aggrs = self._get_aggregates(aggregate_names=aggregate_names,
1512 desired_attributes=desired_attributes)
1513 aggr_space_dict = dict()
1514 for aggr in aggrs:
1515 aggr_name = aggr.get_child_content('aggregate-name')
1516 aggr_space_attrs = aggr.get_child_by_name('aggr-space-attributes')
1518 aggr_space_dict[aggr_name] = {
1519 'available':
1520 int(aggr_space_attrs.get_child_content('size-available')),
1521 'total':
1522 int(aggr_space_attrs.get_child_content('size-total')),
1523 'used':
1524 int(aggr_space_attrs.get_child_content('size-used')),
1525 }
1526 return aggr_space_dict
1528 @na_utils.trace
1529 def get_vserver_aggregate_capacities(self, aggregate_names=None):
1530 """Calculates capacity of one or more aggregates for a vserver.
1532 Returns dictionary of aggregate capacity metrics. This must
1533 be called against a Vserver LIF.
1534 """
1536 if aggregate_names is not None and len(aggregate_names) == 0:
1537 return {}
1539 api_args = {
1540 'desired-attributes': {
1541 'vserver-info': {
1542 'vserver-name': None,
1543 'vserver-aggr-info-list': {
1544 'vserver-aggr-info': {
1545 'aggr-name': None,
1546 'aggr-availsize': None,
1547 },
1548 },
1549 },
1550 },
1551 }
1552 result = self.send_request('vserver-get', api_args)
1553 attributes = result.get_child_by_name('attributes')
1554 if not attributes:
1555 raise exception.NetAppException('Failed to read Vserver info')
1557 vserver_info = attributes.get_child_by_name('vserver-info')
1558 vserver_name = vserver_info.get_child_content('vserver-name')
1559 vserver_aggr_info_element = vserver_info.get_child_by_name(
1560 'vserver-aggr-info-list') or netapp_api.NaElement('none')
1561 vserver_aggr_info_list = vserver_aggr_info_element.get_children()
1563 if not vserver_aggr_info_list:
1564 LOG.warning('No aggregates assigned to Vserver %s.',
1565 vserver_name)
1567 # Return dict of key-value pair of aggr_name:aggr_size_available.
1568 aggr_space_dict = {}
1570 for aggr_info in vserver_aggr_info_list:
1571 aggr_name = aggr_info.get_child_content('aggr-name')
1573 if aggregate_names is None or aggr_name in aggregate_names:
1574 aggr_size = int(aggr_info.get_child_content('aggr-availsize'))
1575 aggr_space_dict[aggr_name] = {'available': aggr_size}
1577 LOG.debug('Found available Vserver aggregates: %s', aggr_space_dict)
1578 return aggr_space_dict
1580 @na_utils.trace
1581 def _get_aggregates(self, aggregate_names=None, desired_attributes=None):
1583 query = {
1584 'aggr-attributes': {
1585 'aggregate-name': '|'.join(aggregate_names),
1586 }
1587 } if aggregate_names else None
1589 api_args = {}
1590 if query:
1591 api_args['query'] = query
1592 if desired_attributes:
1593 api_args['desired-attributes'] = desired_attributes
1595 result = self.send_iter_request('aggr-get-iter', api_args)
1596 if not self._has_records(result):
1597 return []
1598 else:
1599 return result.get_child_by_name('attributes-list').get_children()
1601 def get_performance_instance_uuids(self, object_name, node_name):
1602 """Get UUIDs of performance instances for a cluster node."""
1604 api_args = {
1605 'objectname': object_name,
1606 'query': {
1607 'instance-info': {
1608 'uuid': node_name + ':*',
1609 }
1610 }
1611 }
1613 result = self.send_request('perf-object-instance-list-info-iter',
1614 api_args)
1616 uuids = []
1618 instances = result.get_child_by_name(
1619 'attributes-list') or netapp_api.NaElement('None')
1621 for instance_info in instances.get_children():
1622 uuids.append(instance_info.get_child_content('uuid'))
1624 return uuids
1626 def get_performance_counter_info(self, object_name, counter_name):
1627 """Gets info about one or more Data ONTAP performance counters."""
1629 api_args = {'objectname': object_name}
1630 result = self.send_request('perf-object-counter-list-info', api_args)
1632 counters = result.get_child_by_name(
1633 'counters') or netapp_api.NaElement('None')
1635 for counter in counters.get_children():
1637 if counter.get_child_content('name') == counter_name:
1639 labels = []
1640 label_list = counter.get_child_by_name(
1641 'labels') or netapp_api.NaElement('None')
1642 for label in label_list.get_children():
1643 labels.extend(label.get_content().split(','))
1644 base_counter = counter.get_child_content('base-counter')
1646 return {
1647 'name': counter_name,
1648 'labels': labels,
1649 'base-counter': base_counter,
1650 }
1651 else:
1652 raise exception.NotFound(_('Counter %s not found') % counter_name)
1654 def get_performance_counters(self, object_name, instance_uuids,
1655 counter_names):
1656 """Gets one or more cDOT performance counters."""
1658 api_args = {
1659 'objectname': object_name,
1660 'instance-uuids': [
1661 {'instance-uuid': instance_uuid}
1662 for instance_uuid in instance_uuids
1663 ],
1664 'counters': [
1665 {'counter': counter} for counter in counter_names
1666 ],
1667 }
1669 result = self.send_request('perf-object-get-instances', api_args)
1671 counter_data = []
1673 timestamp = result.get_child_content('timestamp')
1675 instances = result.get_child_by_name(
1676 'instances') or netapp_api.NaElement('None')
1677 for instance in instances.get_children():
1679 instance_name = instance.get_child_content('name')
1680 instance_uuid = instance.get_child_content('uuid')
1681 node_name = instance_uuid.split(':')[0]
1683 counters = instance.get_child_by_name(
1684 'counters') or netapp_api.NaElement('None')
1685 for counter in counters.get_children():
1687 counter_name = counter.get_child_content('name')
1688 counter_value = counter.get_child_content('value')
1690 counter_data.append({
1691 'instance-name': instance_name,
1692 'instance-uuid': instance_uuid,
1693 'node-name': node_name,
1694 'timestamp': timestamp,
1695 counter_name: counter_value,
1696 })
1698 return counter_data
1700 @na_utils.trace
1701 def setup_security_services(self, security_services, vserver_client,
1702 vserver_name, aes_encryption, timeout=30):
1703 api_args = {
1704 'name-mapping-switch': [
1705 {'nmswitch': 'ldap'},
1706 {'nmswitch': 'file'}
1707 ],
1708 'name-server-switch': [
1709 {'nsswitch': 'ldap'},
1710 {'nsswitch': 'file'}
1711 ],
1712 'vserver-name': vserver_name,
1713 }
1714 self.send_request('vserver-modify', api_args)
1716 for security_service in security_services:
1717 if security_service['type'].lower() == 'ldap':
1718 vserver_client.configure_ldap(security_service,
1719 timeout=timeout)
1721 elif security_service['type'].lower() == 'active_directory':
1722 vserver_client.configure_active_directory(security_service,
1723 vserver_name,
1724 aes_encryption)
1725 vserver_client.configure_cifs_options(security_service)
1727 elif security_service['type'].lower() == 'kerberos':
1728 vserver_client.create_kerberos_realm(security_service)
1729 vserver_client.configure_kerberos(security_service,
1730 vserver_name)
1732 else:
1733 msg = _('Unsupported security service type %s for '
1734 'Data ONTAP driver')
1735 raise exception.NetAppException(msg % security_service['type'])
1737 @na_utils.trace
1738 def update_showmount(self, showmount):
1739 """Update show mount for vserver. """
1740 nfs_service_modify_arg = {
1741 'showmount': showmount
1742 }
1743 self.send_request('nfs-service-modify', nfs_service_modify_arg)
1745 @na_utils.trace
1746 def update_pnfs(self, pnfs):
1747 """Update pNFS for vserver. """
1748 nfs_service_modify_arg = {
1749 'is-nfsv41-pnfs-enabled': pnfs
1750 }
1751 self.send_request('nfs-service-modify', nfs_service_modify_arg)
1753 @na_utils.trace
1754 def enable_nfs(self, versions, nfs_config=None):
1755 """Enables NFS on Vserver."""
1756 self.send_request('nfs-enable')
1757 self._enable_nfs_protocols(versions)
1759 if nfs_config:
1760 self._configure_nfs(nfs_config)
1762 self._create_default_nfs_export_rules()
1764 @na_utils.trace
1765 def _enable_nfs_protocols(self, versions):
1766 """Set the enabled NFS protocol versions."""
1767 nfs3 = 'true' if 'nfs3' in versions else 'false'
1768 nfs40 = 'true' if 'nfs4.0' in versions else 'false'
1769 nfs41 = 'true' if 'nfs4.1' in versions else 'false'
1771 nfs_service_modify_args = {
1772 'is-nfsv3-enabled': nfs3,
1773 'is-nfsv40-enabled': nfs40,
1774 'is-nfsv41-enabled': nfs41,
1775 'showmount': 'true',
1776 'is-v3-ms-dos-client-enabled': 'true',
1777 'is-nfsv3-connection-drop-enabled': 'false',
1778 'enable-ejukebox': 'false',
1779 }
1780 self.send_request('nfs-service-modify', nfs_service_modify_args)
1782 @na_utils.trace
1783 def _configure_nfs(self, nfs_config):
1784 """Sets the nfs configuraton"""
1785 self.send_request('nfs-service-modify', nfs_config)
1787 @na_utils.trace
1788 def _create_default_nfs_export_rules(self):
1789 """Create the default export rule for the NFS service."""
1791 export_rule_create_args = {
1792 'client-match': '0.0.0.0/0',
1793 'policy-name': 'default',
1794 'ro-rule': {
1795 'security-flavor': 'any',
1796 },
1797 'rw-rule': {
1798 'security-flavor': 'never',
1799 },
1800 }
1801 self.send_request('export-rule-create', export_rule_create_args)
1802 export_rule_create_args['client-match'] = '::/0'
1803 self.send_request('export-rule-create', export_rule_create_args)
1805 @na_utils.trace
1806 def _create_ldap_client(self, security_service):
1807 ad_domain = security_service.get('domain')
1808 ldap_servers = security_service.get('server')
1809 bind_dn = security_service.get('user')
1810 ldap_schema = 'RFC-2307'
1812 if ad_domain:
1813 if ldap_servers:
1814 msg = _("LDAP client cannot be configured with both 'server' "
1815 "and 'domain' parameters. Use 'server' for Linux/Unix "
1816 "LDAP servers or 'domain' for Active Directory LDAP "
1817 "servers.")
1818 LOG.exception(msg)
1819 raise exception.NetAppException(msg)
1820 # RFC2307bis, for MS Active Directory LDAP server
1821 ldap_schema = 'MS-AD-BIS'
1822 bind_dn = (security_service.get('user') + '@' + ad_domain)
1823 else:
1824 if not ldap_servers:
1825 msg = _("LDAP client cannot be configured without 'server' "
1826 "or 'domain' parameters. Use 'server' for Linux/Unix "
1827 "LDAP servers or 'domain' for Active Directory LDAP "
1828 "server.")
1829 LOG.exception(msg)
1830 raise exception.NetAppException(msg)
1832 if security_service.get('dns_ip'):
1833 self.configure_dns(security_service)
1835 config_name = hashlib.md5(
1836 security_service['id'].encode("latin-1"),
1837 usedforsecurity=False).hexdigest()
1838 api_args = {
1839 'ldap-client-config': config_name,
1840 'tcp-port': '389',
1841 'schema': ldap_schema,
1842 'bind-dn': bind_dn,
1843 'bind-password': security_service.get('password'),
1844 }
1846 if security_service.get('ou'): 1846 ↛ 1848line 1846 didn't jump to line 1848 because the condition on line 1846 was always true
1847 api_args['base-dn'] = security_service['ou']
1848 if ad_domain:
1849 # Active Directory LDAP server
1850 api_args['ad-domain'] = ad_domain
1851 else:
1852 # Linux/Unix LDAP servers
1853 if self.features.LDAP_LDAP_SERVERS: 1853 ↛ 1856line 1853 didn't jump to line 1856 because the condition on line 1853 was always true
1854 servers_key, servers_key_type = 'ldap-servers', 'string'
1855 else:
1856 servers_key, servers_key_type = 'servers', 'ip-address'
1858 api_args[servers_key] = []
1859 for server in ldap_servers.split(','):
1860 api_args[servers_key].append(
1861 {servers_key_type: server.strip()})
1863 self.send_request('ldap-client-create', api_args)
1865 @na_utils.trace
1866 def _enable_ldap_client(self, client_config_name, timeout=30):
1867 # ONTAP ldap query timeout is 3 seconds by default
1868 interval = 3
1869 retries = int(timeout / interval) or 1
1870 api_args = {'client-config': client_config_name,
1871 'client-enabled': 'true'}
1873 @manila_utils.retry(retry_param=exception.ShareBackendException,
1874 interval=interval,
1875 retries=retries,
1876 backoff_rate=1)
1877 def try_enable_ldap_client():
1878 try:
1879 self.send_request('ldap-config-create', api_args)
1880 except netapp_api.NaApiError as e:
1881 msg = _('Unable to enable ldap client configuration. Will '
1882 'retry the operation. Error details: %s') % e.message
1883 LOG.warning(msg)
1884 raise exception.ShareBackendException(msg=msg)
1886 try:
1887 try_enable_ldap_client()
1888 except exception.ShareBackendException:
1889 msg = _("Unable to enable ldap client configuration %s. "
1890 "Retries exhausted. Aborting.") % client_config_name
1891 LOG.exception(msg)
1892 raise exception.NetAppException(message=msg)
1894 @na_utils.trace
1895 def _delete_ldap_client(self, security_service):
1896 config_name = (
1897 hashlib.md5(security_service['id'].encode("latin-1"),
1898 usedforsecurity=False).hexdigest())
1899 api_args = {'ldap-client-config': config_name}
1900 self.send_request('ldap-client-delete', api_args)
1902 @na_utils.trace
1903 def configure_ldap(self, security_service, timeout=30):
1904 """Configures LDAP on Vserver."""
1905 config_name = hashlib.md5(
1906 security_service['id'].encode("latin-1"),
1907 usedforsecurity=False).hexdigest()
1908 self._create_ldap_client(security_service)
1909 self._enable_ldap_client(config_name, timeout=timeout)
1911 @na_utils.trace
1912 def modify_ldap(self, new_security_service, current_security_service):
1913 """Modifies LDAP client on a Vserver."""
1914 # Create a new ldap client
1915 self._create_ldap_client(new_security_service)
1917 # Delete current ldap config
1918 try:
1919 self.send_request('ldap-config-delete')
1920 except netapp_api.NaApiError as e:
1921 if e.code != netapp_api.EOBJECTNOTFOUND: 1921 ↛ 1929line 1921 didn't jump to line 1929 because the condition on line 1921 was always true
1922 # Delete previously created ldap client
1923 self._delete_ldap_client(new_security_service)
1925 msg = _("An error occurred while deleting original LDAP "
1926 "configuration. %s")
1927 raise exception.NetAppException(msg % e.message)
1928 else:
1929 msg = _("Original LDAP configuration was not found. "
1930 "LDAP modification will continue.")
1931 LOG.debug(msg)
1933 new_config_name = (
1934 hashlib.md5(
1935 new_security_service['id'].encode("latin-1"),
1936 usedforsecurity=False).hexdigest())
1937 # Create ldap config with the new client
1938 api_args = {'client-config': new_config_name, 'client-enabled': 'true'}
1939 self.send_request('ldap-config-create', api_args)
1941 # Delete old client configuration
1942 try:
1943 self._delete_ldap_client(current_security_service)
1944 except netapp_api.NaApiError as e:
1945 if e.code != netapp_api.EOBJECTNOTFOUND: 1945 ↛ 1960line 1945 didn't jump to line 1960 because the condition on line 1945 was always true
1946 current_config_name = (
1947 hashlib.md5(
1948 current_security_service['id'].encode(
1949 "latin-1"),
1950 usedforsecurity=False).hexdigest())
1951 msg = _("An error occurred while deleting original LDAP "
1952 "client configuration %(current_config)s. "
1953 "Error details: %(e_msg)s")
1954 msg_args = {
1955 'current_config': current_config_name,
1956 'e_msg': e.message,
1957 }
1958 LOG.warning(msg, msg_args)
1959 else:
1960 msg = _("Original LDAP client configuration was not found.")
1961 LOG.debug(msg)
1963 @na_utils.trace
1964 def _get_cifs_server_name(self, vserver_name):
1965 # 'cifs-server' is CIFS Server NetBIOS Name, max length is 15.
1966 # Should be unique within each domain (data['domain']).
1967 # Cut to 15 char with begin and end, attempt to make valid DNS hostname
1968 cifs_server = (vserver_name[0:8] +
1969 '-' +
1970 vserver_name[-6:]).replace('_', '-').upper()
1971 return cifs_server
1973 @na_utils.trace
1974 def configure_active_directory(self, security_service,
1975 vserver_name, aes_encryption):
1976 """Configures AD on Vserver."""
1977 self.configure_dns(security_service)
1978 self.configure_cifs_aes_encryption(aes_encryption)
1979 self.set_preferred_dc(security_service)
1981 cifs_server = self._get_cifs_server_name(vserver_name)
1983 api_args = {
1984 'admin-username': security_service['user'],
1985 'admin-password': security_service['password'],
1986 'force-account-overwrite': 'true',
1987 'cifs-server': cifs_server,
1988 'domain': security_service['domain'],
1989 }
1991 if security_service['ou'] is not None: 1991 ↛ 1993line 1991 didn't jump to line 1993 because the condition on line 1991 was always true
1992 api_args['organizational-unit'] = security_service['ou']
1993 if security_service.get('default_ad_site'):
1994 api_args['default-site'] = security_service['default_ad_site']
1996 try:
1997 LOG.debug("Trying to setup CIFS server with data: %s", api_args)
1998 self.send_request('cifs-server-create', api_args)
1999 except netapp_api.NaApiError as e:
2000 credential_msg = "could not authenticate"
2001 privilege_msg = "insufficient access"
2002 if (e.code == netapp_api.EAPIERROR and ( 2002 ↛ 2009line 2002 didn't jump to line 2009 because the condition on line 2002 was always true
2003 credential_msg in e.message.lower() or
2004 privilege_msg in e.message.lower())):
2005 auth_msg = _("Failed to create CIFS server entry. "
2006 "Please double check your user credentials "
2007 "or privileges. %s")
2008 raise exception.SecurityServiceFailedAuth(auth_msg % e.message)
2009 msg = _("Failed to create CIFS server entry. %s")
2010 raise exception.NetAppException(msg % e.message)
2012 @na_utils.trace
2013 def modify_active_directory_security_service(
2014 self, vserver_name, differring_keys, new_security_service,
2015 current_security_service):
2016 cifs_server = self._get_cifs_server_name(vserver_name)
2018 current_user_name = current_security_service['user']
2019 new_username = new_security_service['user']
2021 current_cifs_username = cifs_server + '\\' + current_user_name
2023 if 'password' in differring_keys: 2023 ↛ 2034line 2023 didn't jump to line 2034 because the condition on line 2023 was always true
2024 api_args = {
2025 'user-name': current_cifs_username,
2026 'user-password': new_security_service['password']
2027 }
2028 try:
2029 self.send_request('cifs-local-user-set-password', api_args)
2030 except netapp_api.NaApiError as e:
2031 msg = _("Failed to modify existing CIFS server password. %s")
2032 raise exception.NetAppException(msg % e.message)
2034 if 'user' in differring_keys: 2034 ↛ 2045line 2034 didn't jump to line 2045 because the condition on line 2034 was always true
2035 api_args = {
2036 'user-name': current_cifs_username,
2037 'new-user-name': new_username
2038 }
2039 try:
2040 self.send_request('cifs-local-user-rename', api_args)
2041 except netapp_api.NaApiError as e:
2042 msg = _("Failed to modify existing CIFS server user-name. %s")
2043 raise exception.NetAppException(msg % e.message)
2045 if 'default_ad_site' in differring_keys: 2045 ↛ 2064line 2045 didn't jump to line 2064 because the condition on line 2045 was always true
2046 if new_security_service['default_ad_site'] is not None:
2047 cifs_server = self._get_cifs_server_name(vserver_name)
2048 api_args = {
2049 'admin-username': new_security_service['user'],
2050 'admin-password': new_security_service['password'],
2051 'force-account-overwrite': 'true',
2052 'cifs-server': cifs_server,
2053 'default-site': new_security_service['default_ad_site']
2054 }
2055 try:
2056 LOG.debug("Trying to modify CIFS server with data: %s",
2057 api_args)
2058 self.send_request('cifs-server-modify', api_args)
2059 except netapp_api.NaApiError as e:
2060 msg = _("Failed to modify CIFS server entry. %s")
2061 raise exception.NetAppException(msg % e.message)
2062 self.configure_cifs_options(new_security_service)
2064 if 'server' in differring_keys: 2064 ↛ exitline 2064 didn't return from function 'modify_active_directory_security_service' because the condition on line 2064 was always true
2065 if current_security_service['server'] is not None:
2066 self.remove_preferred_dcs(current_security_service)
2068 if new_security_service['server'] is not None:
2069 self.set_preferred_dc(new_security_service)
2070 self.configure_cifs_options(new_security_service)
2072 @na_utils.trace
2073 def create_kerberos_realm(self, security_service):
2074 """Creates Kerberos realm on cluster."""
2076 if not self.features.KERBEROS_VSERVER: 2076 ↛ 2077line 2076 didn't jump to line 2077 because the condition on line 2076 was never true
2077 msg = _('Kerberos realms owned by Vserver are supported on ONTAP '
2078 '8.3 or later.')
2079 raise exception.NetAppException(msg)
2081 api_args = {
2082 'admin-server-ip': security_service['server'],
2083 'admin-server-port': '749',
2084 'clock-skew': '5',
2085 'comment': '',
2086 'kdc-ip': security_service['server'],
2087 'kdc-port': '88',
2088 'kdc-vendor': 'other',
2089 'password-server-ip': security_service['server'],
2090 'password-server-port': '464',
2091 'realm': security_service['domain'].upper(),
2092 }
2093 try:
2094 self.send_request('kerberos-realm-create', api_args)
2095 except netapp_api.NaApiError as e:
2096 if e.code == netapp_api.EDUPLICATEENTRY:
2097 LOG.debug('Kerberos realm config already exists.')
2098 else:
2099 msg = _('Failed to create Kerberos realm. %s')
2100 raise exception.NetAppException(msg % e.message)
2102 @na_utils.trace
2103 def configure_kerberos(self, security_service, vserver_name):
2104 """Configures Kerberos for NFS on Vserver."""
2106 if not self.features.KERBEROS_VSERVER: 2106 ↛ 2107line 2106 didn't jump to line 2107 because the condition on line 2106 was never true
2107 msg = _('Kerberos realms owned by Vserver are supported on ONTAP '
2108 '8.3 or later.')
2109 raise exception.NetAppException(msg)
2111 self.configure_dns(security_service)
2112 spn = self._get_kerberos_service_principal_name(
2113 security_service, vserver_name)
2115 lifs = self.list_network_interfaces()
2116 if not lifs:
2117 msg = _("Cannot set up Kerberos. There are no LIFs configured.")
2118 raise exception.NetAppException(msg)
2120 for lif_name in lifs:
2121 api_args = {
2122 'admin-password': security_service['password'],
2123 'admin-user-name': security_service['user'],
2124 'interface-name': lif_name,
2125 'is-kerberos-enabled': 'true',
2126 'service-principal-name': spn
2127 }
2129 self.send_request('kerberos-config-modify', api_args)
2131 @na_utils.trace
2132 def _get_kerberos_service_principal_name(self, security_service,
2133 vserver_name):
2134 return ('nfs/' + vserver_name.replace('_', '-') + '.' +
2135 security_service['domain'] + '@' +
2136 security_service['domain'].upper())
2138 @na_utils.trace
2139 def update_kerberos_realm(self, security_service):
2140 """Update Kerberos realm info. Only KDC IP can be changed."""
2141 if not self.features.KERBEROS_VSERVER: 2141 ↛ 2142line 2141 didn't jump to line 2142 because the condition on line 2141 was never true
2142 msg = _('Kerberos realms owned by Vserver are supported on ONTAP '
2143 '8.3 or later.')
2144 raise exception.NetAppException(msg)
2146 api_args = {
2147 'admin-server-ip': security_service['server'],
2148 'kdc-ip': security_service['server'],
2149 'password-server-ip': security_service['server'],
2150 'realm': security_service['domain'].upper(),
2151 }
2152 try:
2153 self.send_request('kerberos-realm-modify', api_args)
2154 except netapp_api.NaApiError as e:
2155 msg = _('Failed to update Kerberos realm. %s')
2156 raise exception.NetAppException(msg % e.message)
2158 @na_utils.trace
2159 def disable_kerberos(self, security_service):
2160 """Disable Kerberos in all Vserver LIFs."""
2162 lifs = self.list_network_interfaces()
2163 # NOTE(dviroel): If the Vserver has no LIFs, there are no Kerberos
2164 # to be disabled.
2165 for lif_name in lifs:
2166 api_args = {
2167 'admin-password': security_service['password'],
2168 'admin-user-name': security_service['user'],
2169 'interface-name': lif_name,
2170 'is-kerberos-enabled': 'false',
2171 }
2172 try:
2173 self.send_request('kerberos-config-modify', api_args)
2174 except netapp_api.NaApiError as e:
2175 disabled_msg = "Kerberos is already disabled"
2176 if (e.code == netapp_api.EAPIERROR and 2176 ↛ 2181line 2176 didn't jump to line 2181 because the condition on line 2176 was always true
2177 disabled_msg in e.message):
2178 # NOTE(dviroel): do not raise an error for 'Kerberos is
2179 # already disabled in this LIF'.
2180 continue
2181 msg = _("Failed to disable Kerberos: %s.")
2182 raise exception.NetAppException(msg % e.message)
2184 @na_utils.trace
2185 def is_kerberos_enabled(self):
2186 """Check if Kerberos in enabled in all LIFs."""
2188 if not self.features.KERBEROS_VSERVER: 2188 ↛ 2189line 2188 didn't jump to line 2189 because the condition on line 2188 was never true
2189 msg = _('Kerberos realms owned by Vserver are supported on ONTAP '
2190 '8.3 or later.')
2191 raise exception.NetAppException(msg)
2193 lifs_info = self.get_network_interfaces(protocols=['NFS', 'CIFS'])
2194 if len(lifs_info) == 0: 2194 ↛ 2195line 2194 didn't jump to line 2195 because the condition on line 2194 was never true
2195 LOG.debug("There are no LIFs configured for this Vserver. "
2196 "Kerberos is disabled.")
2197 return False
2199 # NOTE(dviroel): All LIFs must have kerberos enabled
2200 for lif in lifs_info:
2201 api_args = {
2202 'interface-name': lif.get('interface-name'),
2203 'desired-attributes': {
2204 'kerberos-config-info': {
2205 'is-kerberos-enabled': None,
2206 }
2207 }
2208 }
2209 result = None
2210 # Catch the exception in case kerberos is not configured with LIF.
2211 try:
2212 result = self.send_request('kerberos-config-get', api_args)
2213 except netapp_api.NaApiError as e:
2214 with excutils.save_and_reraise_exception() as exc_context:
2215 if "entry doesn't exist" in e.message:
2216 exc_context.reraise = False
2217 return False
2219 attributes = result.get_child_by_name('attributes')
2220 kerberos_info = attributes.get_child_by_name(
2221 'kerberos-config-info')
2222 kerberos_enabled = kerberos_info.get_child_content(
2223 'is-kerberos-enabled')
2224 if kerberos_enabled == 'false': 2224 ↛ 2225line 2224 didn't jump to line 2225 because the condition on line 2224 was never true
2225 return False
2227 return True
2229 @na_utils.trace
2230 def configure_dns(self, security_service):
2231 """Configure DNS address and servers for a vserver."""
2232 api_args = {
2233 'domains': [],
2234 'name-servers': [],
2235 'dns-state': 'enabled',
2236 }
2237 # NOTE(dviroel): Read the current dns configuration and merge with the
2238 # new one. This scenario is expected when 2 security services provide
2239 # a DNS configuration, like 'active_directory' and 'ldap'.
2240 current_dns_config = self.get_dns_config()
2241 domains = set(current_dns_config.get('domains', []))
2242 dns_ips = set(current_dns_config.get('dns-ips', []))
2244 domains.add(security_service['domain'])
2245 for domain in domains:
2246 api_args['domains'].append({'string': domain})
2248 for dns_ip in security_service['dns_ip'].split(','):
2249 dns_ips.add(dns_ip.strip())
2250 for dns_ip in dns_ips:
2251 api_args['name-servers'].append({'ip-address': dns_ip})
2253 try:
2254 if current_dns_config:
2255 self.send_request('net-dns-modify', api_args)
2256 else:
2257 self.send_request('net-dns-create', api_args)
2258 except netapp_api.NaApiError as e:
2259 msg = _("Failed to configure DNS. %s")
2260 raise exception.NetAppException(msg % e.message)
2262 @na_utils.trace
2263 def get_dns_config(self):
2264 """Read DNS servers and domains currently configured in the vserver·"""
2265 api_args = {}
2266 try:
2267 result = self.send_request('net-dns-get', api_args)
2268 except netapp_api.NaApiError as e:
2269 if e.code == netapp_api.EOBJECTNOTFOUND:
2270 return {}
2271 msg = _("Failed to retrieve DNS configuration. %s")
2272 raise exception.NetAppException(msg % e.message)
2274 dns_config = {}
2275 attributes = result.get_child_by_name('attributes')
2276 dns_info = attributes.get_child_by_name('net-dns-info')
2278 dns_config['dns-state'] = dns_info.get_child_content(
2279 'dns-state')
2280 domains = dns_info.get_child_by_name(
2281 'domains') or netapp_api.NaElement('None')
2282 dns_config['domains'] = [domain.get_content()
2283 for domain in domains.get_children()]
2285 servers = dns_info.get_child_by_name(
2286 'name-servers') or netapp_api.NaElement('None')
2287 dns_config['dns-ips'] = [server.get_content()
2288 for server in servers.get_children()]
2289 return dns_config
2291 @na_utils.trace
2292 def update_dns_configuration(self, dns_ips, domains):
2293 """Overrides DNS configuration with the specified IPs and domains."""
2294 current_dns_config = self.get_dns_config()
2295 api_args = {
2296 'domains': [],
2297 'name-servers': [],
2298 'dns-state': 'enabled',
2299 }
2300 for domain in domains:
2301 api_args['domains'].append({'string': domain})
2303 for dns_ip in dns_ips:
2304 api_args['name-servers'].append({'ip-address': dns_ip})
2306 empty_dns_config = (not api_args['domains'] and
2307 not api_args['name-servers'])
2308 if current_dns_config:
2309 api_name, api_args = (
2310 ('net-dns-destroy', {}) if empty_dns_config
2311 else ('net-dns-modify', api_args))
2312 else:
2313 api_name, api_args = 'net-dns-create', api_args
2315 try:
2316 self.send_request(api_name, api_args)
2317 except netapp_api.NaApiError as e:
2318 msg = _("Failed to update DNS configuration. %s")
2319 raise exception.NetAppException(msg % e.message)
2321 @na_utils.trace
2322 def configure_cifs_options(self, security_service):
2323 if security_service.get('server'):
2324 api_args = {'mode': 'none'}
2325 elif security_service.get('default_ad_site'):
2326 api_args = {'mode': 'site'}
2327 else:
2328 api_args = {'mode': 'all'}
2330 try:
2331 self.send_request(
2332 'cifs-domain-server-discovery-mode-modify',
2333 api_args)
2334 except netapp_api.NaApiError as e:
2335 msg = ('Failed to set cifs domain server discovery mode to '
2336 '%(mode)s. Exception: %(exception)s')
2337 msg_args = {'mode': api_args['mode'], 'exception': e.message}
2338 LOG.warning(msg, msg_args)
2340 @na_utils.trace
2341 def configure_cifs_aes_encryption(self, aes_encryption):
2342 if self.features.AES_ENCRYPTION_TYPES:
2343 if aes_encryption:
2344 api_args = {
2345 'advertised-enc-types': [{'cifskrbenctypes': 'aes_128'},
2346 {'cifskrbenctypes': 'aes_256'}]
2347 }
2348 else:
2349 api_args = {
2350 'advertised-enc-types': [{'cifskrbenctypes': 'des'},
2351 {'cifskrbenctypes': 'rc4'}]
2352 }
2353 else:
2354 api_args = {
2355 'is-aes-encryption-enabled': (
2356 'true' if aes_encryption else 'false'),
2357 }
2359 try:
2360 self.send_request('cifs-security-modify', api_args)
2361 except netapp_api.NaApiError as e:
2362 msg = _("Failed to set aes encryption. %s")
2363 raise exception.NetAppException(msg % e.message)
2365 @na_utils.trace
2366 def set_preferred_dc(self, security_service):
2367 # server is optional
2368 if not security_service['server']:
2369 return
2371 api_args = {
2372 'preferred-dc': [],
2373 'domain': security_service['domain'],
2374 }
2376 for dc_ip in security_service['server'].split(','):
2377 api_args['preferred-dc'].append({'string': dc_ip.strip()})
2379 if self.features.CIFS_DC_ADD_SKIP_CHECK:
2380 api_args['skip-config-validation'] = 'false'
2382 try:
2383 self.send_request('cifs-domain-preferred-dc-add', api_args)
2384 except netapp_api.NaApiError as e:
2385 msg = _("Failed to set preferred DC. %s")
2386 raise exception.NetAppException(msg % e.message)
2388 @na_utils.trace
2389 def remove_preferred_dcs(self, security_service):
2390 """Drops all preferred DCs at once."""
2392 api_args = {
2393 'domain': security_service['domain'],
2394 }
2396 try:
2397 self.send_request('cifs-domain-preferred-dc-remove', api_args)
2398 except netapp_api.NaApiError as e:
2399 msg = _("Failed to unset preferred DCs. %s")
2400 raise exception.NetAppException(msg % e.message)
2402 @na_utils.trace
2403 def create_volume(self, aggregate_name, volume_name, size_gb,
2404 thin_provisioned=False, snapshot_policy=None,
2405 language=None, dedup_enabled=False,
2406 compression_enabled=False, max_files=None,
2407 snapshot_reserve=None, volume_type='rw',
2408 qos_policy_group=None, adaptive_qos_policy_group=None,
2409 encrypt=False, mount_point_name=None,
2410 snaplock_type=None, **options):
2411 """Creates a volume."""
2412 if adaptive_qos_policy_group and not self.features.ADAPTIVE_QOS:
2413 msg = 'Adaptive QoS not supported on this backend ONTAP version.'
2414 raise exception.NetAppException(msg)
2416 api_args = {
2417 'containing-aggr-name': aggregate_name,
2418 'size': str(size_gb) + 'g',
2419 'volume': volume_name,
2420 }
2421 api_args.update(self._get_create_volume_api_args(
2422 volume_name, thin_provisioned, snapshot_policy, language,
2423 snapshot_reserve, volume_type, qos_policy_group, encrypt,
2424 adaptive_qos_policy_group, mount_point_name, snaplock_type))
2426 self.send_request('volume-create', api_args)
2428 efficiency_policy = options.get('efficiency_policy', None)
2429 self.update_volume_efficiency_attributes(
2430 volume_name, dedup_enabled, compression_enabled,
2431 efficiency_policy=efficiency_policy
2432 )
2434 if volume_type != 'dp': 2434 ↛ 2444line 2434 didn't jump to line 2444 because the condition on line 2434 was always true
2435 if options.get('max_files_multiplier') is not None: 2435 ↛ 2436line 2435 didn't jump to line 2436 because the condition on line 2435 was never true
2436 max_files_multiplier = options.pop('max_files_multiplier')
2437 max_files = na_utils.calculate_max_files(size_gb,
2438 max_files_multiplier,
2439 max_files)
2441 if max_files is not None:
2442 self.set_volume_max_files(volume_name, max_files)
2444 if snaplock_type is not None:
2445 self.set_snaplock_attributes(volume_name, **options)
2447 @na_utils.trace
2448 def create_volume_async(self, aggregate_list, volume_name, size_gb,
2449 thin_provisioned=False, snapshot_policy=None,
2450 language=None, snapshot_reserve=None,
2451 volume_type='rw', qos_policy_group=None,
2452 encrypt=False, adaptive_qos_policy_group=None,
2453 auto_provisioned=False, mount_point_name=None,
2454 snaplock_type=None, **options):
2455 """Creates a volume asynchronously."""
2457 if adaptive_qos_policy_group and not self.features.ADAPTIVE_QOS:
2458 msg = 'Adaptive QoS not supported on this backend ONTAP version.'
2459 raise exception.NetAppException(msg)
2461 api_args = {
2462 'size': size_gb * units.Gi,
2463 'volume-name': volume_name,
2464 }
2465 if auto_provisioned:
2466 api_args['auto-provision-as'] = 'flexgroup'
2467 else:
2468 api_args['aggr-list'] = [{'aggr-name': aggr}
2469 for aggr in aggregate_list]
2470 api_args.update(self._get_create_volume_api_args(
2471 volume_name, thin_provisioned, snapshot_policy, language,
2472 snapshot_reserve, volume_type, qos_policy_group, encrypt,
2473 adaptive_qos_policy_group, mount_point_name, snaplock_type))
2475 result = self.send_request('volume-create-async', api_args)
2476 job_info = {
2477 'jobid': result.get_child_content('result-jobid'),
2478 'error-code': result.get_child_content('result-error-code'),
2479 'error-message': result.get_child_content('result-error-message')
2480 }
2481 return job_info
2483 def _get_create_volume_api_args(self, volume_name, thin_provisioned,
2484 snapshot_policy, language,
2485 snapshot_reserve, volume_type,
2486 qos_policy_group, encrypt,
2487 adaptive_qos_policy_group,
2488 mount_point_name=None,
2489 snaplock_type=None):
2490 api_args = {
2491 'volume-type': volume_type,
2492 'space-reserve': ('none' if thin_provisioned else 'volume'),
2493 }
2494 if volume_type != 'dp':
2495 api_args['junction-path'] = '/%s' % (mount_point_name
2496 or volume_name)
2497 if snapshot_policy is not None:
2498 api_args['snapshot-policy'] = snapshot_policy
2499 if language is not None:
2500 api_args['language-code'] = language
2501 if snapshot_reserve is not None:
2502 api_args['percentage-snapshot-reserve'] = str(snapshot_reserve)
2503 if qos_policy_group is not None:
2504 api_args['qos-policy-group-name'] = qos_policy_group
2505 if adaptive_qos_policy_group is not None:
2506 api_args['qos-adaptive-policy-group-name'] = (
2507 adaptive_qos_policy_group)
2509 if encrypt is True:
2510 if not self.features.FLEXVOL_ENCRYPTION:
2511 msg = 'Flexvol encryption is not supported on this backend.'
2512 raise exception.NetAppException(msg)
2513 else:
2514 api_args['encrypt'] = 'true'
2515 else:
2516 api_args['encrypt'] = 'false'
2518 if snaplock_type is not None:
2519 api_args['snaplock-type'] = snaplock_type
2521 return api_args
2523 @na_utils.trace
2524 def update_volume_snapshot_policy(self, volume_name, snapshot_policy):
2525 """Set snapshot policy for the specified volume."""
2526 api_args = {
2527 'query': {
2528 'volume-attributes': {
2529 'volume-id-attributes': {
2530 'name': volume_name,
2531 },
2532 },
2533 },
2534 'attributes': {
2535 'volume-attributes': {
2536 'volume-snapshot-attributes': {
2537 'snapshot-policy': snapshot_policy,
2538 },
2539 },
2540 },
2541 }
2542 self.send_request('volume-modify-iter', api_args)
2544 @na_utils.trace
2545 @manila_utils.retry(retry_param=exception.NetAppException,
2546 interval=3,
2547 retries=5,
2548 backoff_rate=1)
2549 def enable_dedup(self, volume_name):
2550 """Enable deduplication on volume."""
2551 api_args = {'path': '/vol/%s' % volume_name}
2552 try:
2553 self.send_request('sis-enable', api_args)
2554 return
2555 except netapp_api.NaApiError as e:
2556 enabled_msg = "has already been enabled"
2557 if (e.code == netapp_api.OPERATION_ALREADY_ENABLED and
2558 enabled_msg in e.message):
2559 return
2560 active_msg = "sis operation is currently active"
2561 if (e.code == netapp_api.OPERATION_ALREADY_ENABLED and 2561 ↛ 2567line 2561 didn't jump to line 2567 because the condition on line 2561 was always true
2562 active_msg in e.message):
2563 msg = _('Unable to enable dedup. Will retry the '
2564 'operation. Error details: %s') % e.message
2565 LOG.warning(msg)
2566 raise exception.NetAppException(msg=msg)
2567 raise e
2569 @na_utils.trace
2570 @manila_utils.retry(retry_param=exception.NetAppException,
2571 interval=3,
2572 retries=5,
2573 backoff_rate=1)
2574 def disable_dedup(self, volume_name):
2575 """Disable deduplication on volume."""
2576 api_args = {'path': '/vol/%s' % volume_name}
2577 try:
2578 self.send_request('sis-disable', api_args)
2579 return
2580 except netapp_api.NaApiError as e:
2581 active_msg = "sis operation is currently active"
2582 if (e.code == netapp_api.OPERATION_ALREADY_ENABLED and 2582 ↛ 2588line 2582 didn't jump to line 2588 because the condition on line 2582 was always true
2583 active_msg in e.message):
2584 msg = _('Unable to disable dedup. Will retry the '
2585 'operation. Error details: %s') % e.message
2586 LOG.warning(msg)
2587 raise exception.NetAppException(msg=msg)
2588 raise e
2590 @na_utils.trace
2591 def enable_compression(self, volume_name):
2592 """Enable compression on volume."""
2593 api_args = {
2594 'path': '/vol/%s' % volume_name,
2595 'enable-compression': 'true'
2596 }
2597 self.send_request('sis-set-config', api_args)
2599 @na_utils.trace
2600 def disable_compression(self, volume_name):
2601 """Disable compression on volume."""
2602 api_args = {
2603 'path': '/vol/%s' % volume_name,
2604 'enable-compression': 'false'
2605 }
2606 self.send_request('sis-set-config', api_args)
2608 @na_utils.trace
2609 def enable_dedupe_async(self, volume_name):
2610 """Enable deduplication on FlexVol/FlexGroup volume asynchronously."""
2611 api_args = {'volume-name': volume_name}
2612 self.send_request('sis-enable-async', api_args)
2614 @na_utils.trace
2615 def disable_dedupe_async(self, volume_name):
2616 """Disable deduplication on FlexVol/FlexGroup volume asynchronously."""
2617 api_args = {'volume-name': volume_name}
2618 self.send_request('sis-disable-async', api_args)
2620 @na_utils.trace
2621 def enable_compression_async(self, volume_name):
2622 """Enable compression on FlexVol/FlexGroup volume asynchronously."""
2623 api_args = {
2624 'volume-name': volume_name,
2625 'enable-compression': 'true'
2626 }
2627 self.send_request('sis-set-config-async', api_args)
2629 @na_utils.trace
2630 def disable_compression_async(self, volume_name):
2631 """Disable compression on FlexVol/FlexGroup volume asynchronously."""
2632 api_args = {
2633 'volume-name': volume_name,
2634 'enable-compression': 'false'
2635 }
2636 self.send_request('sis-set-config-async', api_args)
2638 @na_utils.trace
2639 def apply_volume_efficiency_policy(self, volume_name,
2640 efficiency_policy=None):
2641 """Apply efficiency policy to FlexVol/FlexGroup volume."""
2642 if efficiency_policy:
2643 api_args = {
2644 'path': f'/vol/{volume_name}',
2645 'policy-name': efficiency_policy
2646 }
2647 self.send_request('sis-set-config', api_args)
2649 @na_utils.trace
2650 def apply_volume_efficiency_policy_async(self, volume_name,
2651 efficiency_policy=None):
2652 """Apply efficiency policy to FlexVol volume asynchronously."""
2653 if efficiency_policy:
2654 api_args = {
2655 'path': f'/vol/{volume_name}',
2656 'policy-name': efficiency_policy
2657 }
2658 self.connection.send_request('sis-set-config-async', api_args)
2660 @na_utils.trace
2661 def get_volume_efficiency_status(self, volume_name):
2662 """Get dedupe & compression status for a volume."""
2663 api_args = {
2664 'query': {
2665 'sis-status-info': {
2666 'path': '/vol/%s' % volume_name,
2667 },
2668 },
2669 'desired-attributes': {
2670 'sis-status-info': {
2671 'state': None,
2672 'is-compression-enabled': None,
2673 },
2674 },
2675 }
2676 try:
2677 result = self.send_iter_request('sis-get-iter', api_args)
2678 attributes_list = result.get_child_by_name(
2679 'attributes-list') or netapp_api.NaElement('none')
2680 sis_status_info = attributes_list.get_child_by_name(
2681 'sis-status-info') or netapp_api.NaElement('none')
2682 except exception.NetAppException:
2683 msg = _('Failed to get volume efficiency status for %s.')
2684 LOG.error(msg, volume_name)
2685 sis_status_info = netapp_api.NaElement('none')
2687 return {
2688 'dedupe': True if 'enabled' == sis_status_info.get_child_content(
2689 'state') else False,
2690 'compression': True if 'true' == sis_status_info.get_child_content(
2691 'is-compression-enabled') else False,
2692 }
2694 @na_utils.trace
2695 def set_volume_max_files(self, volume_name, max_files,
2696 retry_allocated=False):
2697 """Set flexvol file limit."""
2698 api_args = {
2699 'query': {
2700 'volume-attributes': {
2701 'volume-id-attributes': {
2702 'name': volume_name,
2703 },
2704 },
2705 },
2706 'attributes': {
2707 'volume-attributes': {
2708 'volume-inode-attributes': {
2709 'files-total': max_files,
2710 },
2711 },
2712 },
2713 }
2714 result = self.send_request('volume-modify-iter', api_args)
2715 failures = result.get_child_content('num-failed')
2716 if failures and int(failures) > 0: 2716 ↛ 2717line 2716 didn't jump to line 2717 because the condition on line 2716 was never true
2717 failure_list = result.get_child_by_name(
2718 'failure-list') or netapp_api.NaElement('none')
2719 errors = failure_list.get_children()
2720 if not errors:
2721 return
2722 error_code = errors[0].get_child_content('error-code')
2723 if retry_allocated:
2724 if error_code == netapp_api.EVOLOPNOTSUPP:
2725 alloc_files = self.get_volume_allocated_files(
2726 volume_name)
2727 new_max_files = alloc_files['used']
2728 # no need to act if current max files are set to
2729 # allocated files
2730 if new_max_files == alloc_files['max']:
2731 return
2733 msg = _('Set higher max files %(new_max_files)s '
2734 'on %(vol)s. The current allocated inodes '
2735 'are larger than requested %(max_files)s.')
2736 msg_args = {'vol': volume_name,
2737 'max_files': max_files,
2738 'new_max_files': new_max_files}
2739 LOG.info(msg, msg_args)
2740 self.set_volume_max_files(volume_name, new_max_files,
2741 retry_allocated=False)
2742 else:
2743 raise netapp_api.NaApiError(
2744 error_code,
2745 errors[0].get_child_content('error-message'))
2747 @na_utils.trace
2748 def set_volume_size(self, volume_name, size_gb):
2749 """Set volume size."""
2750 api_args = {
2751 'query': {
2752 'volume-attributes': {
2753 'volume-id-attributes': {
2754 'name': volume_name,
2755 },
2756 },
2757 },
2758 'attributes': {
2759 'volume-attributes': {
2760 'volume-space-attributes': {
2761 'size': int(size_gb) * units.Gi,
2762 },
2763 },
2764 },
2765 }
2766 result = self.send_request('volume-modify-iter', api_args)
2767 failures = result.get_child_content('num-failed')
2768 if failures and int(failures) > 0:
2769 failure_list = result.get_child_by_name(
2770 'failure-list') or netapp_api.NaElement('none')
2771 errors = failure_list.get_children()
2772 if errors: 2772 ↛ exitline 2772 didn't return from function 'set_volume_size' because the condition on line 2772 was always true
2773 raise netapp_api.NaApiError(
2774 errors[0].get_child_content('error-code'),
2775 errors[0].get_child_content('error-message'))
2777 @na_utils.trace
2778 def set_volume_snapdir_access(self, volume_name, hide_snapdir):
2779 """Set volume snapshot directory visibility."""
2780 api_args = {
2781 'query': {
2782 'volume-attributes': {
2783 'volume-id-attributes': {
2784 'name': volume_name,
2785 },
2786 },
2787 },
2788 'attributes': {
2789 'volume-attributes': {
2790 'volume-snapshot-attributes': {
2791 'snapdir-access-enabled': str(
2792 not hide_snapdir).lower(),
2793 },
2794 },
2795 },
2796 }
2797 result = self.send_request('volume-modify-iter', api_args)
2798 failures = result.get_child_content('num-failed')
2799 if failures and int(failures) > 0: 2799 ↛ 2800line 2799 didn't jump to line 2800 because the condition on line 2799 was never true
2800 failure_list = result.get_child_by_name(
2801 'failure-list') or netapp_api.NaElement('none')
2802 errors = failure_list.get_children()
2803 if errors:
2804 raise netapp_api.NaApiError(
2805 errors[0].get_child_content('error-code'),
2806 errors[0].get_child_content('error-message'))
2808 @na_utils.trace
2809 def set_volume_filesys_size_fixed(self,
2810 volume_name, filesys_size_fixed=False):
2811 """Set volume file system size fixed to true/false."""
2812 api_args = {
2813 'query': {
2814 'volume-attributes': {
2815 'volume-id-attributes': {
2816 'name': volume_name,
2817 },
2818 },
2819 },
2820 'attributes': {
2821 'volume-attributes': {
2822 'volume-space-attributes': {
2823 'is-filesys-size-fixed': str(
2824 filesys_size_fixed).lower(),
2825 },
2826 },
2827 },
2828 }
2829 result = self.send_request('volume-modify-iter', api_args)
2830 failures = result.get_child_content('num-failed')
2831 if failures and int(failures) > 0: 2831 ↛ 2832line 2831 didn't jump to line 2832 because the condition on line 2831 was never true
2832 failure_list = result.get_child_by_name(
2833 'failure-list') or netapp_api.NaElement('none')
2834 errors = failure_list.get_children()
2835 if errors:
2836 raise netapp_api.NaApiError(
2837 errors[0].get_child_content('error-code'),
2838 errors[0].get_child_content('error-message'))
2840 @na_utils.trace
2841 def set_volume_security_style(self, volume_name, security_style='unix'):
2842 """Set volume security style"""
2843 api_args = {
2844 'query': {
2845 'volume-attributes': {
2846 'volume-id-attributes': {
2847 'name': volume_name,
2848 },
2849 },
2850 },
2851 'attributes': {
2852 'volume-attributes': {
2853 'volume-security-attributes': {
2854 'style': security_style,
2855 },
2856 },
2857 },
2858 }
2859 result = self.send_request('volume-modify-iter', api_args)
2860 failures = result.get_child_content('num-failed')
2861 if failures and int(failures) > 0:
2862 failure_list = result.get_child_by_name(
2863 'failure-list') or netapp_api.NaElement('none')
2864 errors = failure_list.get_children()
2865 if errors: 2865 ↛ exitline 2865 didn't return from function 'set_volume_security_style' because the condition on line 2865 was always true
2866 raise netapp_api.NaApiError(
2867 errors[0].get_child_content('error-code'),
2868 errors[0].get_child_content('error-message'))
2870 @na_utils.trace
2871 def set_volume_name(self, volume_name, new_volume_name):
2872 """Set flexvol name."""
2873 api_args = {
2874 'volume': volume_name,
2875 'new-volume-name': new_volume_name,
2876 }
2877 self.send_request('volume-rename', api_args)
2879 @na_utils.trace
2880 def rename_vserver(self, vserver_name, new_vserver_name):
2881 """Rename a vserver."""
2882 api_args = {
2883 'vserver-name': vserver_name,
2884 'new-name': new_vserver_name,
2885 }
2886 self.send_request('vserver-rename', api_args)
2888 @na_utils.trace
2889 def modify_volume(self, aggregate_name, volume_name,
2890 thin_provisioned=False, snapshot_policy=None,
2891 language=None, dedup_enabled=False,
2892 compression_enabled=False, max_files=None,
2893 qos_policy_group=None, hide_snapdir=None,
2894 autosize_attributes=None,
2895 adaptive_qos_policy_group=None, **options):
2896 """Update backend volume for a share as necessary.
2898 :param aggregate_name: either a list or a string. List for aggregate
2899 names where the FlexGroup resides, while a string for the aggregate
2900 name where FlexVol volume is.
2901 :param volume_name: name of the modified volume.
2902 :param thin_provisioned: volume is thin.
2903 :param snapshot_policy: policy of volume snapshot.
2904 :param language: language of the volume.
2905 :param dedup_enabled: is the deduplication enabled for the volume.
2906 :param compression_enabled: is the compression enabled for the volume.
2907 :param max_files: number of maximum files in the volume.
2908 :param qos_policy_group: name of the QoS policy.
2909 :param hide_snapdir: hide snapshot directory.
2910 :param autosize_attributes: autosize for the volume.
2911 :param adaptive_qos_policy_group: name of the adaptive QoS policy.
2912 """
2914 if adaptive_qos_policy_group and not self.features.ADAPTIVE_QOS: 2914 ↛ 2915line 2914 didn't jump to line 2915 because the condition on line 2914 was never true
2915 msg = 'Adaptive QoS not supported on this backend ONTAP version.'
2916 raise exception.NetAppException(msg)
2918 api_args = {
2919 'query': {
2920 'volume-attributes': {
2921 'volume-id-attributes': {
2922 'name': volume_name,
2923 },
2924 },
2925 },
2926 'attributes': {
2927 'volume-attributes': {
2928 'volume-inode-attributes': {},
2929 'volume-language-attributes': {},
2930 'volume-snapshot-attributes': {},
2931 'volume-autosize-attributes': (autosize_attributes
2932 if autosize_attributes
2933 else {}),
2934 'volume-space-attributes': {
2935 'space-guarantee': ('none' if thin_provisioned else
2936 'volume'),
2937 },
2938 },
2939 },
2940 }
2941 if isinstance(aggregate_name, str):
2942 is_flexgroup = False
2943 api_args['query']['volume-attributes']['volume-id-attributes'][
2944 'containing-aggregate-name'] = aggregate_name
2945 elif isinstance(aggregate_name, list): 2945 ↛ 2951line 2945 didn't jump to line 2951 because the condition on line 2945 was always true
2946 is_flexgroup = True
2947 aggr_list = [{'aggr-name': aggr_name} for aggr_name in
2948 aggregate_name]
2949 api_args['query']['volume-attributes']['volume-id-attributes'][
2950 'aggr-list'] = aggr_list
2951 if language:
2952 api_args['attributes']['volume-attributes'][
2953 'volume-language-attributes']['language'] = language
2954 if max_files:
2955 api_args['attributes']['volume-attributes'][
2956 'volume-inode-attributes']['files-total'] = max_files
2957 if snapshot_policy:
2958 api_args['attributes']['volume-attributes'][
2959 'volume-snapshot-attributes'][
2960 'snapshot-policy'] = snapshot_policy
2961 if qos_policy_group:
2962 api_args['attributes']['volume-attributes'][
2963 'volume-qos-attributes'] = {
2964 'policy-group-name': qos_policy_group,
2965 }
2966 if adaptive_qos_policy_group:
2967 api_args['attributes']['volume-attributes'][
2968 'volume-qos-attributes'] = {
2969 'adaptive-policy-group-name': adaptive_qos_policy_group,
2970 }
2971 if hide_snapdir in (True, False):
2972 # Value of hide_snapdir needs to be inverted for ZAPI parameter
2973 api_args['attributes']['volume-attributes'][
2974 'volume-snapshot-attributes'][
2975 'snapdir-access-enabled'] = str(
2976 not hide_snapdir).lower()
2978 self.send_request('volume-modify-iter', api_args)
2979 efficiency_policy = options.get('efficiency_policy', None)
2980 # Efficiency options must be handled separately
2981 self.update_volume_efficiency_attributes(
2982 volume_name, dedup_enabled, compression_enabled,
2983 is_flexgroup=is_flexgroup, efficiency_policy=efficiency_policy
2984 )
2985 if self._is_snaplock_enabled_volume(volume_name): 2985 ↛ exitline 2985 didn't return from function 'modify_volume' because the condition on line 2985 was always true
2986 self.set_snaplock_attributes(volume_name, **options)
2988 @na_utils.trace
2989 def update_volume_efficiency_attributes(self, volume_name, dedup_enabled,
2990 compression_enabled,
2991 is_flexgroup=False,
2992 efficiency_policy=None):
2993 """Update dedupe & compression attributes to match desired values."""
2994 efficiency_status = self.get_volume_efficiency_status(volume_name)
2996 # cDOT compression requires dedup to be enabled
2997 dedup_enabled = dedup_enabled or compression_enabled
2999 # enable/disable dedup if needed
3000 if dedup_enabled and not efficiency_status['dedupe']:
3001 if is_flexgroup:
3002 self.enable_dedupe_async(volume_name)
3003 else:
3004 self.enable_dedup(volume_name)
3005 elif not dedup_enabled and efficiency_status['dedupe']:
3006 if is_flexgroup:
3007 self.disable_dedupe_async(volume_name)
3008 else:
3009 self.disable_dedup(volume_name)
3011 # enable/disable compression if needed
3012 if compression_enabled and not efficiency_status['compression']:
3013 if is_flexgroup:
3014 self.enable_compression_async(volume_name)
3015 else:
3016 self.enable_compression(volume_name)
3017 elif not compression_enabled and efficiency_status['compression']:
3018 if is_flexgroup:
3019 self.disable_compression_async(volume_name)
3020 else:
3021 self.disable_compression(volume_name)
3023 if is_flexgroup:
3024 self.apply_volume_efficiency_policy_async(
3025 volume_name, efficiency_policy=efficiency_policy)
3026 else:
3027 self.apply_volume_efficiency_policy(
3028 volume_name, efficiency_policy=efficiency_policy)
3030 @na_utils.trace
3031 def volume_exists(self, volume_name):
3032 """Checks if volume exists."""
3033 LOG.debug('Checking if volume %s exists', volume_name)
3035 api_args = {
3036 'query': {
3037 'volume-attributes': {
3038 'volume-id-attributes': {
3039 'name': volume_name,
3040 },
3041 },
3042 },
3043 'desired-attributes': {
3044 'volume-attributes': {
3045 'volume-id-attributes': {
3046 'name': None,
3047 },
3048 },
3049 },
3050 }
3051 result = self.send_iter_request('volume-get-iter', api_args)
3052 return self._has_records(result)
3054 @na_utils.trace
3055 def is_flexvol_encrypted(self, volume_name, vserver_name):
3056 """Checks whether the volume is encrypted or not."""
3058 if not self.features.FLEXVOL_ENCRYPTION:
3059 return False
3061 api_args = {
3062 'query': {
3063 'volume-attributes': {
3064 'encrypt': 'true',
3065 'volume-id-attributes': {
3066 'name': volume_name,
3067 'owning-vserver-name': vserver_name,
3068 },
3069 },
3070 },
3071 'desired-attributes': {
3072 'volume-attributes': {
3073 'encrypt': None,
3074 },
3075 },
3076 }
3077 result = self.send_iter_request('volume-get-iter', api_args)
3078 if self._has_records(result): 3078 ↛ 3087line 3078 didn't jump to line 3087 because the condition on line 3078 was always true
3079 attributes_list = result.get_child_by_name(
3080 'attributes-list') or netapp_api.NaElement('none')
3081 volume_attributes = attributes_list.get_child_by_name(
3082 'volume-attributes') or netapp_api.NaElement('none')
3083 encrypt = volume_attributes.get_child_content('encrypt')
3084 if encrypt:
3085 return True
3087 return False
3089 @na_utils.trace
3090 def get_aggregate_for_volume(self, volume_name):
3091 """Get the name of the aggregate containing a volume."""
3093 api_args = {
3094 'query': {
3095 'volume-attributes': {
3096 'volume-id-attributes': {
3097 'name': volume_name,
3098 },
3099 },
3100 },
3101 'desired-attributes': {
3102 'volume-attributes': {
3103 'volume-id-attributes': {
3104 'aggr-list': {
3105 'aggr-name': None,
3106 },
3107 'containing-aggregate-name': None,
3108 'name': None,
3109 },
3110 },
3111 },
3112 }
3113 result = self.send_iter_request('volume-get-iter', api_args)
3115 attributes_list = result.get_child_by_name(
3116 'attributes-list') or netapp_api.NaElement('none')
3117 volume_attributes = attributes_list.get_child_by_name(
3118 'volume-attributes') or netapp_api.NaElement('none')
3119 volume_id_attributes = volume_attributes.get_child_by_name(
3120 'volume-id-attributes') or netapp_api.NaElement('none')
3122 aggregate = volume_id_attributes.get_child_content(
3123 'containing-aggregate-name')
3124 if not aggregate:
3125 aggr_list_attr = volume_id_attributes.get_child_by_name(
3126 'aggr-list') or netapp_api.NaElement('none')
3127 aggregate = [aggr_elem.get_content()
3128 for aggr_elem in aggr_list_attr.get_children()]
3130 if not aggregate:
3131 msg = _('Could not find aggregate for volume %s.')
3132 raise exception.NetAppException(msg % volume_name)
3134 return aggregate
3136 @na_utils.trace
3137 def volume_has_luns(self, volume_name):
3138 """Checks if volume has LUNs."""
3139 LOG.debug('Checking if volume %s has LUNs', volume_name)
3141 api_args = {
3142 'query': {
3143 'lun-info': {
3144 'volume': volume_name,
3145 },
3146 },
3147 'desired-attributes': {
3148 'lun-info': {
3149 'path': None,
3150 },
3151 },
3152 }
3153 result = self.send_iter_request('lun-get-iter', api_args)
3154 return self._has_records(result)
3156 @na_utils.trace
3157 def volume_has_junctioned_volumes(self, junction_path):
3158 """Checks if volume has volumes mounted beneath its junction path."""
3159 if not junction_path:
3160 return False
3162 api_args = {
3163 'query': {
3164 'volume-attributes': {
3165 'volume-id-attributes': {
3166 'junction-path': junction_path + '/*',
3167 },
3168 },
3169 },
3170 'desired-attributes': {
3171 'volume-attributes': {
3172 'volume-id-attributes': {
3173 'name': None,
3174 },
3175 },
3176 },
3177 }
3178 result = self.send_iter_request('volume-get-iter', api_args)
3179 return self._has_records(result)
3181 @na_utils.trace
3182 def get_volume_autosize_attributes(self, volume_name):
3183 """Returns autosize attributes for a given volume name."""
3184 api_args = {
3185 'volume': volume_name,
3186 }
3188 result = self.send_request('volume-autosize-get', api_args)
3189 # NOTE(dviroel): 'is-enabled' is deprecated since ONTAP 8.2, use 'mode'
3190 # to identify if autosize is enabled or not.
3191 return {
3192 'mode': result.get_child_content('mode'),
3193 'grow-threshold-percent': result.get_child_content(
3194 'grow-threshold-percent'),
3195 'shrink-threshold-percent': result.get_child_content(
3196 'shrink-threshold-percent'),
3197 'maximum-size': result.get_child_content('maximum-size'),
3198 'minimum-size': result.get_child_content('minimum-size'),
3199 }
3201 @na_utils.trace
3202 def get_volume_snapshot_attributes(self, volume_name):
3203 """Returns snapshot attributes"""
3205 desired_snapshot_attributes = {
3206 'snapshot-policy': None,
3207 'snapdir-access-enabled': None,
3208 }
3209 api_args = {
3210 'query': {
3211 'volume-attributes': {
3212 'volume-id-attributes': {
3213 'name': volume_name,
3214 },
3215 },
3216 },
3217 'desired-attributes': {
3218 'volume-attributes': {
3219 'volume-snapshot-attributes': desired_snapshot_attributes,
3220 },
3221 },
3222 }
3224 result = self.send_request('volume-get-iter', api_args)
3225 attributes_list = result.get_child_by_name(
3226 'attributes-list') or netapp_api.NaElement('none')
3227 volume_attributes_list = attributes_list.get_children()
3229 if not self._has_records(result): 3229 ↛ 3230line 3229 didn't jump to line 3230 because the condition on line 3229 was never true
3230 raise exception.StorageResourceNotFound(name=volume_name)
3231 elif len(volume_attributes_list) > 1: 3231 ↛ 3232line 3231 didn't jump to line 3232 because the condition on line 3231 was never true
3232 msg = _('Could not find unique volume %(vol)s.')
3233 msg_args = {'vol': volume_name}
3234 raise exception.NetAppException(msg % msg_args)
3236 vol_attr = volume_attributes_list[0]
3237 vol_snapshot_attr = vol_attr.get_child_by_name(
3238 "volume-snapshot-attributes") or netapp_api.NaElement('none')
3240 return {key: vol_snapshot_attr.get_child_content(key)
3241 for key in desired_snapshot_attributes.keys()}
3243 def get_volume_allocated_files(self, volume_name):
3244 """Get flexvol allocated files"""
3246 api_args = {
3247 'query': {
3248 'volume-attributes': {
3249 'volume-id-attributes': {
3250 'name': volume_name,
3251 },
3252 },
3253 },
3254 'desired-attributes': {
3255 'volume-attributes': {
3256 'volume-inode-attributes': {
3257 'inodefile-public-capacity': None,
3258 'files-total': None,
3259 },
3260 },
3261 },
3262 }
3263 result = self.send_iter_request('volume-get-iter', api_args)
3264 if not self._has_records(result):
3265 return None
3267 attributes_list = result.get_child_by_name(
3268 'attributes-list') or netapp_api.NaElement('none')
3269 volume_attributes = attributes_list.get_child_by_name(
3270 'volume-attributes') or netapp_api.NaElement('none')
3271 volume_inode_attributes = volume_attributes.get_child_by_name(
3272 'volume-inode-attributes') or netapp_api.NaElement('none')
3274 return {
3275 'used': volume_inode_attributes.get_child_content(
3276 'inodefile-public-capacity'),
3277 'max': volume_inode_attributes.get_child_content(
3278 'files-total'),
3279 }
3281 @na_utils.trace
3282 def get_volume(self, volume_name):
3283 """Returns the volume with the specified name, if present."""
3285 api_args = {
3286 'query': {
3287 'volume-attributes': {
3288 'volume-id-attributes': {
3289 'name': volume_name,
3290 },
3291 },
3292 },
3293 'desired-attributes': {
3294 'volume-attributes': {
3295 'volume-id-attributes': {
3296 'aggr-list': {
3297 'aggr-name': None,
3298 },
3299 'containing-aggregate-name': None,
3300 'junction-path': None,
3301 'name': None,
3302 'owning-vserver-name': None,
3303 'type': None,
3304 'style': None,
3305 'style-extended': None,
3306 },
3307 'volume-qos-attributes': {
3308 'policy-group-name': None,
3309 },
3310 'volume-space-attributes': {
3311 'size': None,
3312 'size-used': None,
3313 },
3314 'volume-snaplock-attributes': {
3315 'snaplock-type': None,
3316 },
3317 },
3318 },
3319 }
3320 result = self.send_request('volume-get-iter', api_args)
3322 attributes_list = result.get_child_by_name(
3323 'attributes-list') or netapp_api.NaElement('none')
3324 volume_attributes_list = attributes_list.get_children()
3326 if not self._has_records(result):
3327 raise exception.StorageResourceNotFound(name=volume_name)
3328 elif len(volume_attributes_list) > 1:
3329 msg = _('Could not find unique volume %(vol)s.')
3330 msg_args = {'vol': volume_name}
3331 raise exception.NetAppException(msg % msg_args)
3333 volume_attributes = volume_attributes_list[0]
3335 volume_id_attributes = volume_attributes.get_child_by_name(
3336 'volume-id-attributes') or netapp_api.NaElement('none')
3337 volume_qos_attributes = volume_attributes.get_child_by_name(
3338 'volume-qos-attributes') or netapp_api.NaElement('none')
3339 volume_space_attributes = volume_attributes.get_child_by_name(
3340 'volume-space-attributes') or netapp_api.NaElement('none')
3341 volume_snaplock_attributes = volume_attributes.get_child_by_name(
3342 'volume-snaplock-attributes') or netapp_api.NaElement('none')
3344 aggregate = volume_id_attributes.get_child_content(
3345 'containing-aggregate-name')
3346 aggregate_list = []
3347 if not aggregate:
3348 aggregate = ''
3349 aggr_list_attr = volume_id_attributes.get_child_by_name(
3350 'aggr-list') or netapp_api.NaElement('none')
3351 aggregate_list = [aggr_elem.get_content()
3352 for aggr_elem in aggr_list_attr.get_children()]
3354 volume = {
3355 'aggregate': aggregate,
3356 'aggr-list': aggregate_list,
3357 'junction-path': volume_id_attributes.get_child_content(
3358 'junction-path'),
3359 'name': volume_id_attributes.get_child_content('name'),
3360 'owning-vserver-name': volume_id_attributes.get_child_content(
3361 'owning-vserver-name'),
3362 'type': volume_id_attributes.get_child_content('type'),
3363 'style': volume_id_attributes.get_child_content('style'),
3364 'size': volume_space_attributes.get_child_content('size'),
3365 'size-used': volume_space_attributes.get_child_content(
3366 'size-used'),
3367 'qos-policy-group-name': volume_qos_attributes.get_child_content(
3368 'policy-group-name'),
3369 'style-extended': volume_id_attributes.get_child_content(
3370 'style-extended'),
3371 'snaplock-type': volume_snaplock_attributes.get_child_content(
3372 'snaplock-type')
3373 }
3374 return volume
3376 @na_utils.trace
3377 def get_volume_at_junction_path(self, junction_path):
3378 """Returns the volume with the specified junction path, if present."""
3379 if not junction_path:
3380 return None
3382 api_args = {
3383 'query': {
3384 'volume-attributes': {
3385 'volume-id-attributes': {
3386 'junction-path': junction_path,
3387 'style-extended': '%s|%s' % (
3388 na_utils.FLEXGROUP_STYLE_EXTENDED,
3389 na_utils.FLEXVOL_STYLE_EXTENDED),
3390 },
3391 },
3392 },
3393 'desired-attributes': {
3394 'volume-attributes': {
3395 'volume-id-attributes': {
3396 'name': None,
3397 },
3398 },
3399 },
3400 }
3401 result = self.send_iter_request('volume-get-iter', api_args)
3402 if not self._has_records(result):
3403 return None
3405 attributes_list = result.get_child_by_name(
3406 'attributes-list') or netapp_api.NaElement('none')
3407 volume_attributes = attributes_list.get_child_by_name(
3408 'volume-attributes') or netapp_api.NaElement('none')
3409 volume_id_attributes = volume_attributes.get_child_by_name(
3410 'volume-id-attributes') or netapp_api.NaElement('none')
3412 volume = {
3413 'name': volume_id_attributes.get_child_content('name'),
3414 }
3415 return volume
3417 @na_utils.trace
3418 def get_volume_to_manage(self, aggregate_name, volume_name):
3419 """Get flexvol to be managed by Manila.
3421 :param aggregate_name: either a list or a string. List for aggregate
3422 names where the FlexGroup resides, while a string for the aggregate
3423 name where FlexVol volume is.
3424 :param volume_name: name of the managed volume.
3425 """
3427 api_args = {
3428 'query': {
3429 'volume-attributes': {
3430 'volume-id-attributes': {
3431 'name': volume_name,
3432 },
3433 },
3434 },
3435 'desired-attributes': {
3436 'volume-attributes': {
3437 'volume-id-attributes': {
3438 'aggr-list': {
3439 'aggr-name': None,
3440 },
3441 'containing-aggregate-name': None,
3442 'junction-path': None,
3443 'name': None,
3444 'type': None,
3445 'style': None,
3446 'owning-vserver-name': None,
3447 },
3448 'volume-qos-attributes': {
3449 'policy-group-name': None,
3450 },
3451 'volume-space-attributes': {
3452 'size': None,
3453 },
3454 },
3455 },
3456 }
3457 if isinstance(aggregate_name, str):
3458 api_args['query']['volume-attributes']['volume-id-attributes'][
3459 'containing-aggregate-name'] = aggregate_name
3460 elif isinstance(aggregate_name, list): 3460 ↛ 3466line 3460 didn't jump to line 3466 because the condition on line 3460 was always true
3461 aggr_list = [{'aggr-name': aggr_name} for aggr_name in
3462 aggregate_name]
3463 api_args['query']['volume-attributes']['volume-id-attributes'][
3464 'aggr-list'] = aggr_list
3466 result = self.send_iter_request('volume-get-iter', api_args)
3467 if not self._has_records(result):
3468 return None
3470 attributes_list = result.get_child_by_name(
3471 'attributes-list') or netapp_api.NaElement('none')
3472 volume_attributes = attributes_list.get_child_by_name(
3473 'volume-attributes') or netapp_api.NaElement('none')
3474 volume_id_attributes = volume_attributes.get_child_by_name(
3475 'volume-id-attributes') or netapp_api.NaElement('none')
3476 volume_qos_attributes = volume_attributes.get_child_by_name(
3477 'volume-qos-attributes') or netapp_api.NaElement('none')
3478 volume_space_attributes = volume_attributes.get_child_by_name(
3479 'volume-space-attributes') or netapp_api.NaElement('none')
3481 aggregate = volume_id_attributes.get_child_content(
3482 'containing-aggregate-name')
3483 aggregate_list = []
3484 if not aggregate:
3485 aggregate = ''
3486 aggr_list_attr = volume_id_attributes.get_child_by_name(
3487 'aggr-list') or netapp_api.NaElement('none')
3488 aggregate_list = [aggr_elem.get_content()
3489 for aggr_elem in aggr_list_attr.get_children()]
3491 volume = {
3492 'aggregate': aggregate,
3493 'aggr-list': aggregate_list,
3494 'junction-path': volume_id_attributes.get_child_content(
3495 'junction-path'),
3496 'name': volume_id_attributes.get_child_content('name'),
3497 'type': volume_id_attributes.get_child_content('type'),
3498 'style': volume_id_attributes.get_child_content('style'),
3499 'owning-vserver-name': volume_id_attributes.get_child_content(
3500 'owning-vserver-name'),
3501 'size': volume_space_attributes.get_child_content('size'),
3502 'qos-policy-group-name': volume_qos_attributes.get_child_content(
3503 'policy-group-name')
3505 }
3506 return volume
3508 @na_utils.trace
3509 def create_volume_clone(self, volume_name, parent_volume_name,
3510 parent_snapshot_name=None,
3511 qos_policy_group=None,
3512 adaptive_qos_policy_group=None,
3513 mount_point_name=None, **options):
3514 """Clones a volume."""
3515 api_args = {
3516 'volume': volume_name,
3517 'parent-volume': parent_volume_name,
3518 'parent-snapshot': parent_snapshot_name,
3519 'junction-path': '/%s' % (mount_point_name or volume_name),
3520 }
3522 if qos_policy_group is not None:
3523 api_args['qos-policy-group-name'] = qos_policy_group
3525 self.send_request('volume-clone-create', api_args)
3527 if adaptive_qos_policy_group is not None:
3528 self.set_qos_adaptive_policy_group_for_volume(
3529 volume_name, adaptive_qos_policy_group)
3531 @na_utils.trace
3532 def volume_clone_split_start(self, volume_name):
3533 """Begins splitting a clone from its parent."""
3534 try:
3535 api_args = {'volume': volume_name}
3536 self.send_request('volume-clone-split-start', api_args)
3537 except netapp_api.NaApiError as e:
3538 if e.code == netapp_api.EVOL_CLONE_BEING_SPLIT:
3539 return
3540 raise
3542 @na_utils.trace
3543 def volume_clone_split_status(self, volume_name):
3544 """Status of splitting a clone from its parent.
3546 Returns the split status:
3548 CLONE_SPLIT_STATUS_FINISHED means split is done, i.e. volume
3549 is not a clone (anymore)
3550 CLONE_SPLIT_STATUS_UNKNOWN means we cannot tell, because the
3551 split job is not running
3552 CLONE_SPLIT_STATUS_ONGOING means the split job is currently running
3553 """
3554 try:
3555 api_args = {'volume': volume_name}
3556 result = self.send_request('volume-clone-split-status', api_args)
3557 except netapp_api.NaApiError as e:
3558 if e.code in (netapp_api.EVOLOPNOTUNDERWAY,
3559 netapp_api.EVOLUMENOTONLINE,
3560 netapp_api.EPARENTNOTONLINE):
3561 # we cannot tell about the progress
3562 return na_utils.CLONE_SPLIT_STATUS_UNKNOWN
3563 elif e.code == netapp_api.EVOLNOTCLONE:
3564 # volume no longer is a clone means it is 100% split
3565 return na_utils.CLONE_SPLIT_STATUS_FINISHED
3566 else:
3567 # log other exceptions for future code improvement
3568 LOG.exception(f"unexpected volume-clone-split-status error "
3569 f"code {e.code}, message {e.message} for "
3570 f"volume {volume_name}")
3572 return na_utils.CLONE_SPLIT_STATUS_UNKNOWN
3574 clone_split_details = result.get_child_by_name(
3575 'clone-split-details') or netapp_api.NaElement('none')
3576 for clone_split_details_info in clone_split_details.get_children():
3577 percentage = clone_split_details_info.get_child_content(
3578 'block-percentage-complete')
3579 try:
3580 if int(percentage) < 100:
3581 return na_utils.CLONE_SPLIT_STATUS_ONGOING
3582 if int(percentage) == 100:
3583 return na_utils.CLONE_SPLIT_STATUS_FINISHED
3584 except (ValueError, TypeError) as e:
3585 LOG.exception(f"unexpected error converting clone-split "
3586 f"percentage '{percentage}' to integer: "
3587 f"{e}")
3588 return na_utils.CLONE_SPLIT_STATUS_UNKNOWN
3589 return na_utils.CLONE_SPLIT_STATUS_UNKNOWN
3591 @na_utils.trace
3592 def volume_clone_split_stop(self, volume_name):
3593 """Stop splitting a clone from its parent."""
3594 try:
3595 api_args = {'volume': volume_name}
3596 self.send_request('volume-clone-split-stop', api_args)
3597 except netapp_api.NaApiError as e:
3598 if e.code in (netapp_api.EVOLUMEDOESNOTEXIST, 3598 ↛ 3602line 3598 didn't jump to line 3602 because the condition on line 3598 was always true
3599 netapp_api.EVOLNOTCLONE,
3600 netapp_api.EVOLOPNOTUNDERWAY):
3601 return
3602 raise
3604 @na_utils.trace
3605 def check_volume_clone_split_completed(self, volume_name):
3606 """Check if volume clone split operation already finished"""
3607 return self.get_volume_clone_parent_snaphot(volume_name) is None
3609 @na_utils.trace
3610 def get_clones_of_parent_volume(self, vserver, volume):
3611 """get one or more clones of given parent volume"""
3612 api_args = {
3613 'query': {
3614 'volume-clone-info': {
3615 'parent-vserver': vserver,
3616 'parent-volume': volume,
3617 }
3618 },
3619 'desired-attributes': {
3620 'volume-clone-info': {
3621 'volume': None,
3622 },
3623 }
3624 }
3625 result = self.send_request('volume-clone-get-iter', api_args)
3626 attributes_list = result.get_child_by_name(
3627 'attributes-list') or netapp_api.NaElement('none')
3629 if not attributes_list:
3630 return None
3632 clones = []
3633 for clone_info in attributes_list.get_children():
3634 clones.append(clone_info.get_child_content('volume'))
3635 return clones
3637 @na_utils.trace
3638 def get_volume_clone_parent_snaphot(self, volume_name):
3639 """Gets volume's clone parent.
3641 Return the snapshot name of a volume's clone parent, or None if it
3642 doesn't exist.
3643 """
3644 api_args = {
3645 'query': {
3646 'volume-attributes': {
3647 'volume-id-attributes': {
3648 'name': volume_name
3649 }
3650 }
3651 },
3652 'desired-attributes': {
3653 'volume-attributes': {
3654 'volume-clone-attributes': {
3655 'volume-clone-parent-attributes': {
3656 'snapshot-name': ''
3657 }
3658 }
3659 }
3660 }
3661 }
3662 result = self.send_iter_request('volume-get-iter', api_args)
3663 if not self._has_records(result):
3664 return None
3666 attributes_list = result.get_child_by_name(
3667 'attributes-list') or netapp_api.NaElement('none')
3668 volume_attributes = attributes_list.get_child_by_name(
3669 'volume-attributes') or netapp_api.NaElement('none')
3670 vol_clone_attrs = volume_attributes.get_child_by_name(
3671 'volume-clone-attributes') or netapp_api.NaElement('none')
3672 vol_clone_parent_atts = vol_clone_attrs.get_child_by_name(
3673 'volume-clone-parent-attributes') or netapp_api.NaElement(
3674 'none')
3675 snapshot_name = vol_clone_parent_atts.get_child_content(
3676 'snapshot-name')
3677 return snapshot_name
3679 @na_utils.trace
3680 def get_clone_children_for_snapshot(self, volume_name, snapshot_name):
3681 """Returns volumes that are keeping a snapshot locked."""
3683 api_args = {
3684 'query': {
3685 'volume-attributes': {
3686 'volume-clone-attributes': {
3687 'volume-clone-parent-attributes': {
3688 'name': volume_name,
3689 'snapshot-name': snapshot_name,
3690 },
3691 },
3692 },
3693 },
3694 'desired-attributes': {
3695 'volume-attributes': {
3696 'volume-id-attributes': {
3697 'name': None,
3698 },
3699 },
3700 },
3701 }
3702 result = self.send_iter_request('volume-get-iter', api_args)
3703 if not self._has_records(result):
3704 return []
3706 volume_list = []
3707 attributes_list = result.get_child_by_name(
3708 'attributes-list') or netapp_api.NaElement('none')
3710 for volume_attributes in attributes_list.get_children():
3712 volume_id_attributes = volume_attributes.get_child_by_name(
3713 'volume-id-attributes') or netapp_api.NaElement('none')
3715 volume_list.append({
3716 'name': volume_id_attributes.get_child_content('name'),
3717 })
3719 return volume_list
3721 @na_utils.trace
3722 def get_volume_junction_path(self, volume_name, is_style_cifs=False):
3723 """Gets a volume junction path."""
3724 api_args = {
3725 'volume': volume_name,
3726 'is-style-cifs': str(is_style_cifs).lower(),
3727 }
3728 result = self.send_request('volume-get-volume-path', api_args)
3729 return result.get_child_content('junction')
3731 @na_utils.trace
3732 def mount_volume(self, volume_name, junction_path=None):
3733 """Mounts a volume on a junction path."""
3734 api_args = {
3735 'volume-name': volume_name,
3736 'junction-path': (junction_path if junction_path
3737 else '/%s' % volume_name)
3738 }
3739 self.send_request('volume-mount', api_args)
3741 @na_utils.trace
3742 def online_volume(self, volume_name):
3743 """Onlines a volume."""
3744 try:
3745 self.send_request('volume-online', {'name': volume_name})
3746 except netapp_api.NaApiError as e:
3747 raise exception.NetAppException(message=e.message)
3749 @na_utils.trace
3750 def offline_volume(self, volume_name):
3751 """Offlines a volume."""
3752 try:
3753 self.send_request('volume-offline', {'name': volume_name})
3754 except netapp_api.NaApiError as e:
3755 if e.code == netapp_api.EVOLUMEOFFLINE:
3756 return
3757 raise exception.NetAppException(message=e.message)
3759 @na_utils.trace
3760 def _unmount_volume(self, volume_name, force=False):
3761 """Unmounts a volume."""
3762 api_args = {
3763 'volume-name': volume_name,
3764 'force': str(force).lower(),
3765 }
3766 try:
3767 self.send_request('volume-unmount', api_args)
3768 except netapp_api.NaApiError as e:
3769 if e.code == netapp_api.EVOL_NOT_MOUNTED:
3770 return
3771 raise
3773 @na_utils.trace
3774 def unmount_volume(self, volume_name, force=False, wait_seconds=30):
3775 """Unmounts a volume, retrying if a clone split is ongoing.
3777 NOTE(cknight): While unlikely to happen in normal operation, any client
3778 that tries to delete volumes immediately after creating volume clones
3779 is likely to experience failures if cDOT isn't quite ready for the
3780 delete. The volume unmount is the first operation in the delete
3781 path that fails in this case, and there is no proactive check we can
3782 use to reliably predict the failure. And there isn't a specific error
3783 code from volume-unmount, so we have to check for a generic error code
3784 plus certain language in the error code. It's ugly, but it works, and
3785 it's better than hard-coding a fixed delay.
3786 """
3788 # Do the unmount, handling split-related errors with retries.
3789 retry_interval = 3 # seconds
3790 for retry in range(int(wait_seconds / retry_interval)):
3791 try:
3792 self._unmount_volume(volume_name, force=force)
3793 LOG.debug('Volume %s unmounted.', volume_name)
3794 return
3795 except netapp_api.NaApiError as e:
3796 if e.code == netapp_api.EAPIERROR and 'job ID' in e.message:
3797 msg = ('Could not unmount volume %(volume)s due to '
3798 'ongoing volume operation: %(exception)s')
3799 msg_args = {'volume': volume_name, 'exception': e}
3800 LOG.warning(msg, msg_args)
3801 time.sleep(retry_interval)
3802 continue
3803 raise
3805 msg = _('Failed to unmount volume %(volume)s after '
3806 'waiting for %(wait_seconds)s seconds.')
3807 msg_args = {'volume': volume_name, 'wait_seconds': wait_seconds}
3808 LOG.error(msg, msg_args)
3809 raise exception.NetAppException(msg % msg_args)
3811 @na_utils.trace
3812 def rename_volume(self, volume_name, new_volume_name):
3813 """Renames a volume."""
3814 api_args = {'volume': volume_name, 'new-volume-name': new_volume_name}
3815 self.send_request('volume-rename', api_args)
3816 msg = _('Soft-deleted/renamed volume %(volume)s to %(new_volume)s.')
3817 msg_args = {'volume': volume_name, 'new_volume': new_volume_name}
3818 LOG.debug(msg, msg_args)
3820 @na_utils.trace
3821 def soft_delete_volume(self, volume_name):
3822 """Soft deletes a volume."""
3823 try:
3824 self.send_request('volume-destroy', {'name': volume_name})
3825 except netapp_api.NaApiError as e:
3826 if e.code == netapp_api.EVOLDEL_NOT_ALLOW_BY_CLONE: 3826 ↛ exitline 3826 didn't return from function 'soft_delete_volume' because the condition on line 3826 was always true
3827 LOG.warning('Delete volume %s failed, renaming..', volume_name)
3828 self.rename_volume(volume_name, DELETED_PREFIX + volume_name)
3830 @na_utils.trace
3831 def delete_volume(self, volume_name):
3832 """Deletes a volume."""
3833 return self.soft_delete_volume(volume_name)
3835 @na_utils.trace
3836 def prune_deleted_volumes(self):
3837 """Prunes deleted volumes."""
3838 LOG.debug('Checking for deleted volumes to prune.')
3839 api_args = {
3840 'query': {
3841 'volume-attributes': {
3842 'volume-id-attributes': {
3843 'name': DELETED_PREFIX + '*',
3844 'type': 'rw',
3845 },
3846 },
3847 },
3848 'desired-attributes': {
3849 'volume-attributes': {
3850 'volume-id-attributes': {
3851 'name': None,
3852 'owning-vserver-name': None,
3853 },
3854 'volume-state-attributes': {
3855 'state': None,
3856 },
3857 },
3858 },
3859 }
3860 result = self.send_request('volume-get-iter', api_args)
3861 if result.get_child_content('num-records') == '0':
3862 return
3864 attributes_list = result.get_child_by_name('attributes-list')
3865 if not attributes_list:
3866 return
3868 for volume_info in attributes_list.get_children():
3869 volume_name = \
3870 (volume_info
3871 .get_child_by_name('volume-id-attributes')
3872 .get_child_content('name'))
3873 volume_state = \
3874 (volume_info
3875 .get_child_by_name('volume-state-attributes')
3876 .get_child_content('state'))
3877 vserver = \
3878 (volume_info
3879 .get_child_by_name('volume-id-attributes')
3880 .get_child_content('owning-vserver-name'))
3882 LOG.debug('Found volume %(vv)s in state %(vs)s', {
3883 'vv': volume_name, 'vs': volume_state})
3884 if volume_state == 'offline':
3885 clones = self.get_clones_of_parent_volume(vserver, volume_name)
3886 if clones:
3887 # Found parent volume which has multiple clone childs, so
3888 # make volume online. Once the volume will be online, we
3889 # will split clones (in next callback).
3890 client = copy.deepcopy(self)
3891 client.set_vserver(vserver)
3892 try:
3893 client.volume_online(volume_name)
3894 except Exception:
3895 LOG.error("Volume online failed for "
3896 "volume %s", volume_name)
3898 for clone in clones:
3899 try:
3900 client.volume_online(clone)
3901 except Exception:
3902 LOG.error("Volume online failed for "
3903 "volume %s", clone)
3905 else:
3906 # Either child or parent without child clones.
3907 client = copy.deepcopy(self)
3908 client.set_vserver(vserver)
3909 try:
3910 client.send_request(
3911 'volume-destroy', {'name': volume_name})
3912 except netapp_api.NaApiError as e:
3913 LOG.error('Pruning soft-deleted '
3914 'volume %s failed', volume_name)
3915 if e.code == netapp_api.EVOLDEL_NOT_ALLOW_BY_CLONE:
3916 try:
3917 if client.volume_clone_split_status(
3918 volume_name) == (
3919 na_utils.CLONE_SPLIT_STATUS_UNKNOWN):
3920 client.volume_clone_split_start(
3921 volume_name)
3922 LOG.debug('Starting clone split for '
3923 'volume %s ', volume_name)
3924 except Exception:
3925 LOG.error("Volume clone split failed for "
3926 "volume %s", volume_name)
3928 elif volume_state == 'online':
3929 # These must be parent volume which was brought online in
3930 # previous callback. If no clones found means all splits are
3931 # completed, so offline the volume.
3932 clones = self.get_clones_of_parent_volume(vserver, volume_name)
3933 client = copy.deepcopy(self)
3934 client.set_vserver(vserver)
3935 if not clones:
3936 try:
3937 client.volume_offline(volume_name)
3938 except Exception:
3939 LOG.error("Volume offline failed for "
3940 "volume %s", volume_name)
3941 else:
3942 for clone in clones:
3943 try:
3944 clone_status = client.volume_clone_split_status(
3945 clone)
3946 if clone_status == (
3947 na_utils.CLONE_SPLIT_STATUS_UNKNOWN):
3948 client.volume_clone_split_start(clone)
3949 LOG.debug('Starting clone split for '
3950 'volume %s ', clone)
3951 except Exception:
3952 LOG.error("Volume clone split failed for "
3953 "volume %s", clone)
3955 @na_utils.trace
3956 def create_snapshot(self, volume_name, snapshot_name,
3957 snapmirror_label=None):
3958 """Creates a volume snapshot."""
3959 api_args = {'volume': volume_name, 'snapshot': snapshot_name}
3960 if snapmirror_label is not None: 3960 ↛ 3961line 3960 didn't jump to line 3961 because the condition on line 3960 was never true
3961 api_args['snapmirror-label'] = snapmirror_label
3962 self.send_request('snapshot-create', api_args)
3964 @na_utils.trace
3965 def snapshot_exists(self, snapshot_name, volume_name):
3966 """Checks if Snapshot exists for a specified volume."""
3967 LOG.debug('Checking if snapshot %(snapshot)s exists for '
3968 'volume %(volume)s',
3969 {'snapshot': snapshot_name, 'volume': volume_name})
3971 # Gets a single snapshot.
3972 api_args = {
3973 'query': {
3974 'snapshot-info': {
3975 'name': snapshot_name,
3976 'volume': volume_name,
3977 },
3978 },
3979 'desired-attributes': {
3980 'snapshot-info': {
3981 'name': None,
3982 'volume': None,
3983 'busy': None,
3984 'snapshot-owners-list': {
3985 'snapshot-owner': None,
3986 }
3987 },
3988 },
3989 }
3990 result = self.send_request('snapshot-get-iter', api_args)
3992 error_record_list = result.get_child_by_name(
3993 'volume-errors') or netapp_api.NaElement('none')
3994 errors = error_record_list.get_children()
3996 if errors:
3997 error = errors[0]
3998 error_code = error.get_child_content('errno')
3999 error_reason = error.get_child_content('reason')
4000 msg = _('Could not read information for snapshot %(name)s. '
4001 'Code: %(code)s. Reason: %(reason)s')
4002 msg_args = {
4003 'name': snapshot_name,
4004 'code': error_code,
4005 'reason': error_reason
4006 }
4007 if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
4008 raise exception.SnapshotUnavailable(msg % msg_args)
4009 else:
4010 raise exception.NetAppException(msg % msg_args)
4012 return self._has_records(result)
4014 @na_utils.trace
4015 def get_snapshot(self, volume_name, snapshot_name):
4016 """Gets a single snapshot."""
4017 api_args = {
4018 'query': {
4019 'snapshot-info': {
4020 'name': snapshot_name,
4021 'volume': volume_name,
4022 },
4023 },
4024 'desired-attributes': {
4025 'snapshot-info': {
4026 'access-time': None,
4027 'name': None,
4028 'volume': None,
4029 'busy': None,
4030 'snapshot-owners-list': {
4031 'snapshot-owner': None,
4032 }
4033 },
4034 },
4035 }
4036 result = self.send_request('snapshot-get-iter', api_args)
4038 error_record_list = result.get_child_by_name(
4039 'volume-errors') or netapp_api.NaElement('none')
4040 errors = error_record_list.get_children()
4042 if errors:
4043 error = errors[0]
4044 error_code = error.get_child_content('errno')
4045 error_reason = error.get_child_content('reason')
4046 msg = _('Could not read information for snapshot %(name)s. '
4047 'Code: %(code)s. Reason: %(reason)s')
4048 msg_args = {
4049 'name': snapshot_name,
4050 'code': error_code,
4051 'reason': error_reason
4052 }
4053 if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
4054 raise exception.SnapshotUnavailable(msg % msg_args)
4055 else:
4056 raise exception.NetAppException(msg % msg_args)
4058 attributes_list = result.get_child_by_name(
4059 'attributes-list') or netapp_api.NaElement('none')
4060 snapshot_info_list = attributes_list.get_children()
4062 if not self._has_records(result):
4063 raise exception.SnapshotResourceNotFound(name=snapshot_name)
4064 elif len(snapshot_info_list) > 1:
4065 msg = _('Could not find unique snapshot %(snap)s on '
4066 'volume %(vol)s.')
4067 msg_args = {'snap': snapshot_name, 'vol': volume_name}
4068 raise exception.NetAppException(msg % msg_args)
4070 snapshot_info = snapshot_info_list[0]
4071 snapshot = {
4072 'access-time': snapshot_info.get_child_content('access-time'),
4073 'name': snapshot_info.get_child_content('name'),
4074 'volume': snapshot_info.get_child_content('volume'),
4075 'busy': strutils.bool_from_string(
4076 snapshot_info.get_child_content('busy')),
4077 }
4079 snapshot_owners_list = snapshot_info.get_child_by_name(
4080 'snapshot-owners-list') or netapp_api.NaElement('none')
4081 snapshot_owners = set([
4082 snapshot_owner.get_child_content('owner')
4083 for snapshot_owner in snapshot_owners_list.get_children()])
4084 snapshot['owners'] = snapshot_owners
4085 snapshot['locked_by_clone'] = snapshot['owners'] == {'volume clone'}
4087 return snapshot
4089 @na_utils.trace
4090 def rename_snapshot(self, volume_name, snapshot_name, new_snapshot_name):
4091 api_args = {
4092 'volume': volume_name,
4093 'current-name': snapshot_name,
4094 'new-name': new_snapshot_name
4095 }
4096 self.send_request('snapshot-rename', api_args)
4098 @na_utils.trace
4099 def restore_snapshot(self, volume_name, snapshot_name):
4100 """Reverts a volume to the specified snapshot."""
4101 api_args = {
4102 'volume': volume_name,
4103 'snapshot': snapshot_name,
4104 }
4105 self.send_request('snapshot-restore-volume', api_args)
4107 @na_utils.trace
4108 def delete_snapshot(self, volume_name, snapshot_name, ignore_owners=False):
4109 """Deletes a volume snapshot."""
4111 ignore_owners = ('true' if strutils.bool_from_string(ignore_owners)
4112 else 'false')
4114 api_args = {
4115 'volume': volume_name,
4116 'snapshot': snapshot_name,
4117 'ignore-owners': ignore_owners,
4118 }
4119 self.send_request('snapshot-delete', api_args)
4121 @na_utils.trace
4122 def rename_snapshot_and_split_clones(self, volume_name, snapshot_name):
4123 """Renames volume snapshot & splits clones."""
4125 msg_args = {'snap': snapshot_name, 'vol': volume_name}
4126 try:
4127 self.rename_snapshot(volume_name,
4128 snapshot_name,
4129 DELETED_PREFIX + snapshot_name)
4130 msg = _('Soft-deleted snapshot %(snap)s on volume %(vol)s.')
4131 LOG.info(msg, msg_args)
4132 except netapp_api.NaApiError as e:
4133 if e.code == netapp_api.EAPINOTFOUND:
4134 msg = _('Snapshot %(snap)s on volume %(vol)s not found.')
4135 LOG.debug(msg, msg_args)
4136 return
4137 else:
4138 raise
4140 # Snapshots are locked by clone(s), so split the clone(s)
4141 snapshot_children = self.get_clone_children_for_snapshot(
4142 volume_name, DELETED_PREFIX + snapshot_name)
4143 for snapshot_child in snapshot_children:
4144 self.volume_clone_split_start(snapshot_child['name'])
4146 @na_utils.trace
4147 def soft_delete_snapshot(self, volume_name, snapshot_name):
4148 """Deletes a volume snapshot, or renames & splits if delete fails."""
4149 try:
4150 self.delete_snapshot(volume_name, snapshot_name)
4151 except netapp_api.NaApiError:
4152 self.rename_snapshot_and_split_clones(volume_name, snapshot_name)
4154 @na_utils.trace
4155 def prune_deleted_snapshots(self):
4156 """Deletes non-busy snapshots that were previously soft-deleted."""
4158 deleted_snapshots_map = self._get_deleted_snapshots()
4160 for vserver in deleted_snapshots_map:
4161 client = copy.deepcopy(self)
4162 client.set_vserver(vserver)
4164 for snapshot in deleted_snapshots_map[vserver]:
4165 try:
4166 client.delete_snapshot(snapshot['volume'],
4167 snapshot['name'])
4168 except netapp_api.NaApiError:
4169 msg = _('Could not delete snapshot %(snap)s on '
4170 'volume %(volume)s.')
4171 msg_args = {
4172 'snap': snapshot['name'],
4173 'volume': snapshot['volume'],
4174 }
4175 LOG.exception(msg, msg_args)
4177 @na_utils.trace
4178 def _get_deleted_snapshots(self):
4179 """Returns non-busy, soft-deleted snapshots suitable for reaping."""
4180 api_args = {
4181 'query': {
4182 'snapshot-info': {
4183 'name': DELETED_PREFIX + '*',
4184 'busy': 'false',
4185 },
4186 },
4187 'desired-attributes': {
4188 'snapshot-info': {
4189 'name': None,
4190 'vserver': None,
4191 'volume': None,
4192 },
4193 },
4194 }
4195 result = self.send_iter_request('snapshot-get-iter', api_args)
4197 attributes_list = result.get_child_by_name(
4198 'attributes-list') or netapp_api.NaElement('none')
4200 # Build a map of snapshots, one list of snapshots per vserver
4201 snapshot_map = {}
4202 for snapshot_info in attributes_list.get_children():
4203 vserver = snapshot_info.get_child_content('vserver')
4204 snapshot_list = snapshot_map.get(vserver, [])
4205 snapshot_list.append({
4206 'name': snapshot_info.get_child_content('name'),
4207 'volume': snapshot_info.get_child_content('volume'),
4208 'vserver': vserver,
4209 })
4210 snapshot_map[vserver] = snapshot_list
4212 return snapshot_map
4214 @na_utils.trace
4215 def create_cg_snapshot(self, volume_names, snapshot_name):
4216 """Creates a consistency group snapshot of one or more flexvols."""
4217 cg_id = self._start_cg_snapshot(volume_names, snapshot_name)
4218 if not cg_id:
4219 msg = _('Could not start consistency group snapshot %s.')
4220 raise exception.NetAppException(msg % snapshot_name)
4221 self._commit_cg_snapshot(cg_id)
4223 @na_utils.trace
4224 def _start_cg_snapshot(self, volume_names, snapshot_name):
4225 api_args = {
4226 'snapshot': snapshot_name,
4227 'timeout': 'relaxed',
4228 'volumes': [
4229 {'volume-name': volume_name} for volume_name in volume_names
4230 ],
4231 }
4232 result = self.send_request('cg-start', api_args)
4233 return result.get_child_content('cg-id')
4235 @na_utils.trace
4236 def _commit_cg_snapshot(self, cg_id):
4237 api_args = {'cg-id': cg_id}
4238 self.send_request('cg-commit', api_args)
4240 @na_utils.trace
4241 def create_cifs_share(self, share_name, path):
4242 api_args = {'path': path, 'share-name': share_name}
4243 self.send_request('cifs-share-create', api_args)
4245 @na_utils.trace
4246 def cifs_share_exists(self, share_name):
4247 """Check that a cifs share already exists"""
4248 share_path = '/%s' % share_name
4249 api_args = {
4250 'query': {
4251 'cifs-share': {
4252 'share-name': share_name,
4253 'path': share_path,
4254 },
4255 },
4256 'desired-attributes': {
4257 'cifs-share': {
4258 'share-name': None
4259 }
4260 },
4261 }
4262 result = self.send_iter_request('cifs-share-get-iter', api_args)
4263 return self._has_records(result)
4265 @na_utils.trace
4266 def get_cifs_share_access(self, share_name):
4267 api_args = {
4268 'query': {
4269 'cifs-share-access-control': {
4270 'share': share_name,
4271 },
4272 },
4273 'desired-attributes': {
4274 'cifs-share-access-control': {
4275 'user-or-group': None,
4276 'permission': None,
4277 },
4278 },
4279 }
4280 result = self.send_iter_request('cifs-share-access-control-get-iter',
4281 api_args)
4283 attributes_list = result.get_child_by_name(
4284 'attributes-list') or netapp_api.NaElement('none')
4286 rules = {}
4288 for rule in attributes_list.get_children():
4289 user_or_group = rule.get_child_content('user-or-group')
4290 permission = rule.get_child_content('permission')
4291 rules[user_or_group] = permission
4293 return rules
4295 @na_utils.trace
4296 def add_cifs_share_access(self, share_name, user_name, readonly):
4297 try:
4298 api_args = {
4299 'permission': 'read' if readonly else 'full_control',
4300 'share': share_name,
4301 'user-or-group': user_name,
4302 }
4303 self.send_request('cifs-share-access-control-create', api_args)
4304 except netapp_api.NaApiError as e:
4305 if e.code != netapp_api.EDUPLICATEENTRY: 4305 ↛ 4306line 4305 didn't jump to line 4306 because the condition on line 4305 was never true
4306 raise
4308 @na_utils.trace
4309 def modify_cifs_share_access(self, share_name, user_name, readonly):
4310 api_args = {
4311 'permission': 'read' if readonly else 'full_control',
4312 'share': share_name,
4313 'user-or-group': user_name,
4314 }
4315 self.send_request('cifs-share-access-control-modify', api_args)
4317 @na_utils.trace
4318 def remove_cifs_share_access(self, share_name, user_name):
4319 api_args = {'user-or-group': user_name, 'share': share_name}
4320 self.send_request('cifs-share-access-control-delete', api_args)
4322 @na_utils.trace
4323 def remove_cifs_share(self, share_name):
4324 try:
4325 self.send_request('cifs-share-delete', {'share-name': share_name})
4326 except netapp_api.NaApiError as e:
4327 if e.code == netapp_api.EOBJECTNOTFOUND: 4327 ↛ 4329line 4327 didn't jump to line 4329 because the condition on line 4327 was always true
4328 return
4329 raise
4331 @na_utils.trace
4332 def add_nfs_export_rule(self, policy_name, client_match, readonly,
4333 auth_methods):
4334 rule_indices = self._get_nfs_export_rule_indices(policy_name,
4335 client_match)
4336 if not rule_indices:
4337 self._add_nfs_export_rule(policy_name, client_match, readonly,
4338 auth_methods)
4339 else:
4340 # Update first rule and delete the rest
4341 self._update_nfs_export_rule(
4342 policy_name, client_match, readonly, rule_indices.pop(0),
4343 auth_methods)
4344 self._remove_nfs_export_rules(policy_name, rule_indices)
4346 @na_utils.trace
4347 def _add_nfs_export_rule(self, policy_name, client_match, readonly,
4348 auth_methods):
4349 api_args = {
4350 'policy-name': policy_name,
4351 'client-match': client_match,
4352 'ro-rule': [],
4353 'rw-rule': [],
4354 'super-user-security': [],
4355 }
4356 for am in auth_methods:
4357 api_args['ro-rule'].append({'security-flavor': am})
4358 api_args['rw-rule'].append({'security-flavor': am})
4359 api_args['super-user-security'].append({'security-flavor': am})
4360 if readonly:
4361 # readonly, overwrite with auth method 'never'
4362 api_args['rw-rule'] = [{'security-flavor': 'never'}]
4364 self.send_request('export-rule-create', api_args)
4366 @na_utils.trace
4367 def _update_nfs_export_rule(self, policy_name, client_match, readonly,
4368 rule_index, auth_methods):
4369 api_args = {
4370 'policy-name': policy_name,
4371 'rule-index': rule_index,
4372 'client-match': client_match,
4373 'ro-rule': [],
4374 'rw-rule': [],
4375 'super-user-security': [],
4376 }
4377 for am in auth_methods:
4378 api_args['ro-rule'].append({'security-flavor': am})
4379 api_args['rw-rule'].append({'security-flavor': am})
4380 api_args['super-user-security'].append({'security-flavor': am})
4381 if readonly:
4382 api_args['rw-rule'] = [{'security-flavor': 'never'}]
4384 self.send_request('export-rule-modify', api_args)
4386 @na_utils.trace
4387 def _get_nfs_export_rule_indices(self, policy_name, client_match):
4388 api_args = {
4389 'query': {
4390 'export-rule-info': {
4391 'policy-name': policy_name,
4392 'client-match': client_match,
4393 },
4394 },
4395 'desired-attributes': {
4396 'export-rule-info': {
4397 'vserver-name': None,
4398 'policy-name': None,
4399 'client-match': None,
4400 'rule-index': None,
4401 },
4402 },
4403 }
4404 result = self.send_iter_request('export-rule-get-iter', api_args)
4406 attributes_list = result.get_child_by_name(
4407 'attributes-list') or netapp_api.NaElement('none')
4408 export_rule_info_list = attributes_list.get_children()
4410 rule_indices = [int(export_rule_info.get_child_content('rule-index'))
4411 for export_rule_info in export_rule_info_list]
4412 rule_indices.sort()
4413 return [str(rule_index) for rule_index in rule_indices]
4415 @na_utils.trace
4416 def remove_nfs_export_rule(self, policy_name, client_match):
4417 rule_indices = self._get_nfs_export_rule_indices(policy_name,
4418 client_match)
4419 self._remove_nfs_export_rules(policy_name, rule_indices)
4421 @na_utils.trace
4422 def _remove_nfs_export_rules(self, policy_name, rule_indices):
4423 for rule_index in rule_indices:
4424 api_args = {
4425 'policy-name': policy_name,
4426 'rule-index': rule_index
4427 }
4428 try:
4429 self.send_request('export-rule-destroy', api_args)
4430 except netapp_api.NaApiError as e:
4431 if e.code != netapp_api.EOBJECTNOTFOUND:
4432 raise
4434 @na_utils.trace
4435 def clear_nfs_export_policy_for_volume(self, volume_name):
4436 self.set_nfs_export_policy_for_volume(volume_name, 'default')
4438 @na_utils.trace
4439 def set_nfs_export_policy_for_volume(self, volume_name, policy_name):
4440 api_args = {
4441 'query': {
4442 'volume-attributes': {
4443 'volume-id-attributes': {
4444 'name': volume_name,
4445 },
4446 },
4447 },
4448 'attributes': {
4449 'volume-attributes': {
4450 'volume-export-attributes': {
4451 'policy': policy_name,
4452 },
4453 },
4454 },
4455 }
4456 self.send_request('volume-modify-iter', api_args)
4458 @na_utils.trace
4459 def set_qos_policy_group_for_volume(self, volume_name,
4460 qos_policy_group_name):
4461 api_args = {
4462 'query': {
4463 'volume-attributes': {
4464 'volume-id-attributes': {
4465 'name': volume_name,
4466 },
4467 },
4468 },
4469 'attributes': {
4470 'volume-attributes': {
4471 'volume-qos-attributes': {
4472 'policy-group-name': qos_policy_group_name,
4473 },
4474 },
4475 },
4476 }
4477 self.send_request('volume-modify-iter', api_args)
4479 @na_utils.trace
4480 def set_qos_adaptive_policy_group_for_volume(self, volume_name,
4481 qos_policy_group_name):
4482 if not self.features.ADAPTIVE_QOS: 4482 ↛ 4483line 4482 didn't jump to line 4483 because the condition on line 4482 was never true
4483 msg = 'Adaptive QoS not supported on this backend ONTAP version.'
4484 raise exception.NetAppException(msg)
4486 api_args = {
4487 'query': {
4488 'volume-attributes': {
4489 'volume-id-attributes': {
4490 'name': volume_name,
4491 },
4492 },
4493 },
4494 'attributes': {
4495 'volume-attributes': {
4496 'volume-qos-attributes': {
4497 'adaptive-policy-group-name': qos_policy_group_name,
4498 },
4499 },
4500 },
4501 }
4502 self.send_request('volume-modify-iter', api_args)
4504 @na_utils.trace
4505 def get_nfs_export_policy_for_volume(self, volume_name):
4506 """Get the name of the export policy for a volume."""
4508 api_args = {
4509 'query': {
4510 'volume-attributes': {
4511 'volume-id-attributes': {
4512 'name': volume_name,
4513 },
4514 },
4515 },
4516 'desired-attributes': {
4517 'volume-attributes': {
4518 'volume-export-attributes': {
4519 'policy': None,
4520 },
4521 },
4522 },
4523 }
4524 result = self.send_iter_request('volume-get-iter', api_args)
4526 attributes_list = result.get_child_by_name(
4527 'attributes-list') or netapp_api.NaElement('none')
4528 volume_attributes = attributes_list.get_child_by_name(
4529 'volume-attributes') or netapp_api.NaElement('none')
4530 volume_export_attributes = volume_attributes.get_child_by_name(
4531 'volume-export-attributes') or netapp_api.NaElement('none')
4533 export_policy = volume_export_attributes.get_child_content('policy')
4535 if not export_policy:
4536 msg = _('Could not find export policy for volume %s.')
4537 raise exception.NetAppException(msg % volume_name)
4539 return export_policy
4541 @na_utils.trace
4542 def create_nfs_export_policy(self, policy_name):
4543 api_args = {'policy-name': policy_name}
4544 try:
4545 self.send_request('export-policy-create', api_args)
4546 except netapp_api.NaApiError as e:
4547 if e.code != netapp_api.EDUPLICATEENTRY:
4548 raise
4550 @na_utils.trace
4551 def soft_delete_nfs_export_policy(self, policy_name):
4552 try:
4553 self.delete_nfs_export_policy(policy_name)
4554 except netapp_api.NaApiError:
4555 # NOTE(cknight): Policy deletion can fail if called too soon after
4556 # removing from a flexvol. So rename for later harvesting.
4557 self.rename_nfs_export_policy(policy_name,
4558 DELETED_PREFIX + policy_name)
4560 @na_utils.trace
4561 def delete_nfs_export_policy(self, policy_name):
4562 api_args = {'policy-name': policy_name}
4563 try:
4564 self.send_request('export-policy-destroy', api_args)
4565 except netapp_api.NaApiError as e:
4566 if e.code == netapp_api.EOBJECTNOTFOUND:
4567 return
4568 raise
4570 @na_utils.trace
4571 def rename_nfs_export_policy(self, policy_name, new_policy_name):
4572 api_args = {
4573 'policy-name': policy_name,
4574 'new-policy-name': new_policy_name
4575 }
4576 self.send_request('export-policy-rename', api_args)
4578 @na_utils.trace
4579 def prune_deleted_nfs_export_policies(self):
4580 deleted_policy_map = self._get_deleted_nfs_export_policies()
4581 for vserver in deleted_policy_map:
4582 client = copy.deepcopy(self)
4583 client.set_vserver(vserver)
4584 for policy in deleted_policy_map[vserver]:
4585 try:
4586 client.delete_nfs_export_policy(policy)
4587 except netapp_api.NaApiError:
4588 LOG.debug('Could not delete export policy %s.', policy)
4590 @na_utils.trace
4591 def _get_deleted_nfs_export_policies(self):
4592 api_args = {
4593 'query': {
4594 'export-policy-info': {
4595 'policy-name': DELETED_PREFIX + '*',
4596 },
4597 },
4598 'desired-attributes': {
4599 'export-policy-info': {
4600 'policy-name': None,
4601 'vserver': None,
4602 },
4603 },
4604 }
4605 result = self.send_iter_request('export-policy-get-iter', api_args)
4607 attributes_list = result.get_child_by_name(
4608 'attributes-list') or netapp_api.NaElement('none')
4610 policy_map = {}
4611 for export_info in attributes_list.get_children():
4612 vserver = export_info.get_child_content('vserver')
4613 policies = policy_map.get(vserver, [])
4614 policies.append(export_info.get_child_content('policy-name'))
4615 policy_map[vserver] = policies
4617 return policy_map
4619 @na_utils.trace
4620 def _get_ems_log_destination_vserver(self):
4621 """Returns the best vserver destination for EMS messages."""
4622 major, minor = self.get_ontapi_version(cached=True)
4624 if (major > 1) or (major == 1 and minor > 15):
4625 # Prefer admin Vserver (requires cluster credentials).
4626 admin_vservers = self.list_vservers(vserver_type='admin')
4627 if admin_vservers:
4628 return admin_vservers[0]
4630 # Fall back to data Vserver.
4631 data_vservers = self.list_vservers(vserver_type='data')
4632 if data_vservers:
4633 return data_vservers[0]
4635 # If older API version, or no other Vservers found, use node Vserver.
4636 node_vservers = self.list_vservers(vserver_type='node')
4637 if node_vservers:
4638 return node_vservers[0]
4640 raise exception.NotFound("No Vserver found to receive EMS messages.")
4642 @na_utils.trace
4643 def send_ems_log_message(self, message_dict):
4644 """Sends a message to the Data ONTAP EMS log."""
4646 # NOTE(cknight): Cannot use deepcopy on the connection context
4647 node_client = copy.copy(self)
4648 node_client.connection = copy.copy(self.connection.get_client())
4649 node_client.connection.set_timeout(25)
4651 try:
4652 node_client.set_vserver(self._get_ems_log_destination_vserver())
4653 node_client.send_request('ems-autosupport-log', message_dict)
4654 LOG.debug('EMS executed successfully.')
4655 except netapp_api.NaApiError as e:
4656 LOG.warning('Failed to invoke EMS. %s', e)
4658 @na_utils.trace
4659 def get_aggregate(self, aggregate_name):
4660 """Get aggregate attributes needed for the storage service catalog."""
4662 if not aggregate_name:
4663 return {}
4665 desired_attributes = {
4666 'aggr-attributes': {
4667 'aggregate-name': None,
4668 'aggr-raid-attributes': {
4669 'raid-type': None,
4670 'is-hybrid': None,
4671 },
4672 'aggr-ownership-attributes': {
4673 'home-id': None,
4674 'owner-id': None,
4675 },
4676 },
4677 }
4679 if self.features.SNAPLOCK:
4680 snaplock_attributes = {'is-snaplock': None, 'snaplock-type': None}
4681 desired_attributes['aggr-attributes'][
4682 'aggr-snaplock-attributes'] = snaplock_attributes
4683 try:
4684 aggrs = self._get_aggregates(
4685 aggregate_names=[aggregate_name],
4686 desired_attributes=desired_attributes
4687 )
4688 except netapp_api.NaApiError:
4689 msg = _('Failed to get info for aggregate %s.')
4690 LOG.exception(msg, aggregate_name)
4691 return {}
4693 if len(aggrs) < 1:
4694 return {}
4696 aggr_attributes = aggrs[0]
4697 aggr_raid_attrs = aggr_attributes.get_child_by_name(
4698 'aggr-raid-attributes') or netapp_api.NaElement('none')
4699 aggr_owner_attrs = aggr_attributes.get_child_by_name(
4700 'aggr-ownership-attributes') or netapp_api.NaElement('none')
4701 aggr_snaplock_attrs = aggr_attributes.get_child_by_name(
4702 'aggr-snaplock-attributes') or netapp_api.NaElement('none')
4704 aggregate = {
4705 'name': aggr_attributes.get_child_content('aggregate-name'),
4706 'raid-type': aggr_raid_attrs.get_child_content('raid-type'),
4707 'is-hybrid': strutils.bool_from_string(
4708 aggr_raid_attrs.get_child_content('is-hybrid')
4709 ),
4710 'is-home': (aggr_owner_attrs.get_child_content('owner-id') ==
4711 aggr_owner_attrs.get_child_content('home-id')),
4712 'is-snaplock': aggr_snaplock_attrs.get_child_content(
4713 'is-snaplock',
4714 ),
4715 'snaplock-type': aggr_snaplock_attrs.get_child_content(
4716 'snaplock-type',
4717 ),
4718 }
4719 return aggregate
4721 @na_utils.trace
4722 def get_aggregate_disk_types(self, aggregate_name):
4723 """Get the disk type(s) of an aggregate."""
4725 disk_types = set()
4726 disk_types.update(self._get_aggregate_disk_types(aggregate_name))
4727 if self.features.ADVANCED_DISK_PARTITIONING:
4728 disk_types.update(self._get_aggregate_disk_types(aggregate_name,
4729 shared=True))
4731 return list(disk_types) if disk_types else None
4733 @na_utils.trace
4734 def _get_aggregate_disk_types(self, aggregate_name, shared=False):
4735 """Get the disk type(s) of an aggregate."""
4737 disk_types = set()
4739 if shared:
4740 disk_raid_info = {
4741 'disk-shared-info': {
4742 'aggregate-list': {
4743 'shared-aggregate-info': {
4744 'aggregate-name': aggregate_name,
4745 },
4746 },
4747 },
4748 }
4749 else:
4750 disk_raid_info = {
4751 'disk-aggregate-info': {
4752 'aggregate-name': aggregate_name,
4753 },
4754 }
4756 api_args = {
4757 'query': {
4758 'storage-disk-info': {
4759 'disk-raid-info': disk_raid_info,
4760 },
4761 },
4762 'desired-attributes': {
4763 'storage-disk-info': {
4764 'disk-raid-info': {
4765 'effective-disk-type': None,
4766 },
4767 },
4768 },
4769 }
4771 try:
4772 result = self.send_iter_request('storage-disk-get-iter', api_args)
4773 except netapp_api.NaApiError:
4774 msg = _('Failed to get disk info for aggregate %s.')
4775 LOG.exception(msg, aggregate_name)
4776 return disk_types
4778 attributes_list = result.get_child_by_name(
4779 'attributes-list') or netapp_api.NaElement('none')
4781 for storage_disk_info in attributes_list.get_children():
4783 disk_raid_info = storage_disk_info.get_child_by_name(
4784 'disk-raid-info') or netapp_api.NaElement('none')
4785 disk_type = disk_raid_info.get_child_content(
4786 'effective-disk-type')
4787 if disk_type: 4787 ↛ 4781line 4787 didn't jump to line 4781 because the condition on line 4787 was always true
4788 disk_types.add(disk_type)
4790 return disk_types
4792 @na_utils.trace
4793 def check_for_cluster_credentials(self):
4794 try:
4795 self.list_cluster_nodes()
4796 # API succeeded, so definitely a cluster management LIF
4797 return True
4798 except netapp_api.NaApiError as e:
4799 if e.code == netapp_api.EAPINOTFOUND:
4800 LOG.debug('Not connected to cluster management LIF.')
4801 return False
4802 else:
4803 raise
4805 @na_utils.trace
4806 def get_cluster_name(self):
4807 """Gets cluster name."""
4808 api_args = {
4809 'desired-attributes': {
4810 'cluster-identity-info': {
4811 'cluster-name': None,
4812 }
4813 }
4814 }
4815 result = self.send_request('cluster-identity-get', api_args,
4816 enable_tunneling=False)
4817 attributes = result.get_child_by_name('attributes')
4818 cluster_identity = attributes.get_child_by_name(
4819 'cluster-identity-info')
4820 return cluster_identity.get_child_content('cluster-name')
4822 @na_utils.trace
4823 def create_cluster_peer(self, addresses, username=None, password=None,
4824 passphrase=None):
4825 """Creates a cluster peer relationship."""
4827 api_args = {
4828 'peer-addresses': [
4829 {'remote-inet-address': address} for address in addresses
4830 ],
4831 }
4832 if username: 4832 ↛ 4834line 4832 didn't jump to line 4834 because the condition on line 4832 was always true
4833 api_args['user-name'] = username
4834 if password: 4834 ↛ 4836line 4834 didn't jump to line 4836 because the condition on line 4834 was always true
4835 api_args['password'] = password
4836 if passphrase: 4836 ↛ 4839line 4836 didn't jump to line 4839 because the condition on line 4836 was always true
4837 api_args['passphrase'] = passphrase
4839 self.send_request('cluster-peer-create', api_args,
4840 enable_tunneling=False)
4842 @na_utils.trace
4843 def get_cluster_peers(self, remote_cluster_name=None):
4844 """Gets one or more cluster peer relationships."""
4846 api_args = {}
4847 if remote_cluster_name:
4848 api_args['query'] = {
4849 'cluster-peer-info': {
4850 'remote-cluster-name': remote_cluster_name,
4851 }
4852 }
4854 result = self.send_iter_request('cluster-peer-get-iter', api_args)
4855 if not self._has_records(result):
4856 return []
4858 cluster_peers = []
4860 for cluster_peer_info in result.get_child_by_name(
4861 'attributes-list').get_children():
4863 cluster_peer = {
4864 'active-addresses': [],
4865 'peer-addresses': []
4866 }
4868 active_addresses = cluster_peer_info.get_child_by_name(
4869 'active-addresses') or netapp_api.NaElement('none')
4870 for address in active_addresses.get_children():
4871 cluster_peer['active-addresses'].append(address.get_content())
4873 peer_addresses = cluster_peer_info.get_child_by_name(
4874 'peer-addresses') or netapp_api.NaElement('none')
4875 for address in peer_addresses.get_children():
4876 cluster_peer['peer-addresses'].append(address.get_content())
4878 cluster_peer['availability'] = cluster_peer_info.get_child_content(
4879 'availability')
4880 cluster_peer['cluster-name'] = cluster_peer_info.get_child_content(
4881 'cluster-name')
4882 cluster_peer['cluster-uuid'] = cluster_peer_info.get_child_content(
4883 'cluster-uuid')
4884 cluster_peer['remote-cluster-name'] = (
4885 cluster_peer_info.get_child_content('remote-cluster-name'))
4886 cluster_peer['serial-number'] = (
4887 cluster_peer_info.get_child_content('serial-number'))
4888 cluster_peer['timeout'] = cluster_peer_info.get_child_content(
4889 'timeout')
4891 cluster_peers.append(cluster_peer)
4893 return cluster_peers
4895 @na_utils.trace
4896 def delete_cluster_peer(self, cluster_name):
4897 """Deletes a cluster peer relationship."""
4899 api_args = {'cluster-name': cluster_name}
4900 self.send_request('cluster-peer-delete', api_args,
4901 enable_tunneling=False)
4903 @na_utils.trace
4904 def get_cluster_peer_policy(self):
4905 """Gets the cluster peering policy configuration."""
4907 if not self.features.CLUSTER_PEER_POLICY:
4908 return {}
4910 result = self.send_request('cluster-peer-policy-get')
4912 attributes = result.get_child_by_name(
4913 'attributes') or netapp_api.NaElement('none')
4914 cluster_peer_policy = attributes.get_child_by_name(
4915 'cluster-peer-policy') or netapp_api.NaElement('none')
4917 policy = {
4918 'is-unauthenticated-access-permitted':
4919 cluster_peer_policy.get_child_content(
4920 'is-unauthenticated-access-permitted'),
4921 'passphrase-minimum-length':
4922 cluster_peer_policy.get_child_content(
4923 'passphrase-minimum-length'),
4924 }
4926 if policy['is-unauthenticated-access-permitted'] is not None: 4926 ↛ 4930line 4926 didn't jump to line 4930 because the condition on line 4926 was always true
4927 policy['is-unauthenticated-access-permitted'] = (
4928 strutils.bool_from_string(
4929 policy['is-unauthenticated-access-permitted']))
4930 if policy['passphrase-minimum-length'] is not None: 4930 ↛ 4934line 4930 didn't jump to line 4934 because the condition on line 4930 was always true
4931 policy['passphrase-minimum-length'] = int(
4932 policy['passphrase-minimum-length'])
4934 return policy
4936 @na_utils.trace
4937 def set_cluster_peer_policy(self, is_unauthenticated_access_permitted=None,
4938 passphrase_minimum_length=None):
4939 """Modifies the cluster peering policy configuration."""
4941 if not self.features.CLUSTER_PEER_POLICY:
4942 return
4944 if (is_unauthenticated_access_permitted is None and
4945 passphrase_minimum_length is None):
4946 return
4948 api_args = {}
4949 if is_unauthenticated_access_permitted is not None: 4949 ↛ 4953line 4949 didn't jump to line 4953 because the condition on line 4949 was always true
4950 api_args['is-unauthenticated-access-permitted'] = (
4951 'true' if strutils.bool_from_string(
4952 is_unauthenticated_access_permitted) else 'false')
4953 if passphrase_minimum_length is not None: 4953 ↛ 4957line 4953 didn't jump to line 4957 because the condition on line 4953 was always true
4954 api_args['passphrase-minlength'] = str(
4955 passphrase_minimum_length)
4957 self.send_request('cluster-peer-policy-modify', api_args)
4959 @na_utils.trace
4960 def create_vserver_peer(self, vserver_name, peer_vserver_name,
4961 peer_cluster_name=None):
4962 """Creates a Vserver peer relationship for SnapMirrors."""
4963 api_args = {
4964 'vserver': vserver_name,
4965 'peer-vserver': peer_vserver_name,
4966 'applications': [
4967 {'vserver-peer-application': 'snapmirror'},
4968 ],
4969 }
4970 if peer_cluster_name:
4971 api_args['peer-cluster'] = peer_cluster_name
4972 self.send_request('vserver-peer-create', api_args,
4973 enable_tunneling=False)
4975 @na_utils.trace
4976 def delete_vserver_peer(self, vserver_name, peer_vserver_name):
4977 """Deletes a Vserver peer relationship."""
4979 api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
4980 self.send_request('vserver-peer-delete', api_args,
4981 enable_tunneling=False)
4983 @na_utils.trace
4984 def accept_vserver_peer(self, vserver_name, peer_vserver_name):
4985 """Accepts a pending Vserver peer relationship."""
4987 api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
4988 self.send_request('vserver-peer-accept', api_args,
4989 enable_tunneling=False)
4991 @na_utils.trace
4992 def get_vserver_peers(self, vserver_name=None, peer_vserver_name=None):
4993 """Gets one or more Vserver peer relationships."""
4995 api_args = None
4996 if vserver_name or peer_vserver_name: 4996 ↛ 5005line 4996 didn't jump to line 5005 because the condition on line 4996 was always true
4997 api_args = {'query': {'vserver-peer-info': {}}}
4998 if vserver_name: 4998 ↛ 5001line 4998 didn't jump to line 5001 because the condition on line 4998 was always true
4999 api_args['query']['vserver-peer-info']['vserver'] = (
5000 vserver_name)
5001 if peer_vserver_name: 5001 ↛ 5005line 5001 didn't jump to line 5005 because the condition on line 5001 was always true
5002 api_args['query']['vserver-peer-info']['peer-vserver'] = (
5003 peer_vserver_name)
5005 result = self.send_iter_request('vserver-peer-get-iter', api_args)
5006 if not self._has_records(result):
5007 return []
5009 vserver_peers = []
5011 for vserver_peer_info in result.get_child_by_name(
5012 'attributes-list').get_children():
5014 vserver_peer = {
5015 'vserver': vserver_peer_info.get_child_content('vserver'),
5016 'peer-vserver':
5017 vserver_peer_info.get_child_content('peer-vserver'),
5018 'peer-state':
5019 vserver_peer_info.get_child_content('peer-state'),
5020 'peer-cluster':
5021 vserver_peer_info.get_child_content('peer-cluster'),
5022 }
5023 vserver_peers.append(vserver_peer)
5025 return vserver_peers
5027 def _ensure_snapmirror_v2(self):
5028 """Verify support for SnapMirror control plane v2."""
5029 if not self.features.SNAPMIRROR_V2:
5030 msg = _('SnapMirror features require Data ONTAP 8.2 or later.')
5031 raise exception.NetAppException(msg)
5033 @na_utils.trace
5034 def create_snapmirror_vol(self, source_vserver, source_volume,
5035 destination_vserver, destination_volume,
5036 relationship_type, schedule=None,
5037 policy=na_utils.MIRROR_ALL_SNAP_POLICY):
5038 """Creates a SnapMirror relationship between volumes."""
5039 self._create_snapmirror(source_vserver, destination_vserver,
5040 source_volume=source_volume,
5041 destination_volume=destination_volume,
5042 schedule=schedule, policy=policy,
5043 relationship_type=relationship_type)
5045 @na_utils.trace
5046 def create_snapmirror_svm(self, source_vserver, destination_vserver,
5047 schedule=None, policy=None,
5048 relationship_type=na_utils.DATA_PROTECTION_TYPE,
5049 identity_preserve=True,
5050 max_transfer_rate=None):
5051 """Creates a SnapMirror relationship between vServers."""
5052 self._create_snapmirror(source_vserver, destination_vserver,
5053 schedule=schedule, policy=policy,
5054 relationship_type=relationship_type,
5055 identity_preserve=identity_preserve,
5056 max_transfer_rate=max_transfer_rate)
5058 @na_utils.trace
5059 def _create_snapmirror(self, source_vserver, destination_vserver,
5060 source_volume=None, destination_volume=None,
5061 schedule=None, policy=None,
5062 relationship_type=na_utils.DATA_PROTECTION_TYPE,
5063 identity_preserve=None, max_transfer_rate=None):
5064 """Creates a SnapMirror relationship (cDOT 8.2 or later only)."""
5065 self._ensure_snapmirror_v2()
5067 api_args = {
5068 'source-vserver': source_vserver,
5069 'destination-vserver': destination_vserver,
5070 'relationship-type': relationship_type,
5071 }
5072 if source_volume:
5073 api_args['source-volume'] = source_volume
5074 if destination_volume:
5075 api_args['destination-volume'] = destination_volume
5076 if schedule:
5077 api_args['schedule'] = schedule
5078 if policy:
5079 api_args['policy'] = policy
5080 if identity_preserve is not None:
5081 api_args['identity-preserve'] = (
5082 'true' if identity_preserve is True else 'false')
5083 if max_transfer_rate is not None:
5084 api_args['max-transfer-rate'] = max_transfer_rate
5086 try:
5087 self.send_request('snapmirror-create', api_args)
5088 except netapp_api.NaApiError as e:
5089 if e.code != netapp_api.ERELATION_EXISTS:
5090 raise
5092 def _build_snapmirror_request(self, source_path=None, dest_path=None,
5093 source_vserver=None, dest_vserver=None,
5094 source_volume=None, dest_volume=None):
5095 """Build a default SnapMirror request."""
5097 req_args = {}
5098 if source_path:
5099 req_args['source-location'] = source_path
5100 if dest_path:
5101 req_args['destination-location'] = dest_path
5102 if source_vserver:
5103 req_args['source-vserver'] = source_vserver
5104 if source_volume:
5105 req_args['source-volume'] = source_volume
5106 if dest_vserver:
5107 req_args['destination-vserver'] = dest_vserver
5108 if dest_volume:
5109 req_args['destination-volume'] = dest_volume
5111 return req_args
5113 @na_utils.trace
5114 def initialize_snapmirror_vol(self, source_vserver, source_volume,
5115 dest_vserver, dest_volume,
5116 source_snapshot=None,
5117 transfer_priority=None):
5118 """Initializes a SnapMirror relationship between volumes."""
5119 return self._initialize_snapmirror(
5120 source_vserver=source_vserver, dest_vserver=dest_vserver,
5121 source_volume=source_volume, dest_volume=dest_volume,
5122 source_snapshot=source_snapshot,
5123 transfer_priority=transfer_priority)
5125 @na_utils.trace
5126 def initialize_snapmirror_svm(self, source_vserver, dest_vserver,
5127 transfer_priority=None):
5128 """Initializes a SnapMirror relationship between vServer."""
5129 source_path = source_vserver + ':'
5130 dest_path = dest_vserver + ':'
5131 return self._initialize_snapmirror(source_path=source_path,
5132 dest_path=dest_path,
5133 transfer_priority=transfer_priority)
5135 @na_utils.trace
5136 def _initialize_snapmirror(self, source_path=None, dest_path=None,
5137 source_vserver=None, dest_vserver=None,
5138 source_volume=None, dest_volume=None,
5139 source_snapshot=None, transfer_priority=None):
5140 """Initializes a SnapMirror relationship."""
5141 self._ensure_snapmirror_v2()
5143 api_args = self._build_snapmirror_request(
5144 source_path, dest_path, source_vserver,
5145 dest_vserver, source_volume, dest_volume)
5146 if source_snapshot:
5147 api_args['source-snapshot'] = source_snapshot
5148 if transfer_priority:
5149 api_args['transfer-priority'] = transfer_priority
5151 result = self.send_request('snapmirror-initialize', api_args)
5153 result_info = {}
5154 result_info['operation-id'] = result.get_child_content(
5155 'result-operation-id')
5156 result_info['status'] = result.get_child_content('result-status')
5157 result_info['jobid'] = result.get_child_content('result-jobid')
5158 result_info['error-code'] = result.get_child_content(
5159 'result-error-code')
5160 result_info['error-message'] = result.get_child_content(
5161 'result-error-message')
5163 return result_info
5165 @na_utils.trace
5166 def release_snapmirror_vol(self, source_vserver, source_volume,
5167 dest_vserver, dest_volume,
5168 relationship_info_only=False):
5169 """Removes a SnapMirror relationship on the source endpoint."""
5171 self._ensure_snapmirror_v2()
5172 snapmirror_destinations_list = self.get_snapmirror_destinations(
5173 source_vserver=source_vserver,
5174 dest_vserver=dest_vserver,
5175 source_volume=source_volume,
5176 dest_volume=dest_volume,
5177 desired_attributes=['relationship-id'])
5179 if len(snapmirror_destinations_list) > 1:
5180 msg = ("Expected snapmirror relationship to be unique. "
5181 "List returned: %s." % snapmirror_destinations_list)
5182 raise exception.NetAppException(msg)
5184 api_args = self._build_snapmirror_request(
5185 source_vserver=source_vserver, dest_vserver=dest_vserver,
5186 source_volume=source_volume, dest_volume=dest_volume)
5187 api_args['relationship-info-only'] = (
5188 'true' if relationship_info_only else 'false')
5190 # NOTE(nahimsouza): This verification is needed because an empty list
5191 # is returned in snapmirror_destinations_list when a single share is
5192 # created with only one replica and this replica is deleted, thus there
5193 # will be no relationship-id in that case.
5194 if len(snapmirror_destinations_list) == 1:
5195 api_args['relationship-id'] = (
5196 snapmirror_destinations_list[0]['relationship-id'])
5198 self.send_request('snapmirror-release', api_args,
5199 enable_tunneling=True)
5201 @na_utils.trace
5202 def release_snapmirror_svm(self, source_vserver, dest_vserver,
5203 relationship_info_only=False):
5204 """Removes a SnapMirror relationship on the source endpoint."""
5205 source_path = source_vserver + ':'
5206 dest_path = dest_vserver + ':'
5207 dest_info = self._build_snapmirror_request(
5208 source_path=source_path, dest_path=dest_path)
5209 self._ensure_snapmirror_v2()
5210 api_args = {
5211 'query': {
5212 'snapmirror-destination-info': dest_info,
5213 },
5214 'relationship-info-only': (
5215 'true' if relationship_info_only else 'false'),
5216 }
5217 self.send_request('snapmirror-release-iter', api_args,
5218 enable_tunneling=False)
5220 @na_utils.trace
5221 def quiesce_snapmirror_vol(self, source_vserver, source_volume,
5222 dest_vserver, dest_volume):
5223 """Disables future transfers to a SnapMirror destination."""
5224 self._quiesce_snapmirror(source_vserver=source_vserver,
5225 dest_vserver=dest_vserver,
5226 source_volume=source_volume,
5227 dest_volume=dest_volume)
5229 @na_utils.trace
5230 def quiesce_snapmirror_svm(self, source_vserver, dest_vserver):
5231 """Disables future transfers to a SnapMirror destination."""
5232 source_path = source_vserver + ':'
5233 dest_path = dest_vserver + ':'
5234 self._quiesce_snapmirror(source_path=source_path, dest_path=dest_path)
5236 @na_utils.trace
5237 def _quiesce_snapmirror(self, source_path=None, dest_path=None,
5238 source_vserver=None, dest_vserver=None,
5239 source_volume=None, dest_volume=None):
5240 """Disables future transfers to a SnapMirror destination."""
5241 self._ensure_snapmirror_v2()
5243 api_args = self._build_snapmirror_request(
5244 source_path, dest_path, source_vserver,
5245 dest_vserver, source_volume, dest_volume)
5247 self.send_request('snapmirror-quiesce', api_args)
5249 @na_utils.trace
5250 def abort_snapmirror_vol(self, source_vserver, source_volume,
5251 dest_vserver, dest_volume,
5252 clear_checkpoint=False):
5253 """Stops ongoing transfers for a SnapMirror relationship."""
5254 self._abort_snapmirror(source_vserver=source_vserver,
5255 dest_vserver=dest_vserver,
5256 source_volume=source_volume,
5257 dest_volume=dest_volume,
5258 clear_checkpoint=clear_checkpoint)
5260 @na_utils.trace
5261 def abort_snapmirror_svm(self, source_vserver, dest_vserver,
5262 clear_checkpoint=False):
5263 """Stops ongoing transfers for a SnapMirror relationship."""
5264 source_path = source_vserver + ':'
5265 dest_path = dest_vserver + ':'
5266 self._abort_snapmirror(source_path=source_path, dest_path=dest_path,
5267 clear_checkpoint=clear_checkpoint)
5269 @na_utils.trace
5270 def _abort_snapmirror(self, source_path=None, dest_path=None,
5271 source_vserver=None, dest_vserver=None,
5272 source_volume=None, dest_volume=None,
5273 clear_checkpoint=False):
5274 """Stops ongoing transfers for a SnapMirror relationship."""
5275 self._ensure_snapmirror_v2()
5277 api_args = self._build_snapmirror_request(
5278 source_path, dest_path, source_vserver,
5279 dest_vserver, source_volume, dest_volume)
5280 api_args['clear-checkpoint'] = 'true' if clear_checkpoint else 'false'
5282 try:
5283 self.send_request('snapmirror-abort', api_args)
5284 except netapp_api.NaApiError as e:
5285 if e.code != netapp_api.ENOTRANSFER_IN_PROGRESS:
5286 raise
5288 @na_utils.trace
5289 def break_snapmirror_vol(self, source_vserver, source_volume,
5290 dest_vserver, dest_volume):
5291 """Breaks a data protection SnapMirror relationship."""
5292 self._break_snapmirror(source_vserver=source_vserver,
5293 dest_vserver=dest_vserver,
5294 source_volume=source_volume,
5295 dest_volume=dest_volume)
5297 @na_utils.trace
5298 def break_snapmirror_svm(self, source_vserver=None, dest_vserver=None):
5299 """Breaks a data protection SnapMirror relationship."""
5300 source_path = source_vserver + ':' if source_vserver else None
5301 dest_path = dest_vserver + ':' if dest_vserver else None
5302 self._break_snapmirror(source_path=source_path, dest_path=dest_path)
5304 @na_utils.trace
5305 def _break_snapmirror(self, source_path=None, dest_path=None,
5306 source_vserver=None, dest_vserver=None,
5307 source_volume=None, dest_volume=None):
5308 """Breaks a data protection SnapMirror relationship."""
5309 self._ensure_snapmirror_v2()
5311 api_args = self._build_snapmirror_request(
5312 source_path, dest_path, source_vserver,
5313 dest_vserver, source_volume, dest_volume)
5314 try:
5315 self.send_request('snapmirror-break', api_args)
5316 except netapp_api.NaApiError as e:
5317 break_in_progress = 'SnapMirror operation status is "Breaking"'
5318 if not (e.code == netapp_api.ESVMDR_CANNOT_PERFORM_OP_FOR_STATUS
5319 and break_in_progress in e.message):
5320 raise
5322 @na_utils.trace
5323 def modify_snapmirror_vol(self, source_vserver, source_volume,
5324 dest_vserver, dest_volume,
5325 schedule=None, policy=None, tries=None,
5326 max_transfer_rate=None):
5327 """Modifies a SnapMirror relationship between volumes."""
5328 self._modify_snapmirror(source_vserver=source_vserver,
5329 dest_vserver=dest_vserver,
5330 source_volume=source_volume,
5331 dest_volume=dest_volume,
5332 schedule=schedule, policy=policy, tries=tries,
5333 max_transfer_rate=max_transfer_rate)
5335 @na_utils.trace
5336 def _modify_snapmirror(self, source_path=None, dest_path=None,
5337 source_vserver=None, dest_vserver=None,
5338 source_volume=None, dest_volume=None,
5339 schedule=None, policy=None, tries=None,
5340 max_transfer_rate=None):
5341 """Modifies a SnapMirror relationship."""
5342 self._ensure_snapmirror_v2()
5344 api_args = self._build_snapmirror_request(
5345 source_path, dest_path, source_vserver,
5346 dest_vserver, source_volume, dest_volume)
5347 if schedule:
5348 api_args['schedule'] = schedule
5349 if policy:
5350 api_args['policy'] = policy
5351 if tries is not None:
5352 api_args['tries'] = tries
5353 if max_transfer_rate is not None:
5354 api_args['max-transfer-rate'] = max_transfer_rate
5356 self.send_request('snapmirror-modify', api_args)
5358 @na_utils.trace
5359 def delete_snapmirror_vol(self, source_vserver, source_volume,
5360 dest_vserver, dest_volume):
5361 """Destroys a SnapMirror relationship between volumes."""
5362 self._delete_snapmirror(source_vserver=source_vserver,
5363 dest_vserver=dest_vserver,
5364 source_volume=source_volume,
5365 dest_volume=dest_volume)
5367 @na_utils.trace
5368 def delete_snapmirror_svm(self, source_vserver, dest_vserver):
5369 """Destroys a SnapMirror relationship between vServers."""
5370 source_path = source_vserver + ':'
5371 dest_path = dest_vserver + ':'
5372 self._delete_snapmirror(source_path=source_path, dest_path=dest_path)
5374 @na_utils.trace
5375 def _delete_snapmirror(self, source_path=None, dest_path=None,
5376 source_vserver=None, dest_vserver=None,
5377 source_volume=None, dest_volume=None):
5378 """Destroys a SnapMirror relationship."""
5379 self._ensure_snapmirror_v2()
5381 snapmirror_info = self._build_snapmirror_request(
5382 source_path, dest_path, source_vserver,
5383 dest_vserver, source_volume, dest_volume)
5385 api_args = {
5386 'query': {
5387 'snapmirror-info': snapmirror_info
5388 }
5389 }
5390 self.send_request('snapmirror-destroy-iter', api_args)
5392 @na_utils.trace
5393 def update_snapmirror_vol(self, source_vserver, source_volume,
5394 dest_vserver, dest_volume):
5395 """Schedules a snapmirror update between volumes."""
5396 self._update_snapmirror(source_vserver=source_vserver,
5397 dest_vserver=dest_vserver,
5398 source_volume=source_volume,
5399 dest_volume=dest_volume)
5401 @na_utils.trace
5402 def update_snapmirror_svm(self, source_vserver, dest_vserver):
5403 """Schedules a snapmirror update between vServers."""
5404 source_path = source_vserver + ':'
5405 dest_path = dest_vserver + ':'
5406 self._update_snapmirror(source_path=source_path, dest_path=dest_path)
5408 @na_utils.trace
5409 def _update_snapmirror(self, source_path=None, dest_path=None,
5410 source_vserver=None, dest_vserver=None,
5411 source_volume=None, dest_volume=None):
5412 """Schedules a snapmirror update."""
5413 self._ensure_snapmirror_v2()
5415 api_args = self._build_snapmirror_request(
5416 source_path, dest_path, source_vserver,
5417 dest_vserver, source_volume, dest_volume)
5419 try:
5420 self.send_request('snapmirror-update', api_args)
5421 except netapp_api.NaApiError as e:
5422 if (e.code != netapp_api.ETRANSFER_IN_PROGRESS and
5423 e.code != netapp_api.EANOTHER_OP_ACTIVE):
5424 raise
5426 @na_utils.trace
5427 def resume_snapmirror_vol(self, source_vserver, source_volume,
5428 dest_vserver, dest_volume):
5429 """Resume a SnapMirror relationship if it is quiesced."""
5430 self._resume_snapmirror(source_vserver=source_vserver,
5431 dest_vserver=dest_vserver,
5432 source_volume=source_volume,
5433 dest_volume=dest_volume)
5435 @na_utils.trace
5436 def resume_snapmirror_svm(self, source_vserver, dest_vserver):
5437 """Resume a SnapMirror relationship if it is quiesced."""
5438 source_path = source_vserver + ':'
5439 dest_path = dest_vserver + ':'
5440 self._resume_snapmirror(source_path=source_path, dest_path=dest_path)
5442 @na_utils.trace
5443 def _resume_snapmirror(self, source_path=None, dest_path=None,
5444 source_vserver=None, dest_vserver=None,
5445 source_volume=None, dest_volume=None):
5446 """Resume a SnapMirror relationship if it is quiesced."""
5447 self._ensure_snapmirror_v2()
5449 api_args = self._build_snapmirror_request(
5450 source_path, dest_path, source_vserver,
5451 dest_vserver, source_volume, dest_volume)
5453 try:
5454 self.send_request('snapmirror-resume', api_args)
5455 except netapp_api.NaApiError as e:
5456 if e.code != netapp_api.ERELATION_NOT_QUIESCED:
5457 raise
5459 @na_utils.trace
5460 def resync_snapmirror_vol(self, source_vserver, source_volume,
5461 dest_vserver, dest_volume):
5462 """Resync a SnapMirror relationship between volumes."""
5463 self._resync_snapmirror(source_vserver=source_vserver,
5464 dest_vserver=dest_vserver,
5465 source_volume=source_volume,
5466 dest_volume=dest_volume)
5468 @na_utils.trace
5469 def resync_snapmirror_svm(self, source_vserver, dest_vserver):
5470 """Resync a SnapMirror relationship between vServers."""
5471 source_path = source_vserver + ':'
5472 dest_path = dest_vserver + ':'
5473 self._resync_snapmirror(source_path=source_path, dest_path=dest_path)
5475 @na_utils.trace
5476 def _resync_snapmirror(self, source_path=None, dest_path=None,
5477 source_vserver=None, dest_vserver=None,
5478 source_volume=None, dest_volume=None):
5479 """Resync a SnapMirror relationship."""
5480 self._ensure_snapmirror_v2()
5482 api_args = self._build_snapmirror_request(
5483 source_path, dest_path, source_vserver,
5484 dest_vserver, source_volume, dest_volume)
5486 self.send_request('snapmirror-resync', api_args)
5488 @na_utils.trace
5489 def _get_snapmirrors(self, source_path=None, dest_path=None,
5490 source_vserver=None, source_volume=None,
5491 dest_vserver=None, dest_volume=None,
5492 desired_attributes=None):
5493 """Gets one or more SnapMirror relationships."""
5495 snapmirror_info = self._build_snapmirror_request(
5496 source_path, dest_path, source_vserver,
5497 dest_vserver, source_volume, dest_volume)
5498 api_args = {}
5499 if snapmirror_info:
5500 api_args['query'] = {
5501 'snapmirror-info': snapmirror_info
5502 }
5503 if desired_attributes:
5504 api_args['desired-attributes'] = desired_attributes
5506 result = self.send_iter_request('snapmirror-get-iter', api_args)
5507 if not self._has_records(result):
5508 return []
5509 else:
5510 return result.get_child_by_name('attributes-list').get_children()
5512 @na_utils.trace
5513 def get_snapmirrors_svm(self, source_vserver=None, dest_vserver=None,
5514 desired_attributes=None):
5515 source_path = source_vserver + ':' if source_vserver else None
5516 dest_path = dest_vserver + ':' if dest_vserver else None
5517 return self.get_snapmirrors(source_path=source_path,
5518 dest_path=dest_path,
5519 desired_attributes=desired_attributes)
5521 @na_utils.trace
5522 def get_snapmirrors(self, source_path=None, dest_path=None,
5523 source_vserver=None, dest_vserver=None,
5524 source_volume=None, dest_volume=None,
5525 desired_attributes=None):
5526 """Gets one or more SnapMirror relationships.
5528 Either the source or destination info may be omitted.
5529 Desired attributes should be a flat list of attribute names.
5530 """
5531 self._ensure_snapmirror_v2()
5533 if desired_attributes is not None: 5533 ↛ 5538line 5533 didn't jump to line 5538 because the condition on line 5533 was always true
5534 desired_attributes = {
5535 'snapmirror-info': {attr: None for attr in desired_attributes},
5536 }
5538 result = self._get_snapmirrors(
5539 source_path=source_path,
5540 dest_path=dest_path,
5541 source_vserver=source_vserver,
5542 source_volume=source_volume,
5543 dest_vserver=dest_vserver,
5544 dest_volume=dest_volume,
5545 desired_attributes=desired_attributes)
5547 snapmirrors = []
5549 for snapmirror_info in result:
5550 snapmirror = {}
5551 for child in snapmirror_info.get_children():
5552 name = self._strip_xml_namespace(child.get_name())
5553 snapmirror[name] = child.get_content()
5554 snapmirrors.append(snapmirror)
5556 return snapmirrors
5558 @na_utils.trace
5559 def _get_snapmirror_destinations(self, source_path=None, dest_path=None,
5560 source_vserver=None, source_volume=None,
5561 dest_vserver=None, dest_volume=None,
5562 desired_attributes=None):
5563 """Gets one or more SnapMirror at source endpoint."""
5565 snapmirror_info = self._build_snapmirror_request(
5566 source_path, dest_path, source_vserver,
5567 dest_vserver, source_volume, dest_volume)
5568 api_args = {}
5569 if snapmirror_info: 5569 ↛ 5573line 5569 didn't jump to line 5573 because the condition on line 5569 was always true
5570 api_args['query'] = {
5571 'snapmirror-destination-info': snapmirror_info
5572 }
5573 if desired_attributes: 5573 ↛ 5574line 5573 didn't jump to line 5574 because the condition on line 5573 was never true
5574 api_args['desired-attributes'] = desired_attributes
5576 result = self.send_iter_request('snapmirror-get-destination-iter',
5577 api_args)
5578 if not self._has_records(result):
5579 return []
5580 else:
5581 return result.get_child_by_name('attributes-list').get_children()
5583 @na_utils.trace
5584 def get_snapmirror_destinations(self, source_path=None, dest_path=None,
5585 source_vserver=None, dest_vserver=None,
5586 source_volume=None, dest_volume=None,
5587 desired_attributes=None):
5588 """Gets one or more SnapMirror relationships in the source endpoint.
5590 Either the source or destination info may be omitted.
5591 Desired attributes should be a flat list of attribute names.
5592 """
5593 self._ensure_snapmirror_v2()
5595 if desired_attributes is not None: 5595 ↛ 5596line 5595 didn't jump to line 5596 because the condition on line 5595 was never true
5596 desired_attributes = {
5597 'snapmirror-destination-info': {
5598 attr: None for attr in desired_attributes},
5599 }
5601 result = self._get_snapmirror_destinations(
5602 source_path=source_path,
5603 dest_path=dest_path,
5604 source_vserver=source_vserver,
5605 source_volume=source_volume,
5606 dest_vserver=dest_vserver,
5607 dest_volume=dest_volume,
5608 desired_attributes=desired_attributes)
5610 snapmirrors = []
5612 for snapmirror_info in result:
5613 snapmirror = {}
5614 for child in snapmirror_info.get_children():
5615 name = self._strip_xml_namespace(child.get_name())
5616 snapmirror[name] = child.get_content()
5617 snapmirrors.append(snapmirror)
5619 return snapmirrors
5621 @na_utils.trace
5622 def get_snapmirror_destinations_svm(self, source_vserver=None,
5623 dest_vserver=None,
5624 desired_attributes=None):
5625 source_path = source_vserver + ':' if source_vserver else None
5626 dest_path = dest_vserver + ':' if dest_vserver else None
5627 return self.get_snapmirror_destinations(
5628 source_path=source_path, dest_path=dest_path,
5629 desired_attributes=desired_attributes)
5631 def volume_has_snapmirror_relationships(self, volume):
5632 """Return True if snapmirror relationships exist for a given volume.
5634 If we have snapmirror control plane license, we can verify whether
5635 the given volume is part of any snapmirror relationships.
5636 """
5637 try:
5638 # Check if volume is a source snapmirror volume
5639 snapmirrors = self.get_snapmirrors(
5640 source_vserver=volume['owning-vserver-name'],
5641 source_volume=volume['name'])
5642 # Check if volume is a destination snapmirror volume
5643 if not snapmirrors:
5644 snapmirrors = self.get_snapmirrors(
5645 dest_vserver=volume['owning-vserver-name'],
5646 dest_volume=volume['name'])
5648 has_snapmirrors = len(snapmirrors) > 0
5649 except netapp_api.NaApiError:
5650 msg = ("Could not determine if volume %s is part of "
5651 "existing snapmirror relationships.")
5652 LOG.exception(msg, volume['name'])
5653 has_snapmirrors = False
5655 return has_snapmirrors
5657 def list_snapmirror_snapshots(self, volume_name, newer_than=None):
5658 """Gets SnapMirror snapshots on a volume."""
5659 api_args = {
5660 'query': {
5661 'snapshot-info': {
5662 'dependency': 'snapmirror',
5663 'volume': volume_name,
5664 },
5665 },
5666 }
5667 if newer_than:
5668 api_args['query']['snapshot-info'][
5669 'access-time'] = '>' + newer_than
5671 result = self.send_iter_request('snapshot-get-iter', api_args)
5673 attributes_list = result.get_child_by_name(
5674 'attributes-list') or netapp_api.NaElement('none')
5676 return [snapshot_info.get_child_content('name')
5677 for snapshot_info in attributes_list.get_children()]
5679 @na_utils.trace
5680 def create_snapmirror_policy(self, policy_name,
5681 policy_type='async_mirror',
5682 discard_network_info=True,
5683 preserve_snapshots=True,
5684 snapmirror_label='all_source_snapshots',
5685 keep=1
5686 ):
5687 """Creates a SnapMirror policy for a vServer."""
5689 self._ensure_snapmirror_v2()
5691 api_args = {
5692 'policy-name': policy_name,
5693 'type': policy_type,
5694 }
5696 if discard_network_info:
5697 api_args['discard-configs'] = {
5698 'svmdr-config-obj': 'network'
5699 }
5701 self.send_request('snapmirror-policy-create', api_args)
5703 if preserve_snapshots:
5704 api_args = {
5705 'policy-name': policy_name,
5706 'snapmirror-label': snapmirror_label,
5707 'keep': keep,
5708 'preserve': 'false'
5709 }
5711 self.send_request('snapmirror-policy-add-rule', api_args)
5713 @na_utils.trace
5714 def delete_snapmirror_policy(self, policy_name):
5715 """Deletes a SnapMirror policy."""
5717 api_args = {
5718 'policy-name': policy_name,
5719 }
5720 try:
5721 self.send_request('snapmirror-policy-delete', api_args)
5722 except netapp_api.NaApiError as e:
5723 if e.code != netapp_api.EOBJECTNOTFOUND: 5723 ↛ 5724line 5723 didn't jump to line 5724 because the condition on line 5723 was never true
5724 raise
5726 @na_utils.trace
5727 def get_snapmirror_policies(self, vserver_name):
5728 """Get all SnapMirror policies associated to a vServer."""
5730 api_args = {
5731 'query': {
5732 'snapmirror-policy-info': {
5733 'vserver-name': vserver_name,
5734 },
5735 },
5736 'desired-attributes': {
5737 'snapmirror-policy-info': {
5738 'policy-name': None,
5739 },
5740 },
5741 }
5742 result = self.send_iter_request('snapmirror-policy-get-iter', api_args)
5743 attributes_list = result.get_child_by_name(
5744 'attributes-list') or netapp_api.NaElement('none')
5746 return [policy_info.get_child_content('policy-name')
5747 for policy_info in attributes_list.get_children()]
5749 @na_utils.trace
5750 def start_volume_move(self, volume_name, vserver, destination_aggregate,
5751 cutover_action='wait', encrypt_destination=None):
5752 """Moves a FlexVol across Vserver aggregates.
5754 Requires cluster-scoped credentials.
5755 """
5756 self._send_volume_move_request(
5757 volume_name, vserver,
5758 destination_aggregate,
5759 cutover_action=cutover_action,
5760 encrypt_destination=encrypt_destination)
5762 @na_utils.trace
5763 def check_volume_move(self, volume_name, vserver, destination_aggregate,
5764 encrypt_destination=None):
5765 """Moves a FlexVol across Vserver aggregates.
5767 Requires cluster-scoped credentials.
5768 """
5769 self._send_volume_move_request(
5770 volume_name,
5771 vserver,
5772 destination_aggregate,
5773 validation_only=True,
5774 encrypt_destination=encrypt_destination)
5776 @na_utils.trace
5777 def _send_volume_move_request(self, volume_name, vserver,
5778 destination_aggregate,
5779 cutover_action='wait',
5780 validation_only=False,
5781 encrypt_destination=None):
5782 """Send request to check if vol move is possible, or start it.
5784 :param volume_name: Name of the FlexVol to be moved.
5785 :param destination_aggregate: Name of the destination aggregate
5786 :param cutover_action: can have one of ['force', 'defer', 'abort',
5787 'wait']. 'force' will force a cutover despite errors (causing
5788 possible client disruptions), 'wait' will wait for cutover to be
5789 triggered manually. 'abort' will rollback move on errors on
5790 cutover, 'defer' will attempt a cutover, but wait for manual
5791 intervention in case of errors.
5792 :param validation_only: If set to True, only validates if the volume
5793 move is possible, does not trigger data copy.
5794 :param encrypt_destination: If set to True, it encrypts the Flexvol
5795 after the volume move is complete.
5796 """
5797 api_args = {
5798 'source-volume': volume_name,
5799 'vserver': vserver,
5800 'dest-aggr': destination_aggregate,
5801 'cutover-action': CUTOVER_ACTION_MAP[cutover_action],
5802 }
5804 if self.features.FLEXVOL_ENCRYPTION:
5805 if encrypt_destination:
5806 api_args['encrypt-destination'] = 'true'
5807 else:
5808 api_args['encrypt-destination'] = 'false'
5809 elif encrypt_destination:
5810 msg = 'Flexvol encryption is not supported on this backend.'
5811 raise exception.NetAppException(msg)
5813 if validation_only:
5814 api_args['perform-validation-only'] = 'true'
5816 self.send_request('volume-move-start', api_args)
5818 @na_utils.trace
5819 def abort_volume_move(self, volume_name, vserver):
5820 """Aborts an existing volume move operation."""
5821 api_args = {
5822 'source-volume': volume_name,
5823 'vserver': vserver,
5824 }
5825 self.send_request('volume-move-trigger-abort', api_args)
5827 @na_utils.trace
5828 def trigger_volume_move_cutover(self, volume_name, vserver, force=True):
5829 """Triggers the cut-over for a volume in data motion."""
5830 api_args = {
5831 'source-volume': volume_name,
5832 'vserver': vserver,
5833 'force': 'true' if force else 'false',
5834 }
5835 self.send_request('volume-move-trigger-cutover', api_args)
5837 @na_utils.trace
5838 def get_volume_move_status(self, volume_name, vserver):
5839 """Gets the current state of a volume move operation."""
5840 api_args = {
5841 'query': {
5842 'volume-move-info': {
5843 'volume': volume_name,
5844 'vserver': vserver,
5845 },
5846 },
5847 'desired-attributes': {
5848 'volume-move-info': {
5849 'percent-complete': None,
5850 'estimated-completion-time': None,
5851 'state': None,
5852 'details': None,
5853 'cutover-action': None,
5854 'phase': None,
5855 },
5856 },
5857 }
5858 result = self.send_iter_request('volume-move-get-iter', api_args)
5860 if not self._has_records(result):
5861 msg = _("Volume %(vol)s in Vserver %(server)s is not part of any "
5862 "data motion operations.")
5863 msg_args = {'vol': volume_name, 'server': vserver}
5864 raise exception.NetAppException(msg % msg_args)
5866 attributes_list = result.get_child_by_name(
5867 'attributes-list') or netapp_api.NaElement('none')
5868 volume_move_info = attributes_list.get_child_by_name(
5869 'volume-move-info') or netapp_api.NaElement('none')
5871 status_info = {
5872 'percent-complete': volume_move_info.get_child_content(
5873 'percent-complete'),
5874 'estimated-completion-time': volume_move_info.get_child_content(
5875 'estimated-completion-time'),
5876 'state': volume_move_info.get_child_content('state'),
5877 'details': volume_move_info.get_child_content('details'),
5878 'cutover-action': volume_move_info.get_child_content(
5879 'cutover-action'),
5880 'phase': volume_move_info.get_child_content('phase'),
5881 }
5882 return status_info
5884 @na_utils.trace
5885 def qos_policy_group_exists(self, qos_policy_group_name):
5886 """Checks if a QoS policy group exists."""
5887 try:
5888 self.qos_policy_group_get(qos_policy_group_name)
5889 except exception.NetAppException:
5890 return False
5891 return True
5893 @na_utils.trace
5894 def qos_policy_group_get(self, qos_policy_group_name):
5895 """Checks if a QoS policy group exists."""
5896 api_args = {
5897 'query': {
5898 'qos-policy-group-info': {
5899 'policy-group': qos_policy_group_name,
5900 },
5901 },
5902 'desired-attributes': {
5903 'qos-policy-group-info': {
5904 'policy-group': None,
5905 'vserver': None,
5906 'max-throughput': None,
5907 'min-throughput': None,
5908 'num-workloads': None
5909 },
5910 },
5911 }
5913 try:
5914 result = self.send_request('qos-policy-group-get-iter',
5915 api_args,
5916 False)
5917 except netapp_api.NaApiError as e:
5918 if e.code == netapp_api.EAPINOTFOUND:
5919 msg = _("Configured ONTAP login user cannot retrieve "
5920 "QoS policies.")
5921 LOG.error(msg)
5922 raise exception.NetAppException(msg)
5923 else:
5924 raise
5925 if not self._has_records(result):
5926 msg = _("No QoS policy group found with name %s.")
5927 raise exception.NetAppException(msg % qos_policy_group_name)
5929 attributes_list = result.get_child_by_name(
5930 'attributes-list') or netapp_api.NaElement('none')
5932 qos_policy_group_info = attributes_list.get_child_by_name(
5933 'qos-policy-group-info') or netapp_api.NaElement('none')
5935 policy_info = {
5936 'policy-group': qos_policy_group_info.get_child_content(
5937 'policy-group'),
5938 'vserver': qos_policy_group_info.get_child_content('vserver'),
5939 'max-throughput': qos_policy_group_info.get_child_content(
5940 'max-throughput'),
5941 'min-throughput': qos_policy_group_info.get_child_content(
5942 'min-throughput'),
5943 'num-workloads': int(qos_policy_group_info.get_child_content(
5944 'num-workloads')),
5945 }
5946 return policy_info
5948 @na_utils.trace
5949 def qos_policy_group_create(self, qos_policy_group_name, vserver,
5950 max_throughput=None, min_throughput=None):
5951 """Creates a QoS policy group."""
5952 api_args = {
5953 'policy-group': qos_policy_group_name,
5954 'vserver': vserver,
5955 }
5956 if max_throughput:
5957 api_args['max-throughput'] = max_throughput
5958 if min_throughput:
5959 api_args['min-throughput'] = min_throughput
5960 return self.send_request('qos-policy-group-create', api_args, False)
5962 @na_utils.trace
5963 def qos_policy_group_modify(self, qos_policy_group_name, max_throughput,
5964 min_throughput):
5965 """Modifies a QoS policy group."""
5966 api_args = {
5967 'policy-group': qos_policy_group_name,
5968 }
5969 if max_throughput: 5969 ↛ 5971line 5969 didn't jump to line 5971 because the condition on line 5969 was always true
5970 api_args['max-throughput'] = max_throughput
5971 if min_throughput: 5971 ↛ 5973line 5971 didn't jump to line 5973 because the condition on line 5971 was always true
5972 api_args['min-throughput'] = min_throughput
5973 return self.send_request('qos-policy-group-modify', api_args, False)
5975 @na_utils.trace
5976 def qos_policy_group_delete(self, qos_policy_group_name):
5977 """Attempts to delete a QoS policy group."""
5978 api_args = {'policy-group': qos_policy_group_name}
5979 return self.send_request('qos-policy-group-delete', api_args, False)
5981 @na_utils.trace
5982 def qos_policy_group_rename(self, qos_policy_group_name, new_name):
5983 """Renames a QoS policy group."""
5984 if qos_policy_group_name == new_name:
5985 return
5986 api_args = {
5987 'policy-group-name': qos_policy_group_name,
5988 'new-name': new_name,
5989 }
5990 return self.send_request('qos-policy-group-rename', api_args, False)
5992 @na_utils.trace
5993 def mark_qos_policy_group_for_deletion(self, qos_policy_group_name):
5994 """Soft delete backing QoS policy group for a manila share."""
5995 # NOTE(gouthamr): ONTAP deletes storage objects asynchronously. As
5996 # long as garbage collection hasn't occurred, assigned QoS policy may
5997 # still be tagged "in use". So, we rename the QoS policy group using a
5998 # specific pattern and later attempt on a best effort basis to
5999 # delete any QoS policy groups matching that pattern.
6001 if self.qos_policy_group_exists(qos_policy_group_name):
6002 new_name = DELETED_PREFIX + qos_policy_group_name
6003 try:
6004 self.qos_policy_group_rename(qos_policy_group_name, new_name)
6005 except netapp_api.NaApiError as ex:
6006 msg = ('Rename failure in cleanup of cDOT QoS policy '
6007 'group %(name)s: %(ex)s')
6008 msg_args = {'name': qos_policy_group_name, 'ex': ex}
6009 LOG.warning(msg, msg_args)
6010 # Attempt to delete any QoS policies named "deleted_manila-*".
6011 self.remove_unused_qos_policy_groups()
6013 @na_utils.trace
6014 def remove_unused_qos_policy_groups(self):
6015 """Deletes all QoS policy groups that are marked for deletion."""
6016 api_args = {
6017 'query': {
6018 'qos-policy-group-info': {
6019 'policy-group': '%s*' % DELETED_PREFIX,
6020 }
6021 },
6022 'max-records': 3500,
6023 'continue-on-failure': 'true',
6024 'return-success-list': 'false',
6025 'return-failure-list': 'false',
6026 }
6028 try:
6029 self.send_request('qos-policy-group-delete-iter', api_args, False)
6030 except netapp_api.NaApiError as ex:
6031 msg = 'Could not delete QoS policy groups. Details: %(ex)s'
6032 msg_args = {'ex': ex}
6033 LOG.debug(msg, msg_args)
6035 @na_utils.trace
6036 def get_net_options(self):
6037 result = self.send_request('net-options-get', None, False)
6038 options = result.get_child_by_name('net-options')
6039 ipv6_enabled = False
6040 ipv6_info = options.get_child_by_name('ipv6-options-info')
6041 if ipv6_info:
6042 ipv6_enabled = ipv6_info.get_child_content('enabled') == 'true'
6043 return {
6044 'ipv6-enabled': ipv6_enabled,
6045 }
6047 @na_utils.trace
6048 def rehost_volume(self, volume_name, vserver, destination_vserver):
6049 """Rehosts a volume from one Vserver into another Vserver.
6051 :param volume_name: Name of the FlexVol to be rehosted.
6052 :param vserver: Source Vserver name to which target volume belongs.
6053 :param destination_vserver: Destination Vserver name where target
6054 volume must reside after successful volume rehost operation.
6055 """
6056 api_args = {
6057 'volume': volume_name,
6058 'vserver': vserver,
6059 'destination-vserver': destination_vserver,
6060 }
6061 self.send_request('volume-rehost', api_args)
6063 @na_utils.trace
6064 def get_nfs_config(self, desired_args, vserver):
6065 """Gets the NFS config of the given vserver with the desired params"""
6066 api_args = {
6067 'query': {
6068 'nfs-info': {
6069 'vserver': vserver,
6070 },
6071 },
6072 }
6073 nfs_info = {}
6074 for arg in desired_args:
6075 nfs_info[arg] = None
6077 if nfs_info: 6077 ↛ 6080line 6077 didn't jump to line 6080 because the condition on line 6077 was always true
6078 api_args['desired-attributes'] = {'nfs-info': nfs_info}
6080 result = self.send_request('nfs-service-get-iter', api_args)
6081 child_elem = result.get_child_by_name('attributes-list')
6083 return self.parse_nfs_config(child_elem, desired_args)
6085 @na_utils.trace
6086 def get_nfs_config_default(self, desired_args):
6087 """Gets the default NFS config with the desired params"""
6088 result = self.send_request('nfs-service-get-create-defaults', None)
6089 child_elem = result.get_child_by_name('defaults')
6091 return self.parse_nfs_config(child_elem, desired_args)
6093 @na_utils.trace
6094 def parse_nfs_config(self, parent_elem, desired_args):
6095 """Parse the get NFS config operation returning the desired params"""
6096 nfs_info_elem = parent_elem.get_child_by_name('nfs-info')
6098 nfs_config = {}
6099 for arg in desired_args:
6100 nfs_config[arg] = nfs_info_elem.get_child_content(arg)
6102 return nfs_config
6104 @na_utils.trace
6105 def start_vserver(self, vserver, force=None):
6106 """Starts a vServer."""
6107 api_args = {
6108 'vserver-name': vserver,
6109 }
6110 if force is not None:
6111 api_args['force'] = 'true' if force is True else 'false'
6113 try:
6114 self.send_request('vserver-start', api_args,
6115 enable_tunneling=False)
6116 except netapp_api.NaApiError as e:
6117 if e.code == netapp_api.EVSERVERALREADYSTARTED: 6117 ↛ 6121line 6117 didn't jump to line 6121 because the condition on line 6117 was always true
6118 msg = _("Vserver %s is already started.")
6119 LOG.debug(msg, vserver)
6120 else:
6121 raise
6123 @na_utils.trace
6124 def stop_vserver(self, vserver):
6125 """Stops a vServer."""
6126 api_args = {
6127 'vserver-name': vserver,
6128 }
6130 self.send_request('vserver-stop', api_args, enable_tunneling=False)
6132 def is_svm_dr_supported(self):
6133 return self.features.SVM_DR
6135 def create_fpolicy_event(self, share_name, event_name, protocol,
6136 file_operations):
6137 """Creates a new fpolicy policy event.
6139 :param event_name: name of the new fpolicy event
6140 :param protocol: name of protocol for which event is created. Possible
6141 values are: 'nfsv3', 'nfsv4' or 'cifs'.
6142 :param file_operations: name of file operations to be monitored. Values
6143 should be provided as list of strings.
6144 :param share_name: name of share associated with the vserver where the
6145 fpolicy event should be added.
6146 """
6147 api_args = {
6148 'event-name': event_name,
6149 'protocol': protocol,
6150 'file-operations': [],
6151 }
6152 for file_op in file_operations:
6153 api_args['file-operations'].append({'fpolicy-operation': file_op})
6155 self.send_request('fpolicy-policy-event-create', api_args)
6157 def delete_fpolicy_event(self, share_name, event_name):
6158 """Deletes a fpolicy policy event.
6160 :param event_name: name of the event to be deleted
6161 :param share_name: name of share associated with the vserver where the
6162 fpolicy event should be deleted.
6163 """
6164 try:
6165 self.send_request('fpolicy-policy-event-delete',
6166 {'event-name': event_name})
6167 except netapp_api.NaApiError as e:
6168 if e.code in [netapp_api.EEVENTNOTFOUND,
6169 netapp_api.EOBJECTNOTFOUND]:
6170 msg = _("FPolicy event %s not found.")
6171 LOG.debug(msg, event_name)
6172 else:
6173 raise exception.NetAppException(message=e.message)
6175 def get_fpolicy_events(self, event_name=None, protocol=None,
6176 file_operations=None):
6177 """Retrives a list of fpolicy events.
6179 :param event_name: name of the fpolicy event
6180 :param protocol: name of protocol. Possible values are: 'nfsv3',
6181 'nfsv4' or 'cifs'.
6182 :param file_operations: name of file operations to be monitored. Values
6183 should be provided as list of strings.
6184 :returns List of policy events or empty list
6185 """
6186 event_options_config = {}
6187 if event_name: 6187 ↛ 6189line 6187 didn't jump to line 6189 because the condition on line 6187 was always true
6188 event_options_config['event-name'] = event_name
6189 if protocol: 6189 ↛ 6191line 6189 didn't jump to line 6191 because the condition on line 6189 was always true
6190 event_options_config['protocol'] = protocol
6191 if file_operations: 6191 ↛ 6197line 6191 didn't jump to line 6197 because the condition on line 6191 was always true
6192 event_options_config['file-operations'] = []
6193 for file_op in file_operations:
6194 event_options_config['file-operations'].append(
6195 {'fpolicy-operation': file_op})
6197 api_args = {
6198 'query': {
6199 'fpolicy-event-options-config': event_options_config,
6200 },
6201 }
6202 result = self.send_iter_request('fpolicy-policy-event-get-iter',
6203 api_args)
6205 fpolicy_events = []
6206 if self._has_records(result): 6206 ↛ 6229line 6206 didn't jump to line 6229 because the condition on line 6206 was always true
6207 try:
6208 fpolicy_events = []
6209 attributes_list = result.get_child_by_name(
6210 'attributes-list') or netapp_api.NaElement('none')
6211 for event_info in attributes_list.get_children():
6212 name = event_info.get_child_content('event-name')
6213 proto = event_info.get_child_content('protocol')
6214 file_operations_child = event_info.get_child_by_name(
6215 'file-operations') or netapp_api.NaElement('none')
6216 operations = [operation.get_content()
6217 for operation in
6218 file_operations_child.get_children()]
6220 fpolicy_events.append({
6221 'event-name': name,
6222 'protocol': proto,
6223 'file-operations': operations
6224 })
6225 except AttributeError:
6226 msg = _('Could not retrieve fpolicy policy event information.')
6227 raise exception.NetAppException(msg)
6229 return fpolicy_events
6231 def create_fpolicy_policy(self, fpolicy_name, share_name, events,
6232 engine='native'):
6233 """Creates a fpolicy policy resource.
6235 :param fpolicy_name: name of the fpolicy policy to be created.
6236 :param share_name: name of the share to be associated with the new
6237 fpolicy policy.
6238 :param events: list of event names for file access monitoring.
6239 :param engine: name of the engine to be used.
6240 """
6241 api_args = {
6242 'policy-name': fpolicy_name,
6243 'events': [],
6244 'engine-name': engine
6245 }
6246 for event in events:
6247 api_args['events'].append({'event-name': event})
6249 self.send_request('fpolicy-policy-create', api_args)
6251 def delete_fpolicy_policy(self, share_name, policy_name):
6252 """Deletes a fpolicy policy event.
6254 :param policy_name: name of the policy to be deleted.
6255 """
6256 try:
6257 self.send_request('fpolicy-policy-delete',
6258 {'policy-name': policy_name})
6259 except netapp_api.NaApiError as e:
6260 if e.code in [netapp_api.EPOLICYNOTFOUND,
6261 netapp_api.EOBJECTNOTFOUND]:
6262 msg = _("FPolicy policy %s not found.")
6263 LOG.debug(msg, policy_name)
6264 else:
6265 raise exception.NetAppException(message=e.message)
6267 def get_fpolicy_policies(self, share_name, policy_name=None,
6268 engine_name='native', event_names=[]):
6269 """Retrieve one or more fpolicy policies.
6271 :param policy_name: name of the policy to be retrieved
6272 :param engine_name: name of the engine
6273 :param share_name: name of the share associated with the fpolicy
6274 policy.
6275 :param event_names: list of event names that must be associated to the
6276 fpolicy policy
6277 :return: list of fpolicy policies or empty list
6278 """
6279 policy_info = {}
6280 if policy_name: 6280 ↛ 6282line 6280 didn't jump to line 6282 because the condition on line 6280 was always true
6281 policy_info['policy-name'] = policy_name
6282 if engine_name: 6282 ↛ 6284line 6282 didn't jump to line 6284 because the condition on line 6282 was always true
6283 policy_info['engine-name'] = engine_name
6284 if event_names: 6284 ↛ 6289line 6284 didn't jump to line 6289 because the condition on line 6284 was always true
6285 policy_info['events'] = []
6286 for event_name in event_names:
6287 policy_info['events'].append({'event-name': event_name})
6289 api_args = {
6290 'query': {
6291 'fpolicy-policy-info': policy_info,
6292 },
6293 }
6294 result = self.send_iter_request('fpolicy-policy-get-iter', api_args)
6296 fpolicy_policies = []
6297 if self._has_records(result): 6297 ↛ 6318line 6297 didn't jump to line 6318 because the condition on line 6297 was always true
6298 try:
6299 attributes_list = result.get_child_by_name(
6300 'attributes-list') or netapp_api.NaElement('none')
6301 for policy_info in attributes_list.get_children():
6302 name = policy_info.get_child_content('policy-name')
6303 engine = policy_info.get_child_content('engine-name')
6304 events_child = policy_info.get_child_by_name(
6305 'events') or netapp_api.NaElement('none')
6306 events = [event.get_content()
6307 for event in events_child.get_children()]
6309 fpolicy_policies.append({
6310 'policy-name': name,
6311 'engine-name': engine,
6312 'events': events
6313 })
6314 except AttributeError:
6315 msg = _('Could not retrieve fpolicy policy information.')
6316 raise exception.NetAppException(message=msg)
6318 return fpolicy_policies
6320 def create_fpolicy_scope(self, policy_name, share_name,
6321 extensions_to_include=None,
6322 extensions_to_exclude=None):
6323 """Assings a file scope to an existing fpolicy policy.
6325 :param policy_name: name of the policy to associate with the new scope.
6326 :param share_name: name of the share to be associated with the new
6327 scope.
6328 :param extensions_to_include: file extensions included for screening.
6329 Values should be provided as comma separated list
6330 :param extensions_to_exclude: file extensions excluded for screening.
6331 Values should be provided as comma separated list
6332 """
6333 api_args = {
6334 'policy-name': policy_name,
6335 'shares-to-include': {
6336 'string': share_name,
6337 },
6338 'file-extensions-to-include': [],
6339 'file-extensions-to-exclude': [],
6340 }
6341 if extensions_to_include: 6341 ↛ 6346line 6341 didn't jump to line 6346 because the condition on line 6341 was always true
6342 for file_ext in extensions_to_include.split(','):
6343 api_args['file-extensions-to-include'].append(
6344 {'string': file_ext.strip()})
6346 if extensions_to_exclude: 6346 ↛ 6351line 6346 didn't jump to line 6351 because the condition on line 6346 was always true
6347 for file_ext in extensions_to_exclude.split(','):
6348 api_args['file-extensions-to-exclude'].append(
6349 {'string': file_ext.strip()})
6351 self.send_request('fpolicy-policy-scope-create', api_args)
6353 def modify_fpolicy_scope(self, share_name, policy_name,
6354 shares_to_include=[], extensions_to_include=None,
6355 extensions_to_exclude=None):
6356 """Modify an existing fpolicy scope.
6358 :param policy_name: name of the policy associated to the scope.
6359 :param share_name: name of the share associated with the fpolicy scope.
6360 :param shares_to_include: list of shares to include for file access
6361 monitoring.
6362 :param extensions_to_include: file extensions included for screening.
6363 Values should be provided as comma separated list
6364 :param extensions_to_exclude: file extensions excluded for screening.
6365 Values should be provided as comma separated list
6366 """
6367 api_args = {
6368 'policy-name': policy_name,
6369 }
6370 if extensions_to_include: 6370 ↛ 6376line 6370 didn't jump to line 6376 because the condition on line 6370 was always true
6371 api_args['file-extensions-to-include'] = []
6372 for file_ext in extensions_to_include.split(','):
6373 api_args['file-extensions-to-include'].append(
6374 {'string': file_ext.strip()})
6376 if extensions_to_exclude: 6376 ↛ 6382line 6376 didn't jump to line 6382 because the condition on line 6376 was always true
6377 api_args['file-extensions-to-exclude'] = []
6378 for file_ext in extensions_to_exclude.split(','):
6379 api_args['file-extensions-to-exclude'].append(
6380 {'string': file_ext.strip()})
6382 if shares_to_include: 6382 ↛ 6387line 6382 didn't jump to line 6387 because the condition on line 6382 was always true
6383 api_args['shares-to-include'] = [
6384 {'string': share} for share in shares_to_include
6385 ]
6387 self.send_request('fpolicy-policy-scope-modify', api_args)
6389 def delete_fpolicy_scope(self, policy_name):
6390 """Deletes a fpolicy policy scope.
6392 :param policy_name: name of the policy associated to the scope to be
6393 deleted.
6394 """
6395 try:
6396 self.send_request('fpolicy-policy-scope-delete',
6397 {'policy-name': policy_name})
6398 except netapp_api.NaApiError as e:
6399 if e.code in [netapp_api.ESCOPENOTFOUND,
6400 netapp_api.EOBJECTNOTFOUND]:
6401 msg = _("FPolicy scope %s not found.")
6402 LOG.debug(msg, policy_name)
6403 else:
6404 raise exception.NetAppException(message=e.message)
6406 def get_fpolicy_scopes(self, share_name, policy_name=None,
6407 extensions_to_include=None,
6408 extensions_to_exclude=None,
6409 shares_to_include=None):
6410 """Retrieve fpolicy scopes.
6412 :param policy_name: name of the policy associated with a scope.
6413 :param share_name: name of the share associated with the fpolicy scope.
6414 :param extensions_to_include: file extensions included for screening.
6415 Values should be provided as comma separated list
6416 :param extensions_to_exclude: file extensions excluded for screening.
6417 Values should be provided as comma separated list
6418 :param shares_to_include: list of shares to include for file access
6419 monitoring.
6420 :return: list of fpolicy scopes or empty list
6421 """
6422 policy_scope_info = {}
6423 if policy_name: 6423 ↛ 6426line 6423 didn't jump to line 6426 because the condition on line 6423 was always true
6424 policy_scope_info['policy-name'] = policy_name
6426 if shares_to_include: 6426 ↛ 6430line 6426 didn't jump to line 6430 because the condition on line 6426 was always true
6427 policy_scope_info['shares-to-include'] = [
6428 {'string': share} for share in shares_to_include
6429 ]
6430 if extensions_to_include: 6430 ↛ 6435line 6430 didn't jump to line 6435 because the condition on line 6430 was always true
6431 policy_scope_info['file-extensions-to-include'] = []
6432 for file_op in extensions_to_include.split(','):
6433 policy_scope_info['file-extensions-to-include'].append(
6434 {'string': file_op.strip()})
6435 if extensions_to_exclude: 6435 ↛ 6441line 6435 didn't jump to line 6441 because the condition on line 6435 was always true
6436 policy_scope_info['file-extensions-to-exclude'] = []
6437 for file_op in extensions_to_exclude.split(','):
6438 policy_scope_info['file-extensions-to-exclude'].append(
6439 {'string': file_op.strip()})
6441 api_args = {
6442 'query': {
6443 'fpolicy-scope-config': policy_scope_info,
6444 },
6445 }
6446 result = self.send_iter_request('fpolicy-policy-scope-get-iter',
6447 api_args)
6449 fpolicy_scopes = []
6450 if self._has_records(result): 6450 ↛ 6481line 6450 didn't jump to line 6481 because the condition on line 6450 was always true
6451 try:
6452 fpolicy_scopes = []
6453 attributes_list = result.get_child_by_name(
6454 'attributes-list') or netapp_api.NaElement('none')
6455 for policy_scope in attributes_list.get_children():
6456 name = policy_scope.get_child_content('policy-name')
6457 ext_include_child = policy_scope.get_child_by_name(
6458 'file-extensions-to-include') or netapp_api.NaElement(
6459 'none')
6460 ext_include = [ext.get_content()
6461 for ext in ext_include_child.get_children()]
6462 ext_exclude_child = policy_scope.get_child_by_name(
6463 'file-extensions-to-exclude') or netapp_api.NaElement(
6464 'none')
6465 ext_exclude = [ext.get_content()
6466 for ext in ext_exclude_child.get_children()]
6467 shares_child = policy_scope.get_child_by_name(
6468 'shares-to-include') or netapp_api.NaElement('none')
6469 shares_include = [ext.get_content()
6470 for ext in shares_child.get_children()]
6471 fpolicy_scopes.append({
6472 'policy-name': name,
6473 'file-extensions-to-include': ext_include,
6474 'file-extensions-to-exclude': ext_exclude,
6475 'shares-to-include': shares_include,
6476 })
6477 except AttributeError:
6478 msg = _('Could not retrieve fpolicy policy information.')
6479 raise exception.NetAppException(msg)
6481 return fpolicy_scopes
6483 def enable_fpolicy_policy(self, share_name, policy_name, sequence_number):
6484 """Enables a specific named policy.
6486 :param policy_name: name of the policy to be enabled
6487 :param share_name: name of the share associated with the vserver and
6488 the fpolicy
6489 :param sequence_number: policy sequence number
6490 """
6491 api_args = {
6492 'policy-name': policy_name,
6493 'sequence-number': sequence_number,
6494 }
6496 self.send_request('fpolicy-enable-policy', api_args)
6498 def disable_fpolicy_policy(self, policy_name):
6499 """Disables a specific policy.
6501 :param policy_name: name of the policy to be disabled
6502 """
6503 try:
6504 self.send_request('fpolicy-disable-policy',
6505 {'policy-name': policy_name})
6506 except netapp_api.NaApiError as e:
6507 disabled = "policy is already disabled"
6508 if (e.code in [netapp_api.EPOLICYNOTFOUND,
6509 netapp_api.EOBJECTNOTFOUND] or
6510 (e.code == netapp_api.EINVALIDINPUTERROR and
6511 disabled in e.message)):
6512 msg = _("FPolicy policy %s not found or already disabled.")
6513 LOG.debug(msg, policy_name)
6514 else:
6515 raise exception.NetAppException(message=e.message)
6517 def get_fpolicy_policies_status(self, share_name, policy_name=None,
6518 status='true'):
6519 policy_status_info = {}
6520 if policy_name: 6520 ↛ 6523line 6520 didn't jump to line 6523 because the condition on line 6520 was always true
6521 policy_status_info['policy-name'] = policy_name
6522 policy_status_info['status'] = status
6523 api_args = {
6524 'query': {
6525 'fpolicy-policy-status-info': policy_status_info,
6526 },
6527 }
6528 result = self.send_iter_request('fpolicy-policy-status-get-iter',
6529 api_args)
6531 fpolicy_status = []
6532 if self._has_records(result): 6532 ↛ 6550line 6532 didn't jump to line 6550 because the condition on line 6532 was always true
6533 try:
6534 fpolicy_status = []
6535 attributes_list = result.get_child_by_name(
6536 'attributes-list') or netapp_api.NaElement('none')
6537 for policy_status in attributes_list.get_children():
6538 name = policy_status.get_child_content('policy-name')
6539 status = policy_status.get_child_content('status')
6540 seq = policy_status.get_child_content('sequence-number')
6541 fpolicy_status.append({
6542 'policy-name': name,
6543 'status': strutils.bool_from_string(status),
6544 'sequence-number': seq
6545 })
6546 except AttributeError:
6547 msg = _('Could not retrieve fpolicy status information.')
6548 raise exception.NetAppException(msg)
6550 return fpolicy_status
6552 @na_utils.trace
6553 def is_svm_migrate_supported(self):
6554 """Checks if the cluster supports SVM Migrate."""
6555 return self.features.SVM_MIGRATE
6557 def get_volume_state(self, name):
6558 """Returns volume state for a given name"""
6560 api_args = {
6561 'query': {
6562 'volume-attributes': {
6563 'volume-id-attributes': {
6564 'name': name,
6565 },
6566 },
6567 },
6568 'desired-attributes': {
6569 'volume-attributes': {
6570 'volume-state-attributes': {
6571 'state': None
6572 }
6573 }
6574 },
6575 }
6577 result = self.send_iter_request('volume-get-iter', api_args)
6579 volume_state = ''
6580 if self._has_records(result):
6581 attributes_list = result.get_child_by_name(
6582 'attributes-list') or netapp_api.NaElement('none')
6583 volume_attributes = attributes_list.get_child_by_name(
6584 'volume-attributes') or netapp_api.NaElement('none')
6585 volume_state_attributes = volume_attributes.get_child_by_name(
6586 'volume-state-attributes') or netapp_api.NaElement('none')
6587 volume_state = volume_state_attributes.get_child_content('state')
6589 return volume_state
6591 @na_utils.trace
6592 def is_flexgroup_volume(self, volume_name):
6593 """Determines if the ONTAP volume is FlexGroup."""
6595 if not self.is_flexgroup_supported():
6596 return False
6598 api_args = {
6599 'query': {
6600 'volume-attributes': {
6601 'volume-id-attributes': {
6602 'name': volume_name,
6603 },
6604 },
6605 },
6606 'desired-attributes': {
6607 'volume-attributes': {
6608 'volume-id-attributes': {
6609 'style-extended': None,
6610 },
6611 },
6612 },
6613 }
6614 result = self.send_request('volume-get-iter', api_args)
6616 attributes_list = result.get_child_by_name(
6617 'attributes-list') or netapp_api.NaElement('none')
6618 volume_attributes_list = attributes_list.get_children()
6620 if not self._has_records(result):
6621 raise exception.StorageResourceNotFound(name=volume_name)
6622 elif len(volume_attributes_list) > 1:
6623 msg = _('More than one volume with volume name %(vol)s found.')
6624 msg_args = {'vol': volume_name}
6625 raise exception.NetAppException(msg % msg_args)
6627 volume_attributes = volume_attributes_list[0]
6629 volume_id_attributes = volume_attributes.get_child_by_name(
6630 'volume-id-attributes') or netapp_api.NaElement('none')
6632 return na_utils.is_style_extended_flexgroup(
6633 volume_id_attributes.get_child_content('style-extended'))
6635 @na_utils.trace
6636 def is_flexgroup_supported(self):
6637 return self.features.FLEXGROUP
6639 @na_utils.trace
6640 def is_flexgroup_fan_out_supported(self):
6641 return self.features.FLEXGROUP_FAN_OUT
6643 @na_utils.trace
6644 def get_job_state(self, job_id):
6645 """Returns job state for a given job id."""
6647 api_args = {
6648 'query': {
6649 'job-info': {
6650 'job-id': job_id,
6651 },
6652 },
6653 'desired-attributes': {
6654 'job-info': {
6655 'job-state': None,
6656 },
6657 },
6658 }
6660 result = self.send_iter_request('job-get-iter', api_args,
6661 enable_tunneling=False)
6663 attributes_list = result.get_child_by_name(
6664 'attributes-list') or netapp_api.NaElement('none')
6665 job_info_list = attributes_list.get_children()
6666 if not self._has_records(result):
6667 msg = _('Could not find job with ID %(id)s.')
6668 msg_args = {'id': job_id}
6669 raise exception.NetAppException(msg % msg_args)
6670 elif len(job_info_list) > 1:
6671 msg = _('Could not find unique job for ID %(id)s.')
6672 msg_args = {'id': job_id}
6673 raise exception.NetAppException(msg % msg_args)
6675 return job_info_list[0].get_child_content('job-state')
6677 @na_utils.trace
6678 def create_fpolicy_policy_with_scope(self, fpolicy_name, share_name,
6679 events, engine='native',
6680 extensions_to_include=None,
6681 extensions_to_exclude=None):
6683 # Create a fpolicy policy
6684 self.create_fpolicy_policy(fpolicy_name, share_name, events,
6685 engine='native')
6686 # Assign a scope to the fpolicy policy
6687 self.create_fpolicy_scope(fpolicy_name, share_name,
6688 extensions_to_include,
6689 extensions_to_exclude)
6691 @na_utils.trace
6692 def check_snaprestore_license(self):
6693 """Check SnapRestore license for SVM scoped user."""
6694 # NOTE(felipe_rodrigues): workaround to find out whether the
6695 # backend has the license: since without cluster credentials it
6696 # cannot retrieve the ontap licenses, it sends a fake ONTAP
6697 # "snapshot-restore-volume" request which is only available when
6698 # the license exists. By the got error, it checks whether license
6699 # is installed or not.
6700 try:
6701 self.restore_snapshot(
6702 "fake_%s" % uuidutils.generate_uuid(dashed=False), "")
6703 except netapp_api.NaApiError as e:
6704 no_license = 'is not licensed'
6705 LOG.debug('Fake restore_snapshot request failed: %s', e)
6706 return not (e.code == netapp_api.EAPIERROR and
6707 no_license in e.message)
6709 # since it passed an empty snapshot, it should never get here
6710 msg = _("Caught an unexpected behavior: the fake restore to "
6711 "snapshot request using 'fake' volume and empty string "
6712 "snapshot as argument has not failed.")
6713 LOG.exception(msg)
6714 raise exception.NetAppException(msg)
6716 # ------------------------ REST CALLS ONLY ------------------------
6718 # NOTE(nahimsouza): For ONTAP 9.12.1 and newer, if the option
6719 # `netapp_use_legacy_client` is False, REST API client will be used. This
6720 # code was kept here to avoid breaking the SVM migrate feature on older
6721 # ONTAP versions. In the future, when ZAPI is deprecated, this code can
6722 # also be removed.
6724 @na_utils.trace
6725 def _format_request(self, request_data, headers={}, query={},
6726 url_params={}):
6727 """Receives the request data and formats it into a request pattern.
6729 :param request_data: the body to be sent to the request.
6730 :param headers: additional headers to the request.
6731 :param query: filters to the request.
6732 :param url_params: parameters to be added to the request.
6733 """
6734 request = {
6735 "body": request_data,
6736 "headers": headers,
6737 "query": query,
6738 "url_params": url_params
6739 }
6740 return request
6742 @na_utils.trace
6743 def svm_migration_start(
6744 self, source_cluster_name, source_share_server_name,
6745 dest_aggregates, dest_ipspace=None, check_only=False):
6746 """Send a request to start the SVM migration in the backend.
6748 :param source_cluster_name: the name of the source cluster.
6749 :param source_share_server_name: the name of the source server.
6750 :param dest_aggregates: the aggregates where volumes will be placed in
6751 the migration.
6752 :param dest_ipspace: created IPspace for the migration.
6753 :param check_only: If the call will only check the feasibility.
6754 deleted after the cutover or not.
6755 """
6756 request = {
6757 "auto_cutover": False,
6758 "auto_source_cleanup": True,
6759 "check_only": check_only,
6760 "source": {
6761 "cluster": {"name": source_cluster_name},
6762 "svm": {"name": source_share_server_name},
6763 },
6764 "destination": {
6765 "volume_placement": {
6766 "aggregates": dest_aggregates,
6767 },
6768 },
6769 }
6771 if dest_ipspace:
6772 ipspace_data = {
6773 "ipspace": {
6774 "name": dest_ipspace,
6775 }
6776 }
6777 request["destination"].update(ipspace_data)
6779 api_args = self._format_request(request)
6781 return self.send_request(
6782 'svm-migration-start', api_args=api_args, use_zapi=False)
6784 @na_utils.trace
6785 def get_migration_check_job_state(self, job_id):
6786 """Get the job state of a share server migration.
6788 :param job_id: id of the job to be searched.
6789 """
6790 try:
6791 job = self.get_job(job_id)
6792 return job
6793 except netapp_api.NaApiError as e:
6794 if e.code == netapp_api.ENFS_V4_0_ENABLED_MIGRATION_FAILURE:
6795 msg = _(
6796 'NFS v4.0 is not supported while migrating vservers.')
6797 LOG.error(msg)
6798 raise exception.NetAppException(message=e.message)
6799 if e.code == netapp_api.EVSERVER_MIGRATION_TO_NON_AFF_CLUSTER: 6799 ↛ 6804line 6799 didn't jump to line 6804 because the condition on line 6799 was always true
6800 msg = _('Both source and destination clusters must be AFF '
6801 'systems.')
6802 LOG.error(msg)
6803 raise exception.NetAppException(message=e.message)
6804 msg = (_('Failed to check migration support. Reason: '
6805 '%s' % e.message))
6806 LOG.error(msg)
6807 raise exception.NetAppException(msg)
6809 @na_utils.trace
6810 def svm_migrate_complete(self, migration_id):
6811 """Send a request to complete the SVM migration.
6813 :param migration_id: the id of the migration provided by the storage.
6814 """
6815 request = {
6816 "action": "cutover"
6817 }
6818 url_params = {
6819 "svm_migration_id": migration_id
6820 }
6821 api_args = self._format_request(
6822 request, url_params=url_params)
6824 return self.send_request(
6825 'svm-migration-complete', api_args=api_args, use_zapi=False)
6827 @na_utils.trace
6828 def svm_migrate_cancel(self, migration_id):
6829 """Send a request to cancel the SVM migration.
6831 :param migration_id: the id of the migration provided by the storage.
6832 """
6833 request = {}
6834 url_params = {
6835 "svm_migration_id": migration_id
6836 }
6837 api_args = self._format_request(request, url_params=url_params)
6838 return self.send_request(
6839 'svm-migration-cancel', api_args=api_args, use_zapi=False)
6841 @na_utils.trace
6842 def svm_migration_get(self, migration_id):
6843 """Send a request to get the progress of the SVM migration.
6845 :param migration_id: the id of the migration provided by the storage.
6846 """
6847 request = {}
6848 url_params = {
6849 "svm_migration_id": migration_id
6850 }
6851 api_args = self._format_request(request, url_params=url_params)
6852 return self.send_request(
6853 'svm-migration-get', api_args=api_args, use_zapi=False)
6855 @na_utils.trace
6856 def svm_migrate_pause(self, migration_id):
6857 """Send a request to pause a migration.
6859 :param migration_id: the id of the migration provided by the storage.
6860 """
6861 request = {
6862 "action": "pause"
6863 }
6864 url_params = {
6865 "svm_migration_id": migration_id
6866 }
6867 api_args = self._format_request(
6868 request, url_params=url_params)
6869 return self.send_request(
6870 'svm-migration-pause', api_args=api_args, use_zapi=False)
6872 @na_utils.trace
6873 def get_job(self, job_uuid):
6874 """Get a job in ONTAP.
6876 :param job_uuid: uuid of the job to be searched.
6877 """
6878 request = {}
6879 url_params = {
6880 "job_uuid": job_uuid
6881 }
6883 api_args = self._format_request(request, url_params=url_params)
6885 return self.send_request(
6886 'get-job', api_args=api_args, use_zapi=False)
6888 @na_utils.trace
6889 def get_svm_volumes_total_size(self, svm_name):
6890 """Gets volumes sizes sum (GB) from all volumes in SVM by svm_name"""
6892 request = {}
6894 query = {
6895 'svm.name': svm_name,
6896 'fields': 'size'
6897 }
6899 api_args = self._format_request(request, query=query)
6901 response = self.send_request(
6902 'svm-migration-get-progress', api_args=api_args, use_zapi=False)
6904 svm_volumes = response.get('records', [])
6906 if len(svm_volumes) > 0: 6906 ↛ 6914line 6906 didn't jump to line 6914 because the condition on line 6906 was always true
6907 total_volumes_size = 0
6908 for volume in svm_volumes:
6909 # Root volumes are not taking account because they are part of
6910 # SVM creation.
6911 if volume['name'] != 'root': 6911 ↛ 6908line 6911 didn't jump to line 6908 because the condition on line 6911 was always true
6912 total_volumes_size = total_volumes_size + volume['size']
6913 else:
6914 return 0
6916 # Convert Bytes to GBs.
6917 return (total_volumes_size / 1024**3)
6919 @na_utils.trace
6920 def snapmirror_restore_vol(self, source_path=None, dest_path=None,
6921 source_vserver=None, dest_vserver=None,
6922 source_volume=None, dest_volume=None,
6923 des_cluster=None, source_snapshot=None):
6924 """Restore snapshot copy from destination volume to source volume"""
6925 self._ensure_snapmirror_v2()
6927 api_args = self._build_snapmirror_request(
6928 source_path, dest_path, source_vserver,
6929 dest_vserver, source_volume, dest_volume)
6930 if source_snapshot: 6930 ↛ 6932line 6930 didn't jump to line 6932 because the condition on line 6930 was always true
6931 api_args["source-snapshot"] = source_snapshot
6932 self.send_request('snapmirror-restore', api_args)
6934 @na_utils.trace
6935 def list_volume_snapshots(self, volume_name, snapmirror_label=None,
6936 newer_than=None):
6937 """Gets SnapMirror snapshots on a volume."""
6938 api_args = {
6939 'query': {
6940 'snapshot-info': {
6941 'volume': volume_name,
6942 },
6943 },
6944 }
6945 if newer_than:
6946 api_args['query']['snapshot-info'][
6947 'access-time'] = '>' + newer_than
6948 if snapmirror_label:
6949 api_args['query']['snapshot-info'][
6950 'snapmirror-label'] = snapmirror_label
6951 result = self.send_iter_request('snapshot-get-iter', api_args)
6952 attributes_list = result.get_child_by_name(
6953 'attributes-list') or netapp_api.NaElement('none')
6954 return [snapshot_info.get_child_content('name')
6955 for snapshot_info in attributes_list.get_children()]
6957 @na_utils.trace
6958 def is_snaplock_compliance_clock_configured(self, node_name):
6959 """Get the Snaplock compliance is configured for each node"""
6960 api_args = {'node': node_name}
6961 result = self.send_request('snaplock-get-node-compliance-clock',
6962 api_args)
6963 node_compliance_clock = result.get_child_by_name(
6964 "snaplock-node-compliance-clock"
6965 )
6966 if not node_compliance_clock:
6967 raise exception.NetAppException(
6968 "Compliance clock is not configured for node %s",
6969 node_name,
6970 )
6971 clock_info = node_compliance_clock.get_child_by_name(
6972 "compliance-clock-info")
6973 clock_fmt_value = clock_info.get_child_content(
6974 "formatted-snaplock-compliance-clock")
6975 return 'not configured' not in clock_fmt_value.lower()
6977 @na_utils.trace
6978 def set_snaplock_attributes(self, volume_name, **options):
6979 """Set the retention period for SnapLock enabled volume"""
6980 api_args = {}
6981 snaplock_attribute_mapping = {
6982 'snaplock_autocommit_period': 'autocommit-period',
6983 'snaplock_min_retention_period': 'minimum-retention-period',
6984 'snaplock_max_retention_period': 'maximum-retention-period',
6985 'snaplock_default_retention_period': 'default-retention-period',
6986 }
6987 for share_type_attr, na_api_attr in snaplock_attribute_mapping.items():
6988 if options.get(share_type_attr):
6989 api_args[na_api_attr] = options.get(share_type_attr)
6991 if all(value is None for value in api_args.values()):
6992 LOG.debug("All SnapLock attributes are None, not"
6993 " updating SnapLock attributes")
6994 return
6996 api_args['volume'] = volume_name
6997 default_retention_period = options.get(
6998 'snaplock_default_retention_period'
6999 )
7000 if default_retention_period and default_retention_period == "max":
7001 api_args['default-retention-period'] = (
7002 api_args['maximum-retention-period']
7003 )
7004 elif default_retention_period and default_retention_period == "min":
7005 api_args['default-retention-period'] = (
7006 api_args['minimum-retention-period']
7007 )
7008 self.send_request('volume-set-snaplock-attrs', api_args)
7010 @na_utils.trace
7011 def _is_snaplock_enabled_volume(self, volume_name):
7012 """Get whether volume is SnapLock enabled or disabled"""
7013 vol_attr = self.get_volume(volume_name)
7014 return vol_attr.get('snaplock-type') in ("compliance", "enterprise")
7016 @na_utils.trace
7017 def get_vserver_aggr_snaplock_type(self, aggr_name):
7018 """Get SnapLock type for vserver aggregate"""
7019 api_args = {
7020 'query': {
7021 'show-aggregates': {
7022 'aggregate-name': aggr_name,
7023 },
7024 },
7025 'desired-attributes': {
7026 'show-aggregates': {
7027 'snaplock-type': None,
7028 },
7029 },
7030 }
7032 if self.features.SNAPLOCK:
7033 result = self.send_iter_request('vserver-show-aggr-get-iter',
7034 api_args)
7035 else:
7036 return None
7037 if result is not None and self._has_records(result): 7037 ↛ exitline 7037 didn't return from function 'get_vserver_aggr_snaplock_type' because the condition on line 7037 was always true
7038 attributes_list = result.get_child_by_name(
7039 'attributes-list') or netapp_api.NaElement('none')
7040 vs_aggr_attributes = attributes_list.get_child_by_name(
7041 'show-aggregates') or netapp_api.NaElement('none')
7042 return vs_aggr_attributes.get_child_content('snaplock-type')
7044 @na_utils.trace
7045 def get_storage_failover_partner(self, node_name):
7046 """Get the partner node of HA pair"""
7047 api_args = {'node': node_name}
7048 result = self.send_request('cf-get-partner', api_args)
7049 partner_node = result.get_child_content("partner")
7050 return partner_node
7052 @na_utils.trace
7053 def get_migratable_data_lif_for_node(self, node):
7054 """Get available LIFs that can be migrated to another node."""
7055 failover_policy = ['system-defined', 'sfo-partner-only']
7056 protocols = ['nfs', 'cifs']
7057 api_args = {
7058 'query': {
7059 'net-interface-info': {
7060 'failover-policy': '|'.join(failover_policy),
7061 'home-node': node,
7062 'data-protocols': {
7063 'data-protocol': '|'.join(protocols),
7064 }
7065 }
7066 }
7067 }
7068 result = self.send_iter_request('net-interface-get-iter', api_args)
7069 lif_info_list = result.get_child_by_name(
7070 'attributes-list') or netapp_api.NaElement('none')
7071 return [lif_info.get_child_content('interface-name') for lif_info
7072 in lif_info_list.get_children()]
7074 @na_utils.trace
7075 def get_data_lif_details_for_nodes(self):
7076 """Get the data LIF details for each node."""
7077 api_args = {
7078 'desired-attributes': {
7079 'data-lif-capacity-details-info': {
7080 'limit-for-node': None,
7081 'count-for-node': None,
7082 'node': None
7083 },
7084 },
7085 }
7086 result = self.send_iter_request('data-lif-capacity-details', api_args)
7087 data_lif_info_list = result.get_child_by_name(
7088 'attributes-list') or netapp_api.NaElement('none')
7089 data_lif_info = []
7090 for lif_info in data_lif_info_list.get_children():
7091 lif_info_node = {
7092 'limit-for-node': lif_info.get_child_content('limit-for-node'),
7093 'count-for-node': lif_info.get_child_content('count-for-node'),
7094 'node': lif_info.get_child_content('node'),
7095 }
7096 data_lif_info.append(lif_info_node)
7097 return data_lif_info