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

1# Copyright (c) 2014 Alex Meade. All rights reserved. 

2# Copyright (c) 2015 Clinton Knight. All rights reserved. 

3# Copyright (c) 2015 Tom Barron. All rights reserved. 

4# Copyright (c) 2018 Jose Porrua. All rights reserved. 

5# 

6# Licensed under the Apache License, Version 2.0 (the "License"); you may 

7# not use this file except in compliance with the License. You may obtain 

8# a copy of the License at 

9# 

10# http://www.apache.org/licenses/LICENSE-2.0 

11# 

12# Unless required by applicable law or agreed to in writing, software 

13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

15# License for the specific language governing permissions and limitations 

16# under the License. 

17 

18 

19import copy 

20import hashlib 

21import re 

22import time 

23 

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 

30 

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 

37 

38 

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} 

53 

54 

55class NetAppCmodeClient(client_base.NetAppBaseClient): 

56 

57 def __init__(self, **kwargs): 

58 super(NetAppCmodeClient, self).__init__(**kwargs) 

59 self.vserver = kwargs.get('vserver') 

60 self.connection.set_vserver(self.vserver) 

61 

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) 

68 

69 self._init_features() 

70 

71 def _init_features(self): 

72 """Initialize cDOT feature support map.""" 

73 super(NetAppCmodeClient, self)._init_features() 

74 

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) 

90 

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) 

120 

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 

126 

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 

133 

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) 

140 

141 def set_vserver(self, vserver): 

142 self.vserver = vserver 

143 self.connection.set_vserver(vserver) 

144 

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.""" 

149 

150 if not api_args: 

151 api_args = {} 

152 

153 api_args['max-records'] = max_page_length 

154 

155 # Get first page 

156 result = self.send_request(api_name, api_args, 

157 enable_tunneling=enable_tunneling) 

158 

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 

163 

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) 

170 

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) 

177 

178 next_attributes_list = next_result.get_child_by_name( 

179 'attributes-list') or netapp_api.NaElement('none') 

180 

181 for record in next_attributes_list.get_children(): 

182 attributes_list.add_child_elem(record) 

183 

184 num_records += self._get_record_count(next_result) 

185 next_tag = next_result.get_child_content('next-tag') 

186 

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 

191 

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) 

207 

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) 

217 

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 

241 

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 

248 

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') 

253 

254 LOG.debug('Creating Vserver %(vserver)s with create args ' 

255 '%(args)s', {'vserver': vserver_name, 'args': create_args}) 

256 

257 self.send_request('vserver-create', create_args) 

258 

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)}) 

267 

268 self.send_request('vserver-modify', modify_args) 

269 

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.""" 

273 

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 

278 

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 

303 

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) 

308 

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 

321 

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 } 

337 

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 

347 

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) 

365 

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) 

382 

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) 

387 

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) 

418 

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 

426 

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) 

431 

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) 

453 

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) 

470 

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 

480 

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 

486 

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) 

500 

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 

509 

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 

515 

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) 

531 

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 

540 

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 

550 

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()] 

556 

557 @na_utils.trace 

558 def get_vserver_volume_count(self): 

559 """Get the number of volumes present on a cluster or vserver. 

560 

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) 

575 

576 @na_utils.trace 

577 def delete_vserver(self, vserver_name, vserver_client, 

578 security_services=None): 

579 """Deletes a Vserver. 

580 

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 

588 

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() 

592 

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) 

605 

606 elif volumes_count > 1: 

607 msg = _("Cannot delete Vserver. Vserver %s has shares.") 

608 raise exception.NetAppException(msg % vserver_name) 

609 

610 if security_services and not is_dp_destination: 

611 self._terminate_vserver_services(vserver_name, vserver_client, 

612 security_services) 

613 

614 self.send_request('vserver-destroy', {'vserver-name': vserver_name}) 

615 

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) 

641 

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') 

649 

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 

666 

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()] 

682 

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 

696 

697 return strutils.bool_from_string(vol_encryption_supported) 

698 

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] 

703 

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') 

728 

729 ports = [] 

730 for port_info in net_port_info_list.get_children(): 

731 

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 

735 

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) 

742 

743 return self._sort_data_ports_by_speed(ports) 

744 

745 @na_utils.trace 

746 def _sort_data_ports_by_speed(self, ports): 

747 

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 

760 

761 return sorted(ports, key=sort_key, reverse=True) 

762 

763 @na_utils.trace 

764 def list_root_aggregates(self): 

765 """Get names of all aggregates that contain node root volumes.""" 

766 

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) 

777 

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') 

782 

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')) 

787 

788 if local_root or partner_root: 

789 root_aggregates.append(aggr_name) 

790 

791 return root_aggregates 

792 

793 @na_utils.trace 

794 def list_non_root_aggregates(self): 

795 """Get names of all aggregates that don't contain node root volumes.""" 

796 

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) 

806 

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] 

828 

829 @na_utils.trace 

830 def list_vserver_aggregates(self): 

831 """Returns a list of aggregates available to a vserver. 

832 

833 This must be called against a Vserver LIF. 

834 """ 

835 return list(self.get_vserver_aggregate_capacities().keys()) 

836 

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} 

843 

844 if self.features.BROADCAST_DOMAINS: 

845 self._ensure_broadcast_domain_for_port( 

846 node, home_port_name, mtu, ipspace=ipspace) 

847 

848 return home_port_name 

849 

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}) 

858 

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) 

874 

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) 

895 

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) 

923 

924 @na_utils.trace 

925 def get_degraded_ports(self, broadcast_domains, ipspace): 

926 """Get degraded ports for broadcast domains and an ipspace.""" 

927 

928 valid_domains = self._get_valid_broadcast_domains(broadcast_domains) 

929 

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 } 

949 

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') 

953 

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')}") 

960 

961 return ports 

962 

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 

974 

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) 

1006 

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. 

1011 

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 """ 

1019 

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) 

1023 

1024 port_info = self._get_broadcast_domain_for_port(node, port) 

1025 

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 

1031 

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']) 

1037 

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) 

1043 

1044 # Move the port into the broadcast domain where it is needed. 

1045 self._add_port_to_broadcast_domain(node, port, domain, ipspace) 

1046 

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) 

1065 

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) 

1073 

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 

1080 

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) 

1098 

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) 

1108 

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) 

1118 

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) 

1127 

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 

1134 

1135 ipspace = ipspaces[0] 

1136 for broadcast_domain_name in ipspace['broadcast-domains']: 

1137 self._delete_broadcast_domain(broadcast_domain_name, ipspace_name) 

1138 

1139 @na_utils.trace 

1140 def _add_port_to_broadcast_domain(self, node, port, domain, ipspace): 

1141 

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) 

1166 

1167 @na_utils.trace 

1168 def _remove_port_from_broadcast_domain(self, node, port, domain, ipspace): 

1169 

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) 

1179 

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}' 

1186 

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) 

1205 

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()] 

1221 

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] 

1227 

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 

1237 

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') 

1241 

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) 

1256 

1257 return interfaces 

1258 

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) 

1267 

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) 

1276 

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""" 

1280 

1281 if not self.features.IPSPACES: 

1282 return None 

1283 

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} 

1289 

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 

1301 

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') 

1305 

1306 return ipspace_name 

1307 

1308 @na_utils.trace 

1309 def get_ipspaces(self, ipspace_name=None, vserver_name=None): 

1310 """Gets one or more IPSpaces. 

1311 

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) 

1318 

1319 if not self.features.IPSPACES: 

1320 return [] 

1321 

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 } 

1337 

1338 result = self.send_iter_request('net-ipspaces-get-iter', api_args) 

1339 if not self._has_records(result): 

1340 return [] 

1341 

1342 ipspaces = [] 

1343 

1344 for net_ipspaces_info in result.get_child_by_name( 

1345 'attributes-list').get_children(): 

1346 

1347 ipspace = { 

1348 'ports': [], 

1349 'vservers': [], 

1350 'broadcast-domains': [], 

1351 } 

1352 

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()) 

1357 

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()) 

1362 

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()) 

1368 

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') 

1372 

1373 ipspaces.append(ipspace) 

1374 

1375 return ipspaces 

1376 

1377 @na_utils.trace 

1378 def ipspace_exists(self, ipspace_name): 

1379 """Checks if IPspace exists.""" 

1380 

1381 if not self.features.IPSPACES: 

1382 return False 

1383 

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) 

1398 

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) 

1404 

1405 @na_utils.trace 

1406 def delete_ipspace(self, ipspace_name): 

1407 """Deletes an IPspace. 

1408 

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 

1415 

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 

1418 

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 

1426 

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 

1434 

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 

1442 

1443 return True 

1444 

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) 

1450 

1451 @na_utils.trace 

1452 def get_node_for_aggregate(self, aggregate_name): 

1453 """Get home node for the specified aggregate. 

1454 

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 """ 

1458 

1459 if not aggregate_name: 

1460 return None 

1461 

1462 desired_attributes = { 

1463 'aggr-attributes': { 

1464 'aggregate-name': None, 

1465 'aggr-ownership-attributes': { 

1466 'home-name': None, 

1467 }, 

1468 }, 

1469 } 

1470 

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 

1479 

1480 if len(aggrs) < 1: 

1481 return None 

1482 

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') 

1486 

1487 @na_utils.trace 

1488 def get_cluster_aggregate_capacities(self, aggregate_names): 

1489 """Calculates capacity of one or more aggregates. 

1490 

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 """ 

1497 

1498 if aggregate_names is not None and len(aggregate_names) == 0: 

1499 return {} 

1500 

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') 

1517 

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 

1527 

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. 

1531 

1532 Returns dictionary of aggregate capacity metrics. This must 

1533 be called against a Vserver LIF. 

1534 """ 

1535 

1536 if aggregate_names is not None and len(aggregate_names) == 0: 

1537 return {} 

1538 

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') 

1556 

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() 

1562 

1563 if not vserver_aggr_info_list: 

1564 LOG.warning('No aggregates assigned to Vserver %s.', 

1565 vserver_name) 

1566 

1567 # Return dict of key-value pair of aggr_name:aggr_size_available. 

1568 aggr_space_dict = {} 

1569 

1570 for aggr_info in vserver_aggr_info_list: 

1571 aggr_name = aggr_info.get_child_content('aggr-name') 

1572 

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} 

1576 

1577 LOG.debug('Found available Vserver aggregates: %s', aggr_space_dict) 

1578 return aggr_space_dict 

1579 

1580 @na_utils.trace 

1581 def _get_aggregates(self, aggregate_names=None, desired_attributes=None): 

1582 

1583 query = { 

1584 'aggr-attributes': { 

1585 'aggregate-name': '|'.join(aggregate_names), 

1586 } 

1587 } if aggregate_names else None 

1588 

1589 api_args = {} 

1590 if query: 

1591 api_args['query'] = query 

1592 if desired_attributes: 

1593 api_args['desired-attributes'] = desired_attributes 

1594 

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() 

1600 

1601 def get_performance_instance_uuids(self, object_name, node_name): 

1602 """Get UUIDs of performance instances for a cluster node.""" 

1603 

1604 api_args = { 

1605 'objectname': object_name, 

1606 'query': { 

1607 'instance-info': { 

1608 'uuid': node_name + ':*', 

1609 } 

1610 } 

1611 } 

1612 

1613 result = self.send_request('perf-object-instance-list-info-iter', 

1614 api_args) 

1615 

1616 uuids = [] 

1617 

1618 instances = result.get_child_by_name( 

1619 'attributes-list') or netapp_api.NaElement('None') 

1620 

1621 for instance_info in instances.get_children(): 

1622 uuids.append(instance_info.get_child_content('uuid')) 

1623 

1624 return uuids 

1625 

1626 def get_performance_counter_info(self, object_name, counter_name): 

1627 """Gets info about one or more Data ONTAP performance counters.""" 

1628 

1629 api_args = {'objectname': object_name} 

1630 result = self.send_request('perf-object-counter-list-info', api_args) 

1631 

1632 counters = result.get_child_by_name( 

1633 'counters') or netapp_api.NaElement('None') 

1634 

1635 for counter in counters.get_children(): 

1636 

1637 if counter.get_child_content('name') == counter_name: 

1638 

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') 

1645 

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) 

1653 

1654 def get_performance_counters(self, object_name, instance_uuids, 

1655 counter_names): 

1656 """Gets one or more cDOT performance counters.""" 

1657 

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 } 

1668 

1669 result = self.send_request('perf-object-get-instances', api_args) 

1670 

1671 counter_data = [] 

1672 

1673 timestamp = result.get_child_content('timestamp') 

1674 

1675 instances = result.get_child_by_name( 

1676 'instances') or netapp_api.NaElement('None') 

1677 for instance in instances.get_children(): 

1678 

1679 instance_name = instance.get_child_content('name') 

1680 instance_uuid = instance.get_child_content('uuid') 

1681 node_name = instance_uuid.split(':')[0] 

1682 

1683 counters = instance.get_child_by_name( 

1684 'counters') or netapp_api.NaElement('None') 

1685 for counter in counters.get_children(): 

1686 

1687 counter_name = counter.get_child_content('name') 

1688 counter_value = counter.get_child_content('value') 

1689 

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 }) 

1697 

1698 return counter_data 

1699 

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) 

1715 

1716 for security_service in security_services: 

1717 if security_service['type'].lower() == 'ldap': 

1718 vserver_client.configure_ldap(security_service, 

1719 timeout=timeout) 

1720 

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) 

1726 

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) 

1731 

1732 else: 

1733 msg = _('Unsupported security service type %s for ' 

1734 'Data ONTAP driver') 

1735 raise exception.NetAppException(msg % security_service['type']) 

1736 

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) 

1744 

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) 

1752 

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) 

1758 

1759 if nfs_config: 

1760 self._configure_nfs(nfs_config) 

1761 

1762 self._create_default_nfs_export_rules() 

1763 

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' 

1770 

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) 

1781 

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) 

1786 

1787 @na_utils.trace 

1788 def _create_default_nfs_export_rules(self): 

1789 """Create the default export rule for the NFS service.""" 

1790 

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) 

1804 

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' 

1811 

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) 

1831 

1832 if security_service.get('dns_ip'): 

1833 self.configure_dns(security_service) 

1834 

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 } 

1845 

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' 

1857 

1858 api_args[servers_key] = [] 

1859 for server in ldap_servers.split(','): 

1860 api_args[servers_key].append( 

1861 {servers_key_type: server.strip()}) 

1862 

1863 self.send_request('ldap-client-create', api_args) 

1864 

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'} 

1872 

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) 

1885 

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) 

1893 

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) 

1901 

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) 

1910 

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) 

1916 

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) 

1924 

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) 

1932 

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) 

1940 

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) 

1962 

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 

1972 

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) 

1980 

1981 cifs_server = self._get_cifs_server_name(vserver_name) 

1982 

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 } 

1990 

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'] 

1995 

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) 

2011 

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) 

2017 

2018 current_user_name = current_security_service['user'] 

2019 new_username = new_security_service['user'] 

2020 

2021 current_cifs_username = cifs_server + '\\' + current_user_name 

2022 

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) 

2033 

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) 

2044 

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) 

2063 

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) 

2067 

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) 

2071 

2072 @na_utils.trace 

2073 def create_kerberos_realm(self, security_service): 

2074 """Creates Kerberos realm on cluster.""" 

2075 

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) 

2080 

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) 

2101 

2102 @na_utils.trace 

2103 def configure_kerberos(self, security_service, vserver_name): 

2104 """Configures Kerberos for NFS on Vserver.""" 

2105 

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) 

2110 

2111 self.configure_dns(security_service) 

2112 spn = self._get_kerberos_service_principal_name( 

2113 security_service, vserver_name) 

2114 

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) 

2119 

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 } 

2128 

2129 self.send_request('kerberos-config-modify', api_args) 

2130 

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()) 

2137 

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) 

2145 

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) 

2157 

2158 @na_utils.trace 

2159 def disable_kerberos(self, security_service): 

2160 """Disable Kerberos in all Vserver LIFs.""" 

2161 

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) 

2183 

2184 @na_utils.trace 

2185 def is_kerberos_enabled(self): 

2186 """Check if Kerberos in enabled in all LIFs.""" 

2187 

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) 

2192 

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 

2198 

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 

2218 

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 

2226 

2227 return True 

2228 

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', [])) 

2243 

2244 domains.add(security_service['domain']) 

2245 for domain in domains: 

2246 api_args['domains'].append({'string': domain}) 

2247 

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}) 

2252 

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) 

2261 

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) 

2273 

2274 dns_config = {} 

2275 attributes = result.get_child_by_name('attributes') 

2276 dns_info = attributes.get_child_by_name('net-dns-info') 

2277 

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()] 

2284 

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 

2290 

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}) 

2302 

2303 for dns_ip in dns_ips: 

2304 api_args['name-servers'].append({'ip-address': dns_ip}) 

2305 

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 

2314 

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) 

2320 

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'} 

2329 

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) 

2339 

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 } 

2358 

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) 

2364 

2365 @na_utils.trace 

2366 def set_preferred_dc(self, security_service): 

2367 # server is optional 

2368 if not security_service['server']: 

2369 return 

2370 

2371 api_args = { 

2372 'preferred-dc': [], 

2373 'domain': security_service['domain'], 

2374 } 

2375 

2376 for dc_ip in security_service['server'].split(','): 

2377 api_args['preferred-dc'].append({'string': dc_ip.strip()}) 

2378 

2379 if self.features.CIFS_DC_ADD_SKIP_CHECK: 

2380 api_args['skip-config-validation'] = 'false' 

2381 

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) 

2387 

2388 @na_utils.trace 

2389 def remove_preferred_dcs(self, security_service): 

2390 """Drops all preferred DCs at once.""" 

2391 

2392 api_args = { 

2393 'domain': security_service['domain'], 

2394 } 

2395 

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) 

2401 

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) 

2415 

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)) 

2425 

2426 self.send_request('volume-create', api_args) 

2427 

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 ) 

2433 

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) 

2440 

2441 if max_files is not None: 

2442 self.set_volume_max_files(volume_name, max_files) 

2443 

2444 if snaplock_type is not None: 

2445 self.set_snaplock_attributes(volume_name, **options) 

2446 

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.""" 

2456 

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) 

2460 

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)) 

2474 

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 

2482 

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) 

2508 

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' 

2517 

2518 if snaplock_type is not None: 

2519 api_args['snaplock-type'] = snaplock_type 

2520 

2521 return api_args 

2522 

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) 

2543 

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 

2568 

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 

2589 

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) 

2598 

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) 

2607 

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) 

2613 

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) 

2619 

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) 

2628 

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) 

2637 

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) 

2648 

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) 

2659 

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') 

2686 

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 } 

2693 

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 

2732 

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')) 

2746 

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')) 

2776 

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')) 

2807 

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')) 

2839 

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')) 

2869 

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) 

2878 

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) 

2887 

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. 

2897 

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 """ 

2913 

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) 

2917 

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() 

2977 

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) 

2987 

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) 

2995 

2996 # cDOT compression requires dedup to be enabled 

2997 dedup_enabled = dedup_enabled or compression_enabled 

2998 

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) 

3010 

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) 

3022 

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) 

3029 

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) 

3034 

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) 

3053 

3054 @na_utils.trace 

3055 def is_flexvol_encrypted(self, volume_name, vserver_name): 

3056 """Checks whether the volume is encrypted or not.""" 

3057 

3058 if not self.features.FLEXVOL_ENCRYPTION: 

3059 return False 

3060 

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 

3086 

3087 return False 

3088 

3089 @na_utils.trace 

3090 def get_aggregate_for_volume(self, volume_name): 

3091 """Get the name of the aggregate containing a volume.""" 

3092 

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) 

3114 

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') 

3121 

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()] 

3129 

3130 if not aggregate: 

3131 msg = _('Could not find aggregate for volume %s.') 

3132 raise exception.NetAppException(msg % volume_name) 

3133 

3134 return aggregate 

3135 

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) 

3140 

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) 

3155 

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 

3161 

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) 

3180 

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 } 

3187 

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 } 

3200 

3201 @na_utils.trace 

3202 def get_volume_snapshot_attributes(self, volume_name): 

3203 """Returns snapshot attributes""" 

3204 

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 } 

3223 

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() 

3228 

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) 

3235 

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') 

3239 

3240 return {key: vol_snapshot_attr.get_child_content(key) 

3241 for key in desired_snapshot_attributes.keys()} 

3242 

3243 def get_volume_allocated_files(self, volume_name): 

3244 """Get flexvol allocated files""" 

3245 

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 

3266 

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') 

3273 

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 } 

3280 

3281 @na_utils.trace 

3282 def get_volume(self, volume_name): 

3283 """Returns the volume with the specified name, if present.""" 

3284 

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) 

3321 

3322 attributes_list = result.get_child_by_name( 

3323 'attributes-list') or netapp_api.NaElement('none') 

3324 volume_attributes_list = attributes_list.get_children() 

3325 

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) 

3332 

3333 volume_attributes = volume_attributes_list[0] 

3334 

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') 

3343 

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()] 

3353 

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 

3375 

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 

3381 

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 

3404 

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') 

3411 

3412 volume = { 

3413 'name': volume_id_attributes.get_child_content('name'), 

3414 } 

3415 return volume 

3416 

3417 @na_utils.trace 

3418 def get_volume_to_manage(self, aggregate_name, volume_name): 

3419 """Get flexvol to be managed by Manila. 

3420 

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 """ 

3426 

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 

3465 

3466 result = self.send_iter_request('volume-get-iter', api_args) 

3467 if not self._has_records(result): 

3468 return None 

3469 

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') 

3480 

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()] 

3490 

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') 

3504 

3505 } 

3506 return volume 

3507 

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 } 

3521 

3522 if qos_policy_group is not None: 

3523 api_args['qos-policy-group-name'] = qos_policy_group 

3524 

3525 self.send_request('volume-clone-create', api_args) 

3526 

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) 

3530 

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 

3541 

3542 @na_utils.trace 

3543 def volume_clone_split_status(self, volume_name): 

3544 """Status of splitting a clone from its parent. 

3545 

3546 Returns the split status: 

3547 

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}") 

3571 

3572 return na_utils.CLONE_SPLIT_STATUS_UNKNOWN 

3573 

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 

3590 

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 

3603 

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 

3608 

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') 

3628 

3629 if not attributes_list: 

3630 return None 

3631 

3632 clones = [] 

3633 for clone_info in attributes_list.get_children(): 

3634 clones.append(clone_info.get_child_content('volume')) 

3635 return clones 

3636 

3637 @na_utils.trace 

3638 def get_volume_clone_parent_snaphot(self, volume_name): 

3639 """Gets volume's clone parent. 

3640 

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 

3665 

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 

3678 

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.""" 

3682 

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 [] 

3705 

3706 volume_list = [] 

3707 attributes_list = result.get_child_by_name( 

3708 'attributes-list') or netapp_api.NaElement('none') 

3709 

3710 for volume_attributes in attributes_list.get_children(): 

3711 

3712 volume_id_attributes = volume_attributes.get_child_by_name( 

3713 'volume-id-attributes') or netapp_api.NaElement('none') 

3714 

3715 volume_list.append({ 

3716 'name': volume_id_attributes.get_child_content('name'), 

3717 }) 

3718 

3719 return volume_list 

3720 

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') 

3730 

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) 

3740 

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) 

3748 

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) 

3758 

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 

3772 

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. 

3776 

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 """ 

3787 

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 

3804 

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) 

3810 

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) 

3819 

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) 

3829 

3830 @na_utils.trace 

3831 def delete_volume(self, volume_name): 

3832 """Deletes a volume.""" 

3833 return self.soft_delete_volume(volume_name) 

3834 

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 

3863 

3864 attributes_list = result.get_child_by_name('attributes-list') 

3865 if not attributes_list: 

3866 return 

3867 

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')) 

3881 

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) 

3897 

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) 

3904 

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) 

3927 

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) 

3954 

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) 

3963 

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}) 

3970 

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) 

3991 

3992 error_record_list = result.get_child_by_name( 

3993 'volume-errors') or netapp_api.NaElement('none') 

3994 errors = error_record_list.get_children() 

3995 

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) 

4011 

4012 return self._has_records(result) 

4013 

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) 

4037 

4038 error_record_list = result.get_child_by_name( 

4039 'volume-errors') or netapp_api.NaElement('none') 

4040 errors = error_record_list.get_children() 

4041 

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) 

4057 

4058 attributes_list = result.get_child_by_name( 

4059 'attributes-list') or netapp_api.NaElement('none') 

4060 snapshot_info_list = attributes_list.get_children() 

4061 

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) 

4069 

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 } 

4078 

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'} 

4086 

4087 return snapshot 

4088 

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) 

4097 

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) 

4106 

4107 @na_utils.trace 

4108 def delete_snapshot(self, volume_name, snapshot_name, ignore_owners=False): 

4109 """Deletes a volume snapshot.""" 

4110 

4111 ignore_owners = ('true' if strutils.bool_from_string(ignore_owners) 

4112 else 'false') 

4113 

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) 

4120 

4121 @na_utils.trace 

4122 def rename_snapshot_and_split_clones(self, volume_name, snapshot_name): 

4123 """Renames volume snapshot & splits clones.""" 

4124 

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 

4139 

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']) 

4145 

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) 

4153 

4154 @na_utils.trace 

4155 def prune_deleted_snapshots(self): 

4156 """Deletes non-busy snapshots that were previously soft-deleted.""" 

4157 

4158 deleted_snapshots_map = self._get_deleted_snapshots() 

4159 

4160 for vserver in deleted_snapshots_map: 

4161 client = copy.deepcopy(self) 

4162 client.set_vserver(vserver) 

4163 

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) 

4176 

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) 

4196 

4197 attributes_list = result.get_child_by_name( 

4198 'attributes-list') or netapp_api.NaElement('none') 

4199 

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 

4211 

4212 return snapshot_map 

4213 

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) 

4222 

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') 

4234 

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) 

4239 

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) 

4244 

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) 

4264 

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) 

4282 

4283 attributes_list = result.get_child_by_name( 

4284 'attributes-list') or netapp_api.NaElement('none') 

4285 

4286 rules = {} 

4287 

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 

4292 

4293 return rules 

4294 

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 

4307 

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) 

4316 

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) 

4321 

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 

4330 

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) 

4345 

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'}] 

4363 

4364 self.send_request('export-rule-create', api_args) 

4365 

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'}] 

4383 

4384 self.send_request('export-rule-modify', api_args) 

4385 

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) 

4405 

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() 

4409 

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] 

4414 

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) 

4420 

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 

4433 

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') 

4437 

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) 

4457 

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) 

4478 

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) 

4485 

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) 

4503 

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.""" 

4507 

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) 

4525 

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') 

4532 

4533 export_policy = volume_export_attributes.get_child_content('policy') 

4534 

4535 if not export_policy: 

4536 msg = _('Could not find export policy for volume %s.') 

4537 raise exception.NetAppException(msg % volume_name) 

4538 

4539 return export_policy 

4540 

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 

4549 

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) 

4559 

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 

4569 

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) 

4577 

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) 

4589 

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) 

4606 

4607 attributes_list = result.get_child_by_name( 

4608 'attributes-list') or netapp_api.NaElement('none') 

4609 

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 

4616 

4617 return policy_map 

4618 

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) 

4623 

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] 

4629 

4630 # Fall back to data Vserver. 

4631 data_vservers = self.list_vservers(vserver_type='data') 

4632 if data_vservers: 

4633 return data_vservers[0] 

4634 

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] 

4639 

4640 raise exception.NotFound("No Vserver found to receive EMS messages.") 

4641 

4642 @na_utils.trace 

4643 def send_ems_log_message(self, message_dict): 

4644 """Sends a message to the Data ONTAP EMS log.""" 

4645 

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) 

4650 

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) 

4657 

4658 @na_utils.trace 

4659 def get_aggregate(self, aggregate_name): 

4660 """Get aggregate attributes needed for the storage service catalog.""" 

4661 

4662 if not aggregate_name: 

4663 return {} 

4664 

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 } 

4678 

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 {} 

4692 

4693 if len(aggrs) < 1: 

4694 return {} 

4695 

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') 

4703 

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 

4720 

4721 @na_utils.trace 

4722 def get_aggregate_disk_types(self, aggregate_name): 

4723 """Get the disk type(s) of an aggregate.""" 

4724 

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)) 

4730 

4731 return list(disk_types) if disk_types else None 

4732 

4733 @na_utils.trace 

4734 def _get_aggregate_disk_types(self, aggregate_name, shared=False): 

4735 """Get the disk type(s) of an aggregate.""" 

4736 

4737 disk_types = set() 

4738 

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 } 

4755 

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 } 

4770 

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 

4777 

4778 attributes_list = result.get_child_by_name( 

4779 'attributes-list') or netapp_api.NaElement('none') 

4780 

4781 for storage_disk_info in attributes_list.get_children(): 

4782 

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) 

4789 

4790 return disk_types 

4791 

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 

4804 

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') 

4821 

4822 @na_utils.trace 

4823 def create_cluster_peer(self, addresses, username=None, password=None, 

4824 passphrase=None): 

4825 """Creates a cluster peer relationship.""" 

4826 

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 

4838 

4839 self.send_request('cluster-peer-create', api_args, 

4840 enable_tunneling=False) 

4841 

4842 @na_utils.trace 

4843 def get_cluster_peers(self, remote_cluster_name=None): 

4844 """Gets one or more cluster peer relationships.""" 

4845 

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 } 

4853 

4854 result = self.send_iter_request('cluster-peer-get-iter', api_args) 

4855 if not self._has_records(result): 

4856 return [] 

4857 

4858 cluster_peers = [] 

4859 

4860 for cluster_peer_info in result.get_child_by_name( 

4861 'attributes-list').get_children(): 

4862 

4863 cluster_peer = { 

4864 'active-addresses': [], 

4865 'peer-addresses': [] 

4866 } 

4867 

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()) 

4872 

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()) 

4877 

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') 

4890 

4891 cluster_peers.append(cluster_peer) 

4892 

4893 return cluster_peers 

4894 

4895 @na_utils.trace 

4896 def delete_cluster_peer(self, cluster_name): 

4897 """Deletes a cluster peer relationship.""" 

4898 

4899 api_args = {'cluster-name': cluster_name} 

4900 self.send_request('cluster-peer-delete', api_args, 

4901 enable_tunneling=False) 

4902 

4903 @na_utils.trace 

4904 def get_cluster_peer_policy(self): 

4905 """Gets the cluster peering policy configuration.""" 

4906 

4907 if not self.features.CLUSTER_PEER_POLICY: 

4908 return {} 

4909 

4910 result = self.send_request('cluster-peer-policy-get') 

4911 

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') 

4916 

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 } 

4925 

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']) 

4933 

4934 return policy 

4935 

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.""" 

4940 

4941 if not self.features.CLUSTER_PEER_POLICY: 

4942 return 

4943 

4944 if (is_unauthenticated_access_permitted is None and 

4945 passphrase_minimum_length is None): 

4946 return 

4947 

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) 

4956 

4957 self.send_request('cluster-peer-policy-modify', api_args) 

4958 

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) 

4974 

4975 @na_utils.trace 

4976 def delete_vserver_peer(self, vserver_name, peer_vserver_name): 

4977 """Deletes a Vserver peer relationship.""" 

4978 

4979 api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name} 

4980 self.send_request('vserver-peer-delete', api_args, 

4981 enable_tunneling=False) 

4982 

4983 @na_utils.trace 

4984 def accept_vserver_peer(self, vserver_name, peer_vserver_name): 

4985 """Accepts a pending Vserver peer relationship.""" 

4986 

4987 api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name} 

4988 self.send_request('vserver-peer-accept', api_args, 

4989 enable_tunneling=False) 

4990 

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.""" 

4994 

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) 

5004 

5005 result = self.send_iter_request('vserver-peer-get-iter', api_args) 

5006 if not self._has_records(result): 

5007 return [] 

5008 

5009 vserver_peers = [] 

5010 

5011 for vserver_peer_info in result.get_child_by_name( 

5012 'attributes-list').get_children(): 

5013 

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) 

5024 

5025 return vserver_peers 

5026 

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) 

5032 

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) 

5044 

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) 

5057 

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() 

5066 

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 

5085 

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 

5091 

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.""" 

5096 

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 

5110 

5111 return req_args 

5112 

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) 

5124 

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) 

5134 

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() 

5142 

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 

5150 

5151 result = self.send_request('snapmirror-initialize', api_args) 

5152 

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') 

5162 

5163 return result_info 

5164 

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.""" 

5170 

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']) 

5178 

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) 

5183 

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') 

5189 

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']) 

5197 

5198 self.send_request('snapmirror-release', api_args, 

5199 enable_tunneling=True) 

5200 

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) 

5219 

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) 

5228 

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) 

5235 

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() 

5242 

5243 api_args = self._build_snapmirror_request( 

5244 source_path, dest_path, source_vserver, 

5245 dest_vserver, source_volume, dest_volume) 

5246 

5247 self.send_request('snapmirror-quiesce', api_args) 

5248 

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) 

5259 

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) 

5268 

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() 

5276 

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' 

5281 

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 

5287 

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) 

5296 

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) 

5303 

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() 

5310 

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 

5321 

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) 

5334 

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() 

5343 

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 

5355 

5356 self.send_request('snapmirror-modify', api_args) 

5357 

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) 

5366 

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) 

5373 

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() 

5380 

5381 snapmirror_info = self._build_snapmirror_request( 

5382 source_path, dest_path, source_vserver, 

5383 dest_vserver, source_volume, dest_volume) 

5384 

5385 api_args = { 

5386 'query': { 

5387 'snapmirror-info': snapmirror_info 

5388 } 

5389 } 

5390 self.send_request('snapmirror-destroy-iter', api_args) 

5391 

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) 

5400 

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) 

5407 

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() 

5414 

5415 api_args = self._build_snapmirror_request( 

5416 source_path, dest_path, source_vserver, 

5417 dest_vserver, source_volume, dest_volume) 

5418 

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 

5425 

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) 

5434 

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) 

5441 

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() 

5448 

5449 api_args = self._build_snapmirror_request( 

5450 source_path, dest_path, source_vserver, 

5451 dest_vserver, source_volume, dest_volume) 

5452 

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 

5458 

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) 

5467 

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) 

5474 

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() 

5481 

5482 api_args = self._build_snapmirror_request( 

5483 source_path, dest_path, source_vserver, 

5484 dest_vserver, source_volume, dest_volume) 

5485 

5486 self.send_request('snapmirror-resync', api_args) 

5487 

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.""" 

5494 

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 

5505 

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() 

5511 

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) 

5520 

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. 

5527 

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() 

5532 

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 } 

5537 

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) 

5546 

5547 snapmirrors = [] 

5548 

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) 

5555 

5556 return snapmirrors 

5557 

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.""" 

5564 

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 

5575 

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() 

5582 

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. 

5589 

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() 

5594 

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 } 

5600 

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) 

5609 

5610 snapmirrors = [] 

5611 

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) 

5618 

5619 return snapmirrors 

5620 

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) 

5630 

5631 def volume_has_snapmirror_relationships(self, volume): 

5632 """Return True if snapmirror relationships exist for a given volume. 

5633 

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']) 

5647 

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 

5654 

5655 return has_snapmirrors 

5656 

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 

5670 

5671 result = self.send_iter_request('snapshot-get-iter', api_args) 

5672 

5673 attributes_list = result.get_child_by_name( 

5674 'attributes-list') or netapp_api.NaElement('none') 

5675 

5676 return [snapshot_info.get_child_content('name') 

5677 for snapshot_info in attributes_list.get_children()] 

5678 

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.""" 

5688 

5689 self._ensure_snapmirror_v2() 

5690 

5691 api_args = { 

5692 'policy-name': policy_name, 

5693 'type': policy_type, 

5694 } 

5695 

5696 if discard_network_info: 

5697 api_args['discard-configs'] = { 

5698 'svmdr-config-obj': 'network' 

5699 } 

5700 

5701 self.send_request('snapmirror-policy-create', api_args) 

5702 

5703 if preserve_snapshots: 

5704 api_args = { 

5705 'policy-name': policy_name, 

5706 'snapmirror-label': snapmirror_label, 

5707 'keep': keep, 

5708 'preserve': 'false' 

5709 } 

5710 

5711 self.send_request('snapmirror-policy-add-rule', api_args) 

5712 

5713 @na_utils.trace 

5714 def delete_snapmirror_policy(self, policy_name): 

5715 """Deletes a SnapMirror policy.""" 

5716 

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 

5725 

5726 @na_utils.trace 

5727 def get_snapmirror_policies(self, vserver_name): 

5728 """Get all SnapMirror policies associated to a vServer.""" 

5729 

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') 

5745 

5746 return [policy_info.get_child_content('policy-name') 

5747 for policy_info in attributes_list.get_children()] 

5748 

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. 

5753 

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) 

5761 

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. 

5766 

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) 

5775 

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. 

5783 

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 } 

5803 

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) 

5812 

5813 if validation_only: 

5814 api_args['perform-validation-only'] = 'true' 

5815 

5816 self.send_request('volume-move-start', api_args) 

5817 

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) 

5826 

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) 

5836 

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) 

5859 

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) 

5865 

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') 

5870 

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 

5883 

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 

5892 

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 } 

5912 

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) 

5928 

5929 attributes_list = result.get_child_by_name( 

5930 'attributes-list') or netapp_api.NaElement('none') 

5931 

5932 qos_policy_group_info = attributes_list.get_child_by_name( 

5933 'qos-policy-group-info') or netapp_api.NaElement('none') 

5934 

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 

5947 

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) 

5961 

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) 

5974 

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) 

5980 

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) 

5991 

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. 

6000 

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() 

6012 

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 } 

6027 

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) 

6034 

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 } 

6046 

6047 @na_utils.trace 

6048 def rehost_volume(self, volume_name, vserver, destination_vserver): 

6049 """Rehosts a volume from one Vserver into another Vserver. 

6050 

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) 

6062 

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 

6076 

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} 

6079 

6080 result = self.send_request('nfs-service-get-iter', api_args) 

6081 child_elem = result.get_child_by_name('attributes-list') 

6082 

6083 return self.parse_nfs_config(child_elem, desired_args) 

6084 

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') 

6090 

6091 return self.parse_nfs_config(child_elem, desired_args) 

6092 

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') 

6097 

6098 nfs_config = {} 

6099 for arg in desired_args: 

6100 nfs_config[arg] = nfs_info_elem.get_child_content(arg) 

6101 

6102 return nfs_config 

6103 

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' 

6112 

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 

6122 

6123 @na_utils.trace 

6124 def stop_vserver(self, vserver): 

6125 """Stops a vServer.""" 

6126 api_args = { 

6127 'vserver-name': vserver, 

6128 } 

6129 

6130 self.send_request('vserver-stop', api_args, enable_tunneling=False) 

6131 

6132 def is_svm_dr_supported(self): 

6133 return self.features.SVM_DR 

6134 

6135 def create_fpolicy_event(self, share_name, event_name, protocol, 

6136 file_operations): 

6137 """Creates a new fpolicy policy event. 

6138 

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}) 

6154 

6155 self.send_request('fpolicy-policy-event-create', api_args) 

6156 

6157 def delete_fpolicy_event(self, share_name, event_name): 

6158 """Deletes a fpolicy policy event. 

6159 

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) 

6174 

6175 def get_fpolicy_events(self, event_name=None, protocol=None, 

6176 file_operations=None): 

6177 """Retrives a list of fpolicy events. 

6178 

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}) 

6196 

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) 

6204 

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()] 

6219 

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) 

6228 

6229 return fpolicy_events 

6230 

6231 def create_fpolicy_policy(self, fpolicy_name, share_name, events, 

6232 engine='native'): 

6233 """Creates a fpolicy policy resource. 

6234 

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}) 

6248 

6249 self.send_request('fpolicy-policy-create', api_args) 

6250 

6251 def delete_fpolicy_policy(self, share_name, policy_name): 

6252 """Deletes a fpolicy policy event. 

6253 

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) 

6266 

6267 def get_fpolicy_policies(self, share_name, policy_name=None, 

6268 engine_name='native', event_names=[]): 

6269 """Retrieve one or more fpolicy policies. 

6270 

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}) 

6288 

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) 

6295 

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()] 

6308 

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) 

6317 

6318 return fpolicy_policies 

6319 

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. 

6324 

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()}) 

6345 

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()}) 

6350 

6351 self.send_request('fpolicy-policy-scope-create', api_args) 

6352 

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. 

6357 

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()}) 

6375 

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()}) 

6381 

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 ] 

6386 

6387 self.send_request('fpolicy-policy-scope-modify', api_args) 

6388 

6389 def delete_fpolicy_scope(self, policy_name): 

6390 """Deletes a fpolicy policy scope. 

6391 

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) 

6405 

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. 

6411 

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 

6425 

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()}) 

6440 

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) 

6448 

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) 

6480 

6481 return fpolicy_scopes 

6482 

6483 def enable_fpolicy_policy(self, share_name, policy_name, sequence_number): 

6484 """Enables a specific named policy. 

6485 

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 } 

6495 

6496 self.send_request('fpolicy-enable-policy', api_args) 

6497 

6498 def disable_fpolicy_policy(self, policy_name): 

6499 """Disables a specific policy. 

6500 

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) 

6516 

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) 

6530 

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) 

6549 

6550 return fpolicy_status 

6551 

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 

6556 

6557 def get_volume_state(self, name): 

6558 """Returns volume state for a given name""" 

6559 

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 } 

6576 

6577 result = self.send_iter_request('volume-get-iter', api_args) 

6578 

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') 

6588 

6589 return volume_state 

6590 

6591 @na_utils.trace 

6592 def is_flexgroup_volume(self, volume_name): 

6593 """Determines if the ONTAP volume is FlexGroup.""" 

6594 

6595 if not self.is_flexgroup_supported(): 

6596 return False 

6597 

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) 

6615 

6616 attributes_list = result.get_child_by_name( 

6617 'attributes-list') or netapp_api.NaElement('none') 

6618 volume_attributes_list = attributes_list.get_children() 

6619 

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) 

6626 

6627 volume_attributes = volume_attributes_list[0] 

6628 

6629 volume_id_attributes = volume_attributes.get_child_by_name( 

6630 'volume-id-attributes') or netapp_api.NaElement('none') 

6631 

6632 return na_utils.is_style_extended_flexgroup( 

6633 volume_id_attributes.get_child_content('style-extended')) 

6634 

6635 @na_utils.trace 

6636 def is_flexgroup_supported(self): 

6637 return self.features.FLEXGROUP 

6638 

6639 @na_utils.trace 

6640 def is_flexgroup_fan_out_supported(self): 

6641 return self.features.FLEXGROUP_FAN_OUT 

6642 

6643 @na_utils.trace 

6644 def get_job_state(self, job_id): 

6645 """Returns job state for a given job id.""" 

6646 

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 } 

6659 

6660 result = self.send_iter_request('job-get-iter', api_args, 

6661 enable_tunneling=False) 

6662 

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) 

6674 

6675 return job_info_list[0].get_child_content('job-state') 

6676 

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): 

6682 

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) 

6690 

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) 

6708 

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) 

6715 

6716 # ------------------------ REST CALLS ONLY ------------------------ 

6717 

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. 

6723 

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. 

6728 

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 

6741 

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. 

6747 

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 } 

6770 

6771 if dest_ipspace: 

6772 ipspace_data = { 

6773 "ipspace": { 

6774 "name": dest_ipspace, 

6775 } 

6776 } 

6777 request["destination"].update(ipspace_data) 

6778 

6779 api_args = self._format_request(request) 

6780 

6781 return self.send_request( 

6782 'svm-migration-start', api_args=api_args, use_zapi=False) 

6783 

6784 @na_utils.trace 

6785 def get_migration_check_job_state(self, job_id): 

6786 """Get the job state of a share server migration. 

6787 

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) 

6808 

6809 @na_utils.trace 

6810 def svm_migrate_complete(self, migration_id): 

6811 """Send a request to complete the SVM migration. 

6812 

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) 

6823 

6824 return self.send_request( 

6825 'svm-migration-complete', api_args=api_args, use_zapi=False) 

6826 

6827 @na_utils.trace 

6828 def svm_migrate_cancel(self, migration_id): 

6829 """Send a request to cancel the SVM migration. 

6830 

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) 

6840 

6841 @na_utils.trace 

6842 def svm_migration_get(self, migration_id): 

6843 """Send a request to get the progress of the SVM migration. 

6844 

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) 

6854 

6855 @na_utils.trace 

6856 def svm_migrate_pause(self, migration_id): 

6857 """Send a request to pause a migration. 

6858 

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) 

6871 

6872 @na_utils.trace 

6873 def get_job(self, job_uuid): 

6874 """Get a job in ONTAP. 

6875 

6876 :param job_uuid: uuid of the job to be searched. 

6877 """ 

6878 request = {} 

6879 url_params = { 

6880 "job_uuid": job_uuid 

6881 } 

6882 

6883 api_args = self._format_request(request, url_params=url_params) 

6884 

6885 return self.send_request( 

6886 'get-job', api_args=api_args, use_zapi=False) 

6887 

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""" 

6891 

6892 request = {} 

6893 

6894 query = { 

6895 'svm.name': svm_name, 

6896 'fields': 'size' 

6897 } 

6898 

6899 api_args = self._format_request(request, query=query) 

6900 

6901 response = self.send_request( 

6902 'svm-migration-get-progress', api_args=api_args, use_zapi=False) 

6903 

6904 svm_volumes = response.get('records', []) 

6905 

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 

6915 

6916 # Convert Bytes to GBs. 

6917 return (total_volumes_size / 1024**3) 

6918 

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() 

6926 

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) 

6933 

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()] 

6956 

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() 

6976 

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) 

6990 

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 

6995 

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) 

7009 

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") 

7015 

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 } 

7031 

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') 

7043 

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 

7051 

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()] 

7073 

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