Coverage for manila/share/drivers/netapp/dataontap/cluster_mode/lib_base.py: 93%

2649 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

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

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

3# 

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

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

6# a copy of the License at 

7# 

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

9# 

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

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

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

13# License for the specific language governing permissions and limitations 

14# under the License. 

15""" 

16NetApp Data ONTAP cDOT base storage driver library. 

17 

18This library is the abstract base for subclasses that complete the 

19single-SVM or multi-SVM functionality needed by the cDOT Manila drivers. 

20""" 

21 

22import copy 

23import datetime 

24from enum import Enum 

25import json 

26import math 

27import re 

28import socket 

29 

30from manila.exception import SnapshotResourceNotFound 

31from oslo_config import cfg 

32from oslo_log import log 

33from oslo_service import loopingcall 

34from oslo_utils import excutils 

35from oslo_utils import timeutils 

36from oslo_utils import units 

37from oslo_utils import uuidutils 

38 

39from manila.common import constants 

40from manila import coordination 

41from manila import exception 

42from manila.i18n import _ 

43from manila.message import api as message_api 

44from manila.share.drivers.netapp.dataontap.client import api as netapp_api 

45from manila.share.drivers.netapp.dataontap.client import client_cmode 

46from manila.share.drivers.netapp.dataontap.client import client_cmode_rest 

47from manila.share.drivers.netapp.dataontap.client import rest_api as rest_api 

48from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion 

49from manila.share.drivers.netapp.dataontap.cluster_mode import performance 

50from manila.share.drivers.netapp.dataontap.protocols import cifs_cmode 

51from manila.share.drivers.netapp.dataontap.protocols import nfs_cmode 

52from manila.share.drivers.netapp import options as na_opts 

53from manila.share.drivers.netapp import utils as na_utils 

54from manila.share import share_types 

55from manila.share import utils as share_utils 

56from manila import utils as manila_utils 

57 

58LOG = log.getLogger(__name__) 

59CONF = cfg.CONF 

60 

61 

62class Backup(Enum): 

63 """Enum for share backup""" 

64 BACKUP_TYPE = "backup_type" 

65 BACKEND_NAME = "netapp_backup_backend_section_name" 

66 DES_VSERVER = "netapp_backup_vserver" 

67 DES_VOLUME = "netapp_backup_volume" 

68 SM_LABEL = "backup" 

69 DES_VSERVER_PREFIX = "backup" 

70 DES_VOLUME_PREFIX = "backup_volume" 

71 VOLUME_TYPE = "dp" 

72 SM_POLICY = "os_backup_policy" 

73 TOTAL_PROGRESS_HUNDRED = "100" 

74 TOTAL_PROGRESS_ZERO = "0" 

75 

76 

77class NetAppCmodeFileStorageLibrary(object): 

78 AUTOSUPPORT_INTERVAL_SECONDS = 3600 # hourly 

79 SSC_UPDATE_INTERVAL_SECONDS = 3600 # hourly 

80 HOUSEKEEPING_INTERVAL_SECONDS = 600 # ten minutes 

81 

82 SUPPORTED_PROTOCOLS = ('nfs', 'cifs') 

83 

84 DEFAULT_FILTER_FUNCTION = 'capabilities.utilization < 70' 

85 DEFAULT_GOODNESS_FUNCTION = '100 - capabilities.utilization' 

86 DEFAULT_FLEXGROUP_FILTER_FUNCTION = 'share.size >= %s' 

87 

88 # ONTAP requires 100G per FlexGroup member, since the driver is deploying 

89 # with default number of members (four), the min size is 400G. 

90 FLEXGROUP_MIN_SIZE_PER_AGGR = 400 

91 

92 # Internal states when dealing with data motion 

93 STATE_SPLITTING_VOLUME_CLONE = 'splitting_volume_clone' 

94 STATE_MOVING_VOLUME = 'moving_volume' 

95 STATE_SNAPMIRROR_DATA_COPYING = 'snapmirror_data_copying' 

96 

97 # Maximum number of FPolicis per vServer 

98 FPOLICY_MAX_VSERVER_POLICIES = 10 

99 

100 # Maps NetApp qualified extra specs keys to corresponding backend API 

101 # client library argument keywords. When we expose more backend 

102 # capabilities here, we will add them to this map. 

103 BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP = { 

104 'netapp:thin_provisioned': 'thin_provisioned', 

105 'netapp:dedup': 'dedup_enabled', 

106 'netapp:compression': 'compression_enabled', 

107 'netapp:split_clone_on_create': 'split', 

108 'netapp:hide_snapdir': 'hide_snapdir', 

109 } 

110 

111 STRING_QUALIFIED_EXTRA_SPECS_MAP = { 

112 'netapp:snapshot_policy': 'snapshot_policy', 

113 'netapp:language': 'language', 

114 'netapp:max_files': 'max_files', 

115 'netapp:max_files_multiplier': 'max_files_multiplier', 

116 'netapp:adaptive_qos_policy_group': 'adaptive_qos_policy_group', 

117 'netapp:fpolicy_extensions_to_include': 

118 'fpolicy_extensions_to_include', 

119 'netapp:fpolicy_extensions_to_exclude': 

120 'fpolicy_extensions_to_exclude', 

121 'netapp:fpolicy_file_operations': 'fpolicy_file_operations', 

122 'netapp:efficiency_policy': 'efficiency_policy', 

123 'netapp_snaplock_type': 'snaplock_type', 

124 'netapp:snaplock_autocommit_period': 

125 'snaplock_autocommit_period', 

126 'netapp:snaplock_min_retention_period': 

127 'snaplock_min_retention_period', 

128 'netapp:snaplock_max_retention_period': 

129 'snaplock_max_retention_period', 

130 'netapp:snaplock_default_retention_period': 

131 'snaplock_default_retention_period', 

132 } 

133 

134 # Maps standard extra spec keys to legacy NetApp keys 

135 STANDARD_BOOLEAN_EXTRA_SPECS_MAP = { 

136 'thin_provisioning': 'netapp:thin_provisioned', 

137 'dedupe': 'netapp:dedup', 

138 'compression': 'netapp:compression', 

139 } 

140 

141 MAX_QOS_SPECS = { 

142 'netapp:maxiops': 'maxiops', 

143 'netapp:maxiopspergib': 'maxiopspergib', 

144 'netapp:maxbps': 'maxbps', 

145 'netapp:maxbpspergib': 'maxbpspergib', 

146 } 

147 

148 MIN_QOS_SPECS = { 

149 'netapp:miniops': 'miniops', 

150 'netapp:miniopspergib': 'miniopspergib', 

151 'netapp:minbps': 'minbps', 

152 'netapp:minbpspergib': 'minbpspergib', 

153 } 

154 

155 HIDE_SNAPDIR_CFG_MAP = { 

156 'visible': False, 

157 'hidden': True, 

158 'default': None, 

159 } 

160 

161 SIZE_DEPENDENT_QOS_SPECS = { 

162 'maxiopspergib', 'maxbpspergib', 'miniopspergib', 'minbpspergib' 

163 } 

164 

165 # Maps the NFS config used by share-servers 

166 NFS_CONFIG_EXTRA_SPECS_MAP = { 

167 'netapp:tcp_max_xfer_size': 'tcp-max-xfer-size', 

168 'netapp:udp_max_xfer_size': 'udp-max-xfer-size', 

169 } 

170 

171 FPOLICY_FILE_OPERATIONS_LIST = [ 

172 'close', 'create', 'create_dir', 'delete', 'delete_dir', 'getattr', 

173 'link', 'lookup', 'open', 'read', 'write', 'rename', 'rename_dir', 

174 'setattr', 'symlink'] 

175 

176 SNAPLOCK_TYPE = ['compliance', 'enterprise'] 

177 

178 def __init__(self, driver_name, **kwargs): 

179 na_utils.validate_driver_instantiation(**kwargs) 

180 

181 self.driver_name = driver_name 

182 

183 self.private_storage = kwargs['private_storage'] 

184 self.configuration = kwargs['configuration'] 

185 self.configuration.append_config_values(na_opts.netapp_connection_opts) 

186 self.configuration.append_config_values(na_opts.netapp_basicauth_opts) 

187 self.configuration.append_config_values( 

188 na_opts.netapp_certificateauth_opts) 

189 self.configuration.append_config_values(na_opts.netapp_transport_opts) 

190 self.configuration.append_config_values(na_opts.netapp_support_opts) 

191 self.configuration.append_config_values(na_opts.netapp_cluster_opts) 

192 self.configuration.append_config_values( 

193 na_opts.netapp_provisioning_opts) 

194 self.configuration.append_config_values( 

195 na_opts.netapp_data_motion_opts) 

196 

197 self._licenses = [] 

198 self._client = None 

199 self._clients = {} 

200 self._backend_clients = {} 

201 self._ssc_stats = {} 

202 self._have_cluster_creds = None 

203 self._revert_to_snapshot_support = False 

204 self._cluster_info = {} 

205 self._default_nfs_config = None 

206 self.is_nfs_config_supported = False 

207 self._cache_pool_status = None 

208 self._flexgroup_pools = {} 

209 self._is_flexgroup_auto = False 

210 self._is_snaplock_compliance_configured = False 

211 

212 self._app_version = kwargs.get('app_version', 'unknown') 

213 

214 na_utils.setup_tracing(self.configuration.netapp_trace_flags, 

215 self.configuration.netapp_api_trace_pattern) 

216 self._backend_name = self.configuration.safe_get( 

217 'share_backend_name') or driver_name 

218 self.message_api = message_api.API() 

219 self._snapmirror_schedule = self._convert_schedule_to_seconds( 

220 schedule=self.configuration.netapp_snapmirror_schedule) 

221 self._cluster_name = self.configuration.netapp_cluster_name 

222 self.is_volume_backup_before = False 

223 

224 @na_utils.trace 

225 def do_setup(self, context): 

226 self._client = self._get_api_client() 

227 self._have_cluster_creds = self._client.check_for_cluster_credentials() 

228 if self._have_cluster_creds is True: 

229 self._set_cluster_info() 

230 # Set SnapLock compliance clock configured on both the nodes 

231 nodes = self._client.list_cluster_nodes() 

232 for node in nodes: 

233 self._is_snaplock_compliance_configured = ( 

234 self._client.is_snaplock_compliance_clock_configured(node) 

235 ) 

236 if not self._is_snaplock_compliance_configured: 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true

237 break 

238 

239 self._licenses = self._get_licenses() 

240 self._revert_to_snapshot_support = self._check_snaprestore_license() 

241 

242 # Performance monitoring library 

243 self._perf_library = performance.PerformanceLibrary(self._client) 

244 

245 # NOTE(felipe_rodrigues): In case adding a parameter that can be 

246 # configured in old versions too, the "is_nfs_config_supported" should 

247 # be removed (always supporting), adding the logic of skipping the 

248 # transfer limit parameters when building the server nfs_config. 

249 if self._client.features.TRANSFER_LIMIT_NFS_CONFIG: 249 ↛ 256line 249 didn't jump to line 256 because the condition on line 249 was always true

250 self.is_nfs_config_supported = True 

251 self._default_nfs_config = self._client.get_nfs_config_default( 

252 list(self.NFS_CONFIG_EXTRA_SPECS_MAP.values())) 

253 LOG.debug('The default NFS configuration: %s', 

254 self._default_nfs_config) 

255 

256 self._cache_pool_status = na_utils.DataCache( 

257 self.configuration.netapp_cached_aggregates_status_lifetime) 

258 

259 @na_utils.trace 

260 def _set_cluster_info(self): 

261 self._cluster_info['nve_support'] = ( 

262 self._client.is_nve_supported() 

263 and self._client.features.FLEXVOL_ENCRYPTION) 

264 

265 @na_utils.trace 

266 def check_for_setup_error(self): 

267 self._start_periodic_tasks() 

268 

269 def _get_vserver(self, share_server=None): 

270 raise NotImplementedError() 

271 

272 def _get_client(self, config, vserver=None): 

273 if config.netapp_use_legacy_client: 273 ↛ 290line 273 didn't jump to line 290 because the condition on line 273 was always true

274 client = client_cmode.NetAppCmodeClient( 

275 transport_type=config.netapp_transport_type, 

276 ssl_cert_path=config.netapp_ssl_cert_path, 

277 username=config.netapp_login, 

278 password=config.netapp_password, 

279 hostname=config.netapp_server_hostname, 

280 port=config.netapp_server_port, 

281 vserver=vserver, 

282 trace=na_utils.TRACE_API, 

283 api_trace_pattern=na_utils.API_TRACE_PATTERN, 

284 private_key_file=config.netapp_private_key_file, 

285 certificate_file=config.netapp_certificate_file, 

286 ca_certificate_file=config.netapp_ca_certificate_file, 

287 certificate_host_validation=( 

288 config.netapp_certificate_host_validation)) 

289 else: 

290 client = client_cmode_rest.NetAppRestClient( 

291 transport_type=config.netapp_transport_type, 

292 ssl_cert_path=config.netapp_ssl_cert_path, 

293 username=config.netapp_login, 

294 password=config.netapp_password, 

295 hostname=config.netapp_server_hostname, 

296 port=config.netapp_server_port, 

297 vserver=vserver, 

298 trace=na_utils.TRACE_API, 

299 async_rest_timeout=( 

300 config.netapp_rest_operation_timeout), 

301 api_trace_pattern=na_utils.API_TRACE_PATTERN, 

302 private_key_file=config.netapp_private_key_file, 

303 certificate_file=config.netapp_certificate_file, 

304 ca_certificate_file=config.netapp_ca_certificate_file, 

305 certificate_host_validation=( 

306 config.netapp_certificate_host_validation)) 

307 return client 

308 

309 @na_utils.trace 

310 def _get_api_client(self, vserver=None): 

311 

312 # Use cached value to prevent redo calls during client initialization. 

313 client = self._clients.get(vserver) 

314 if not client: 

315 client = self._get_client(self.configuration, vserver=vserver) 

316 self._clients[vserver] = client 

317 return client 

318 

319 @na_utils.trace 

320 def _get_api_client_for_backend(self, backend_name, vserver=None): 

321 key = f"{backend_name}-{vserver}" 

322 client = self._backend_clients.get(key) 

323 if not client: 323 ↛ 327line 323 didn't jump to line 327 because the condition on line 323 was always true

324 config = data_motion.get_backend_configuration(backend_name) 

325 client = self._get_client(config, vserver=vserver) 

326 self._backend_clients[key] = client 

327 return client 

328 

329 @na_utils.trace 

330 def _get_licenses(self): 

331 

332 if not self._have_cluster_creds: 

333 LOG.debug('License info not available without cluster credentials') 

334 return [] 

335 

336 self._licenses = self._client.get_licenses() 

337 

338 log_data = { 

339 'backend': self._backend_name, 

340 'licenses': ', '.join(self._licenses), 

341 } 

342 LOG.info('Available licenses on %(backend)s ' 

343 'are %(licenses)s.', log_data) 

344 

345 if 'nfs' not in self._licenses and 'cifs' not in self._licenses: 

346 msg = 'Neither NFS nor CIFS is licensed on %(backend)s' 

347 msg_args = {'backend': self._backend_name} 

348 LOG.error(msg, msg_args) 

349 

350 return self._licenses 

351 

352 @na_utils.trace 

353 def _start_periodic_tasks(self): 

354 

355 # Run the task once in the current thread so prevent a race with 

356 # the first invocation of get_share_stats. 

357 self._update_ssc_info() 

358 

359 # Start the task that updates the slow-changing storage service catalog 

360 ssc_periodic_task = loopingcall.FixedIntervalLoopingCall( 

361 self._update_ssc_info) 

362 ssc_periodic_task.start(interval=self.SSC_UPDATE_INTERVAL_SECONDS, 

363 initial_delay=self.SSC_UPDATE_INTERVAL_SECONDS) 

364 

365 # Start the task that logs autosupport (EMS) data to the controller 

366 ems_periodic_task = loopingcall.FixedIntervalLoopingCall( 

367 self._handle_ems_logging) 

368 ems_periodic_task.start(interval=self.AUTOSUPPORT_INTERVAL_SECONDS, 

369 initial_delay=0) 

370 

371 # Start the task that runs other housekeeping tasks, such as deletion 

372 # of previously soft-deleted storage artifacts. 

373 housekeeping_periodic_task = loopingcall.FixedIntervalLoopingCall( 

374 self._handle_housekeeping_tasks) 

375 housekeeping_periodic_task.start( 

376 interval=self.HOUSEKEEPING_INTERVAL_SECONDS, 

377 initial_delay=0, 

378 stop_on_exception=False) 

379 

380 def _get_backend_share_name(self, share_id): 

381 """Get share name according to share name template.""" 

382 return self.configuration.netapp_volume_name_template % { 

383 'share_id': share_id.replace('-', '_')} 

384 

385 def _get_backend_snapshot_name(self, snapshot_id): 

386 """Get snapshot name according to snapshot name template.""" 

387 return 'share_snapshot_' + snapshot_id.replace('-', '_') 

388 

389 def _get_backend_cg_snapshot_name(self, snapshot_id): 

390 """Get snapshot name according to snapshot name template.""" 

391 return 'share_cg_snapshot_' + snapshot_id.replace('-', '_') 

392 

393 def _get_backend_qos_policy_group_name(self, share_id): 

394 """Get QoS policy name according to QoS policy group name template.""" 

395 return self.configuration.netapp_qos_policy_group_name_template % { 

396 'share_id': share_id.replace('-', '_')} 

397 

398 def _get_backend_snapmirror_policy_name_svm(self, share_server_id): 

399 return (self.configuration.netapp_snapmirror_policy_name_svm_template 

400 % {'share_server_id': share_server_id.replace('-', '_')}) 

401 

402 def _get_backend_fpolicy_policy_name(self, share_id): 

403 """Get FPolicy policy name according with the configured template.""" 

404 return (self.configuration.netapp_fpolicy_policy_name_template 

405 % {'share_id': share_id.replace('-', '_')}) 

406 

407 def _get_backend_fpolicy_event_name(self, share_id, protocol): 

408 """Get FPolicy event name according with the configured template.""" 

409 return (self.configuration.netapp_fpolicy_event_name_template 

410 % {'protocol': protocol.lower(), 

411 'share_id': share_id.replace('-', '_')}) 

412 

413 @na_utils.trace 

414 def _get_aggregate_space(self, aggr_set): 

415 if self._have_cluster_creds: 

416 return self._client.get_cluster_aggregate_capacities(aggr_set) 

417 else: 

418 return self._client.get_vserver_aggregate_capacities(aggr_set) 

419 

420 @na_utils.trace 

421 def _check_snaprestore_license(self): 

422 """Check if snaprestore license is enabled.""" 

423 if self._have_cluster_creds: 

424 return 'snaprestore' in self._licenses 

425 else: 

426 return self._client.check_snaprestore_license() 

427 

428 @na_utils.trace 

429 def _get_aggregate_node(self, aggregate_name, cluster_client=None): 

430 """Get home node for the specified aggregate, or None.""" 

431 if cluster_client: 431 ↛ 432line 431 didn't jump to line 432 because the condition on line 431 was never true

432 return cluster_client.get_node_for_aggregate(aggregate_name) 

433 elif self._have_cluster_creds: 

434 return self._client.get_node_for_aggregate(aggregate_name) 

435 else: 

436 return None 

437 

438 def get_default_filter_function(self, pool=None): 

439 """Get the default filter_function string.""" 

440 if not self._is_flexgroup_pool(pool): 

441 return self.DEFAULT_FILTER_FUNCTION 

442 

443 min_size = self._get_minimum_flexgroup_size(pool) 

444 return self.DEFAULT_FLEXGROUP_FILTER_FUNCTION % min_size 

445 

446 def get_default_goodness_function(self): 

447 """Get the default goodness_function string.""" 

448 return self.DEFAULT_GOODNESS_FUNCTION 

449 

450 @na_utils.trace 

451 def get_share_stats(self, get_filter_function=None, 

452 goodness_function=None): 

453 """Retrieve stats info from Data ONTAP backend.""" 

454 

455 # NOTE(felipe_rodrigues): the share group stats is reported to the 

456 # entire backend, not per pool. So, if there is a FlexGroup pool, the 

457 # driver drops for all pools the consistent snapshot support. 

458 consistent_snapshot_support = 'host' 

459 if self._flexgroup_pools: 

460 consistent_snapshot_support = None 

461 

462 data = { 

463 'share_backend_name': self._backend_name, 

464 'driver_name': self.driver_name, 

465 'vendor_name': 'NetApp', 

466 'driver_version': '1.0', 

467 'netapp_storage_family': 'ontap_cluster', 

468 'storage_protocol': 'NFS_CIFS', 

469 'pools': self._get_pools(get_filter_function=get_filter_function, 

470 goodness_function=goodness_function), 

471 'share_group_stats': { 

472 'consistent_snapshot_support': consistent_snapshot_support, 

473 }, 

474 } 

475 

476 if self.configuration.replication_domain: 

477 data['replication_type'] = [constants.REPLICATION_TYPE_DR, 

478 constants.REPLICATION_TYPE_READABLE] 

479 data['replication_domain'] = self.configuration.replication_domain 

480 

481 return data 

482 

483 @na_utils.trace 

484 def get_share_server_pools(self, share_server): 

485 """Return list of pools related to a particular share server. 

486 

487 Note that the multi-SVM cDOT driver assigns all available pools to 

488 each Vserver, so there is no need to filter the pools any further 

489 by share_server. 

490 

491 :param share_server: ShareServer class instance. 

492 """ 

493 

494 if self._cache_pool_status.is_expired(): 494 ↛ 497line 494 didn't jump to line 497 because the condition on line 494 was always true

495 return self._get_pools() 

496 

497 return self._cache_pool_status.get_data() 

498 

499 @na_utils.trace 

500 def _get_pools(self, get_filter_function=None, goodness_function=None): 

501 """Retrieve list of pools available to this backend.""" 

502 

503 pools = [] 

504 cached_pools = [] 

505 aggr_pool = set(self._find_matching_aggregates()) 

506 flexgroup_pools = self._flexgroup_pools 

507 flexgroup_aggr = self._get_flexgroup_aggr_set() 

508 aggr_space = self._get_aggregate_space(aggr_pool.union(flexgroup_aggr)) 

509 

510 cluster_name = self._cluster_name 

511 if self._have_cluster_creds and not cluster_name: 511 ↛ 518line 511 didn't jump to line 518 because the condition on line 511 was always true

512 # Get up-to-date node utilization metrics just once. 

513 self._perf_library.update_performance_cache({}, self._ssc_stats) 

514 cluster_name = self._client.get_cluster_name() 

515 self._cluster_name = cluster_name 

516 

517 # Add FlexVol pools. 

518 filter_function = (get_filter_function() if get_filter_function 

519 else None) 

520 for aggr_name in sorted(aggr_pool): 

521 total_gb, free_gb, used_gb = self._get_flexvol_pool_space( 

522 aggr_space, aggr_name) 

523 

524 pool = self._get_pool(aggr_name, total_gb, free_gb, used_gb) 

525 

526 cached_pools.append(pool) 

527 pool_with_func = copy.deepcopy(pool) 

528 pool_with_func['filter_function'] = filter_function 

529 pool_with_func['goodness_function'] = goodness_function 

530 pool_with_func['netapp_cluster_name'] = self._cluster_name 

531 

532 pools.append(pool_with_func) 

533 

534 # Add FlexGroup pools. 

535 for pool_name, aggr_list in flexgroup_pools.items(): 

536 filter_function = (get_filter_function(pool=pool_name) 

537 if get_filter_function else None) 

538 total_gb, free_gb, used_gb = self._get_flexgroup_pool_space( 

539 aggr_space, aggr_list) 

540 

541 pool = self._get_pool(pool_name, total_gb, free_gb, used_gb) 

542 cached_pools.append(pool) 

543 pool_with_func = copy.deepcopy(pool) 

544 pool_with_func['filter_function'] = filter_function 

545 pool_with_func['goodness_function'] = goodness_function 

546 pool_with_func['netapp_cluster_name'] = self._cluster_name 

547 

548 pools.append(pool_with_func) 

549 

550 self._cache_pool_status.update_data(cached_pools) 

551 

552 return pools 

553 

554 @na_utils.trace 

555 def _get_pool(self, pool_name, total_capacity_gb, free_capacity_gb, 

556 allocated_capacity_gb): 

557 """Gets the pool dictionary.""" 

558 if self._have_cluster_creds: 

559 qos_support = True 

560 else: 

561 qos_support = False 

562 

563 # Share-server/share encryption support with NetApp will only be 

564 # possible with DHSS=True 

565 encryption_support = None if self.configuration.safe_get( 

566 'netapp_vserver') else ['share_server'] 

567 

568 netapp_flexvol_encryption = self._cluster_info.get( 

569 'nve_support', False) 

570 reserved_percentage = self.configuration.reserved_share_percentage 

571 reserved_snapshot_percentage = ( 

572 self.configuration.reserved_share_from_snapshot_percentage or 

573 reserved_percentage) 

574 reserved_shr_extend_percentage = ( 

575 self.configuration.reserved_share_extend_percentage or 

576 reserved_percentage) 

577 max_over_ratio = self.configuration.max_over_subscription_ratio 

578 

579 if total_capacity_gb == 0.0: 579 ↛ 580line 579 didn't jump to line 580 because the condition on line 579 was never true

580 total_capacity_gb = 'unknown' 

581 

582 pool = { 

583 'pool_name': pool_name, 

584 'filter_function': None, 

585 'goodness_function': None, 

586 'netapp_cluster_name': '', 

587 'total_capacity_gb': total_capacity_gb, 

588 'free_capacity_gb': free_capacity_gb, 

589 'allocated_capacity_gb': allocated_capacity_gb, 

590 'qos': qos_support, 

591 'reserved_percentage': reserved_percentage, 

592 'reserved_snapshot_percentage': reserved_snapshot_percentage, 

593 'reserved_share_extend_percentage': reserved_shr_extend_percentage, 

594 'max_over_subscription_ratio': max_over_ratio, 

595 'dedupe': [True, False], 

596 'compression': [True, False], 

597 'netapp_flexvol_encryption': netapp_flexvol_encryption, 

598 'thin_provisioning': [True, False], 

599 'snapshot_support': True, 

600 'create_share_from_snapshot_support': True, 

601 'revert_to_snapshot_support': self._revert_to_snapshot_support, 

602 'security_service_update_support': True, 

603 'share_server_multiple_subnet_support': True, 

604 'mount_point_name_support': True, 

605 'share_replicas_migration_support': True, 

606 'encryption_support': encryption_support, 

607 } 

608 

609 # Add storage service catalog data. 

610 pool_ssc_stats = self._ssc_stats.get(pool_name) 

611 if pool_ssc_stats: 611 ↛ 615line 611 didn't jump to line 615 because the condition on line 611 was always true

612 pool.update(pool_ssc_stats) 

613 

614 # Add utilization info, or nominal value if not available. 

615 utilization = self._perf_library.get_node_utilization_for_pool( 

616 pool_name) 

617 pool['utilization'] = na_utils.round_down(utilization) 

618 

619 return pool 

620 

621 @na_utils.trace 

622 def _get_flexvol_pool_space(self, aggr_space_map, aggr): 

623 """Returns the space info tuple for a FlexVol pool.""" 

624 total_capacity_gb = na_utils.round_down(float( 

625 aggr_space_map[aggr].get('total', 0)) / units.Gi) 

626 free_capacity_gb = na_utils.round_down(float( 

627 aggr_space_map[aggr].get('available', 0)) / units.Gi) 

628 allocated_capacity_gb = na_utils.round_down(float( 

629 aggr_space_map[aggr].get('used', 0)) / units.Gi) 

630 

631 return total_capacity_gb, free_capacity_gb, allocated_capacity_gb 

632 

633 @na_utils.trace 

634 def _get_flexgroup_pool_space(self, aggr_space_map, aggr_pool): 

635 """Returns the space info tuple for a FlexGroup pool. 

636 

637 Given that the set of aggregates that form a FlexGroup pool may have 

638 different space information, the space info for the pool is not 

639 calculated by summing their values. The FlexGroup share must have its 

640 total size divided equally through the pool aggregates. So, the pool 

641 size is limited by the least aggregate size: 

642 

643 - free_size = least_aggregate_free_size * number_aggregates 

644 - total_size = least_aggregate_total_size * number_aggregates 

645 - used_size = total_size - free_size 

646 

647 :param aggr_space_map: space info dict for the driver aggregates. 

648 :param aggr_pool: list of aggregate names for the FlexGroup pool. 

649 """ 

650 min_total = None 

651 min_free = None 

652 for aggr_name in aggr_pool: 

653 if aggr_name not in aggr_space_map: 

654 continue 

655 aggr_total = aggr_space_map[aggr_name].get('total', 0) 

656 aggr_free = aggr_space_map[aggr_name].get('available', 0) 

657 

658 if not min_total or min_total.get('total', 0) > aggr_total: 

659 min_total = aggr_space_map[aggr_name] 

660 

661 if not min_free or min_free.get('available', 0) > aggr_free: 

662 min_free = aggr_space_map[aggr_name] 

663 

664 total_gb = na_utils.round_down(0) 

665 if min_total: 

666 min_total_pool = min_total.get('total', 0) * len(aggr_pool) 

667 total_gb = na_utils.round_down(float(min_total_pool) / units.Gi) 

668 

669 free_gb = na_utils.round_down(0) 

670 if min_free: 

671 min_free_pool = min_free.get('available', 0) * len(aggr_pool) 

672 free_gb = na_utils.round_down(float(min_free_pool) / units.Gi) 

673 

674 used_gb = na_utils.round_down(0) 

675 if total_gb > free_gb: 

676 used_gb = na_utils.round_down(total_gb - free_gb) 

677 

678 return total_gb, free_gb, used_gb 

679 

680 @na_utils.trace 

681 def _handle_ems_logging(self): 

682 """Build and send an EMS log message.""" 

683 self._client.send_ems_log_message(self._build_ems_log_message_0()) 

684 self._client.send_ems_log_message(self._build_ems_log_message_1()) 

685 

686 def _build_base_ems_log_message(self): 

687 """Construct EMS Autosupport log message common to all events.""" 

688 

689 ems_log = { 

690 'computer-name': socket.gethostname() or 'Manila_node', 

691 'event-source': 'Manila driver %s' % self.driver_name, 

692 'app-version': self._app_version, 

693 'category': 'provisioning', 

694 'log-level': '5', 

695 'auto-support': 'false', 

696 } 

697 return ems_log 

698 

699 @na_utils.trace 

700 def _build_ems_log_message_0(self): 

701 """Construct EMS Autosupport log message with deployment info.""" 

702 

703 ems_log = self._build_base_ems_log_message() 

704 ems_log.update({ 

705 'event-id': '0', 

706 'event-description': 'OpenStack Manila connected to cluster node', 

707 }) 

708 return ems_log 

709 

710 @na_utils.trace 

711 def _build_ems_log_message_1(self): 

712 """Construct EMS Autosupport log message with storage pool info.""" 

713 

714 message = self._get_ems_pool_info() 

715 

716 ems_log = self._build_base_ems_log_message() 

717 ems_log.update({ 

718 'event-id': '1', 

719 'event-description': json.dumps(message), 

720 }) 

721 return ems_log 

722 

723 def _get_ems_pool_info(self): 

724 raise NotImplementedError() 

725 

726 @na_utils.trace 

727 def _handle_housekeeping_tasks(self): 

728 """Handle various cleanup activities.""" 

729 

730 def _find_matching_aggregates(self, aggregate_names=None): 

731 """Find all aggregates match pattern.""" 

732 raise NotImplementedError() 

733 

734 def _get_backup_vserver(self, backup, share_server=None): 

735 """Get/Create the vserver for backup """ 

736 raise NotImplementedError() 

737 

738 def _delete_backup_vserver(self, backup, des_vserver): 

739 """Delete the vserver for backup """ 

740 raise NotImplementedError() 

741 

742 @na_utils.trace 

743 def _get_flexgroup_aggr_set(self): 

744 aggr = set() 

745 for aggr_list in self._flexgroup_pools.values(): 

746 aggr = aggr.union(aggr_list) 

747 return aggr 

748 

749 @na_utils.trace 

750 def _get_helper(self, share): 

751 """Returns driver which implements share protocol.""" 

752 share_protocol = share['share_proto'].lower() 

753 

754 if share_protocol not in self.SUPPORTED_PROTOCOLS: 

755 err_msg = _("Invalid NAS protocol supplied: %s.") % share_protocol 

756 raise exception.NetAppException(err_msg) 

757 

758 self._check_license_for_protocol(share_protocol) 

759 

760 if share_protocol == 'nfs': 

761 return nfs_cmode.NetAppCmodeNFSHelper() 

762 elif share_protocol == 'cifs': 762 ↛ exitline 762 didn't return from function '_get_helper' because the condition on line 762 was always true

763 return cifs_cmode.NetAppCmodeCIFSHelper() 

764 

765 @na_utils.trace 

766 def _check_license_for_protocol(self, share_protocol): 

767 """Validates protocol license if cluster APIs are accessible.""" 

768 if not self._have_cluster_creds: 

769 return 

770 

771 if share_protocol.lower() not in self._licenses: 

772 current_licenses = self._get_licenses() 

773 if share_protocol.lower() not in current_licenses: 

774 msg_args = { 

775 'protocol': share_protocol, 

776 'host': self.configuration.netapp_server_hostname 

777 } 

778 msg = _('The protocol %(protocol)s is not licensed on ' 

779 'controller %(host)s') % msg_args 

780 LOG.error(msg) 

781 raise exception.NetAppException(msg) 

782 

783 @na_utils.trace 

784 def get_pool(self, share): 

785 """Returns the name of the pool for a given share.""" 

786 

787 pool = share_utils.extract_host(share['host'], level='pool') 

788 if pool: 

789 return pool 

790 

791 share_name = self._get_backend_share_name(share['id']) 

792 aggr = self._client.get_aggregate_for_volume(share_name) 

793 if isinstance(aggr, list): 

794 pool = self._get_flexgroup_pool_name(aggr) 

795 else: 

796 pool = aggr 

797 

798 if not pool: 

799 msg = _('Could not find out the pool name for the share %s.') 

800 raise exception.NetAppException(msg % share_name) 

801 

802 return pool 

803 

804 @na_utils.trace 

805 def create_share(self, context, share, share_server): 

806 """Creates new share.""" 

807 vserver, vserver_client = self._get_vserver(share_server=share_server) 

808 self._allocate_container(share, vserver, vserver_client) 

809 return self._create_export(share, share_server, vserver, 

810 vserver_client) 

811 

812 @na_utils.trace 

813 def create_share_from_snapshot(self, context, share, snapshot, 

814 share_server=None, parent_share=None): 

815 """Creates new share from snapshot.""" 

816 # TODO(dviroel) return progress info in asynchronous answers 

817 

818 # NOTE(felipe_rodrigues): when using FlexGroup, the NetApp driver will 

819 # drop consistent snapshot support, calling this create from snap 

820 # method for each member (no parent share is set). 

821 is_group_snapshot = share.get('source_share_group_snapshot_member_id') 

822 if is_group_snapshot or parent_share['host'] == share['host']: 

823 src_vserver, src_vserver_client = self._get_vserver( 

824 share_server=share_server) 

825 # Creating a new share from snapshot in the source share's pool 

826 self._allocate_container_from_snapshot( 

827 share, snapshot, src_vserver, src_vserver_client) 

828 return self._create_export(share, share_server, src_vserver, 

829 src_vserver_client) 

830 

831 parent_share_server = {} 

832 if parent_share['share_server'] is not None: 832 ↛ 841line 832 didn't jump to line 841 because the condition on line 832 was always true

833 # Get only the information needed by Data Motion 

834 ss_keys = ['id', 'identifier', 'backend_details', 'host'] 

835 for key in ss_keys: 

836 parent_share_server[key] = ( 

837 parent_share['share_server'].get(key)) 

838 

839 # Information to be saved in the private_storage that will need to be 

840 # retrieved later, in order to continue with the share creation flow 

841 src_share_instance = { 

842 'id': share['id'], 

843 'host': parent_share.get('host'), 

844 'share_server': parent_share_server or None 

845 } 

846 # NOTE(dviroel): Data Motion functions access share's 'share_server' 

847 # attribute to get vserser information. 

848 dest_share = copy.copy(share.to_dict()) 

849 dest_share['share_server'] = share_server 

850 

851 dm_session = data_motion.DataMotionSession() 

852 # Source host info 

853 __, src_vserver, src_backend = ( 

854 dm_session.get_backend_info_for_share(parent_share)) 

855 src_vserver_client = data_motion.get_client_for_backend( 

856 src_backend, vserver_name=src_vserver) 

857 

858 # Destination host info 

859 dest_vserver, dest_vserver_client = self._get_vserver(share_server) 

860 

861 src_cluster_name = None 

862 dest_cluster_name = None 

863 if self._have_cluster_creds: 

864 src_cluster_name = src_vserver_client.get_cluster_name() 

865 dest_cluster_name = dest_vserver_client.get_cluster_name() 

866 

867 # the parent share and new share must reside on same pool type: either 

868 # FlexGroup or FlexVol. 

869 parent_share_name = self._get_backend_share_name(parent_share['id']) 

870 parent_is_flexgroup = self._is_flexgroup_share(src_vserver_client, 

871 parent_share_name) 

872 dest_pool_name = share_utils.extract_host(share['host'], level='pool') 

873 dest_is_flexgroup = self._is_flexgroup_pool(dest_pool_name) 

874 if parent_is_flexgroup != dest_is_flexgroup: 

875 parent_type = 'FlexGroup' if parent_is_flexgroup else 'FlexVol' 

876 dest_type = 'FlexGroup' if dest_is_flexgroup else 'FlexVol' 

877 msg = _('Could not create share %(share_id)s from snapshot ' 

878 '%(snapshot_id)s in the destination host %(dest_host)s. ' 

879 'The snapshot is from %(parent_type)s style, while the ' 

880 'destination host is %(dest_type)s style.') 

881 msg_args = {'share_id': share['id'], 'snapshot_id': snapshot['id'], 

882 'dest_host': dest_share['host'], 

883 'parent_type': parent_type, 'dest_type': dest_type} 

884 raise exception.NetAppException(msg % msg_args) 

885 

886 # FlexGroup pools must have the same number of aggregates 

887 if dest_is_flexgroup: 

888 parent_aggr = src_vserver_client.get_aggregate_for_volume( 

889 parent_share_name) 

890 

891 # NOTE(felipe_rodrigues): when FlexGroup is auto provisioned the 

892 # match of number of aggregates cannot be checked. So, it might 

893 # fail during snapmirror setup. 

894 if not self._is_flexgroup_auto: 894 ↛ 910line 894 didn't jump to line 910 because the condition on line 894 was always true

895 dest_aggr = self._get_flexgroup_aggregate_list(dest_pool_name) 

896 if len(parent_aggr) != len(dest_aggr): 

897 msg = _('Could not create share %(share_id)s from ' 

898 'snapshot %(snapshot_id)s in the destination host ' 

899 '%(dest_host)s. The source and destination ' 

900 'FlexGroup pools must have the same number of ' 

901 'aggregates.') 

902 msg_args = {'share_id': share['id'], 

903 'snapshot_id': snapshot['id'], 

904 'dest_host': dest_share['host']} 

905 raise exception.NetAppException(msg % msg_args) 

906 else: 

907 parent_aggr = share_utils.extract_host(parent_share['host'], 

908 level='pool') 

909 

910 try: 

911 # NOTE(felipe_rodrigues): no support to move volumes that are 

912 # FlexGroup or without the cluster credential. So, performs the 

913 # workaround using snapmirror even being in the same cluster. 

914 

915 if (src_cluster_name != dest_cluster_name or dest_is_flexgroup or 

916 not self._have_cluster_creds): 

917 # 1. Create a clone on source (temporary volume). We don't need 

918 # to split from clone in order to replicate data. We don't need 

919 # to create fpolicies since this copy will be deleted. 

920 temp_share = copy.deepcopy(dest_share) 

921 temp_uuid = uuidutils.generate_uuid() 

922 temp_share['id'] = str(temp_uuid) 

923 self._allocate_container_from_snapshot( 

924 temp_share, snapshot, src_vserver, src_vserver_client, 

925 split=False, create_fpolicy=False) 

926 # 2. Create a replica in destination host. 

927 self._allocate_container( 

928 dest_share, dest_vserver, dest_vserver_client, 

929 replica=True, set_qos=False) 

930 # 3. Initialize snapmirror relationship with cloned share. 

931 src_share_instance['replica_state'] = ( 

932 constants.REPLICA_STATE_ACTIVE) 

933 relationship_type = na_utils.get_relationship_type( 

934 dest_is_flexgroup) 

935 src_share_instance["id"] = temp_share['id'] 

936 dm_session.create_snapmirror( 

937 src_share_instance, 

938 dest_share, 

939 relationship_type) 

940 # The snapmirror data copy can take some time to be concluded, 

941 # we'll answer this call asynchronously. 

942 state = self.STATE_SNAPMIRROR_DATA_COPYING 

943 else: 

944 # NOTE(dviroel): there's a need to split the cloned share from 

945 # its parent in order to move it to a different aggregate or 

946 # vserver. 

947 self._allocate_container_from_snapshot( 

948 dest_share, snapshot, src_vserver, 

949 src_vserver_client, split=True) 

950 # The split volume clone operation can take some time to be 

951 # concluded and we'll answer the call asynchronously. 

952 state = self.STATE_SPLITTING_VOLUME_CLONE 

953 except Exception: 

954 # If the share exists on the source vserver, we need to delete it 

955 # since it's a temporary share, not managed by the system. 

956 dm_session.delete_snapmirror(src_share_instance, dest_share) 

957 self._delete_share(src_share_instance, src_vserver, 

958 src_vserver_client, remove_export=False) 

959 msg = _('Could not create share %(share_id)s from snapshot ' 

960 '%(snapshot_id)s in the destination host %(dest_host)s.') 

961 msg_args = {'share_id': dest_share['id'], 

962 'snapshot_id': snapshot['id'], 

963 'dest_host': dest_share['host']} 

964 raise exception.NetAppException(msg % msg_args) 

965 

966 # Store source share info on private storage using destination share id 

967 src_share_instance['aggregate'] = parent_aggr 

968 src_share_instance['internal_state'] = state 

969 src_share_instance['status'] = constants.STATUS_ACTIVE 

970 self.private_storage.update(dest_share['id'], { 

971 'source_share': json.dumps(src_share_instance) 

972 }) 

973 return { 

974 'status': constants.STATUS_CREATING_FROM_SNAPSHOT, 

975 } 

976 

977 def _update_create_from_snapshot_status(self, share, share_server=None): 

978 # TODO(dviroel) return progress info in asynchronous answers 

979 # If the share is creating from snapshot and copying data in background 

980 # we'd verify if the operation has finished and trigger new operations 

981 # if necessary. 

982 source_share_str = self.private_storage.get(share['id'], 

983 'source_share') 

984 if source_share_str is None: 

985 msg = _('Could not update share %(share_id)s status due to invalid' 

986 ' internal state. Aborting share creation.') 

987 msg_args = {'share_id': share['id']} 

988 LOG.error(msg, msg_args) 

989 return {'status': constants.STATUS_ERROR} 

990 try: 

991 # Check if current operation had finished and continue to move the 

992 # source share towards its destination 

993 return self._create_from_snapshot_continue(share, share_server) 

994 except Exception: 

995 # Delete everything associated to the temporary clone created on 

996 # the source host. 

997 source_share = json.loads(source_share_str) 

998 dm_session = data_motion.DataMotionSession() 

999 

1000 dm_session.delete_snapmirror(source_share, share) 

1001 __, src_vserver, src_backend = ( 

1002 dm_session.get_backend_info_for_share(source_share)) 

1003 src_vserver_client = data_motion.get_client_for_backend( 

1004 src_backend, vserver_name=src_vserver) 

1005 

1006 self._delete_share(source_share, src_vserver, src_vserver_client, 

1007 remove_export=False) 

1008 msg = _('Could not complete share %(share_id)s creation due to an ' 

1009 'internal error.') 

1010 msg_args = {'share_id': share['id']} 

1011 LOG.error(msg, msg_args) 

1012 return {'status': constants.STATUS_ERROR} 

1013 

1014 def _create_from_snapshot_continue(self, share, share_server=None): 

1015 return_values = { 

1016 'status': constants.STATUS_CREATING_FROM_SNAPSHOT 

1017 } 

1018 apply_qos_on_dest = False 

1019 # Data motion session used to extract host info and manage snapmirrors 

1020 dm_session = data_motion.DataMotionSession() 

1021 # Get info from private storage 

1022 src_share_str = self.private_storage.get(share['id'], 'source_share') 

1023 src_share = json.loads(src_share_str) 

1024 current_state = src_share['internal_state'] 

1025 share['share_server'] = share_server 

1026 

1027 # Source host info 

1028 __, src_vserver, src_backend = ( 

1029 dm_session.get_backend_info_for_share(src_share)) 

1030 src_aggr = src_share['aggregate'] 

1031 src_vserver_client = data_motion.get_client_for_backend( 

1032 src_backend, vserver_name=src_vserver) 

1033 # Destination host info 

1034 dest_vserver, dest_vserver_client = self._get_vserver(share_server) 

1035 dest_aggr = share_utils.extract_host(share['host'], level='pool') 

1036 if self._is_flexgroup_pool(dest_aggr): 

1037 if self._is_flexgroup_auto: 1037 ↛ 1038line 1037 didn't jump to line 1038 because the condition on line 1037 was never true

1038 dest_share_name = self._get_backend_share_name(share['id']) 

1039 dest_aggr = dest_vserver_client.get_aggregate_for_volume( 

1040 dest_share_name) 

1041 else: 

1042 dest_aggr = self._get_flexgroup_aggregate_list(dest_aggr) 

1043 

1044 if current_state == self.STATE_SPLITTING_VOLUME_CLONE: 

1045 if self._check_volume_clone_split_completed( 1045 ↛ 1134line 1045 didn't jump to line 1134 because the condition on line 1045 was always true

1046 src_share, src_vserver_client): 

1047 # Rehost volume if source and destination are hosted in 

1048 # different vservers 

1049 if src_vserver != dest_vserver: 1049 ↛ 1064line 1049 didn't jump to line 1064 because the condition on line 1049 was always true

1050 # NOTE(dviroel): some volume policies, policy rules and 

1051 # configurations are lost from the source volume after 

1052 # rehost operation. 

1053 qos_policy_for_share = ( 

1054 self._get_backend_qos_policy_group_name(share['id'])) 

1055 src_vserver_client.mark_qos_policy_group_for_deletion( 

1056 qos_policy_for_share) 

1057 # Apply QoS on destination share 

1058 apply_qos_on_dest = True 

1059 

1060 self._rehost_and_mount_volume( 

1061 share, src_vserver, src_vserver_client, 

1062 dest_vserver, dest_vserver_client) 

1063 # Move the share to the expected aggregate 

1064 if set(src_aggr) != set(dest_aggr): 

1065 # Move volume and 'defer' the cutover. If it fails, the 

1066 # share will be deleted afterwards 

1067 self._move_volume_after_splitting( 

1068 src_share, share, share_server, cutover_action='defer') 

1069 # Move a volume can take longer, we'll answer 

1070 # asynchronously 

1071 current_state = self.STATE_MOVING_VOLUME 

1072 else: 

1073 return_values['status'] = constants.STATUS_AVAILABLE 

1074 

1075 elif current_state == self.STATE_MOVING_VOLUME: 

1076 if self._check_volume_move_completed(share, share_server): 

1077 if src_vserver != dest_vserver: 1077 ↛ 1082line 1077 didn't jump to line 1082 because the condition on line 1077 was always true

1078 # NOTE(dviroel): at this point we already rehosted the 

1079 # share, but we missed applying the qos since it was moving 

1080 # the share between aggregates 

1081 apply_qos_on_dest = True 

1082 return_values['status'] = constants.STATUS_AVAILABLE 

1083 

1084 elif current_state == self.STATE_SNAPMIRROR_DATA_COPYING: 

1085 replica_state = self.update_replica_state( 

1086 None, # no context is needed 

1087 [src_share], 

1088 share, 

1089 [], # access_rules 

1090 [], # snapshot list 

1091 share_server, 

1092 replication=False) 

1093 if replica_state in [None, constants.STATUS_ERROR]: 1093 ↛ 1094line 1093 didn't jump to line 1094 because the condition on line 1093 was never true

1094 msg = _("Destination share has failed on replicating data " 

1095 "from source share.") 

1096 LOG.exception(msg) 

1097 raise exception.NetAppException(msg) 

1098 elif replica_state == constants.REPLICA_STATE_IN_SYNC: 

1099 try: 

1100 # 1. Start an update to try to get a last minute 

1101 # transfer before we quiesce and break 

1102 dm_session.update_snapmirror(src_share, share) 

1103 except exception.StorageCommunicationException: 

1104 # Ignore any errors since the current source replica 

1105 # may be unreachable 

1106 pass 

1107 # 2. Break SnapMirror 

1108 # NOTE(dviroel): if it fails on break/delete a snapmirror 

1109 # relationship, we won't be able to delete the share. 

1110 dm_session.break_snapmirror(src_share, share) 

1111 dm_session.delete_snapmirror(src_share, share) 

1112 # 3. Delete the source volume 

1113 self._delete_share(src_share, src_vserver, src_vserver_client, 

1114 remove_export=False) 

1115 # 4. Set File system size fixed to false 

1116 dest_share_name = self._get_backend_share_name(share['id']) 

1117 dest_vserver_client.set_volume_filesys_size_fixed( 

1118 dest_share_name, filesys_size_fixed=False) 

1119 apply_qos_on_dest = True 

1120 return_values['status'] = constants.STATUS_AVAILABLE 

1121 else: 

1122 # Delete this share from private storage since we'll abort this 

1123 # operation. 

1124 self.private_storage.delete(share['id']) 

1125 msg_args = { 

1126 'state': current_state, 

1127 'id': share['id'], 

1128 } 

1129 msg = _("Caught an unexpected internal state '%(state)s' for " 

1130 "share %(id)s. Aborting operation.") % msg_args 

1131 LOG.exception(msg) 

1132 raise exception.NetAppException(msg) 

1133 

1134 if return_values['status'] == constants.STATUS_AVAILABLE: 

1135 if apply_qos_on_dest: 1135 ↛ 1150line 1135 didn't jump to line 1150 because the condition on line 1135 was always true

1136 extra_specs = share_types.get_extra_specs_from_share(share) 

1137 provisioning_options = self._get_provisioning_options( 

1138 extra_specs) 

1139 qos_policy_group_name = ( 

1140 self._modify_or_create_qos_for_existing_share( 

1141 share, extra_specs, dest_vserver, dest_vserver_client)) 

1142 if qos_policy_group_name: 1142 ↛ 1145line 1142 didn't jump to line 1145 because the condition on line 1142 was always true

1143 provisioning_options['qos_policy_group'] = ( 

1144 qos_policy_group_name) 

1145 share_name = self._get_backend_share_name(share['id']) 

1146 # Modify volume to match extra specs 

1147 dest_vserver_client.modify_volume( 

1148 dest_aggr, share_name, **provisioning_options) 

1149 

1150 self.private_storage.delete(share['id']) 

1151 return_values['export_locations'] = self._create_export( 

1152 share, share_server, dest_vserver, dest_vserver_client, 

1153 clear_current_export_policy=False) 

1154 else: 

1155 new_src_share = copy.deepcopy(src_share) 

1156 new_src_share['internal_state'] = current_state 

1157 self.private_storage.update(share['id'], { 

1158 'source_share': json.dumps(new_src_share) 

1159 }) 

1160 return return_values 

1161 

1162 @na_utils.trace 

1163 def _allocate_container(self, share, vserver, vserver_client, 

1164 replica=False, create_fpolicy=True, 

1165 set_qos=True): 

1166 """Create new share on aggregate.""" 

1167 share_name = self._get_backend_share_name(share['id']) 

1168 

1169 # Get Data ONTAP aggregate name as pool name. 

1170 pool_name = share_utils.extract_host(share['host'], level='pool') 

1171 if pool_name is None: 

1172 msg = _("Pool is not available in the share host field.") 

1173 raise exception.InvalidHost(reason=msg) 

1174 

1175 provisioning_options = self._get_provisioning_options_for_share( 

1176 share, vserver, vserver_client=vserver_client, set_qos=set_qos) 

1177 

1178 if provisioning_options.get('snaplock_type'): 

1179 self._check_snaplock_compatibility() 

1180 

1181 if replica: 

1182 # If this volume is intended to be a replication destination, 

1183 # create it as the 'data-protection' type 

1184 provisioning_options['volume_type'] = 'dp' 

1185 

1186 hide_snapdir = provisioning_options.pop('hide_snapdir') 

1187 mount_point_name = share.get('mount_point_name') 

1188 

1189 if share.get('encryption_key_ref'): 

1190 provisioning_options['encrypt'] = True 

1191 LOG.debug('Creating an encrypted share %(share)s on pool %(pool)s ' 

1192 'with provisioning options %(options)s', 

1193 {'share': share_name, 'pool': pool_name, 

1194 'options': provisioning_options}) 

1195 else: 

1196 LOG.debug('Creating share %(share)s on pool %(pool)s with ' 

1197 'provisioning options %(options)s', 

1198 {'share': share_name, 'pool': pool_name, 

1199 'options': provisioning_options}) 

1200 

1201 if self._is_flexgroup_pool(pool_name): 

1202 aggr_list = self._get_flexgroup_aggregate_list(pool_name) 

1203 self._create_flexgroup_share( 

1204 vserver_client, aggr_list, share_name, 

1205 share['size'], 

1206 self.configuration.netapp_volume_snapshot_reserve_percent, 

1207 mount_point_name=mount_point_name, 

1208 **provisioning_options) 

1209 else: 

1210 vserver_client.create_volume( 

1211 pool_name, share_name, share['size'], 

1212 snapshot_reserve=self.configuration. 

1213 netapp_volume_snapshot_reserve_percent, 

1214 mount_point_name=mount_point_name, 

1215 **provisioning_options) 

1216 

1217 if hide_snapdir: 

1218 self._apply_snapdir_visibility( 

1219 hide_snapdir, share_name, vserver_client) 

1220 

1221 if create_fpolicy: 

1222 fpolicy_ext_to_include = provisioning_options.get( 

1223 'fpolicy_extensions_to_include') 

1224 fpolicy_ext_to_exclude = provisioning_options.get( 

1225 'fpolicy_extensions_to_exclude') 

1226 if fpolicy_ext_to_include or fpolicy_ext_to_exclude: 

1227 self._create_fpolicy_for_share(share, vserver, vserver_client, 

1228 **provisioning_options) 

1229 

1230 def _apply_snapdir_visibility( 

1231 self, hide_snapdir, share_name, vserver_client): 

1232 

1233 LOG.debug('Applying snapshot visibility according to hide_snapdir ' 

1234 'value of %(hide_snapdir)s on share %(share)s.', 

1235 {'hide_snapdir': hide_snapdir, 'share': share_name}) 

1236 

1237 vserver_client.set_volume_snapdir_access(share_name, hide_snapdir) 

1238 

1239 @na_utils.trace 

1240 def _create_flexgroup_share(self, vserver_client, aggr_list, share_name, 

1241 size, snapshot_reserve, dedup_enabled=False, 

1242 compression_enabled=False, max_files=None, 

1243 mount_point_name=None, snaplock_type=None, 

1244 **provisioning_options): 

1245 """Create a FlexGroup share using async API with job.""" 

1246 

1247 start_timeout = ( 

1248 self.configuration.netapp_flexgroup_aggregate_not_busy_timeout) 

1249 job_info = self.wait_for_start_create_flexgroup( 

1250 start_timeout, vserver_client, aggr_list, share_name, size, 

1251 snapshot_reserve, mount_point_name, snaplock_type, 

1252 **provisioning_options) 

1253 

1254 if not job_info['jobid'] or job_info['error-code']: 

1255 msg = "Error creating FlexGroup share: %s." 

1256 raise exception.NetAppException(msg % job_info['error-message']) 

1257 

1258 timeout = self.configuration.netapp_flexgroup_volume_online_timeout 

1259 self.wait_for_flexgroup_deployment(vserver_client, job_info['jobid'], 

1260 timeout) 

1261 efficiency_policy = provisioning_options.get('efficiency_policy', None) 

1262 

1263 vserver_client.update_volume_efficiency_attributes( 

1264 share_name, dedup_enabled, compression_enabled, is_flexgroup=True, 

1265 efficiency_policy=efficiency_policy) 

1266 

1267 if provisioning_options.get('max_files_multiplier') is not None: 1267 ↛ 1268line 1267 didn't jump to line 1268 because the condition on line 1267 was never true

1268 max_files_multiplier = provisioning_options.pop( 

1269 'max_files_multiplier') 

1270 max_files = na_utils.calculate_max_files(size, 

1271 max_files_multiplier, 

1272 max_files) 

1273 

1274 if max_files is not None: 

1275 vserver_client.set_volume_max_files(share_name, max_files) 

1276 

1277 if snaplock_type is not None: 1277 ↛ exitline 1277 didn't return from function '_create_flexgroup_share' because the condition on line 1277 was always true

1278 vserver_client.set_snaplock_attributes(share_name, 

1279 **provisioning_options) 

1280 

1281 @na_utils.trace 

1282 def wait_for_start_create_flexgroup(self, start_timeout, vserver_client, 

1283 aggr_list, share_name, size, 

1284 snapshot_reserve, 

1285 mount_point_name, 

1286 snaplock_type, 

1287 **provisioning_options): 

1288 """Wait for starting create FlexGroup volume succeed. 

1289 

1290 Create FlexGroup volume fails in case any of the selected aggregates 

1291 are being used for provision another volume. Instead of failing, tries 

1292 several times. 

1293 

1294 :param start_timeout: time in seconds to try create. 

1295 :param vserver_client: the client to call the create operation. 

1296 :param aggr_list: list of aggregates to create the FlexGroup. 

1297 :param share_name: name of the FlexGroup volume. 

1298 :param size: size to be provisioned. 

1299 :param snapshot_reserve: snapshot reserve option. 

1300 :param mount_point_name: junction_path_name. 

1301 :param snaplock_type: SnapLock type 

1302 :param provisioning_options: other provision not required options. 

1303 """ 

1304 

1305 interval = 5 

1306 retries = (start_timeout / interval or 1) 

1307 

1308 @manila_utils.retry(exception.NetAppBusyAggregateForFlexGroupException, 

1309 interval=interval, retries=retries, backoff_rate=1) 

1310 def _start_create_flexgroup_volume(): 

1311 try: 

1312 return vserver_client.create_volume_async( 

1313 aggr_list, share_name, size, 

1314 is_flexgroup=True, 

1315 snapshot_reserve=snapshot_reserve, 

1316 auto_provisioned=self._is_flexgroup_auto, 

1317 mount_point_name=mount_point_name, 

1318 snaplock_type=snaplock_type, 

1319 **provisioning_options) 

1320 except netapp_api.NaApiError as e: 

1321 with excutils.save_and_reraise_exception() as raise_ctxt: 

1322 try_msg = "try the command again" in e.message 

1323 if ((e.code == netapp_api.EAPIERROR or 1323 ↛ exitline 1323 didn't jump to the function exit

1324 e.code == rest_api.EREST_ANOTHER_VOLUME_OPERATION) 

1325 and try_msg): 

1326 msg = _("Another volume is currently being " 

1327 "provisioned using one or more of the " 

1328 "aggregates selected to provision FlexGroup " 

1329 "volume %(share_name)s.") 

1330 msg_args = {'share_name': share_name} 

1331 LOG.error(msg, msg_args) 

1332 raise_ctxt.reraise = False 

1333 raise (exception. 

1334 NetAppBusyAggregateForFlexGroupException()) 

1335 

1336 try: 

1337 return _start_create_flexgroup_volume() 

1338 except exception.NetAppBusyAggregateForFlexGroupException: 

1339 msg_err = _("Unable to start provision the FlexGroup volume %s. " 

1340 "Retries exhausted. Aborting") 

1341 raise exception.NetAppException(message=msg_err % share_name) 

1342 

1343 @na_utils.trace 

1344 def wait_for_flexgroup_deployment(self, vserver_client, job_id, timeout): 

1345 """Wait for creating FlexGroup share job to get finished. 

1346 

1347 :param vserver_client: the client to call the get job status. 

1348 :param job_id: create FlexGroup job ID. 

1349 :param timeout: time in seconds to wait create. 

1350 """ 

1351 

1352 interval = 5 

1353 retries = (timeout / interval or 1) 

1354 

1355 @manila_utils.retry(exception.ShareBackendException, interval=interval, 

1356 retries=retries, backoff_rate=1) 

1357 def _wait_job_is_completed(): 

1358 job_state = vserver_client.get_job_state(job_id) 

1359 LOG.debug("Waiting for creating FlexGroup job %(job_id)s in " 

1360 "state: %(job_state)s.", 

1361 {'job_id': job_id, 'job_state': job_state}) 

1362 if job_state == 'failure' or job_state == 'error': 

1363 msg_error = "Error performing the create FlexGroup job %s." 

1364 raise exception.NetAppException(msg_error % job_id) 

1365 elif job_state != 'success': 

1366 msg = _('FlexGroup share is being created. Will wait the ' 

1367 'job.') 

1368 LOG.warning(msg) 

1369 raise exception.ShareBackendException(msg=msg) 

1370 try: 

1371 _wait_job_is_completed() 

1372 except exception.ShareBackendException: 

1373 msg = _("Timeout waiting for FlexGroup create job %s to be " 

1374 "finished.") 

1375 raise exception.NetAppException(msg % job_id) 

1376 

1377 @na_utils.trace 

1378 def _remap_standard_boolean_extra_specs(self, extra_specs): 

1379 """Replace standard boolean extra specs with NetApp-specific ones.""" 

1380 specs = copy.deepcopy(extra_specs) 

1381 for (key, netapp_key) in self.STANDARD_BOOLEAN_EXTRA_SPECS_MAP.items(): 

1382 if key in specs: 

1383 bool_value = share_types.parse_boolean_extra_spec(key, 

1384 specs[key]) 

1385 specs[netapp_key] = 'true' if bool_value else 'false' 

1386 del specs[key] 

1387 return specs 

1388 

1389 @na_utils.trace 

1390 def _check_extra_specs_validity(self, share, extra_specs): 

1391 """Check if the extra_specs have valid values.""" 

1392 self._check_boolean_extra_specs_validity( 

1393 share, extra_specs, list(self.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP)) 

1394 self._check_string_extra_specs_validity(share, extra_specs) 

1395 

1396 @na_utils.trace 

1397 def _check_string_extra_specs_validity(self, share, extra_specs): 

1398 """Check if the string_extra_specs have valid values.""" 

1399 if 'netapp:max_files' in extra_specs: 

1400 self._check_if_max_files_is_valid(share, 

1401 extra_specs['netapp:max_files']) 

1402 

1403 if 'netapp:max_files_multiplier' in extra_specs: 1403 ↛ 1404line 1403 didn't jump to line 1404 because the condition on line 1403 was never true

1404 self._check_if_max_files_multiplier_is_valid( 

1405 share, extra_specs['netapp:max_files_multiplier']) 

1406 

1407 if 'netapp:fpolicy_file_operations' in extra_specs: 

1408 self._check_fpolicy_file_operations( 

1409 share, extra_specs['netapp:fpolicy_file_operations']) 

1410 

1411 # Validate extra_specs for SnapLock 

1412 snaplock_attributes = [ 

1413 'netapp:snaplock_autocommit_period', 

1414 'netapp:snaplock_min_retention_period', 

1415 'netapp:snaplock_max_retention_period', 

1416 'netapp:snaplock_default_retention_period' 

1417 ] 

1418 for attribute in snaplock_attributes: 

1419 if attribute in extra_specs: 

1420 self._check_snaplock_attributes(share, attribute, 

1421 extra_specs[attribute]) 

1422 

1423 @na_utils.trace 

1424 def _check_if_max_files_is_valid(self, share, value): 

1425 """Check if max_files has a valid value.""" 

1426 if int(value) < 0: 

1427 args = {'value': value, 'key': 'netapp:max_files', 

1428 'type_id': share['share_type_id'], 

1429 'share_id': share['id']} 

1430 msg = _('Invalid value "%(value)s" for extra_spec "%(key)s" ' 

1431 'in share_type %(type_id)s for share %(share_id)s.') 

1432 raise exception.NetAppException(msg % args) 

1433 

1434 @na_utils.trace 

1435 def _check_if_max_files_multiplier_is_valid(self, share, value): 

1436 """Check if max_files_multiplier has a valid value.""" 

1437 try: 

1438 if float(value) <= 0 or float(value) > 8: 

1439 args = {'value': value, 

1440 'key': 'netapp:max_files_multiplier', 

1441 'type_id': share['share_type_id'], 

1442 'share_id': share['id']} 

1443 msg = _('Invalid value "%(value)s" for extra_spec "%(key)s" ' 

1444 'in share_type %(type_id)s for share %(share_id)s. ' 

1445 'Must be between "0" and "8".') 

1446 raise exception.NetAppException(msg % args) 

1447 except ValueError as e: 

1448 raise exception.InvalidInput(e) 

1449 

1450 @na_utils.trace 

1451 def _check_fpolicy_file_operations(self, share, value): 

1452 """Check if the provided fpolicy file operations are valid.""" 

1453 for file_op in value.split(','): 

1454 if file_op.strip() not in self.FPOLICY_FILE_OPERATIONS_LIST: 

1455 args = {'file_op': file_op, 

1456 'extra_spec': 'netapp:fpolicy_file_operations', 

1457 'type_id': share['share_type_id'], 

1458 'share_id': share['id']} 

1459 msg = _('Invalid value "%(file_op)s" for extra_spec ' 

1460 '"%(extra_spec)s" in share_type %(type_id)s for share ' 

1461 '%(share_id)s.') 

1462 raise exception.NetAppException(msg % args) 

1463 

1464 @na_utils.trace 

1465 def _check_snaplock_attributes(self, share, key, value): 

1466 """Validate the SnapLock retention periods""" 

1467 valid_units_for_period = ["minutes", "hours", "days", 

1468 "months", "years"] 

1469 pattern = re.compile(r'^\d+\s*(minutes|hours|days|months|years)$') 

1470 common_msg = ("a number followed suffix, valid suffix are: " 

1471 f"{valid_units_for_period}. For example, a value" 

1472 f" if '2hours' represents a {key}" 

1473 " of 2 hours.") 

1474 

1475 if key == 'netapp:snaplock_autocommit_period': 

1476 is_matched = pattern.match(value) 

1477 extra_msg = (f"The value of the {key} should be" 

1478 f" {common_msg} ") 

1479 if not is_matched: 

1480 self._raise_snaplock_exception(share, key, value, extra_msg) 

1481 elif (key == 'netapp:snaplock_min_retention_period' 

1482 or key == 'netapp:snaplock_max_retention_period'): 

1483 is_matched = pattern.match(value) or value == "infinite" 

1484 extra_msg = (f"The value of the {key} should be " 

1485 f"'infinite' or {common_msg}") 

1486 if not is_matched: 

1487 self._raise_snaplock_exception(share, key, value, extra_msg) 

1488 elif key == 'netapp:snaplock_default_retention_period': 1488 ↛ exitline 1488 didn't return from function '_check_snaplock_attributes' because the condition on line 1488 was always true

1489 is_matched = (pattern.match(value) or value == "infinite" 

1490 or value == "min" or value == "max") 

1491 extra_msg = (f"The value of the {key} should be " 

1492 f"'infinite', 'min', 'max', or {common_msg}") 

1493 if not is_matched: 

1494 self._raise_snaplock_exception(share, key, value, extra_msg) 

1495 

1496 def _raise_snaplock_exception(self, share, key, value, extra_msg): 

1497 args = {'value': value, 

1498 'extra_spec': key, 

1499 'type_id': share['share_type_id'], 

1500 'share_id': share['id'], 

1501 'extra_msg': extra_msg} 

1502 msg = _('Invalid value "%(value)s" for extra_spec ' 

1503 '"%(extra_spec)s" in share_type %(type_id)s for share ' 

1504 '%(share_id)s. %(extra_msg)s') 

1505 raise exception.NetAppException(msg % args) 

1506 

1507 @na_utils.trace 

1508 def _check_snaplock_compatibility(self): 

1509 """Check SnapLock license and compliance clock sync with the nodes""" 

1510 # Check SnapLock license is enabled on cluster 

1511 if self._have_cluster_creds: 

1512 if 'snaplock' not in self._licenses: 1512 ↛ 1515line 1512 didn't jump to line 1515 because the condition on line 1512 was always true

1513 exception.NetAppException("SnapLock License is not" 

1514 " available on ONTAP") 

1515 if not self._is_snaplock_compliance_configured: 

1516 msg = _('Compliance clock is not configured for one' 

1517 ' of the nodes.') 

1518 raise exception.NetAppException(msg) 

1519 else: 

1520 LOG.warning("Unable to verify if SnapLock is enabled for" 

1521 " the cluster.") 

1522 

1523 @na_utils.trace 

1524 def _check_boolean_extra_specs_validity(self, share, specs, 

1525 keys_of_interest): 

1526 # cDOT compression requires deduplication. 

1527 dedup = specs.get('netapp:dedup', None) 

1528 compression = specs.get('netapp:compression', None) 

1529 if dedup is not None and compression is not None: 

1530 if dedup.lower() == 'false' and compression.lower() == 'true': 

1531 spec = {'netapp:dedup': dedup, 

1532 'netapp:compression': compression} 

1533 type_id = share['share_type_id'] 

1534 share_id = share['id'] 

1535 args = {'type_id': type_id, 'share_id': share_id, 

1536 'spec': spec} 

1537 msg = _('Invalid combination of extra_specs in share_type ' 

1538 '%(type_id)s for share %(share_id)s: %(spec)s: ' 

1539 'deduplication must be enabled in order for ' 

1540 'compression to be enabled.') 

1541 raise exception.Invalid(msg % args) 

1542 """Check if the boolean_extra_specs have valid values.""" 

1543 # Extra spec values must be (ignoring case) 'true' or 'false'. 

1544 for key in keys_of_interest: 

1545 value = specs.get(key) 

1546 if value is not None and value.lower() not in ['true', 'false']: 

1547 type_id = share['share_type_id'] 

1548 share_id = share['id'] 

1549 arg_map = {'value': value, 'key': key, 'type_id': type_id, 

1550 'share_id': share_id} 

1551 msg = _('Invalid value "%(value)s" for extra_spec "%(key)s" ' 

1552 'in share_type %(type_id)s for share %(share_id)s.') 

1553 raise exception.Invalid(msg % arg_map) 

1554 

1555 @na_utils.trace 

1556 def _get_boolean_provisioning_options(self, extra_specs, 

1557 boolean_specs_map): 

1558 """Given extra specs, return corresponding client library kwargs. 

1559 

1560 Build a full set of client library provisioning kwargs, filling in a 

1561 default value if an explicit value has not been supplied via a 

1562 corresponding extra spec. Boolean extra spec values are "true" or 

1563 "false", with missing specs treated as "false". Provisioning kwarg 

1564 values are True or False. 

1565 """ 

1566 specs = copy.deepcopy(extra_specs) 

1567 # Extract the extra spec keys of concern and their corresponding 

1568 # kwarg keys as lists. 

1569 keys_of_interest = list(boolean_specs_map) 

1570 provisioning_args = [boolean_specs_map[key] 

1571 for key in keys_of_interest] 

1572 # Set missing spec values to 'false' 

1573 for key in keys_of_interest: 

1574 if key not in specs: 

1575 specs[key] = 'false' 

1576 # Build a list of Boolean provisioning arguments from the string 

1577 # equivalents in the spec values. 

1578 provisioning_values = [specs[key].lower() == 'true' for key in 

1579 keys_of_interest] 

1580 # Combine the list of provisioning args and the list of provisioning 

1581 # values into a dictionary suitable for use as kwargs when invoking 

1582 # provisioning methods from the client API library. 

1583 return dict(zip(provisioning_args, provisioning_values)) 

1584 

1585 @na_utils.trace 

1586 def get_string_provisioning_options(self, extra_specs, string_specs_map): 

1587 """Given extra specs, return corresponding client library kwargs. 

1588 

1589 Build a full set of client library provisioning kwargs, filling in a 

1590 default value if an explicit value has not been supplied via a 

1591 corresponding extra spec. 

1592 """ 

1593 specs = copy.deepcopy(extra_specs) 

1594 # Extract the extra spec keys of concern and their corresponding 

1595 # kwarg keys as lists. 

1596 keys_of_interest = list(string_specs_map) 

1597 provisioning_args = [string_specs_map[key] 

1598 for key in keys_of_interest] 

1599 # Set missing spec values to 'false' 

1600 for key in keys_of_interest: 

1601 if key not in specs: 

1602 specs[key] = None 

1603 provisioning_values = [specs[key] for key in keys_of_interest] 

1604 

1605 # Combine the list of provisioning args and the list of provisioning 

1606 # values into a dictionary suitable for use as kwargs when invoking 

1607 # provisioning methods from the client API library. 

1608 return dict(zip(provisioning_args, provisioning_values)) 

1609 

1610 def _get_normalized_qos_specs(self, extra_specs): 

1611 if not extra_specs.get('qos'): 

1612 return {} 

1613 

1614 normalized_max_qos_specs = { 

1615 self.MAX_QOS_SPECS[key.lower()]: value 

1616 for key, value in extra_specs.items() 

1617 if self.MAX_QOS_SPECS.get(key.lower()) 

1618 } 

1619 

1620 normalized_min_qos_specs = { 

1621 self.MIN_QOS_SPECS[key.lower()]: value 

1622 for key, value in extra_specs.items() 

1623 if self.MIN_QOS_SPECS.get(key.lower()) 

1624 } 

1625 

1626 if not (normalized_max_qos_specs or normalized_min_qos_specs): 

1627 msg = _("The extra-spec 'qos' is set to True, but no netapp " 

1628 "supported qos-specs have been specified in the share " 

1629 "type. Cannot provision a QoS policy. Specify any of the " 

1630 "following max-throughput extra-specs and/or any of the " 

1631 "following min-throughput extra-specs and try again: %s") 

1632 specs = self.MAX_QOS_SPECS.copy() 

1633 specs.update(self.MIN_QOS_SPECS) 

1634 raise exception.NetAppException(msg % list(specs)) 

1635 

1636 # TODO(gouthamr): Modify check when throughput floors are allowed 

1637 if len(normalized_max_qos_specs) > 1: 

1638 msg = _('Only one NetApp max-throughput QoS spec can be set at a ' 

1639 'time. Specified QoS limits: %s') 

1640 raise exception.NetAppException(msg % normalized_max_qos_specs) 

1641 

1642 if len(normalized_min_qos_specs) > 1: 

1643 msg = _('Only one NetApp min-throughput QoS spec can be set at a ' 

1644 'time. Specified QoS limits: %s') 

1645 raise exception.NetAppException(msg % normalized_min_qos_specs) 

1646 

1647 normalized_max_qos_specs.update(normalized_min_qos_specs) 

1648 return normalized_max_qos_specs 

1649 

1650 def _get_max_throughput(self, share_size, qos_specs): 

1651 # QoS limits are exclusive of one another. 

1652 if 'maxiops' in qos_specs: 

1653 return '%siops' % qos_specs['maxiops'] 

1654 elif 'maxiopspergib' in qos_specs: 

1655 return '%siops' % str( 

1656 int(qos_specs['maxiopspergib']) * int(share_size)) 

1657 elif 'maxbps' in qos_specs: 

1658 return '%sB/s' % qos_specs['maxbps'] 

1659 elif 'maxbpspergib' in qos_specs: 1659 ↛ exitline 1659 didn't return from function '_get_max_throughput' because the condition on line 1659 was always true

1660 return '%sB/s' % str( 

1661 int(qos_specs['maxbpspergib']) * int(share_size)) 

1662 

1663 def _get_min_throughput(self, share_size, qos_specs): 

1664 # QoS limits are exclusive of one another. 

1665 if 'miniops' in qos_specs: 

1666 return '%siops' % qos_specs['miniops'] 

1667 elif 'miniopspergib' in qos_specs: 

1668 return '%siops' % str( 

1669 int(qos_specs['miniopspergib']) * int(share_size)) 

1670 elif 'minbps' in qos_specs: 

1671 return '%sB/s' % qos_specs['minbps'] 

1672 elif 'minbpspergib' in qos_specs: 

1673 return '%sB/s' % str( 

1674 int(qos_specs['minbpspergib']) * int(share_size)) 

1675 

1676 @na_utils.trace 

1677 def _create_qos_policy_group(self, share, vserver, qos_specs, 

1678 vserver_client=None): 

1679 max_throughput = self._get_max_throughput(share['size'], qos_specs) 

1680 min_throughput = self._get_min_throughput(share['size'], qos_specs) 

1681 qos_policy_group_name = self._get_backend_qos_policy_group_name( 

1682 share['id']) 

1683 client = vserver_client or self._client 

1684 client.qos_policy_group_create(qos_policy_group_name, vserver, 

1685 max_throughput=max_throughput, 

1686 min_throughput=min_throughput) 

1687 return qos_policy_group_name 

1688 

1689 @na_utils.trace 

1690 def _get_provisioning_options_for_share( 

1691 self, share, vserver, vserver_client=None, set_qos=True): 

1692 """Return provisioning options from a share. 

1693 

1694 Starting with a share, this method gets the extra specs, rationalizes 

1695 NetApp vs. standard extra spec values, ensures their validity, and 

1696 returns them in a form suitable for passing to various API client 

1697 methods. 

1698 """ 

1699 extra_specs = share_types.get_extra_specs_from_share(share) 

1700 extra_specs = self._remap_standard_boolean_extra_specs(extra_specs) 

1701 self._check_extra_specs_validity(share, extra_specs) 

1702 provisioning_options = self._get_provisioning_options(extra_specs) 

1703 qos_specs = self._get_normalized_qos_specs(extra_specs) 

1704 self.validate_provisioning_options_for_share(provisioning_options, 

1705 extra_specs=extra_specs, 

1706 qos_specs=qos_specs) 

1707 if qos_specs and set_qos: 

1708 qos_policy_group = self._create_qos_policy_group( 

1709 share, vserver, qos_specs, vserver_client) 

1710 provisioning_options['qos_policy_group'] = qos_policy_group 

1711 return provisioning_options 

1712 

1713 @na_utils.trace 

1714 def _get_provisioning_options(self, specs): 

1715 """Return a merged result of string and binary provisioning options.""" 

1716 boolean_args = self._get_boolean_provisioning_options( 

1717 specs, self.BOOLEAN_QUALIFIED_EXTRA_SPECS_MAP) 

1718 

1719 string_args = self.get_string_provisioning_options( 

1720 specs, self.STRING_QUALIFIED_EXTRA_SPECS_MAP) 

1721 result = boolean_args.copy() 

1722 result.update(string_args) 

1723 

1724 result['encrypt'] = self._get_nve_option(specs) 

1725 

1726 return result 

1727 

1728 @na_utils.trace 

1729 def validate_provisioning_options_for_share(self, provisioning_options, 

1730 extra_specs=None, 

1731 qos_specs=None): 

1732 """Checks if provided provisioning options are valid.""" 

1733 adaptive_qos = provisioning_options.get('adaptive_qos_policy_group') 

1734 max_files = provisioning_options.get('max_files') 

1735 max_files_multiplier = provisioning_options.get('max_files_multiplier') 

1736 replication_type = (extra_specs.get('replication_type') 

1737 if extra_specs else None) 

1738 if adaptive_qos and qos_specs: 

1739 msg = _('Share cannot be provisioned with both qos_specs ' 

1740 '%(qos_specs_string)s and adaptive_qos_policy_group ' 

1741 '%(adaptive_qos_policy_group)s.') 

1742 qos_specs_string = "" 

1743 for key in qos_specs: 

1744 qos_specs_string += key + "=" + str(qos_specs[key]) + " " 

1745 msg_args = { 

1746 'adaptive_qos_policy_group': 

1747 provisioning_options['adaptive_qos_policy_group'], 

1748 'qos_specs_string': qos_specs_string 

1749 } 

1750 raise exception.NetAppException(msg % msg_args) 

1751 

1752 if adaptive_qos and replication_type: 

1753 msg = _("The extra spec 'adaptive_qos_policy_group' is not " 

1754 "supported by share replication feature.") 

1755 raise exception.NetAppException(msg) 

1756 

1757 if max_files and max_files_multiplier: 1757 ↛ 1758line 1757 didn't jump to line 1758 because the condition on line 1757 was never true

1758 msg = _("Share cannot be provisioned with both 'max_files' and " 

1759 "'max_files_multiplier' extra specs.") 

1760 raise exception.NetAppException(msg) 

1761 

1762 # NOTE(dviroel): This validation will need to be updated if newer 

1763 # versions of ONTAP stop requiring cluster credentials to associate 

1764 # QoS to volumes. 

1765 if (adaptive_qos or qos_specs) and not self._have_cluster_creds: 

1766 msg = _('Share cannot be provisioned with QoS without having ' 

1767 'cluster credentials.') 

1768 raise exception.NetAppException(msg) 

1769 

1770 fpolicy_ext_to_include = provisioning_options.get( 

1771 'fpolicy_extensions_to_include') 

1772 fpolicy_ext_to_exclude = provisioning_options.get( 

1773 'fpolicy_extensions_to_exclude') 

1774 if provisioning_options.get('fpolicy_file_operations') and not ( 

1775 fpolicy_ext_to_include or fpolicy_ext_to_exclude): 

1776 msg = _('The extra spec "fpolicy_file_operations" can only ' 

1777 'be configured together with ' 

1778 '"fpolicy_extensions_to_include" or ' 

1779 '"fpolicy_extensions_to_exclude".') 

1780 raise exception.NetAppException(msg) 

1781 

1782 if replication_type and ( 

1783 fpolicy_ext_to_include or fpolicy_ext_to_exclude): 

1784 msg = _("The extra specs 'fpolicy_extensions_to_include' and " 

1785 "'fpolicy_extensions_to_exclude' are not " 

1786 "supported by share replication feature.") 

1787 raise exception.NetAppException(msg) 

1788 

1789 def _get_nve_option(self, specs): 

1790 if 'netapp_flexvol_encryption' in specs: 

1791 nve = specs['netapp_flexvol_encryption'].lower() == 'true' 

1792 else: 

1793 nve = False 

1794 

1795 return nve 

1796 

1797 @na_utils.trace 

1798 def _check_aggregate_extra_specs_validity(self, pool_name, specs): 

1799 

1800 for specs_key in ('netapp_disk_type', 'netapp_raid_type'): 

1801 aggr_value = self._ssc_stats.get(pool_name, {}).get(specs_key) 

1802 specs_value = specs.get(specs_key) 

1803 

1804 if aggr_value and specs_value and aggr_value != specs_value: 

1805 msg = _('Invalid value "%(value)s" for extra_spec "%(key)s" ' 

1806 'in pool %(pool)s.') 

1807 msg_args = { 

1808 'value': specs_value, 

1809 'key': specs_key, 

1810 'pool': pool_name 

1811 } 

1812 raise exception.NetAppException(msg % msg_args) 

1813 

1814 @na_utils.trace 

1815 def _allocate_container_from_snapshot( 

1816 self, share, snapshot, vserver, vserver_client, 

1817 snapshot_name_func=_get_backend_snapshot_name, split=None, 

1818 create_fpolicy=True): 

1819 """Clones existing share.""" 

1820 share_name = self._get_backend_share_name(share['id']) 

1821 parent_share_name = self._get_backend_share_name(snapshot['share_id']) 

1822 if snapshot.get('provider_location') is None: 

1823 parent_snapshot_name = snapshot_name_func(self, snapshot['id']) 

1824 else: 

1825 parent_snapshot_name = snapshot['provider_location'] 

1826 

1827 provisioning_options = self._get_provisioning_options_for_share( 

1828 share, vserver, vserver_client=vserver_client) 

1829 

1830 hide_snapdir = provisioning_options.pop('hide_snapdir') 

1831 mount_point_name = share.get('mount_point_name') 

1832 

1833 # split in args takes precedence over split in provisioning_options 

1834 if split is None: 

1835 split = provisioning_options.pop('split') 

1836 

1837 LOG.debug('Creating share from snapshot %s', snapshot['id']) 

1838 vserver_client.create_volume_clone( 

1839 share_name, parent_share_name, parent_snapshot_name, 

1840 mount_point_name=mount_point_name, 

1841 **provisioning_options) 

1842 

1843 if share['size'] > snapshot['size']: 

1844 vserver_client.set_volume_size(share_name, share['size']) 

1845 if provisioning_options.get('max_files_multiplier') is not None: 1845 ↛ 1846line 1845 didn't jump to line 1846 because the condition on line 1845 was never true

1846 max_files_multiplier = provisioning_options.pop( 

1847 'max_files_multiplier') 

1848 max_files = na_utils.calculate_max_files(share['size'], 

1849 max_files_multiplier) 

1850 vserver_client.set_volume_max_files(share_name, max_files) 

1851 

1852 if hide_snapdir: 

1853 self._apply_snapdir_visibility( 

1854 hide_snapdir, share_name, vserver_client) 

1855 

1856 if create_fpolicy: 

1857 fpolicy_ext_to_include = provisioning_options.get( 

1858 'fpolicy_extensions_to_include') 

1859 fpolicy_ext_to_exclude = provisioning_options.get( 

1860 'fpolicy_extensions_to_exclude') 

1861 if fpolicy_ext_to_include or fpolicy_ext_to_exclude: 1861 ↛ 1866line 1861 didn't jump to line 1866 because the condition on line 1861 was always true

1862 self._create_fpolicy_for_share(share, vserver, vserver_client, 

1863 **provisioning_options) 

1864 

1865 # split at the end: not be blocked by a busy volume 

1866 if split: 

1867 # can only delete the additional snap in child, if we plan to split 

1868 vserver_client.rename_snapshot_and_split_clones( 

1869 share_name, 

1870 parent_snapshot_name 

1871 ) 

1872 vserver_client.volume_clone_split_start(share_name) 

1873 

1874 @na_utils.trace 

1875 def _share_exists(self, share_name, vserver_client): 

1876 return vserver_client.volume_exists(share_name) 

1877 

1878 @na_utils.trace 

1879 def _delete_share(self, share, vserver, vserver_client, 

1880 remove_export=True, remove_qos=True): 

1881 share_name = self._get_backend_share_name(share['id']) 

1882 # Share doesn't need to exist to be assigned to a fpolicy scope 

1883 self._delete_fpolicy_for_share(share, vserver, vserver_client) 

1884 

1885 if self._share_exists(share_name, vserver_client): 

1886 clone_status = vserver_client.volume_clone_split_status(share_name) 

1887 if clone_status == na_utils.CLONE_SPLIT_STATUS_ONGOING: 1887 ↛ 1888line 1887 didn't jump to line 1888 because the condition on line 1887 was never true

1888 vserver_client.volume_clone_split_stop(share_name) 

1889 if remove_export: 

1890 self._remove_export(share, vserver_client) 

1891 self._deallocate_container(share_name, vserver_client) 

1892 if remove_qos: 

1893 qos_policy_for_share = self._get_backend_qos_policy_group_name( 

1894 share['id']) 

1895 vserver_client.mark_qos_policy_group_for_deletion( 

1896 qos_policy_for_share) 

1897 else: 

1898 LOG.info("Share %s does not exist.", share['id']) 

1899 try: 

1900 self.private_storage.delete(share['id']) 

1901 except Exception as e: 

1902 LOG.warning( 

1903 "Failed to delete private storage for share %s: %s", 

1904 share['id'], e) 

1905 

1906 @na_utils.trace 

1907 def delete_share(self, context, share, share_server=None): 

1908 """Deletes share.""" 

1909 try: 

1910 vserver, vserver_client = self._get_vserver( 

1911 share_server=share_server) 

1912 except (exception.InvalidInput, 

1913 exception.VserverNotSpecified, 

1914 exception.VserverNotFound) as error: 

1915 LOG.warning("Could not determine share server for share being " 

1916 "deleted: %(share)s. Deletion of share record " 

1917 "will proceed anyway. Error: %(error)s", 

1918 {'share': share['id'], 'error': error}) 

1919 return 

1920 self._delete_share(share, vserver, vserver_client) 

1921 

1922 @na_utils.trace 

1923 def _deallocate_container(self, share_name, vserver_client): 

1924 """Free share space.""" 

1925 vserver_client.unmount_volume(share_name, force=True) 

1926 vserver_client.offline_volume(share_name) 

1927 vserver_client.delete_volume(share_name) 

1928 

1929 @na_utils.trace 

1930 def _create_export(self, share, share_server, vserver, vserver_client, 

1931 cluster_client=None, 

1932 clear_current_export_policy=True, 

1933 ensure_share_already_exists=False, replica=False, 

1934 share_host=None): 

1935 """Creates NAS storage.""" 

1936 helper = self._get_helper(share) 

1937 helper.set_client(vserver_client) 

1938 share_name = self._get_backend_share_name(share['id']) 

1939 

1940 interfaces = vserver_client.get_network_interfaces( 

1941 protocols=[share['share_proto']]) 

1942 

1943 if not interfaces: 

1944 msg = _('Cannot find network interfaces for Vserver %(vserver)s ' 

1945 'and protocol %(proto)s.') 

1946 msg_args = {'vserver': vserver, 'proto': share['share_proto']} 

1947 raise exception.NetAppException(msg % msg_args) 

1948 

1949 host = share_host if share_host else share['host'] 

1950 

1951 # Get LIF addresses with metadata 

1952 export_addresses = self._get_export_addresses_with_metadata( 

1953 share, share_server, interfaces, host, cluster_client) 

1954 

1955 # Create the share and get a callback for generating export locations 

1956 pool = share_utils.extract_host(share['host'], level='pool') 

1957 callback = helper.create_share( 

1958 share, share_name, 

1959 clear_current_export_policy=clear_current_export_policy, 

1960 ensure_share_already_exists=ensure_share_already_exists, 

1961 replica=replica, 

1962 is_flexgroup=self._is_flexgroup_pool(pool)) 

1963 

1964 # Generate export locations using addresses, metadata and callback 

1965 export_locations = [ 

1966 { 

1967 'path': callback(export_address), 

1968 'is_admin_only': metadata.pop('is_admin_only', False), 

1969 'metadata': metadata, 

1970 } 

1971 for export_address, metadata 

1972 in copy.deepcopy(export_addresses).items() 

1973 ] 

1974 

1975 # Sort the export locations to report preferred paths first 

1976 export_locations = self._sort_export_locations_by_preferred_paths( 

1977 export_locations) 

1978 

1979 return export_locations 

1980 

1981 @na_utils.trace 

1982 def _get_export_addresses_with_metadata(self, share, share_server, 

1983 interfaces, share_host, 

1984 cluster_client=None): 

1985 """Return interface addresses with locality and other metadata.""" 

1986 

1987 # Get home nodes so we can identify preferred paths 

1988 pool = share_utils.extract_host(share_host, level='pool') 

1989 home_node_set = set() 

1990 if self._is_flexgroup_pool(pool): 

1991 for aggregate_name in self._get_flexgroup_aggregate_list(pool): 

1992 home_node = self._get_aggregate_node( 

1993 aggregate_name, cluster_client) 

1994 if home_node: 1994 ↛ 1991line 1994 didn't jump to line 1991 because the condition on line 1994 was always true

1995 home_node_set.add(home_node) 

1996 else: 

1997 home_node = self._get_aggregate_node(pool, cluster_client) 

1998 if home_node: 

1999 home_node_set.add(home_node) 

2000 

2001 # Get admin LIF addresses so we can identify admin export locations 

2002 admin_addresses = self._get_admin_addresses_for_share_server( 

2003 share_server) 

2004 

2005 addresses = {} 

2006 for interface in interfaces: 

2007 

2008 address = interface['address'] 

2009 is_admin_only = address in admin_addresses 

2010 

2011 preferred = interface.get('home-node') in home_node_set 

2012 

2013 addresses[address] = { 

2014 'is_admin_only': is_admin_only, 

2015 'preferred': preferred, 

2016 } 

2017 

2018 return addresses 

2019 

2020 @na_utils.trace 

2021 def _get_admin_addresses_for_share_server(self, share_server): 

2022 

2023 if not share_server: 

2024 return [] 

2025 

2026 admin_addresses = [] 

2027 for network_allocation in share_server.get('network_allocations'): 

2028 if network_allocation['label'] == 'admin': 

2029 admin_addresses.append(network_allocation['ip_address']) 

2030 

2031 return admin_addresses 

2032 

2033 @na_utils.trace 

2034 def _sort_export_locations_by_preferred_paths(self, export_locations): 

2035 """Sort the export locations to report preferred paths first.""" 

2036 

2037 sort_key = lambda location: location.get( # noqa: E731 

2038 'metadata', {}).get('preferred') is not True 

2039 

2040 return sorted(export_locations, key=sort_key) 

2041 

2042 @na_utils.trace 

2043 def _remove_export(self, share, vserver_client): 

2044 """Deletes NAS storage.""" 

2045 helper = self._get_helper(share) 

2046 helper.set_client(vserver_client) 

2047 share_name = self._get_backend_share_name(share['id']) 

2048 target = helper.get_target(share) 

2049 # Share may be in error state, so there's no share and target. 

2050 if target: 

2051 helper.delete_share(share, share_name) 

2052 

2053 @na_utils.trace 

2054 def create_snapshot(self, context, snapshot, share_server=None): 

2055 """Creates a snapshot of a share.""" 

2056 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2057 share_name = self._get_backend_share_name(snapshot['share_id']) 

2058 snapshot_name = self._get_backend_snapshot_name(snapshot['id']) 

2059 LOG.debug('Creating snapshot %s', snapshot_name) 

2060 vserver_client.create_snapshot(share_name, snapshot_name) 

2061 if not vserver_client.snapshot_exists(snapshot_name, share_name): 2061 ↛ 2062line 2061 didn't jump to line 2062 because the condition on line 2061 was never true

2062 raise exception.SnapshotResourceNotFound( 

2063 name=snapshot_name) 

2064 

2065 return {'provider_location': snapshot_name} 

2066 

2067 def revert_to_snapshot(self, context, snapshot, share_server=None): 

2068 """Reverts a share (in place) to the specified snapshot.""" 

2069 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2070 share_name = self._get_backend_share_name(snapshot['share_id']) 

2071 snapshot_name = (snapshot.get('provider_location') or 

2072 self._get_backend_snapshot_name(snapshot['id'])) 

2073 LOG.debug('Restoring snapshot %s', snapshot_name) 

2074 vserver_client.restore_snapshot(share_name, snapshot_name) 

2075 volume = vserver_client.get_volume(share_name) 

2076 

2077 # When calculating the size, round up to the next GB. 

2078 return int(math.ceil(float(volume['size']) / units.Gi)) 

2079 

2080 @na_utils.trace 

2081 def delete_snapshot(self, context, snapshot, share_server=None, 

2082 snapshot_name=None): 

2083 """Deletes a snapshot of a share.""" 

2084 try: 

2085 vserver, vserver_client = self._get_vserver( 

2086 share_server=share_server) 

2087 except (exception.InvalidInput, 

2088 exception.VserverNotSpecified, 

2089 exception.VserverNotFound) as error: 

2090 LOG.warning("Could not determine share server for snapshot " 

2091 "being deleted: %(snap)s. Deletion of snapshot " 

2092 "record will proceed anyway. Error: %(error)s", 

2093 {'snap': snapshot['id'], 'error': error}) 

2094 return 

2095 

2096 share_name = self._get_backend_share_name(snapshot['share_id']) 

2097 snapshot_name = (snapshot.get('provider_location') or snapshot_name or 

2098 self._get_backend_snapshot_name(snapshot['id'])) 

2099 

2100 try: 

2101 is_flexgroup = self._is_flexgroup_share(vserver_client, share_name) 

2102 except exception.ShareNotFound: 

2103 msg = _('Could not determine if the share %(share)s is FlexGroup ' 

2104 'or FlexVol style. Share does not exist.') 

2105 msg_args = {'share': share_name} 

2106 LOG.info(msg, msg_args) 

2107 is_flexgroup = False 

2108 

2109 try: 

2110 self._delete_snapshot(vserver_client, share_name, snapshot_name, 

2111 is_flexgroup=is_flexgroup) 

2112 except exception.SnapshotResourceNotFound: 

2113 msg = "Snapshot %(snap)s does not exist on share %(share)s." 

2114 msg_args = {'snap': snapshot_name, 'share': share_name} 

2115 LOG.info(msg, msg_args) 

2116 

2117 def _delete_snapshot(self, vserver_client, share_name, snapshot_name, 

2118 is_flexgroup=False): 

2119 """Deletes a backend snapshot, handling busy snapshots as needed.""" 

2120 

2121 backend_snapshot = vserver_client.get_snapshot(share_name, 

2122 snapshot_name) 

2123 

2124 LOG.debug('Deleting snapshot %(snap)s for share %(share)s.', 

2125 {'snap': snapshot_name, 'share': share_name}) 

2126 

2127 if not backend_snapshot['busy']: 

2128 vserver_client.delete_snapshot(share_name, snapshot_name) 

2129 

2130 elif backend_snapshot['locked_by_clone']: 

2131 if is_flexgroup: 

2132 # Snapshots are locked by clone(s), so split the clone(s) 

2133 snap_children = vserver_client.get_clone_children_for_snapshot( 

2134 share_name, snapshot_name) 

2135 for snapshot_child in snap_children: 

2136 vserver_client.volume_clone_split_start( 

2137 snapshot_child['name']) 

2138 

2139 # NOTE(felipe_rodrigues): ONTAP does not allow rename a 

2140 # FlexGroup snapshot, so it cannot be soft deleted. It will 

2141 # wait for all split clones complete. 

2142 self._delete_busy_snapshot(vserver_client, share_name, 

2143 snapshot_name) 

2144 else: 

2145 vserver_client.soft_delete_snapshot(share_name, snapshot_name) 

2146 

2147 else: 

2148 raise exception.ShareSnapshotIsBusy(snapshot_name=snapshot_name) 

2149 

2150 @na_utils.trace 

2151 def _delete_busy_snapshot(self, vserver_client, share_name, snapshot_name): 

2152 """Delete the snapshot waiting for it to not be busy.""" 

2153 

2154 timeout = (self.configuration. 

2155 netapp_delete_busy_flexgroup_snapshot_timeout) 

2156 interval = 5 

2157 retries = (int(timeout / interval) or 1) 

2158 

2159 @manila_utils.retry(exception.ShareSnapshotIsBusy, interval=interval, 

2160 retries=retries, backoff_rate=1) 

2161 def _wait_snapshot_is_not_busy(): 

2162 backend_snapshot = vserver_client.get_snapshot(share_name, 

2163 snapshot_name) 

2164 if backend_snapshot['busy']: 

2165 msg = _("Cannot delete snapshot %s that is busy. Will wait it " 

2166 "for not be busy.") 

2167 LOG.debug(msg, snapshot_name) 

2168 raise exception.ShareSnapshotIsBusy( 

2169 snapshot_name=snapshot_name) 

2170 

2171 try: 

2172 _wait_snapshot_is_not_busy() 

2173 vserver_client.delete_snapshot(share_name, snapshot_name) 

2174 except exception.ShareSnapshotIsBusy: 

2175 msg = _("Error deleting the snapshot %s: timeout waiting for " 

2176 "FlexGroup snapshot to not be busy.") 

2177 raise exception.NetAppException(msg % snapshot_name) 

2178 

2179 @na_utils.trace 

2180 def manage_existing(self, share, driver_options, share_server=None): 

2181 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2182 share_size = self._manage_container(share, vserver, vserver_client) 

2183 export_locations = self._create_export(share, share_server, vserver, 

2184 vserver_client) 

2185 return {'size': share_size, 'export_locations': export_locations} 

2186 

2187 @na_utils.trace 

2188 def unmanage(self, share, share_server=None): 

2189 pass 

2190 

2191 @na_utils.trace 

2192 def _manage_container(self, share, vserver, vserver_client): 

2193 """Bring existing volume under management as a share.""" 

2194 

2195 protocol_helper = self._get_helper(share) 

2196 protocol_helper.set_client(vserver_client) 

2197 

2198 volume_name = protocol_helper.get_share_name_for_share(share) 

2199 if not volume_name: 

2200 msg = _('Volume could not be determined from export location ' 

2201 '%(export)s.') 

2202 msg_args = {'export': share['export_location']} 

2203 raise exception.ManageInvalidShare(reason=msg % msg_args) 

2204 

2205 # NOTE(felipe_rodrigues): depending on volume style, the aggregate_name 

2206 # is a string (FlexVol) or a list (FlexGroup). 

2207 pool_name = share_utils.extract_host(share['host'], level='pool') 

2208 flexgroup_pool = False 

2209 aggregate_name = pool_name 

2210 if self._is_flexgroup_pool(pool_name): 

2211 flexgroup_pool = True 

2212 if self._is_flexgroup_auto: 2212 ↛ 2213line 2212 didn't jump to line 2213 because the condition on line 2212 was never true

2213 aggregate_name = self._client.get_aggregate_for_volume( 

2214 volume_name) 

2215 else: 

2216 aggregate_name = self._get_flexgroup_aggregate_list(pool_name) 

2217 

2218 # Check that share and pool are from same style. 

2219 flexgroup_vol = self._is_flexgroup_share(vserver_client, volume_name) 

2220 if flexgroup_vol != flexgroup_pool: 

2221 share_style = 'FlexGroup' if flexgroup_vol else 'FlexVol' 

2222 pool_style = 'FlexGroup' if flexgroup_pool else 'FlexVol' 

2223 msg = _('Could not manage share %(share)s on the specified pool ' 

2224 '%(pool_name)s. The share is from %(share_style)s style, ' 

2225 'while the pool is for %(pool_style)s style.') 

2226 msg_args = {'share': volume_name, 'pool_name': pool_name, 

2227 'share_style': share_style, 'pool_style': pool_style} 

2228 raise exception.ManageInvalidShare(reason=msg % msg_args) 

2229 

2230 # Get existing volume info. 

2231 volume = vserver_client.get_volume_to_manage(aggregate_name, 

2232 volume_name) 

2233 

2234 if not volume: 

2235 msg = _('Volume %(volume)s not found on pool %(pool)s.') 

2236 msg_args = {'volume': volume_name, 'pool': pool_name} 

2237 raise exception.ManageInvalidShare(reason=msg % msg_args) 

2238 

2239 # When calculating the size, round up to the next GB. 

2240 volume_size = int(math.ceil(float(volume['size']) / units.Gi)) 

2241 

2242 # Validate extra specs. 

2243 extra_specs = share_types.get_extra_specs_from_share(share) 

2244 extra_specs = self._remap_standard_boolean_extra_specs(extra_specs) 

2245 try: 

2246 self._check_extra_specs_validity(share, extra_specs) 

2247 self._check_aggregate_extra_specs_validity(pool_name, extra_specs) 

2248 except exception.ManilaException as ex: 

2249 raise exception.ManageExistingShareTypeMismatch( 

2250 reason=str(ex)) 

2251 

2252 # Ensure volume is manageable. 

2253 self._validate_volume_for_manage(volume, vserver_client) 

2254 

2255 provisioning_options = self._get_provisioning_options(extra_specs) 

2256 qos_specs = self._get_normalized_qos_specs(extra_specs) 

2257 self.validate_provisioning_options_for_share(provisioning_options, 

2258 extra_specs=extra_specs, 

2259 qos_specs=qos_specs) 

2260 # Check fpolicy extra-specs. 

2261 fpolicy_ext_include = provisioning_options.get( 

2262 'fpolicy_extensions_to_include') 

2263 fpolicy_ext_exclude = provisioning_options.get( 

2264 'fpolicy_extensions_to_exclude') 

2265 fpolicy_file_operations = provisioning_options.get( 

2266 'fpolicy_file_operations') 

2267 

2268 fpolicy_scope = None 

2269 if fpolicy_ext_include or fpolicy_ext_include: 

2270 fpolicy_scope = self._find_reusable_fpolicy_scope( 

2271 share, vserver_client, 

2272 fpolicy_extensions_to_include=fpolicy_ext_include, 

2273 fpolicy_extensions_to_exclude=fpolicy_ext_exclude, 

2274 fpolicy_file_operations=fpolicy_file_operations, 

2275 shares_to_include=[volume_name] 

2276 ) 

2277 if fpolicy_scope is None: 

2278 msg = _('Volume %(volume)s does not contains the expected ' 

2279 'fpolicy configuration.') 

2280 msg_args = {'volume': volume_name} 

2281 raise exception.ManageExistingShareTypeMismatch( 

2282 reason=msg % msg_args) 

2283 

2284 share_name = self._get_backend_share_name(share['id']) 

2285 mount_point_name = share.get('mount_point_name') 

2286 

2287 # Rename & remount volume on new path. 

2288 vserver_client.unmount_volume(volume_name) 

2289 vserver_client.set_volume_name(volume_name, share_name) 

2290 vserver_client.mount_volume(share_name, mount_point_name) 

2291 

2292 qos_policy_group_name = self._modify_or_create_qos_for_existing_share( 

2293 share, extra_specs, vserver, vserver_client) 

2294 if qos_policy_group_name: 

2295 provisioning_options['qos_policy_group'] = qos_policy_group_name 

2296 

2297 snap_attributes = self._get_provisioning_options_for_snap_attributes( 

2298 vserver_client, share_name) 

2299 provisioning_options.update(snap_attributes) 

2300 

2301 debug_args = { 

2302 'share': share_name, 

2303 'aggr': (",".join(aggregate_name) if flexgroup_vol 

2304 else aggregate_name), 

2305 'options': provisioning_options 

2306 } 

2307 LOG.debug('Managing share %(share)s on aggregate(s) %(aggr)s with ' 

2308 'provisioning options %(options)s', debug_args) 

2309 

2310 # Modify volume to match extra specs. 

2311 vserver_client.modify_volume(aggregate_name, share_name, 

2312 **provisioning_options) 

2313 

2314 # Update fpolicy to include the new share name and remove the old one. 

2315 if fpolicy_scope is not None: 

2316 shares_to_include = copy.deepcopy( 

2317 fpolicy_scope.get('shares-to-include', [])) 

2318 shares_to_include.remove(volume_name) 

2319 shares_to_include.append(share_name) 

2320 policy_name = fpolicy_scope.get('policy-name') 

2321 # Update. 

2322 vserver_client.modify_fpolicy_scope( 

2323 share_name, policy_name, shares_to_include=shares_to_include) 

2324 

2325 if provisioning_options.get('max_files_multiplier') is not None: 2325 ↛ 2326line 2325 didn't jump to line 2326 because the condition on line 2325 was never true

2326 max_files_multiplier = provisioning_options.pop( 

2327 'max_files_multiplier') 

2328 max_files = na_utils.calculate_max_files(volume_size, 

2329 max_files_multiplier) 

2330 vserver_client.set_volume_max_files(share_name, max_files, 

2331 retry_allocated=True) 

2332 

2333 # Save original volume info to private storage. 

2334 original_data = { 

2335 'original_name': volume['name'], 

2336 'original_junction_path': volume['junction-path'] 

2337 } 

2338 self.private_storage.update(share['id'], original_data) 

2339 

2340 return volume_size 

2341 

2342 @na_utils.trace 

2343 def _get_provisioning_options_for_snap_attributes(self, vserver_client, 

2344 volume_name): 

2345 provisioning_options = {} 

2346 snapshot_attributes = vserver_client.get_volume_snapshot_attributes( 

2347 volume_name) 

2348 if ( 2348 ↛ 2352line 2348 didn't jump to line 2352 because the condition on line 2348 was never true

2349 snapshot_attributes['snapshot-policy'].lower() 

2350 in self.configuration.netapp_volume_snapshot_policy_exceptions 

2351 ): 

2352 provisioning_options['snapshot_policy'] = ( 

2353 snapshot_attributes['snapshot-policy']) 

2354 provisioning_options['hide_snapdir'] = ( 

2355 snapshot_attributes['snapdir-access-enabled'].lower() != 'true' 

2356 ) 

2357 return provisioning_options 

2358 

2359 @na_utils.trace 

2360 def _validate_volume_for_manage(self, volume, vserver_client): 

2361 """Ensure volume is a candidate for becoming a share.""" 

2362 

2363 # Check volume info, extra specs validity 

2364 if volume['type'] != 'rw' or volume['style'] != 'flex': 

2365 msg = _('Volume %(volume)s must be a read-write flexible volume.') 

2366 msg_args = {'volume': volume['name']} 

2367 raise exception.ManageInvalidShare(reason=msg % msg_args) 

2368 

2369 if vserver_client.volume_has_luns(volume['name']): 

2370 msg = _('Volume %(volume)s must not contain LUNs.') 

2371 msg_args = {'volume': volume['name']} 

2372 raise exception.ManageInvalidShare(reason=msg % msg_args) 

2373 

2374 if vserver_client.volume_has_junctioned_volumes( 

2375 volume['junction-path']): 

2376 msg = _('Volume %(volume)s must not have junctioned volumes.') 

2377 msg_args = {'volume': volume['name']} 

2378 raise exception.ManageInvalidShare(reason=msg % msg_args) 

2379 

2380 if vserver_client.volume_has_snapmirror_relationships(volume): 

2381 msg = _('Volume %(volume)s must not be in any snapmirror ' 

2382 'relationships.') 

2383 msg_args = {'volume': volume['name']} 

2384 raise exception.ManageInvalidShare(reason=msg % msg_args) 

2385 

2386 @na_utils.trace 

2387 def manage_existing_snapshot( 

2388 self, snapshot, driver_options, share_server=None): 

2389 """Brings an existing snapshot under Manila management. 

2390 

2391 The managed snapshot keeps with its name, not renaming to the driver 

2392 snapshot name pattern (share_snapshot_<id>), because the rename is lost 

2393 when reverting to the snapshot, causing some issues (bug #1936648). 

2394 """ 

2395 

2396 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2397 share_name = self._get_backend_share_name(snapshot['share_id']) 

2398 existing_snapshot_name = snapshot.get('provider_location') 

2399 

2400 if not existing_snapshot_name: 

2401 msg = _('provider_location not specified.') 

2402 raise exception.ManageInvalidShareSnapshot(reason=msg) 

2403 

2404 # Get the volume containing the snapshot so we can report its size. 

2405 try: 

2406 volume = vserver_client.get_volume(share_name) 

2407 except (netapp_api.NaApiError, 

2408 exception.StorageResourceNotFound, 

2409 exception.NetAppException): 

2410 msg = _('Could not determine snapshot %(snap)s size from ' 

2411 'volume %(vol)s.') 

2412 msg_args = {'snap': existing_snapshot_name, 'vol': share_name} 

2413 LOG.exception(msg, msg_args) 

2414 raise exception.ShareNotFound(share_id=snapshot['share_id']) 

2415 

2416 # Ensure snapshot is from the share. 

2417 if not vserver_client.snapshot_exists( 

2418 existing_snapshot_name, share_name): 

2419 msg = _('Snapshot %(snap)s is not from the share %(vol)s.') 

2420 msg_args = {'snap': existing_snapshot_name, 'vol': share_name} 

2421 raise exception.ManageInvalidShareSnapshot(reason=msg % msg_args) 

2422 

2423 # Ensure there aren't any mirrors on this volume. 

2424 if vserver_client.volume_has_snapmirror_relationships(volume): 

2425 msg = _('Share %s has SnapMirror relationships.') 

2426 msg_args = {'vol': share_name} 

2427 raise exception.ManageInvalidShareSnapshot(reason=msg % msg_args) 

2428 

2429 # When calculating the size, round up to the next GB. 

2430 size = int(math.ceil(float(volume['size']) / units.Gi)) 

2431 

2432 return {'size': size} 

2433 

2434 @na_utils.trace 

2435 def unmanage_snapshot(self, snapshot, share_server=None): 

2436 """Removes the specified snapshot from Manila management.""" 

2437 

2438 @na_utils.trace 

2439 def create_consistency_group_from_cgsnapshot( 

2440 self, context, cg_dict, cgsnapshot_dict, share_server=None): 

2441 """Creates a consistency group from an existing CG snapshot.""" 

2442 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2443 

2444 # Ensure there is something to do 

2445 if not cgsnapshot_dict['share_group_snapshot_members']: 

2446 return None, None 

2447 

2448 clone_list = self._collate_cg_snapshot_info(cg_dict, cgsnapshot_dict) 

2449 share_update_list = [] 

2450 

2451 LOG.debug('Creating consistency group from CG snapshot %s.', 

2452 cgsnapshot_dict['id']) 

2453 

2454 for clone in clone_list: 

2455 

2456 self._allocate_container_from_snapshot( 

2457 clone['share'], clone['snapshot'], vserver, vserver_client, 

2458 NetAppCmodeFileStorageLibrary._get_backend_cg_snapshot_name) 

2459 

2460 export_locations = self._create_export(clone['share'], 

2461 share_server, 

2462 vserver, 

2463 vserver_client) 

2464 share_update_list.append({ 

2465 'id': clone['share']['id'], 

2466 'export_locations': export_locations, 

2467 }) 

2468 

2469 return None, share_update_list 

2470 

2471 def _collate_cg_snapshot_info(self, cg_dict, cgsnapshot_dict): 

2472 """Collate the data for a clone of a CG snapshot. 

2473 

2474 Given two data structures, a CG snapshot (cgsnapshot_dict) and a new 

2475 CG to be cloned from the snapshot (cg_dict), match up both structures 

2476 into a list of dicts (share & snapshot) suitable for use by existing 

2477 driver methods that clone individual share snapshots. 

2478 """ 

2479 

2480 clone_list = list() 

2481 

2482 for share in cg_dict['shares']: 

2483 

2484 clone_info = {'share': share} 

2485 

2486 for cgsnapshot_member in ( 

2487 cgsnapshot_dict['share_group_snapshot_members']): 

2488 if (share['source_share_group_snapshot_member_id'] == 

2489 cgsnapshot_member['id']): 

2490 clone_info['snapshot'] = { 

2491 'share_id': cgsnapshot_member['share_id'], 

2492 'id': cgsnapshot_dict['id'], 

2493 'size': cgsnapshot_member['size'], 

2494 } 

2495 break 

2496 

2497 else: 

2498 msg = _("Invalid data supplied for creating consistency group " 

2499 "from CG snapshot %s.") % cgsnapshot_dict['id'] 

2500 raise exception.InvalidShareGroup(reason=msg) 

2501 

2502 clone_list.append(clone_info) 

2503 

2504 return clone_list 

2505 

2506 @na_utils.trace 

2507 def create_cgsnapshot(self, context, snap_dict, share_server=None): 

2508 """Creates a consistency group snapshot.""" 

2509 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2510 

2511 share_names = [self._get_backend_share_name(member['share_id']) 

2512 for member in 

2513 snap_dict.get('share_group_snapshot_members', [])] 

2514 snapshot_name = self._get_backend_cg_snapshot_name(snap_dict['id']) 

2515 

2516 if share_names: 

2517 LOG.debug('Creating CG snapshot %s.', snapshot_name) 

2518 vserver_client.create_cg_snapshot(share_names, snapshot_name) 

2519 

2520 return None, None 

2521 

2522 @na_utils.trace 

2523 def delete_cgsnapshot(self, context, snap_dict, share_server=None): 

2524 """Deletes a consistency group snapshot.""" 

2525 try: 

2526 vserver, vserver_client = self._get_vserver( 

2527 share_server=share_server) 

2528 except (exception.InvalidInput, 

2529 exception.VserverNotSpecified, 

2530 exception.VserverNotFound) as error: 

2531 LOG.warning("Could not determine share server for CG snapshot " 

2532 "being deleted: %(snap)s. Deletion of CG snapshot " 

2533 "record will proceed anyway. Error: %(error)s", 

2534 {'snap': snap_dict['id'], 'error': error}) 

2535 return None, None 

2536 

2537 share_names = [self._get_backend_share_name(member['share_id']) 

2538 for member in ( 

2539 snap_dict.get('share_group_snapshot_members', []))] 

2540 snapshot_name = self._get_backend_cg_snapshot_name(snap_dict['id']) 

2541 

2542 for share_name in share_names: 

2543 try: 

2544 self._delete_snapshot( 

2545 vserver_client, share_name, snapshot_name) 

2546 except exception.SnapshotResourceNotFound: 

2547 msg = ("Snapshot %(snap)s does not exist on share " 

2548 "%(share)s.") 

2549 msg_args = {'snap': snapshot_name, 'share': share_name} 

2550 LOG.info(msg, msg_args) 

2551 continue 

2552 

2553 return None, None 

2554 

2555 @staticmethod 

2556 def _is_group_cg(context, share_group): 

2557 return 'host' == share_group.consistent_snapshot_support 

2558 

2559 @na_utils.trace 

2560 def create_group_snapshot(self, context, snap_dict, fallback_create, 

2561 share_server=None): 

2562 share_group = snap_dict['share_group'] 

2563 if self._is_group_cg(context, share_group): 

2564 return self.create_cgsnapshot(context, snap_dict, 

2565 share_server=share_server) 

2566 else: 

2567 return fallback_create(context, snap_dict, 

2568 share_server=share_server) 

2569 

2570 @na_utils.trace 

2571 def delete_group_snapshot(self, context, snap_dict, fallback_delete, 

2572 share_server=None): 

2573 share_group = snap_dict['share_group'] 

2574 if self._is_group_cg(context, share_group): 

2575 return self.delete_cgsnapshot(context, snap_dict, 

2576 share_server=share_server) 

2577 else: 

2578 return fallback_delete(context, snap_dict, 

2579 share_server=share_server) 

2580 

2581 @na_utils.trace 

2582 def create_group_from_snapshot(self, context, share_group, 

2583 snapshot_dict, fallback_create, 

2584 share_server=None): 

2585 share_group2 = snapshot_dict['share_group'] 

2586 if self._is_group_cg(context, share_group2): 

2587 return self.create_consistency_group_from_cgsnapshot( 

2588 context, share_group, snapshot_dict, 

2589 share_server=share_server) 

2590 else: 

2591 return fallback_create(context, share_group, snapshot_dict, 

2592 share_server=share_server) 

2593 

2594 @na_utils.trace 

2595 def _adjust_qos_policy_with_volume_resize(self, share, new_size, 

2596 vserver_client): 

2597 # Adjust QoS policy on a share if any 

2598 if self._have_cluster_creds: 

2599 share_name = self._get_backend_share_name(share['id']) 

2600 share_on_the_backend = vserver_client.get_volume(share_name) 

2601 qos_policy_on_share = share_on_the_backend['qos-policy-group-name'] 

2602 if qos_policy_on_share is None: 

2603 return 

2604 

2605 extra_specs = share_types.get_extra_specs_from_share(share) 

2606 qos_specs = self._get_normalized_qos_specs(extra_specs) 

2607 size_dependent_specs = {k: v for k, v in qos_specs.items() if k in 

2608 self.SIZE_DEPENDENT_QOS_SPECS} 

2609 if size_dependent_specs: 

2610 max_throughput = self._get_max_throughput( 

2611 new_size, size_dependent_specs) 

2612 min_throughput = self._get_min_throughput( 

2613 new_size, size_dependent_specs) 

2614 self._client.qos_policy_group_modify( 

2615 qos_policy_on_share, max_throughput, min_throughput) 

2616 

2617 @na_utils.trace 

2618 def extend_share(self, share, new_size, share_server=None): 

2619 """Extends size of existing share.""" 

2620 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2621 share_name = self._get_backend_share_name(share['id']) 

2622 extra_specs = share_types.get_extra_specs_from_share(share) 

2623 provisioning_options = self._get_provisioning_options(extra_specs) 

2624 

2625 vserver_client.set_volume_filesys_size_fixed(share_name, 

2626 filesys_size_fixed=False) 

2627 LOG.debug('Extending share %(name)s to %(size)s GB.', 

2628 {'name': share_name, 'size': new_size}) 

2629 vserver_client.set_volume_size(share_name, new_size) 

2630 

2631 if provisioning_options.get('max_files_multiplier') is not None: 2631 ↛ 2632line 2631 didn't jump to line 2632 because the condition on line 2631 was never true

2632 max_files_multiplier = provisioning_options.pop( 

2633 'max_files_multiplier') 

2634 max_files = na_utils.calculate_max_files(new_size, 

2635 max_files_multiplier) 

2636 vserver_client.set_volume_max_files(share_name, max_files) 

2637 

2638 self._adjust_qos_policy_with_volume_resize(share, new_size, 

2639 vserver_client) 

2640 

2641 @na_utils.trace 

2642 def shrink_share(self, share, new_size, share_server=None): 

2643 """Shrinks size of existing share.""" 

2644 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2645 share_name = self._get_backend_share_name(share['id']) 

2646 extra_specs = share_types.get_extra_specs_from_share(share) 

2647 provisioning_options = self._get_provisioning_options(extra_specs) 

2648 

2649 vserver_client.set_volume_filesys_size_fixed(share_name, 

2650 filesys_size_fixed=False) 

2651 LOG.debug('Shrinking share %(name)s to %(size)s GB.', 

2652 {'name': share_name, 'size': new_size}) 

2653 

2654 try: 

2655 vserver_client.set_volume_size(share_name, new_size) 

2656 except netapp_api.NaApiError as e: 

2657 if e.code == netapp_api.EVOLOPNOTSUPP: 2657 ↛ 2666line 2657 didn't jump to line 2666 because the condition on line 2657 was always true

2658 msg = _('Failed to shrink share %(share_id)s. ' 

2659 'The current used space is larger than the the size' 

2660 ' requested.') 

2661 msg_args = {'share_id': share['id']} 

2662 LOG.error(msg, msg_args) 

2663 raise exception.ShareShrinkingPossibleDataLoss( 

2664 share_id=share['id']) 

2665 

2666 self._adjust_qos_policy_with_volume_resize( 

2667 share, new_size, vserver_client) 

2668 

2669 if provisioning_options.get('max_files_multiplier') is not None: 2669 ↛ 2670line 2669 didn't jump to line 2670 because the condition on line 2669 was never true

2670 max_files_multiplier = provisioning_options.pop( 

2671 'max_files_multiplier') 

2672 max_files = na_utils.calculate_max_files(new_size, 

2673 max_files_multiplier) 

2674 vserver_client.set_volume_max_files(share_name, max_files, 

2675 retry_allocated=True) 

2676 

2677 @na_utils.trace 

2678 def update_access(self, context, share, access_rules, add_rules, 

2679 delete_rules, update_rules, share_server=None): 

2680 """Updates access rules for a share.""" 

2681 

2682 # NOTE(felipe_rodrigues): do not add export rules to a non-active 

2683 # replica that is DR type, it might not have its policy yet. 

2684 replica_state = share.get('replica_state') 

2685 if (replica_state is not None and 

2686 replica_state != constants.REPLICA_STATE_ACTIVE and 

2687 not self._is_readable_replica(share)): 

2688 return 

2689 try: 

2690 vserver, vserver_client = self._get_vserver( 

2691 share_server=share_server) 

2692 except (exception.InvalidInput, 

2693 exception.VserverNotSpecified, 

2694 exception.VserverNotFound) as error: 

2695 LOG.warning("Could not determine share server for share " 

2696 "%(share)s during access rules update. " 

2697 "Error: %(error)s", 

2698 {'share': share['id'], 'error': error}) 

2699 return 

2700 

2701 share_name = self._get_backend_share_name(share['id']) 

2702 if self._share_exists(share_name, vserver_client): 

2703 helper = self._get_helper(share) 

2704 helper.set_client(vserver_client) 

2705 helper.update_access(share, share_name, access_rules) 

2706 else: 

2707 raise exception.ShareResourceNotFound(share_id=share['id']) 

2708 

2709 def setup_server(self, network_info, metadata=None): 

2710 raise NotImplementedError() 

2711 

2712 def teardown_server(self, server_details, security_services=None): 

2713 raise NotImplementedError() 

2714 

2715 def get_network_allocations_number(self): 

2716 """Get number of network interfaces to be created.""" 

2717 raise NotImplementedError() 

2718 

2719 @na_utils.trace 

2720 def _update_ssc_info(self): 

2721 """Periodically runs to update Storage Service Catalog data. 

2722 

2723 The self._ssc_stats attribute is updated with the following format. 

2724 {<aggregate_name> : {<ssc_key>: <ssc_value>}} 

2725 """ 

2726 LOG.info("Updating storage service catalog information for " 

2727 "backend '%s'", self._backend_name) 

2728 

2729 # Work on a copy and update the ssc data atomically before returning. 

2730 ssc_stats = copy.deepcopy(self._ssc_stats) 

2731 

2732 aggregate_names = self._find_matching_aggregates() 

2733 

2734 # Initialize entries for each aggregate. 

2735 for aggregate_name in aggregate_names: 

2736 if aggregate_name not in ssc_stats: 2736 ↛ 2735line 2736 didn't jump to line 2735 because the condition on line 2736 was always true

2737 ssc_stats[aggregate_name] = { 

2738 'netapp_aggregate': aggregate_name, 

2739 'netapp_flexgroup': False, 

2740 } 

2741 

2742 # Initialize entries for each FlexGroup pool 

2743 flexgroup_pools = self._flexgroup_pools 

2744 for pool_name, aggr_list in flexgroup_pools.items(): 

2745 if pool_name not in ssc_stats: 2745 ↛ 2744line 2745 didn't jump to line 2744 because the condition on line 2745 was always true

2746 ssc_stats[pool_name] = { 

2747 'netapp_aggregate': " ".join(aggr_list), 

2748 'netapp_flexgroup': True, 

2749 } 

2750 

2751 # Add the SnapLock info for FlexVol 

2752 for aggr_name in aggregate_names: 

2753 if self._client.features.UNIFIED_AGGR: 

2754 snaplock_dict = {'netapp_snaplock_type': self.SNAPLOCK_TYPE} 

2755 else: 

2756 snaplock_dict = { 

2757 'netapp_snaplock_type': 

2758 self._get_aggregate_snaplock_type(aggr_name) 

2759 } 

2760 ssc_stats[aggr_name].update(snaplock_dict) 

2761 

2762 # Add aggregate specs for pools 

2763 aggr_set = set(aggregate_names).union(self._get_flexgroup_aggr_set()) 

2764 if self._have_cluster_creds and aggr_set: 

2765 aggr_info = self._get_aggregate_info(aggr_set) 

2766 

2767 # FlexVol pools 

2768 aggr_info_flexvol = copy.deepcopy(aggr_info) 

2769 for aggr_name in aggregate_names: 

2770 if self._client.features.UNIFIED_AGGR: 

2771 aggr_info_flexvol[aggr_name]['netapp_snaplock_type'] = \ 

2772 self.SNAPLOCK_TYPE 

2773 ssc_stats[aggr_name].update(aggr_info_flexvol[aggr_name]) 

2774 

2775 # FlexGroup pools 

2776 for pool_name, aggr_list in flexgroup_pools.items(): 

2777 raid_type = set() 

2778 hybrid = set() 

2779 disk_type = set() 

2780 snaplock_type = set() 

2781 for aggr in aggr_list: 

2782 raid_type.add(aggr_info[aggr]['netapp_raid_type']) 

2783 hybrid.add(aggr_info[aggr]['netapp_hybrid_aggregate']) 

2784 disk_type = disk_type.union( 

2785 aggr_info[aggr]['netapp_disk_type']) 

2786 snaplock_type.add(aggr_info[aggr]['netapp_snaplock_type']) 

2787 

2788 ssc_stats[pool_name].update({ 

2789 'netapp_raid_type': " ".join(sorted(raid_type)), 

2790 'netapp_hybrid_aggregate': " ".join(sorted(hybrid)), 

2791 'netapp_disk_type': sorted(list(disk_type)), 

2792 'netapp_snaplock_type': self.SNAPLOCK_TYPE 

2793 if self._client.features.UNIFIED_AGGR 

2794 else " ".join(sorted(snaplock_type)), 

2795 

2796 }) 

2797 

2798 self._ssc_stats = ssc_stats 

2799 

2800 @na_utils.trace 

2801 def _get_aggregate_info(self, aggregate_names): 

2802 """Gets the disk type information for the driver aggregates. 

2803 

2804 :param aggregate_names: The aggregates this driver cares about 

2805 """ 

2806 aggr_info = {} 

2807 for aggregate_name in aggregate_names: 

2808 

2809 aggregate = self._client.get_aggregate(aggregate_name) 

2810 hybrid = (str(aggregate.get('is-hybrid')).lower() 

2811 if 'is-hybrid' in aggregate else None) 

2812 disk_types = self._client.get_aggregate_disk_types(aggregate_name) 

2813 

2814 aggr_info[aggregate_name] = { 

2815 'netapp_raid_type': aggregate.get('raid-type'), 

2816 'netapp_hybrid_aggregate': hybrid, 

2817 'netapp_disk_type': disk_types, 

2818 'netapp_is_home': aggregate.get('is-home'), 

2819 'netapp_snaplock_type': aggregate.get('snaplock-type'), 

2820 } 

2821 

2822 return aggr_info 

2823 

2824 def find_active_replica(self, replica_list): 

2825 # NOTE(ameade): Find current active replica. There can only be one 

2826 # active replica (SnapMirror source volume) at a time in cDOT. 

2827 for r in replica_list: 2827 ↛ exitline 2827 didn't return from function 'find_active_replica' because the loop on line 2827 didn't complete

2828 if r['replica_state'] == constants.REPLICA_STATE_ACTIVE: 

2829 return r 

2830 

2831 def _find_nonactive_replicas(self, replica_list): 

2832 """Returns a list of all except the active replica.""" 

2833 return [replica for replica in replica_list 

2834 if replica['replica_state'] != constants.REPLICA_STATE_ACTIVE] 

2835 

2836 def create_replica(self, context, replica_list, new_replica, 

2837 access_rules, share_snapshots, share_server=None): 

2838 """Creates the new replica on this backend and sets up SnapMirror.""" 

2839 active_replica = self.find_active_replica(replica_list) 

2840 dm_session = data_motion.DataMotionSession() 

2841 

2842 # check that the source and new replica reside in the same pool type: 

2843 # either FlexGroup or FlexVol. 

2844 src_share_name, src_vserver, src_backend = ( 

2845 dm_session.get_backend_info_for_share(active_replica)) 

2846 src_client = data_motion.get_client_for_backend( 

2847 src_backend, vserver_name=src_vserver) 

2848 src_is_flexgroup = self._is_flexgroup_share(src_client, src_share_name) 

2849 

2850 pool_name = share_utils.extract_host(new_replica['host'], level='pool') 

2851 dest_is_flexgroup = self._is_flexgroup_pool(pool_name) 

2852 

2853 if src_is_flexgroup != dest_is_flexgroup: 

2854 src_type = 'FlexGroup' if src_is_flexgroup else 'FlexVol' 

2855 dest_type = 'FlexGroup' if dest_is_flexgroup else 'FlexVol' 

2856 msg = _('Could not create replica %(replica_id)s from share ' 

2857 '%(share_id)s in the destination host %(dest_host)s. The ' 

2858 'source share is from %(src_type)s style, while the ' 

2859 'destination replica host is %(dest_type)s style.') 

2860 msg_args = {'replica_id': new_replica['id'], 

2861 'share_id': new_replica['share_id'], 

2862 'dest_host': new_replica['host'], 

2863 'src_type': src_type, 'dest_type': dest_type} 

2864 raise exception.NetAppException(msg % msg_args) 

2865 

2866 # NOTE(felipe_rodrigues): The FlexGroup replication does not support 

2867 # several replicas (fan-out) in some ONTAP versions, while FlexVol is 

2868 # always supported. 

2869 if dest_is_flexgroup: 

2870 fan_out = (src_client.is_flexgroup_fan_out_supported() and 

2871 self._client.is_flexgroup_fan_out_supported()) 

2872 if not fan_out and len(replica_list) > 2: 2872 ↛ 2882line 2872 didn't jump to line 2882 because the condition on line 2872 was always true

2873 msg = _('Could not create replica %(replica_id)s from share ' 

2874 '%(share_id)s in the destination host %(dest_host)s. ' 

2875 'The share does not support more than one replica.') 

2876 msg_args = {'replica_id': new_replica['id'], 

2877 'share_id': new_replica['share_id'], 

2878 'dest_host': new_replica['host']} 

2879 raise exception.NetAppException(msg % msg_args) 

2880 

2881 # 1. Create the destination share 

2882 dest_backend = share_utils.extract_host(new_replica['host'], 

2883 level='backend_name') 

2884 

2885 vserver = (dm_session.get_vserver_from_share(new_replica) or 

2886 self.configuration.netapp_vserver) 

2887 

2888 vserver_client = data_motion.get_client_for_backend( 

2889 dest_backend, vserver_name=vserver) 

2890 

2891 is_readable = self._is_readable_replica(new_replica) 

2892 self._allocate_container(new_replica, vserver, vserver_client, 

2893 replica=True, create_fpolicy=False, 

2894 set_qos=is_readable) 

2895 

2896 # 2. Setup SnapMirror with mounting replica whether 'readable' type. 

2897 relationship_type = na_utils.get_relationship_type(dest_is_flexgroup) 

2898 dm_session.create_snapmirror(active_replica, new_replica, 

2899 relationship_type, mount=is_readable) 

2900 

2901 # 3. Create export location 

2902 model_update = { 

2903 'export_locations': [], 

2904 'replica_state': constants.REPLICA_STATE_OUT_OF_SYNC, 

2905 'access_rules_status': constants.STATUS_ACTIVE, 

2906 } 

2907 if is_readable: 

2908 model_update['export_locations'] = self._create_export( 

2909 new_replica, share_server, vserver, vserver_client, 

2910 replica=True) 

2911 

2912 if access_rules: 2912 ↛ 2922line 2912 didn't jump to line 2922 because the condition on line 2912 was always true

2913 helper = self._get_helper(new_replica) 

2914 helper.set_client(vserver_client) 

2915 share_name = self._get_backend_share_name(new_replica['id']) 

2916 try: 

2917 helper.update_access(new_replica, share_name, access_rules) 

2918 except Exception: 

2919 model_update['access_rules_status'] = ( 

2920 constants.SHARE_INSTANCE_RULES_ERROR) 

2921 

2922 return model_update 

2923 

2924 def delete_replica(self, context, replica_list, replica, share_snapshots, 

2925 share_server=None): 

2926 """Removes the replica on this backend and destroys SnapMirror.""" 

2927 dm_session = data_motion.DataMotionSession() 

2928 # 1. Remove SnapMirror 

2929 dest_backend = share_utils.extract_host(replica['host'], 

2930 level='backend_name') 

2931 vserver = (dm_session.get_vserver_from_share(replica) or 

2932 self.configuration.netapp_vserver) 

2933 

2934 # Ensure that all potential snapmirror relationships and their metadata 

2935 # involving the replica are destroyed. 

2936 for other_replica in replica_list: 

2937 if other_replica['id'] != replica['id']: 

2938 dm_session.delete_snapmirror(other_replica, replica) 

2939 dm_session.delete_snapmirror(replica, other_replica) 

2940 

2941 # 2. Delete share 

2942 is_readable = self._is_readable_replica(replica) 

2943 vserver_client = data_motion.get_client_for_backend( 

2944 dest_backend, vserver_name=vserver) 

2945 self._delete_share(replica, vserver, vserver_client, 

2946 remove_export=is_readable, remove_qos=is_readable) 

2947 

2948 @na_utils.trace 

2949 def _convert_schedule_to_seconds(self, schedule='hourly'): 

2950 """Convert snapmirror schedule to seconds.""" 

2951 results = re.findall(r'[\d]+|[^d]+', schedule) 

2952 if not results or len(results) > 2: 2952 ↛ 2953line 2952 didn't jump to line 2953 because the condition on line 2952 was never true

2953 return 3600 # default (1 hour) 

2954 

2955 if len(results) == 2: 

2956 try: 

2957 num = int(results[0]) 

2958 except ValueError: 

2959 return 3600 

2960 schedule = results[1] 

2961 else: 

2962 num = 1 

2963 schedule = results[0] 

2964 schedule = schedule.lower() 

2965 if schedule in ['min', 'minute']: 

2966 return (num * 60) 

2967 if schedule in ['hour', 'hourly']: 

2968 return (num * 3600) 

2969 if schedule in ['day', 'daily']: 

2970 return (num * 24 * 3600) 

2971 if schedule in ['week', 'weekly']: 2971 ↛ 2972line 2971 didn't jump to line 2972 because the condition on line 2971 was never true

2972 return (num * 7 * 24 * 3600) 

2973 if schedule in ['month', 'monthly']: 2973 ↛ 2974line 2973 didn't jump to line 2974 because the condition on line 2973 was never true

2974 return (num * 30 * 24 * 2600) 

2975 return 3600 

2976 

2977 def update_replica_state(self, context, replica_list, replica, 

2978 access_rules, share_snapshots, share_server=None, 

2979 replication=True): 

2980 """Returns the status of the given replica on this backend.""" 

2981 active_replica = self.find_active_replica(replica_list) 

2982 

2983 share_name = self._get_backend_share_name(replica['id']) 

2984 vserver, vserver_client = self._get_vserver(share_server=share_server) 

2985 

2986 if not vserver_client.volume_exists(share_name): 

2987 msg = _("Volume %(share_name)s does not exist on vserver " 

2988 "%(vserver)s.") 

2989 msg_args = {'share_name': share_name, 'vserver': vserver} 

2990 raise exception.ShareResourceNotFound(msg % msg_args) 

2991 

2992 # NOTE(cknight): The SnapMirror may have been intentionally broken by 

2993 # a revert-to-snapshot operation, in which case this method should not 

2994 # attempt to change anything. 

2995 if active_replica['status'] == constants.STATUS_REVERTING: 

2996 return None 

2997 

2998 dm_session = data_motion.DataMotionSession() 

2999 try: 

3000 snapmirrors = dm_session.get_snapmirrors(active_replica, replica) 

3001 except netapp_api.NaApiError: 

3002 LOG.exception("Could not get snapmirrors for replica %s.", 

3003 replica['id']) 

3004 return constants.STATUS_ERROR 

3005 

3006 is_readable = replication and self._is_readable_replica(replica) 

3007 if not snapmirrors: 

3008 if replica['status'] != constants.STATUS_CREATING: 

3009 try: 

3010 pool_name = share_utils.extract_host(replica['host'], 

3011 level='pool') 

3012 relationship_type = na_utils.get_relationship_type( 

3013 self._is_flexgroup_pool(pool_name)) 

3014 dm_session.create_snapmirror(active_replica, replica, 

3015 relationship_type, 

3016 mount=is_readable) 

3017 except netapp_api.NaApiError: 

3018 LOG.exception("Could not create snapmirror for " 

3019 "replica %s.", replica['id']) 

3020 return constants.STATUS_ERROR 

3021 return constants.REPLICA_STATE_OUT_OF_SYNC 

3022 

3023 snapmirror = snapmirrors[0] 

3024 # NOTE(dviroel): Don't try to resume or resync a SnapMirror that has 

3025 # one of the in progress transfer states, because the storage will 

3026 # answer with an error. 

3027 in_progress_status = ['preparing', 'transferring', 'finalizing', 

3028 'synchronizing'] 

3029 if (snapmirror.get('mirror-state') != 'snapmirrored' and 

3030 (snapmirror.get('relationship-status') in in_progress_status or 

3031 snapmirror.get('transferring-state') in in_progress_status)): 

3032 return constants.REPLICA_STATE_OUT_OF_SYNC 

3033 

3034 if snapmirror.get('mirror-state') != 'snapmirrored': 

3035 try: 

3036 vserver_client.resume_snapmirror_vol( 

3037 snapmirror['source-vserver'], 

3038 snapmirror['source-volume'], 

3039 vserver, 

3040 share_name) 

3041 vserver_client.resync_snapmirror_vol( 

3042 snapmirror['source-vserver'], 

3043 snapmirror['source-volume'], 

3044 vserver, 

3045 share_name) 

3046 return constants.REPLICA_STATE_OUT_OF_SYNC 

3047 except netapp_api.NaApiError: 

3048 LOG.exception("Could not resync snapmirror.") 

3049 return constants.STATUS_ERROR 

3050 

3051 current_schedule = snapmirror.get('schedule') 

3052 new_schedule = self.configuration.netapp_snapmirror_schedule 

3053 if current_schedule != new_schedule: 

3054 dm_session.modify_snapmirror(active_replica, replica, 

3055 schedule=new_schedule) 

3056 LOG.debug('Modify snapmirror schedule for replica:' 

3057 '%(replica)s from %(from)s to %(to)s', 

3058 {'replica': replica['id'], 

3059 'from': current_schedule, 

3060 'to': new_schedule}) 

3061 

3062 last_update_timestamp = float( 

3063 snapmirror.get('last-transfer-end-timestamp', 0)) 

3064 # Recovery Point Objective (RPO) indicates the point in time to 

3065 # which data can be recovered. The RPO target is typically less 

3066 # than twice the replication schedule. 

3067 if (last_update_timestamp and 

3068 (timeutils.is_older_than( 

3069 datetime.datetime.fromtimestamp( 

3070 last_update_timestamp, 

3071 tz=datetime.timezone.utc).replace(tzinfo=None) 

3072 .isoformat(), (2 * self._snapmirror_schedule)))): 

3073 return constants.REPLICA_STATE_OUT_OF_SYNC 

3074 

3075 last_transfer_error = snapmirror.get('last-transfer-error', None) 

3076 if last_transfer_error: 3076 ↛ 3077line 3076 didn't jump to line 3077 because the condition on line 3076 was never true

3077 LOG.debug('Found last-transfer-error: %(error)s for replica: ' 

3078 '%(replica)s.', {'replica': replica['id'], 

3079 'error': last_transfer_error}) 

3080 return constants.REPLICA_STATE_OUT_OF_SYNC 

3081 

3082 # Check all snapshots exist 

3083 snapshots = [snap['share_replica_snapshot'] 

3084 for snap in share_snapshots] 

3085 for snap in snapshots: 

3086 snapshot_name = snap.get('provider_location') 

3087 if (not snapshot_name or 

3088 not vserver_client.snapshot_exists(snapshot_name, 

3089 share_name)): 

3090 return constants.REPLICA_STATE_OUT_OF_SYNC 

3091 

3092 # NOTE(sfernand): When promoting replicas, the previous source volume 

3093 # and its destinations are put in an 'out of sync' state and must be 

3094 # cleaned up once to avoid retaining unused snapshots from the previous 

3095 # relationship. Replicas already 'in-sync' won't try another cleanup 

3096 # attempt. 

3097 if replica['replica_state'] == constants.REPLICA_STATE_OUT_OF_SYNC: 

3098 dm_session.cleanup_previous_snapmirror_relationships( 

3099 replica, replica_list) 

3100 

3101 return constants.REPLICA_STATE_IN_SYNC 

3102 

3103 def promote_replica(self, context, replica_list, replica, access_rules, 

3104 share_server=None, quiesce_wait_time=None): 

3105 """Switch SnapMirror relationships and allow r/w ops on replica. 

3106 

3107 Creates a DataMotion session and switches the direction of the 

3108 SnapMirror relationship between the currently 'active' instance ( 

3109 SnapMirror source volume) and the replica. Also attempts setting up 

3110 SnapMirror relationships between the other replicas and the new 

3111 SnapMirror source volume ('active' instance). 

3112 

3113 For DR style, the promotion creates the QoS policy and export policy 

3114 for the new active replica. While for 'readable', those specs are only 

3115 updated without unmounting. 

3116 

3117 :param context: Request Context 

3118 :param replica_list: List of replicas, including the 'active' instance 

3119 :param replica: Replica to promote to SnapMirror source 

3120 :param access_rules: Access rules to apply to the replica 

3121 :param share_server: ShareServer class instance of replica 

3122 :param quiesce_wait_time: Wait time in seconds for snapmirror quiesce 

3123 :return: Updated replica_list 

3124 """ 

3125 orig_active_replica = self.find_active_replica(replica_list) 

3126 

3127 dm_session = data_motion.DataMotionSession() 

3128 

3129 new_replica_list = [] 

3130 

3131 # Setup the new active replica 

3132 try: 

3133 new_active_replica = ( 

3134 self._convert_destination_replica_to_independent( 

3135 context, dm_session, orig_active_replica, replica, 

3136 access_rules, share_server=share_server, 

3137 quiesce_wait_time=quiesce_wait_time)) 

3138 except exception.StorageCommunicationException: 

3139 LOG.exception("Could not communicate with the backend " 

3140 "for replica %s during promotion.", 

3141 replica['id']) 

3142 new_active_replica = replica.copy() 

3143 new_active_replica['replica_state'] = ( 

3144 constants.STATUS_ERROR) 

3145 new_active_replica['status'] = constants.STATUS_ERROR 

3146 return [new_active_replica] 

3147 

3148 new_replica_list.append(new_active_replica) 

3149 

3150 # Change the source replica for all destinations to the new 

3151 # active replica. 

3152 is_dr = not self._is_readable_replica(replica) 

3153 pool_name = share_utils.extract_host(replica['host'], level='pool') 

3154 is_flexgroup = self._is_flexgroup_pool(pool_name) 

3155 for r in replica_list: 

3156 if r['id'] != replica['id']: 

3157 r = self._safe_change_replica_source(dm_session, r, 

3158 orig_active_replica, 

3159 replica, replica_list, 

3160 is_dr, access_rules, 

3161 share_server=share_server, 

3162 is_flexgroup=is_flexgroup) 

3163 new_replica_list.append(r) 

3164 

3165 if is_dr: 

3166 # NOTE(felipe_rodrigues): non active DR replica does not have the 

3167 # export location set, so during replica deletion the driver cannot 

3168 # delete the ONTAP export. Clean up it when becoming non active. 

3169 orig_active_vserver = dm_session.get_vserver_from_share( 

3170 orig_active_replica) 

3171 orig_active_replica_backend = ( 

3172 share_utils.extract_host(orig_active_replica['host'], 

3173 level='backend_name')) 

3174 orig_active_replica_name = self._get_backend_share_name( 

3175 orig_active_replica['id']) 

3176 orig_active_vserver_client = data_motion.get_client_for_backend( 

3177 orig_active_replica_backend, vserver_name=orig_active_vserver) 

3178 orig_active_replica_helper = self._get_helper(orig_active_replica) 

3179 orig_active_replica_helper.set_client(orig_active_vserver_client) 

3180 try: 

3181 orig_active_replica_helper.cleanup_demoted_replica( 

3182 orig_active_replica, orig_active_replica_name) 

3183 except exception.StorageCommunicationException: 

3184 LOG.exception( 

3185 "Could not cleanup the original active replica export %s.", 

3186 orig_active_replica['id']) 

3187 

3188 self._unmount_orig_active_replica(orig_active_replica, 

3189 orig_active_vserver) 

3190 

3191 self._handle_qos_on_replication_change(dm_session, 

3192 new_active_replica, 

3193 orig_active_replica, 

3194 is_dr, 

3195 share_server=share_server) 

3196 

3197 extra_specs = share_types.get_extra_specs_from_share( 

3198 new_active_replica) 

3199 provisioning_options = self._get_provisioning_options(extra_specs) 

3200 if provisioning_options.get('max_files_multiplier') is not None: 3200 ↛ 3201line 3200 didn't jump to line 3201 because the condition on line 3200 was never true

3201 max_files_multiplier = provisioning_options.pop( 

3202 'max_files_multiplier') 

3203 max_files = na_utils.calculate_max_files( 

3204 new_active_replica['size'], max_files_multiplier) 

3205 new_active_replica_share_name = self._get_backend_share_name( 

3206 new_active_replica['id']) 

3207 __, vserver_client = self._get_vserver( 

3208 share_server=share_server) 

3209 vserver_client.set_volume_max_files(new_active_replica_share_name, 

3210 max_files) 

3211 

3212 self._update_autosize_attributes_after_promote_replica( 

3213 orig_active_replica, new_active_replica, dm_session) 

3214 

3215 return new_replica_list 

3216 

3217 def _unmount_orig_active_replica(self, orig_active_replica, 

3218 orig_active_vserver=None): 

3219 orig_active_replica_backend = ( 

3220 share_utils.extract_host(orig_active_replica['host'], 

3221 level='backend_name')) 

3222 orig_active_vserver_client = data_motion.get_client_for_backend( 

3223 orig_active_replica_backend, 

3224 vserver_name=orig_active_vserver) 

3225 share_name = self._get_backend_share_name( 

3226 orig_active_replica['id']) 

3227 try: 

3228 orig_active_vserver_client.unmount_volume(share_name, 

3229 force=True) 

3230 LOG.info("Unmount of the original active replica %s successful.", 

3231 orig_active_replica['id']) 

3232 except exception.StorageCommunicationException: 

3233 LOG.exception("Could not unmount the original active replica %s.", 

3234 orig_active_replica['id']) 

3235 

3236 def _get_replica_info(self, replica, dm_session): 

3237 """Retrieves a dict with the replica information. 

3238 

3239 :param replica: Share replica. 

3240 :param dm_session: Data Motion session. 

3241 

3242 :return: A dict with the replica information. 

3243 """ 

3244 extra_specs = share_types.get_extra_specs_from_share(replica) 

3245 provisioning_options = self._get_provisioning_options(extra_specs) 

3246 replica_name = self._get_backend_share_name(replica['id']) 

3247 vserver = dm_session.get_vserver_from_share(replica) 

3248 

3249 replica_backend = share_utils.extract_host(replica['host'], 

3250 level='backend_name') 

3251 

3252 replica_client = data_motion.get_client_for_backend( 

3253 replica_backend, vserver_name=vserver) 

3254 

3255 pool_name = share_utils.extract_host(replica['host'], level='pool') 

3256 is_flexgroup = self._is_flexgroup_pool(pool_name) 

3257 

3258 if is_flexgroup: 

3259 replica_aggregate = self._get_flexgroup_aggregate_list(pool_name) 

3260 else: 

3261 replica_aggregate = share_utils.extract_host( 

3262 replica['host'], level='pool') 

3263 

3264 replica_info = { 

3265 'client': replica_client, 

3266 'aggregate': replica_aggregate, 

3267 'name': replica_name, 

3268 'provisioning_options': provisioning_options, 

3269 } 

3270 

3271 return replica_info 

3272 

3273 def _update_autosize_attributes_after_promote_replica( 

3274 self, orig_active_replica, new_active_replica, dm_session): 

3275 """Update autosize attributes after replica is promoted""" 

3276 

3277 # 1. Get the info from original active replica 

3278 orig_active_replica_info = self._get_replica_info( 

3279 orig_active_replica, dm_session) 

3280 

3281 # 2. Get the info from the promoted replica (new_active_replica) 

3282 new_active_replica_info = self._get_replica_info( 

3283 new_active_replica, dm_session) 

3284 

3285 # 3. Set autosize attributes for orig_active_replica 

3286 

3287 # Reset the autosize attributes according to the volume type (RW or DP) 

3288 orig_active_replica_autosize_attributes = {'reset': 'true'} 

3289 

3290 orig_provisioning_opts = ( 

3291 orig_active_replica_info['provisioning_options']) 

3292 

3293 orig_provisioning_opts['autosize_attributes'] = ( 

3294 orig_active_replica_autosize_attributes) 

3295 

3296 orig_active_replica_info['client'].modify_volume( 

3297 orig_active_replica_info['aggregate'], 

3298 orig_active_replica_info['name'], 

3299 **orig_provisioning_opts) 

3300 

3301 # 4. Set autosize attributes for new_active_replica 

3302 

3303 # Reset the autosize attributes according to the volume type (RW or DP) 

3304 new_active_replica_autosize_attributes = {'reset': 'true'} 

3305 

3306 new_provisioning_opts = ( 

3307 new_active_replica_info['provisioning_options']) 

3308 

3309 new_provisioning_opts['autosize_attributes'] = ( 

3310 new_active_replica_autosize_attributes) 

3311 

3312 new_active_replica_info['client'].modify_volume( 

3313 new_active_replica_info['aggregate'], 

3314 new_active_replica_info['name'], 

3315 **new_provisioning_opts) 

3316 

3317 def _handle_qos_on_replication_change(self, dm_session, new_active_replica, 

3318 orig_active_replica, is_dr, 

3319 share_server=None): 

3320 """Handle QoS change while promoting a replica.""" 

3321 

3322 # QoS is only available for cluster credentials. 

3323 if not self._have_cluster_creds: 

3324 return 

3325 

3326 extra_specs = share_types.get_extra_specs_from_share( 

3327 orig_active_replica) 

3328 qos_specs = self._get_normalized_qos_specs(extra_specs) 

3329 

3330 if is_dr and qos_specs: 

3331 dm_session.remove_qos_on_old_active_replica(orig_active_replica) 

3332 

3333 if qos_specs: 

3334 # Check if a QoS policy already exists for the promoted replica, 

3335 # if it does, modify it as necessary, else create it: 

3336 try: 

3337 new_active_replica_qos_policy = ( 

3338 self._get_backend_qos_policy_group_name( 

3339 new_active_replica['id'])) 

3340 vserver, vserver_client = self._get_vserver( 

3341 share_server=share_server) 

3342 

3343 volume_name_on_backend = self._get_backend_share_name( 

3344 new_active_replica['id']) 

3345 if not self._client.qos_policy_group_exists( 

3346 new_active_replica_qos_policy): 

3347 self._create_qos_policy_group( 

3348 new_active_replica, vserver, qos_specs) 

3349 else: 

3350 max_throughput = self._get_max_throughput( 

3351 new_active_replica['size'], qos_specs) 

3352 min_throughput = self._get_min_throughput( 

3353 new_active_replica['size'], qos_specs) 

3354 self._client.qos_policy_group_modify( 

3355 new_active_replica_qos_policy, max_throughput, 

3356 min_throughput) 

3357 vserver_client.set_qos_policy_group_for_volume( 

3358 volume_name_on_backend, new_active_replica_qos_policy) 

3359 

3360 LOG.info("QoS policy applied successfully for promoted " 

3361 "replica: %s", new_active_replica['id']) 

3362 except Exception: 

3363 LOG.exception("Could not apply QoS to the promoted replica.") 

3364 

3365 def _convert_destination_replica_to_independent( 

3366 self, context, dm_session, orig_active_replica, replica, 

3367 access_rules, share_server=None, quiesce_wait_time=None): 

3368 """Breaks SnapMirror and allows r/w ops on the destination replica. 

3369 

3370 For promotion, the existing SnapMirror relationship must be broken 

3371 and access rules have to be granted to the broken off replica to 

3372 use it as an independent share. 

3373 

3374 :param context: Request Context 

3375 :param dm_session: Data motion object for SnapMirror operations 

3376 :param orig_active_replica: Original SnapMirror source 

3377 :param replica: Replica to promote to SnapMirror source 

3378 :param access_rules: Access rules to apply to the replica 

3379 :param share_server: ShareServer class instance of replica 

3380 :param quiesce_wait_time: Wait time in seconds for snapmirror quiesce 

3381 :return: Updated replica 

3382 """ 

3383 vserver, vserver_client = self._get_vserver(share_server=share_server) 

3384 share_name = self._get_backend_share_name(replica['id']) 

3385 

3386 try: 

3387 # 1. Start an update to try to get a last minute transfer before we 

3388 # quiesce and break 

3389 dm_session.update_snapmirror(orig_active_replica, replica) 

3390 except exception.StorageCommunicationException: 

3391 # Ignore any errors since the current source replica may be 

3392 # unreachable 

3393 pass 

3394 # 2. Break SnapMirror 

3395 dm_session.break_snapmirror(orig_active_replica, replica, 

3396 quiesce_wait_time=quiesce_wait_time) 

3397 

3398 # 3. Setup access rules 

3399 new_active_replica = replica.copy() 

3400 new_active_replica['export_locations'] = self._create_export( 

3401 new_active_replica, share_server, vserver, vserver_client) 

3402 

3403 helper = self._get_helper(replica) 

3404 helper.set_client(vserver_client) 

3405 try: 

3406 helper.update_access(replica, share_name, access_rules) 

3407 except Exception: 

3408 new_active_replica['access_rules_status'] = ( 

3409 constants.SHARE_INSTANCE_RULES_SYNCING) 

3410 else: 

3411 new_active_replica['access_rules_status'] = constants.STATUS_ACTIVE 

3412 

3413 new_active_replica['replica_state'] = constants.REPLICA_STATE_ACTIVE 

3414 

3415 # 4. Set File system size fixed to false 

3416 vserver_client.set_volume_filesys_size_fixed(share_name, 

3417 filesys_size_fixed=False) 

3418 

3419 return new_active_replica 

3420 

3421 def _safe_change_replica_source(self, dm_session, replica, 

3422 orig_source_replica, 

3423 new_source_replica, replica_list, 

3424 is_dr, access_rules, 

3425 share_server=None, is_flexgroup=False): 

3426 """Attempts to change the SnapMirror source to new source. 

3427 

3428 If the attempt fails, 'replica_state' is set to 'error'. 

3429 

3430 :param dm_session: Data motion object for SnapMirror operations. 

3431 :param replica: Replica that requires a change of source. 

3432 :param orig_source_replica: Original SnapMirror source volume. 

3433 :param new_source_replica: New SnapMirror source volume. 

3434 :param is_dr: the replication type is dr, otherwise it is readable. 

3435 :param access_rules: share access rules to be applied. 

3436 :param share_server: share server. 

3437 :param is_flexgroup: the replication is over FlexGroup style 

3438 :return: Updated replica. 

3439 """ 

3440 try: 

3441 dm_session.change_snapmirror_source(replica, 

3442 orig_source_replica, 

3443 new_source_replica, 

3444 replica_list, 

3445 is_flexgroup=is_flexgroup) 

3446 except exception.StorageCommunicationException: 

3447 replica['status'] = constants.STATUS_ERROR 

3448 replica['replica_state'] = constants.STATUS_ERROR 

3449 if is_dr: 3449 ↛ 3451line 3449 didn't jump to line 3451 because the condition on line 3449 was always true

3450 replica['export_locations'] = [] 

3451 msg = ("Failed to change replica (%s) to a SnapMirror " 

3452 "destination. Replica backend is unreachable.") 

3453 

3454 LOG.exception(msg, replica['id']) 

3455 return replica 

3456 except netapp_api.NaApiError: 

3457 replica['status'] = constants.STATUS_ERROR 

3458 replica['replica_state'] = constants.STATUS_ERROR 

3459 if is_dr: 3459 ↛ 3461line 3459 didn't jump to line 3461 because the condition on line 3459 was always true

3460 replica['export_locations'] = [] 

3461 msg = ("Failed to change replica (%s) to a SnapMirror " 

3462 "destination.") 

3463 LOG.exception(msg, replica['id']) 

3464 return replica 

3465 

3466 replica['replica_state'] = constants.REPLICA_STATE_OUT_OF_SYNC 

3467 replica['status'] = constants.STATUS_AVAILABLE 

3468 if is_dr: 

3469 replica['export_locations'] = [] 

3470 return replica 

3471 

3472 # NOTE(felipe_rodrigues): readable replica might be in an error 

3473 # state without mounting and export. Retries to recover it. 

3474 replica_volume_name, replica_vserver, replica_backend = ( 

3475 dm_session.get_backend_info_for_share(replica)) 

3476 replica_client = data_motion.get_client_for_backend( 

3477 replica_backend, vserver_name=replica_vserver) 

3478 

3479 try: 

3480 replica_config = data_motion.get_backend_configuration( 

3481 replica_backend) 

3482 dm_session.wait_for_mount_replica( 

3483 replica_client, replica_volume_name, 

3484 timeout=replica_config.netapp_mount_replica_timeout) 

3485 except netapp_api.NaApiError: 

3486 replica['status'] = constants.STATUS_ERROR 

3487 replica['replica_state'] = constants.STATUS_ERROR 

3488 msg = "Failed to mount readable replica (%s)." 

3489 LOG.exception(msg, replica['id']) 

3490 return replica 

3491 

3492 try: 

3493 replica_cluster_client = self._get_api_client_for_backend( 

3494 replica_backend) 

3495 replica['export_locations'] = self._create_export( 

3496 replica, share_server, replica_vserver, replica_client, 

3497 cluster_client=replica_cluster_client, 

3498 replica=True) 

3499 except netapp_api.NaApiError: 

3500 replica['status'] = constants.STATUS_ERROR 

3501 replica['replica_state'] = constants.STATUS_ERROR 

3502 msg = "Failed to create export for readable replica (%s)." 

3503 LOG.exception(msg, replica['id']) 

3504 return replica 

3505 

3506 helper = self._get_helper(replica) 

3507 helper.set_client(replica_client) 

3508 try: 

3509 helper.update_access( 

3510 replica, replica_volume_name, access_rules) 

3511 except Exception: 

3512 replica['access_rules_status'] = ( 

3513 constants.SHARE_INSTANCE_RULES_ERROR) 

3514 else: 

3515 replica['access_rules_status'] = constants.STATUS_ACTIVE 

3516 

3517 return replica 

3518 

3519 def create_replicated_snapshot(self, context, replica_list, 

3520 snapshot_instances, share_server=None): 

3521 active_replica = self.find_active_replica(replica_list) 

3522 active_snapshot = [x for x in snapshot_instances 

3523 if x['share_id'] == active_replica['id']][0] 

3524 snapshot_name = self._get_backend_snapshot_name(active_snapshot['id']) 

3525 

3526 self.create_snapshot(context, active_snapshot, 

3527 share_server=share_server) 

3528 

3529 active_snapshot['status'] = constants.STATUS_AVAILABLE 

3530 active_snapshot['provider_location'] = snapshot_name 

3531 snapshots = [active_snapshot] 

3532 instances = zip(sorted(replica_list, 

3533 key=lambda x: x['id']), 

3534 sorted(snapshot_instances, 

3535 key=lambda x: x['share_id'])) 

3536 

3537 for replica, snapshot in instances: 

3538 if snapshot['id'] != active_snapshot['id']: 

3539 snapshot['provider_location'] = snapshot_name 

3540 snapshots.append(snapshot) 

3541 dm_session = data_motion.DataMotionSession() 

3542 if replica.get('host'): 

3543 try: 

3544 dm_session.update_snapmirror(active_replica, 

3545 replica) 

3546 except netapp_api.NaApiError as e: 

3547 not_initialized = 'not initialized' 

3548 if (e.code != netapp_api.EOBJECTNOTFOUND and 

3549 not_initialized not in e.message): 

3550 raise 

3551 return snapshots 

3552 

3553 def delete_replicated_snapshot(self, context, replica_list, 

3554 snapshot_instances, share_server=None): 

3555 active_replica = self.find_active_replica(replica_list) 

3556 active_snapshot = [x for x in snapshot_instances 

3557 if x['share_id'] == active_replica['id']][0] 

3558 

3559 self.delete_snapshot(context, active_snapshot, 

3560 share_server=share_server, 

3561 snapshot_name=active_snapshot['provider_location'] 

3562 ) 

3563 active_snapshot['status'] = constants.STATUS_DELETED 

3564 instances = zip(sorted(replica_list, 

3565 key=lambda x: x['id']), 

3566 sorted(snapshot_instances, 

3567 key=lambda x: x['share_id'])) 

3568 

3569 for replica, snapshot in instances: 

3570 if snapshot['id'] != active_snapshot['id']: 

3571 dm_session = data_motion.DataMotionSession() 

3572 if replica.get('host'): 

3573 try: 

3574 dm_session.update_snapmirror(active_replica, replica) 

3575 except netapp_api.NaApiError as e: 

3576 not_initialized = 'not initialized' 

3577 if (e.code != netapp_api.EOBJECTNOTFOUND and 

3578 not_initialized not in e.message): 

3579 raise 

3580 

3581 return [active_snapshot] 

3582 

3583 def update_replicated_snapshot(self, replica_list, share_replica, 

3584 snapshot_instances, snapshot_instance, 

3585 share_server=None): 

3586 active_replica = self.find_active_replica(replica_list) 

3587 vserver, vserver_client = self._get_vserver(share_server=share_server) 

3588 share_name = self._get_backend_share_name( 

3589 snapshot_instance['share_id']) 

3590 snapshot_name = snapshot_instance.get('provider_location') 

3591 # NOTE(ameade): If there is no provider location, 

3592 # then grab from active snapshot instance 

3593 if snapshot_name is None: 

3594 active_snapshot = [x for x in snapshot_instances 

3595 if x['share_id'] == active_replica['id']][0] 

3596 snapshot_name = active_snapshot.get('provider_location') 

3597 if not snapshot_name: 

3598 return 

3599 

3600 try: 

3601 snapshot_exists = vserver_client.snapshot_exists(snapshot_name, 

3602 share_name) 

3603 except exception.SnapshotUnavailable: 

3604 # The volume must still be offline 

3605 return 

3606 

3607 if (snapshot_exists and 

3608 snapshot_instance['status'] == constants.STATUS_CREATING): 

3609 return { 

3610 'status': constants.STATUS_AVAILABLE, 

3611 'provider_location': snapshot_name, 

3612 } 

3613 elif (not snapshot_exists and 

3614 snapshot_instance['status'] == constants.STATUS_DELETING): 

3615 raise exception.SnapshotResourceNotFound( 

3616 name=snapshot_instance.get('provider_location')) 

3617 

3618 dm_session = data_motion.DataMotionSession() 

3619 try: 

3620 dm_session.update_snapmirror(active_replica, share_replica) 

3621 except netapp_api.NaApiError as e: 

3622 # ignore exception in case the relationship does not exist or it 

3623 # is not completely initialized yet. 

3624 not_initialized = 'not initialized' 

3625 if (e.code != netapp_api.EOBJECTNOTFOUND and 

3626 not_initialized not in e.message): 

3627 raise 

3628 

3629 def revert_to_replicated_snapshot(self, context, active_replica, 

3630 replica_list, active_replica_snapshot, 

3631 replica_snapshots, share_server=None): 

3632 """Reverts a replicated share (in place) to the specified snapshot.""" 

3633 vserver, vserver_client = self._get_vserver(share_server=share_server) 

3634 share_name = self._get_backend_share_name( 

3635 active_replica_snapshot['share_id']) 

3636 snapshot_name = ( 

3637 active_replica_snapshot.get('provider_location') or 

3638 self._get_backend_snapshot_name(active_replica_snapshot['id'])) 

3639 

3640 LOG.debug('Restoring snapshot %s', snapshot_name) 

3641 

3642 dm_session = data_motion.DataMotionSession() 

3643 non_active_replica_list = self._find_nonactive_replicas(replica_list) 

3644 

3645 # Ensure source snapshot exists 

3646 vserver_client.get_snapshot(share_name, snapshot_name) 

3647 

3648 # Break all mirrors 

3649 for replica in non_active_replica_list: 

3650 try: 

3651 dm_session.break_snapmirror( 

3652 active_replica, replica, mount=False) 

3653 except netapp_api.NaApiError as e: 

3654 if e.code != netapp_api.EOBJECTNOTFOUND: 

3655 raise 

3656 

3657 # Delete source SnapMirror snapshots that will prevent a snap restore 

3658 snapmirror_snapshot_names = vserver_client.list_snapmirror_snapshots( 

3659 share_name) 

3660 for snapmirror_snapshot_name in snapmirror_snapshot_names: 

3661 vserver_client.delete_snapshot( 

3662 share_name, snapmirror_snapshot_name, ignore_owners=True) 

3663 

3664 # Restore source snapshot of interest 

3665 vserver_client.restore_snapshot(share_name, snapshot_name) 

3666 

3667 # Reestablish mirrors 

3668 for replica in non_active_replica_list: 

3669 try: 

3670 dm_session.resync_snapmirror(active_replica, replica) 

3671 except netapp_api.NaApiError as e: 

3672 if e.code != netapp_api.EOBJECTNOTFOUND: 

3673 raise 

3674 

3675 def _is_readable_replica(self, replica): 

3676 """Check the replica type to find out if the replica is readable.""" 

3677 extra_specs = share_types.get_extra_specs_from_share(replica) 

3678 return (extra_specs.get('replication_type') == 

3679 constants.REPLICATION_TYPE_READABLE) 

3680 

3681 def _check_destination_vserver_for_vol_move(self, source_share, 

3682 source_vserver, 

3683 dest_share_server): 

3684 try: 

3685 destination_vserver, __ = self._get_vserver( 

3686 share_server=dest_share_server) 

3687 except exception.InvalidParameterValue: 

3688 destination_vserver = None 

3689 

3690 if source_vserver != destination_vserver: 

3691 msg = _("Cannot migrate %(shr)s efficiently from source " 

3692 "VServer %(src)s to destination VServer %(dest)s.") 

3693 msg_args = { 

3694 'shr': source_share['id'], 

3695 'src': source_vserver, 

3696 'dest': destination_vserver, 

3697 } 

3698 raise exception.NetAppException(msg % msg_args) 

3699 

3700 def migration_check_compatibility(self, context, source_share, 

3701 destination_share, share_server=None, 

3702 destination_share_server=None): 

3703 """Checks compatibility between self.host and destination host.""" 

3704 # We need cluster creds to perform an intra-cluster data motion 

3705 compatible = False 

3706 destination_host = destination_share['host'] 

3707 

3708 if self._have_cluster_creds: 

3709 try: 

3710 backend = share_utils.extract_host( 

3711 destination_host, level='backend_name') 

3712 destination_aggregate = share_utils.extract_host( 

3713 destination_host, level='pool') 

3714 source_pool = share_utils.extract_host( 

3715 source_share['host'], level='pool') 

3716 

3717 # Check the source/destination pool type, they must be FlexVol. 

3718 if self._is_flexgroup_pool(source_pool): 

3719 msg = _("Cannot migrate share because it resides on a " 

3720 "FlexGroup pool.") 

3721 raise exception.NetAppException(msg) 

3722 

3723 dm_session = data_motion.DataMotionSession() 

3724 if self.is_flexgroup_destination_host(destination_host, 

3725 dm_session): 

3726 msg = _("Cannot migrate share because the destination " 

3727 "pool is FlexGroup type.") 

3728 raise exception.NetAppException(msg) 

3729 

3730 # Check the source/destination pool SnapLock type, for 

3731 # ONTAP version < 9.10.1 

3732 if not self._is_snaplock_compatible_for_migration( 

3733 source_pool, 

3734 destination_aggregate 

3735 ): 

3736 msg = _("Cannot migrate share because the source and " 

3737 "destination pool support different SnapLock" 

3738 " type.") 

3739 raise exception.NetAppException(msg) 

3740 

3741 # Validate new extra-specs are valid on the destination 

3742 extra_specs = share_types.get_extra_specs_from_share( 

3743 destination_share) 

3744 self._check_extra_specs_validity( 

3745 destination_share, extra_specs) 

3746 # NOTE(dviroel): Check if the destination share-type has valid 

3747 # provisioning options. 

3748 provisioning_options = self._get_provisioning_options( 

3749 extra_specs) 

3750 qos_specs = self._get_normalized_qos_specs(extra_specs) 

3751 self.validate_provisioning_options_for_share( 

3752 provisioning_options, extra_specs=extra_specs, 

3753 qos_specs=qos_specs) 

3754 # Validate destination against fpolicy extra specs 

3755 fpolicy_ext_include = provisioning_options.get( 

3756 'fpolicy_extensions_to_include') 

3757 fpolicy_ext_exclude = provisioning_options.get( 

3758 'fpolicy_extensions_to_exclude') 

3759 fpolicy_file_operations = provisioning_options.get( 

3760 'fpolicy_file_operations') 

3761 if fpolicy_ext_include or fpolicy_ext_include: 

3762 __, dest_client = self._get_vserver( 

3763 share_server=destination_share_server) 

3764 fpolicies = dest_client.get_fpolicy_policies_status() 

3765 if len(fpolicies) >= self.FPOLICY_MAX_VSERVER_POLICIES: 3765 ↛ 3785line 3765 didn't jump to line 3785 because the condition on line 3765 was always true

3766 # If we can't create a new policy for the new share, 

3767 # we need to reuse an existing one. 

3768 reusable_scopes = self._find_reusable_fpolicy_scope( 

3769 destination_share, dest_client, 

3770 fpolicy_extensions_to_include=fpolicy_ext_include, 

3771 fpolicy_extensions_to_exclude=fpolicy_ext_exclude, 

3772 fpolicy_file_operations=fpolicy_file_operations) 

3773 if not reusable_scopes: 3773 ↛ 3774line 3773 didn't jump to line 3774 because the condition on line 3773 was never true

3774 msg = _( 

3775 "Cannot migrate share because the destination " 

3776 "reached its maximum number of policies.") 

3777 raise exception.NetAppException(msg) 

3778 # NOTE (felipe_rodrigues): NetApp only can migrate within the 

3779 # same server, so it does not need to check that the 

3780 # destination share has the same NFS config as the destination 

3781 # server. 

3782 

3783 # TODO(gouthamr): Check whether QoS min-throughputs can be 

3784 # honored on the destination aggregate when supported. 

3785 self._check_aggregate_extra_specs_validity( 

3786 destination_aggregate, extra_specs) 

3787 

3788 data_motion.get_backend_configuration(backend) 

3789 

3790 source_vserver, __ = self._get_vserver( 

3791 share_server=share_server) 

3792 share_volume = self._get_backend_share_name( 

3793 source_share['id']) 

3794 # NOTE(dviroel): If source and destination vservers are 

3795 # compatible for volume move, the provisioning option 

3796 # 'adaptive_qos_policy_group' will also be supported since the 

3797 # share will remain in the same vserver. 

3798 self._check_destination_vserver_for_vol_move( 

3799 source_share, source_vserver, destination_share_server) 

3800 

3801 encrypt_dest = self._get_dest_flexvol_encryption_value( 

3802 destination_share) 

3803 self._client.check_volume_move( 

3804 share_volume, source_vserver, destination_aggregate, 

3805 encrypt_destination=encrypt_dest) 

3806 

3807 except Exception: 

3808 msg = ("Cannot migrate share %(shr)s efficiently between " 

3809 "%(src)s and %(dest)s.") 

3810 msg_args = { 

3811 'shr': source_share['id'], 

3812 'src': source_share['host'], 

3813 'dest': destination_host, 

3814 } 

3815 LOG.exception(msg, msg_args) 

3816 else: 

3817 compatible = True 

3818 else: 

3819 msg = ("Cluster credentials have not been configured " 

3820 "with this share driver. Cannot perform volume move " 

3821 "operations.") 

3822 LOG.warning(msg) 

3823 

3824 compatibility = { 

3825 'compatible': compatible, 

3826 'writable': compatible, 

3827 'nondisruptive': compatible, 

3828 'preserve_metadata': compatible, 

3829 'preserve_snapshots': compatible, 

3830 } 

3831 

3832 return compatibility 

3833 

3834 def _move_volume_after_splitting(self, source_share, destination_share, 

3835 share_server=None, cutover_action='wait'): 

3836 retries = (self.configuration.netapp_start_volume_move_timeout / 5 

3837 or 1) 

3838 

3839 @manila_utils.retry(retry_param=exception.ShareBusyException, 

3840 interval=5, 

3841 retries=retries, 

3842 backoff_rate=1) 

3843 def try_move_volume(): 

3844 try: 

3845 self._move_volume(source_share, destination_share, 

3846 share_server, cutover_action) 

3847 except netapp_api.NaApiError as e: 

3848 undergoing_split = 'undergoing a clone split' 

3849 msg_args = {'id': source_share['id']} 

3850 if (e.code == netapp_api.EAPIERROR and 

3851 undergoing_split in e.message): 

3852 msg = _('The volume %(id)s is undergoing a clone split ' 

3853 'operation. Will retry the operation.') % msg_args 

3854 LOG.warning(msg) 

3855 raise exception.ShareBusyException(reason=msg) 

3856 else: 

3857 msg = _("Unable to perform move operation for the volume " 

3858 "%(id)s. Caught an unexpected error. Not " 

3859 "retrying.") % msg_args 

3860 raise exception.NetAppException(message=msg) 

3861 try: 

3862 try_move_volume() 

3863 except exception.ShareBusyException: 

3864 msg_args = {'id': source_share['id']} 

3865 msg = _("Unable to perform move operation for the volume %(id)s " 

3866 "because a clone split operation is still in progress. " 

3867 "Retries exhausted. Not retrying.") % msg_args 

3868 raise exception.NetAppException(message=msg) 

3869 

3870 def _move_volume(self, source_share, destination_share, share_server=None, 

3871 cutover_action='wait'): 

3872 # Intra-cluster migration 

3873 vserver, vserver_client = self._get_vserver(share_server=share_server) 

3874 share_volume = self._get_backend_share_name(source_share['id']) 

3875 destination_aggregate = share_utils.extract_host( 

3876 destination_share['host'], level='pool') 

3877 

3878 # If the destination's share type extra-spec for Flexvol encryption 

3879 # is different than the source's, then specify the volume-move 

3880 # operation to set the correct 'encrypt' attribute on the destination 

3881 # volume. 

3882 encrypt_dest = self._get_dest_flexvol_encryption_value( 

3883 destination_share) 

3884 

3885 self._client.start_volume_move( 

3886 share_volume, 

3887 vserver, 

3888 destination_aggregate, 

3889 cutover_action=cutover_action, 

3890 encrypt_destination=encrypt_dest) 

3891 

3892 msg = ("Began volume move operation of share %(shr)s from %(src)s " 

3893 "to %(dest)s.") 

3894 msg_args = { 

3895 'shr': source_share['id'], 

3896 'src': source_share['host'], 

3897 'dest': destination_share['host'], 

3898 } 

3899 LOG.info(msg, msg_args) 

3900 

3901 def migration_start(self, context, source_share, destination_share, 

3902 source_snapshots, snapshot_mappings, 

3903 share_server=None, destination_share_server=None): 

3904 """Begins data motion from source_share to destination_share.""" 

3905 self._move_volume(source_share, destination_share, share_server) 

3906 

3907 def _get_volume_move_status(self, source_share, share_server): 

3908 vserver, vserver_client = self._get_vserver(share_server=share_server) 

3909 share_volume = self._get_backend_share_name(source_share['id']) 

3910 status = self._client.get_volume_move_status(share_volume, vserver) 

3911 return status 

3912 

3913 def _check_volume_clone_split_completed(self, share, vserver_client): 

3914 share_volume = self._get_backend_share_name(share['id']) 

3915 return vserver_client.check_volume_clone_split_completed(share_volume) 

3916 

3917 def _get_dest_flexvol_encryption_value(self, destination_share): 

3918 dest_share_type_encrypted_val = share_types.get_share_type_extra_specs( 

3919 destination_share['share_type_id'], 

3920 'netapp_flexvol_encryption') 

3921 encrypt_destination = share_types.parse_boolean_extra_spec( 

3922 'netapp_flexvol_encryption', dest_share_type_encrypted_val) 

3923 

3924 return encrypt_destination 

3925 

3926 def _check_volume_move_completed(self, source_share, share_server): 

3927 """Check progress of volume move operation.""" 

3928 status = self._get_volume_move_status(source_share, share_server) 

3929 completed_phases = ( 

3930 'cutover_hard_deferred', 'cutover_soft_deferred', 

3931 'completed', 'success') 

3932 

3933 move_phase = status['phase'].lower() 

3934 if move_phase == 'failed': 

3935 msg_args = { 

3936 'shr': source_share['id'], 

3937 'reason': status['details'], 

3938 } 

3939 msg = _("Volume move operation for share %(shr)s failed. Reason: " 

3940 "%(reason)s") % msg_args 

3941 LOG.exception(msg) 

3942 raise exception.NetAppException(msg) 

3943 elif move_phase in completed_phases: 

3944 return True 

3945 

3946 return False 

3947 

3948 def migration_continue(self, context, source_share, destination_share, 

3949 source_snapshots, snapshot_mappings, 

3950 share_server=None, destination_share_server=None): 

3951 """Check progress of migration, try to repair data motion errors.""" 

3952 return self._check_volume_move_completed(source_share, share_server) 

3953 

3954 def _get_volume_move_progress(self, source_share, share_server): 

3955 status = self._get_volume_move_status(source_share, share_server) 

3956 

3957 # NOTE (gouthamr): If the volume move is waiting for a manual 

3958 # intervention to cut-over, the copy is done with respect to the 

3959 # user. Volume move copies the rest of the data before cut-over anyway. 

3960 if status['phase'] in ('cutover_hard_deferred', 

3961 'cutover_soft_deferred'): 

3962 status['percent-complete'] = 100 

3963 

3964 msg = ("Volume move status for share %(share)s: (State) %(state)s. " 

3965 "(Phase) %(phase)s. Details: %(details)s") 

3966 msg_args = { 

3967 'state': status['state'], 

3968 'details': status['details'], 

3969 'share': source_share['id'], 

3970 'phase': status['phase'], 

3971 } 

3972 LOG.info(msg, msg_args) 

3973 

3974 return { 

3975 'total_progress': status['percent-complete'] or 0, 

3976 'state': status['state'], 

3977 'estimated_completion_time': status['estimated-completion-time'], 

3978 'phase': status['phase'], 

3979 'details': status['details'], 

3980 } 

3981 

3982 def migration_get_progress(self, context, source_share, 

3983 destination_share, source_snapshots, 

3984 snapshot_mappings, share_server=None, 

3985 destination_share_server=None): 

3986 """Return detailed progress of the migration in progress.""" 

3987 return self._get_volume_move_progress(source_share, share_server) 

3988 

3989 def migration_cancel(self, context, source_share, destination_share, 

3990 source_snapshots, snapshot_mappings, 

3991 share_server=None, destination_share_server=None): 

3992 """Abort an ongoing migration.""" 

3993 vserver, vserver_client = self._get_vserver(share_server=share_server) 

3994 share_volume = self._get_backend_share_name(source_share['id']) 

3995 retries = (math.ceil( 

3996 self.configuration.netapp_migration_cancel_timeout / 5) or 1) 

3997 

3998 try: 

3999 self._get_volume_move_status(source_share, share_server) 

4000 except exception.NetAppException: 

4001 LOG.exception("Could not get volume move status.") 

4002 return 

4003 

4004 self._client.abort_volume_move(share_volume, vserver) 

4005 

4006 @manila_utils.retry(retry_param=exception.InUse, interval=5, 

4007 retries=retries, backoff_rate=1) 

4008 def wait_for_migration_cancel_complete(): 

4009 move_status = self._get_volume_move_status(source_share, 

4010 share_server) 

4011 if move_status['state'] == 'failed': 

4012 return 

4013 else: 

4014 msg = "Migration cancelation isn't finished yet." 

4015 raise exception.InUse(message=msg) 

4016 

4017 try: 

4018 wait_for_migration_cancel_complete() 

4019 except exception.InUse: 

4020 move_status = self._get_volume_move_status(source_share, 

4021 share_server) 

4022 msg_args = { 

4023 'share_move_state': move_status['state'] 

4024 } 

4025 msg = _("Migration cancellation was not successful. The share " 

4026 "migration state failed while transitioning from " 

4027 "%(share_move_state)s state to 'failed'. Retries " 

4028 "exhausted.") % msg_args 

4029 raise exception.NetAppException(message=msg) 

4030 except exception.NetAppException: 

4031 LOG.exception("Could not get volume move status.") 

4032 

4033 msg = ("Share volume move operation for share %(shr)s from host " 

4034 "%(src)s to %(dest)s was successfully aborted.") 

4035 msg_args = { 

4036 'shr': source_share['id'], 

4037 'src': source_share['host'], 

4038 'dest': destination_share['host'], 

4039 } 

4040 LOG.info(msg, msg_args) 

4041 

4042 def migration_complete(self, context, source_share, destination_share, 

4043 source_snapshots, snapshot_mappings, 

4044 share_server=None, destination_share_server=None): 

4045 """Initiate the cutover to destination share after move is complete.""" 

4046 vserver, vserver_client = self._get_vserver(share_server=share_server) 

4047 share_volume = self._get_backend_share_name(source_share['id']) 

4048 

4049 status = self._get_volume_move_status(source_share, share_server) 

4050 

4051 move_phase = status['phase'].lower() 

4052 if move_phase == 'completed': 

4053 LOG.debug("Volume move operation was already successfully " 

4054 "completed for share %(shr)s.", 

4055 {'shr': source_share['id']}) 

4056 elif move_phase in ('cutover_hard_deferred', 'cutover_soft_deferred'): 

4057 self._client.trigger_volume_move_cutover(share_volume, vserver) 

4058 self._wait_for_cutover_completion( 

4059 source_share, share_server) 

4060 else: 

4061 msg_args = { 

4062 'shr': source_share['id'], 

4063 'status': status['state'], 

4064 'phase': status['phase'], 

4065 'details': status['details'], 

4066 } 

4067 msg = _("Cannot complete volume move operation for share %(shr)s. " 

4068 "Current volume move status: %(status)s, phase: " 

4069 "%(phase)s. Details: %(details)s") % msg_args 

4070 LOG.exception(msg) 

4071 raise exception.NetAppException(msg) 

4072 

4073 new_share_volume_name = self._get_backend_share_name( 

4074 destination_share['id']) 

4075 vserver_client.set_volume_name(share_volume, new_share_volume_name) 

4076 

4077 # Modify volume properties per share type extra-specs 

4078 extra_specs = share_types.get_extra_specs_from_share( 

4079 destination_share) 

4080 extra_specs = self._remap_standard_boolean_extra_specs(extra_specs) 

4081 self._check_extra_specs_validity(destination_share, extra_specs) 

4082 provisioning_options = self._get_provisioning_options(extra_specs) 

4083 qos_policy_group_name = self._modify_or_create_qos_for_existing_share( 

4084 destination_share, extra_specs, vserver, vserver_client) 

4085 if qos_policy_group_name: 

4086 provisioning_options['qos_policy_group'] = qos_policy_group_name 

4087 else: 

4088 # Removing the QOS Policy on the migrated share as the 

4089 # new extra-spec for which this share is being migrated to 

4090 # does not specify any QOS settings. 

4091 provisioning_options['qos_policy_group'] = "none" 

4092 

4093 qos_policy_of_src_share = self._get_backend_qos_policy_group_name( 

4094 source_share['id']) 

4095 self._client.mark_qos_policy_group_for_deletion( 

4096 qos_policy_of_src_share) 

4097 

4098 snap_attributes = self._get_provisioning_options_for_snap_attributes( 

4099 vserver_client, new_share_volume_name) 

4100 provisioning_options.update(snap_attributes) 

4101 

4102 destination_aggregate = share_utils.extract_host( 

4103 destination_share['host'], level='pool') 

4104 

4105 # Modify volume to match extra specs 

4106 vserver_client.modify_volume(destination_aggregate, 

4107 new_share_volume_name, 

4108 **provisioning_options) 

4109 

4110 # Create or reuse fpolicy 

4111 fpolicy_ext_to_include = provisioning_options.get( 

4112 'fpolicy_extensions_to_include') 

4113 fpolicy_ext_to_exclude = provisioning_options.get( 

4114 'fpolicy_extensions_to_exclude') 

4115 if fpolicy_ext_to_include or fpolicy_ext_to_exclude: 

4116 self._create_fpolicy_for_share(destination_share, vserver, 

4117 vserver_client, 

4118 **provisioning_options) 

4119 # Delete old fpolicies if needed 

4120 self._delete_fpolicy_for_share(source_share, vserver, vserver_client) 

4121 

4122 msg = ("Volume move operation for share %(shr)s has completed " 

4123 "successfully. Share has been moved from %(src)s to " 

4124 "%(dest)s.") 

4125 msg_args = { 

4126 'shr': source_share['id'], 

4127 'src': source_share['host'], 

4128 'dest': destination_share['host'], 

4129 } 

4130 LOG.info(msg, msg_args) 

4131 

4132 # NOTE(gouthamr): For NFS nondisruptive migration, current export 

4133 # policy will not be cleared, the export policy will be renamed to 

4134 # match the name of the share. 

4135 # NOTE (caiquemello): For CIFS nondisruptive migration, current CIFS 

4136 # share cannot be renamed, so keep the previous CIFS share. 

4137 share_protocol = source_share['share_proto'].lower() 

4138 if share_protocol != 'cifs': 4138 ↛ 4143line 4138 didn't jump to line 4143 because the condition on line 4138 was always true

4139 export_locations = self._create_export( 

4140 destination_share, share_server, vserver, vserver_client, 

4141 clear_current_export_policy=False) 

4142 else: 

4143 export_locations = [] 

4144 for item in source_share['export_locations']: 

4145 export_locations.append( 

4146 { 

4147 'path': item['path'], 

4148 'is_admin_only': item['is_admin_only'], 

4149 'metadata': item['el_metadata'] 

4150 } 

4151 ) 

4152 

4153 # Sort the export locations to report preferred paths first 

4154 export_locations = self._sort_export_locations_by_preferred_paths( 

4155 export_locations) 

4156 

4157 src_snaps_dict = {s['id']: s for s in source_snapshots} 

4158 snapshot_updates = {} 

4159 

4160 for source_snap_id, destination_snap in snapshot_mappings.items(): 

4161 p_location = src_snaps_dict[source_snap_id]['provider_location'] 

4162 

4163 snapshot_updates.update( 

4164 {destination_snap['id']: {'provider_location': p_location}}) 

4165 

4166 return { 

4167 'export_locations': export_locations, 

4168 'snapshot_updates': snapshot_updates, 

4169 } 

4170 

4171 @na_utils.trace 

4172 def _modify_or_create_qos_for_existing_share(self, share, extra_specs, 

4173 vserver, vserver_client): 

4174 """Gets/Creates QoS policy for an existing FlexVol. 

4175 

4176 The share's assigned QoS policy is renamed and adjusted if the policy 

4177 is exclusive to the FlexVol. If the policy includes other workloads 

4178 besides the FlexVol, a new policy is created with the specs necessary. 

4179 """ 

4180 qos_specs = self._get_normalized_qos_specs(extra_specs) 

4181 if not qos_specs: 

4182 return 

4183 

4184 backend_share_name = self._get_backend_share_name(share['id']) 

4185 qos_policy_group_name = self._get_backend_qos_policy_group_name( 

4186 share['id']) 

4187 

4188 create_new_qos_policy_group = True 

4189 

4190 backend_volume = vserver_client.get_volume( 

4191 backend_share_name) 

4192 backend_volume_size = int( 

4193 math.ceil(float(backend_volume['size']) / units.Gi)) 

4194 

4195 LOG.debug("Checking for a pre-existing QoS policy group that " 

4196 "is exclusive to the volume %s.", backend_share_name) 

4197 

4198 # Does the volume have an exclusive QoS policy that we can rename? 

4199 if backend_volume['qos-policy-group-name'] is not None: 

4200 existing_qos_policy_group = self._client.qos_policy_group_get( 

4201 backend_volume['qos-policy-group-name']) 

4202 if existing_qos_policy_group['num-workloads'] == 1: 

4203 # Yay, can set max-throughput and rename 

4204 

4205 msg = ("Found pre-existing QoS policy %(policy)s and it is " 

4206 "exclusive to the volume %(volume)s. Modifying and " 

4207 "renaming this policy to %(new_policy)s.") 

4208 msg_args = { 

4209 'policy': backend_volume['qos-policy-group-name'], 

4210 'volume': backend_share_name, 

4211 'new_policy': qos_policy_group_name, 

4212 } 

4213 LOG.debug(msg, msg_args) 

4214 

4215 max_throughput = self._get_max_throughput( 

4216 backend_volume_size, qos_specs) 

4217 min_throughput = self._get_min_throughput( 

4218 backend_volume_size, qos_specs) 

4219 if (existing_qos_policy_group['max-throughput'] 

4220 != max_throughput or 

4221 existing_qos_policy_group['min-throughput'] 

4222 != min_throughput): 

4223 self._client.qos_policy_group_modify( 

4224 backend_volume['qos-policy-group-name'], 

4225 max_throughput, min_throughput) 

4226 self._client.qos_policy_group_rename( 

4227 backend_volume['qos-policy-group-name'], 

4228 qos_policy_group_name) 

4229 create_new_qos_policy_group = False 

4230 

4231 if create_new_qos_policy_group: 

4232 share_obj = { 

4233 'size': backend_volume_size, 

4234 'id': share['id'], 

4235 } 

4236 LOG.debug("No existing QoS policy group found for " 

4237 "volume. Creating a new one with name %s.", 

4238 qos_policy_group_name) 

4239 self._create_qos_policy_group(share_obj, vserver, qos_specs, 

4240 vserver_client=vserver_client) 

4241 return qos_policy_group_name 

4242 

4243 def _wait_for_cutover_completion(self, source_share, share_server): 

4244 

4245 retries = (self.configuration.netapp_volume_move_cutover_timeout / 5 

4246 or 1) 

4247 

4248 @manila_utils.retry(retry_param=exception.ShareBusyException, 

4249 interval=5, 

4250 retries=retries, 

4251 backoff_rate=1) 

4252 def check_move_completion(): 

4253 status = self._get_volume_move_status(source_share, share_server) 

4254 if status['phase'].lower() != 'completed': 

4255 msg_args = { 

4256 'shr': source_share['id'], 

4257 'phs': status['phase'], 

4258 } 

4259 msg = _('Volume move operation for share %(shr)s is not ' 

4260 'complete. Current Phase: %(phs)s. ' 

4261 'Retrying.') % msg_args 

4262 LOG.warning(msg) 

4263 raise exception.ShareBusyException(reason=msg) 

4264 

4265 try: 

4266 check_move_completion() 

4267 except exception.ShareBusyException: 

4268 msg = _("Volume move operation did not complete after cut-over " 

4269 "was triggered. Retries exhausted. Not retrying.") 

4270 raise exception.NetAppException(message=msg) 

4271 

4272 def get_backend_info(self, context): 

4273 snapdir_visibility = self.configuration.netapp_reset_snapdir_visibility 

4274 return { 

4275 'snapdir_visibility': snapdir_visibility, 

4276 } 

4277 

4278 def ensure_shares(self, context, shares): 

4279 cfg_snapdir = self.configuration.netapp_reset_snapdir_visibility 

4280 hide_snapdir = self.HIDE_SNAPDIR_CFG_MAP[cfg_snapdir.lower()] 

4281 if hide_snapdir is not None: 

4282 for share in shares: 

4283 share_server = share.get('share_server') 

4284 vserver, vserver_client = self._get_vserver( 

4285 share_server=share_server) 

4286 share_name = self._get_backend_share_name(share['id']) 

4287 self._apply_snapdir_visibility( 

4288 hide_snapdir, share_name, vserver_client) 

4289 

4290 def get_share_status(self, share, share_server=None): 

4291 if share['status'] == constants.STATUS_CREATING_FROM_SNAPSHOT: 

4292 return self._update_create_from_snapshot_status(share, 

4293 share_server) 

4294 else: 

4295 LOG.warning("Caught an unexpected share status '%s' during share " 

4296 "status update routine. Skipping.", share['status']) 

4297 

4298 def volume_rehost(self, share, src_vserver, dest_vserver): 

4299 volume_name = self._get_backend_share_name(share['id']) 

4300 msg = ("Rehosting volume of share %(shr)s from vserver %(src)s " 

4301 "to vserver %(dest)s.") 

4302 msg_args = { 

4303 'shr': share['id'], 

4304 'src': src_vserver, 

4305 'dest': dest_vserver, 

4306 } 

4307 LOG.info(msg, msg_args) 

4308 self._client.rehost_volume(volume_name, src_vserver, dest_vserver) 

4309 

4310 def _rehost_and_mount_volume(self, share, src_vserver, src_vserver_client, 

4311 dest_vserver, dest_vserver_client): 

4312 volume_name = self._get_backend_share_name(share['id']) 

4313 mount_point_name = share.get('mount_point_name') 

4314 # Unmount volume in the source vserver: 

4315 src_vserver_client.unmount_volume(volume_name) 

4316 # Rehost the volume 

4317 self.volume_rehost(share, src_vserver, dest_vserver) 

4318 # Mount the volume on the destination vserver 

4319 dest_vserver_client.mount_volume(volume_name, mount_point_name) 

4320 

4321 def _check_capacity_compatibility(self, pools, thin_provision, size): 

4322 """Check if the size requested is suitable for the available pools""" 

4323 

4324 backend_free_capacity = 0.0 

4325 

4326 for pool in pools: 

4327 if "unknown" in (pool['free_capacity_gb'], 

4328 pool['total_capacity_gb']): 

4329 return False 

4330 reserved = float(pool['reserved_percentage']) / 100 

4331 

4332 total_pool_free = math.floor( 

4333 pool['free_capacity_gb'] - 

4334 pool['total_capacity_gb'] * reserved) 

4335 

4336 if thin_provision: 

4337 # If thin provision is enabled it's necessary recalculate the 

4338 # total_pool_free considering the max over subscription ratio 

4339 # for each pool. After summing the free space for each pool we 

4340 # have the total backend free capacity to compare with the 

4341 # requested size. 

4342 if pool['max_over_subscription_ratio'] >= 1: 4342 ↛ 4346line 4342 didn't jump to line 4346 because the condition on line 4342 was always true

4343 total_pool_free = math.floor( 

4344 total_pool_free * pool['max_over_subscription_ratio']) 

4345 

4346 backend_free_capacity += total_pool_free 

4347 

4348 return size <= backend_free_capacity 

4349 

4350 def _find_reusable_fpolicy_scope( 

4351 self, share, vserver_client, fpolicy_extensions_to_include=None, 

4352 fpolicy_extensions_to_exclude=None, fpolicy_file_operations=None, 

4353 shares_to_include=None): 

4354 """Searches a fpolicy scope that can be reused for a share.""" 

4355 protocols = ( 

4356 ['nfsv3', 'nfsv4'] if share['share_proto'].lower() == 'nfs' 

4357 else ['cifs']) 

4358 protocols.sort() 

4359 

4360 requested_ext_to_include = [] 

4361 if fpolicy_extensions_to_include: 4361 ↛ 4366line 4361 didn't jump to line 4366 because the condition on line 4361 was always true

4362 requested_ext_to_include = na_utils.convert_string_to_list( 

4363 fpolicy_extensions_to_include) 

4364 requested_ext_to_include.sort() 

4365 

4366 requested_ext_to_exclude = [] 

4367 if fpolicy_extensions_to_exclude: 4367 ↛ 4372line 4367 didn't jump to line 4372 because the condition on line 4367 was always true

4368 requested_ext_to_exclude = na_utils.convert_string_to_list( 

4369 fpolicy_extensions_to_exclude) 

4370 requested_ext_to_exclude.sort() 

4371 

4372 if fpolicy_file_operations: 4372 ↛ 4376line 4372 didn't jump to line 4376 because the condition on line 4372 was always true

4373 requested_file_operations = na_utils.convert_string_to_list( 

4374 fpolicy_file_operations) 

4375 else: 

4376 requested_file_operations = ( 

4377 self.configuration.netapp_fpolicy_default_file_operations) 

4378 requested_file_operations.sort() 

4379 

4380 share_name = self._get_backend_share_name(share['id']) 

4381 reusable_scopes = vserver_client.get_fpolicy_scopes( 

4382 share_name=share_name, 

4383 extensions_to_exclude=fpolicy_extensions_to_exclude, 

4384 extensions_to_include=fpolicy_extensions_to_include, 

4385 shares_to_include=shares_to_include) 

4386 # NOTE(dviroel): get_fpolicy_scopes can return scopes that don't match 

4387 # the exact requirements. 

4388 for scope in reusable_scopes[:]: 

4389 scope_ext_include = copy.deepcopy( 

4390 scope.get('file-extensions-to-include', [])) 

4391 scope_ext_include.sort() 

4392 scope_ext_exclude = copy.deepcopy( 

4393 scope.get('file-extensions-to-exclude', [])) 

4394 scope_ext_exclude.sort() 

4395 

4396 if scope_ext_include != requested_ext_to_include: 4396 ↛ 4397line 4396 didn't jump to line 4397 because the condition on line 4396 was never true

4397 LOG.debug( 

4398 "Excluding scope for policy %(policy_name)s because " 

4399 "it doesn't match 'file-extensions-to-include' " 

4400 "configuration.", {'policy_name': scope['policy-name']}) 

4401 reusable_scopes.remove(scope) 

4402 elif scope_ext_exclude != requested_ext_to_exclude: 4402 ↛ 4403line 4402 didn't jump to line 4403 because the condition on line 4402 was never true

4403 LOG.debug( 

4404 "Excluding scope for policy %(policy_name)s because " 

4405 "it doesn't match 'file-extensions-to-exclude' " 

4406 "configuration.", {'policy_name': scope['policy-name']}) 

4407 reusable_scopes.remove(scope) 

4408 

4409 for scope in reusable_scopes[:]: 

4410 fpolicy_policy = vserver_client.get_fpolicy_policies( 

4411 share_name=share_name, 

4412 policy_name=scope['policy-name']) 

4413 for policy in fpolicy_policy: 

4414 event_names = copy.deepcopy(policy.get('events', [])) 

4415 match_event_protocols = [] 

4416 for event_name in event_names: 

4417 events = vserver_client.get_fpolicy_events( 

4418 share_name=share_name, 

4419 event_name=event_name) 

4420 for event in events: 

4421 event_file_ops = copy.deepcopy( 

4422 event.get('file-operations', [])) 

4423 event_file_ops.sort() 

4424 if event_file_ops == requested_file_operations: 4424 ↛ 4420line 4424 didn't jump to line 4420 because the condition on line 4424 was always true

4425 # Event has same file operations 

4426 match_event_protocols.append(event.get('protocol')) 

4427 match_event_protocols.sort() 

4428 

4429 if match_event_protocols != protocols: 4429 ↛ 4430line 4429 didn't jump to line 4430 because the condition on line 4429 was never true

4430 LOG.debug( 

4431 "Excluding scope for policy %(policy_name)s because " 

4432 "it doesn't match 'events' configuration of file " 

4433 "operations per protocol.", 

4434 {'policy_name': scope['policy-name']}) 

4435 reusable_scopes.remove(scope) 

4436 

4437 return reusable_scopes[0] if reusable_scopes else None 

4438 

4439 def _create_fpolicy_for_share( 

4440 self, share, vserver, vserver_client, 

4441 fpolicy_extensions_to_include=None, 

4442 fpolicy_extensions_to_exclude=None, fpolicy_file_operations=None, 

4443 **options): 

4444 """Creates or reuses a fpolicy for a new share.""" 

4445 share_name = self._get_backend_share_name(share['id']) 

4446 

4447 @manila_utils.synchronized('netapp-fpolicy-%s' % vserver, 

4448 external=True) 

4449 def _create_fpolicy_with_lock(): 

4450 nonlocal share_name 

4451 # 1. Try to reuse an existing FPolicy if matches the same 

4452 # requirements 

4453 reusable_scope = self._find_reusable_fpolicy_scope( 

4454 share, vserver_client, 

4455 fpolicy_extensions_to_include=fpolicy_extensions_to_include, 

4456 fpolicy_extensions_to_exclude=fpolicy_extensions_to_exclude, 

4457 fpolicy_file_operations=fpolicy_file_operations) 

4458 

4459 if reusable_scope: 

4460 shares_to_include = copy.deepcopy( 

4461 reusable_scope.get('shares-to-include')) 

4462 shares_to_include.append(share_name) 

4463 # Add the new share to the existing policy scope 

4464 vserver_client.modify_fpolicy_scope( 

4465 share_name, 

4466 reusable_scope.get('policy-name'), 

4467 shares_to_include=shares_to_include) 

4468 

4469 LOG.debug("Share %(share_id)s was added to an existing " 

4470 "fpolicy scope.", {'share_id': share['id']}) 

4471 return 

4472 

4473 # 2. Since we can't reuse any scope, start creating a new fpolicy 

4474 protocols = ( 

4475 ['nfsv3', 'nfsv4'] if share['share_proto'].lower() == 'nfs' 

4476 else ['cifs']) 

4477 

4478 if fpolicy_file_operations: 4478 ↛ 4482line 4478 didn't jump to line 4482 because the condition on line 4478 was always true

4479 file_operations = na_utils.convert_string_to_list( 

4480 fpolicy_file_operations) 

4481 else: 

4482 file_operations = ( 

4483 self.configuration.netapp_fpolicy_default_file_operations) 

4484 

4485 # NOTE(dviroel): ONTAP limit of fpolicies for a vserser is 10. 

4486 # DHSS==True backends can create new share servers or fail earlier 

4487 # in choose_share_server_for_share. 

4488 share_name = self._get_backend_share_name(share['id']) 

4489 vserver_policies = (vserver_client.get_fpolicy_policies_status( 

4490 share_name=share_name)) 

4491 if len(vserver_policies) >= self.FPOLICY_MAX_VSERVER_POLICIES: 

4492 msg_args = {'share_id': share['id']} 

4493 msg = _("Cannot configure a new FPolicy for share " 

4494 "%(share_id)s. The maximum number of fpolicies was " 

4495 "already reached.") % msg_args 

4496 LOG.exception(msg) 

4497 raise exception.NetAppException(message=msg) 

4498 

4499 seq_number_list = [int(policy['sequence-number']) 

4500 for policy in vserver_policies] 

4501 available_seq_number = None 

4502 for number in range(1, self.FPOLICY_MAX_VSERVER_POLICIES + 1): 4502 ↛ 4507line 4502 didn't jump to line 4507 because the loop on line 4502 didn't complete

4503 if number not in seq_number_list: 4503 ↛ 4502line 4503 didn't jump to line 4502 because the condition on line 4503 was always true

4504 available_seq_number = number 

4505 break 

4506 

4507 events = [] 

4508 policy_name = self._get_backend_fpolicy_policy_name(share['id']) 

4509 try: 

4510 for protocol in protocols: 

4511 event_name = self._get_backend_fpolicy_event_name( 

4512 share['id'], protocol) 

4513 vserver_client.create_fpolicy_event(share_name, 

4514 event_name, 

4515 protocol, 

4516 file_operations) 

4517 events.append(event_name) 

4518 

4519 # 2. Create a fpolicy policy and assign a scope 

4520 vserver_client.create_fpolicy_policy_with_scope( 

4521 policy_name, share_name, events, 

4522 extensions_to_include=fpolicy_extensions_to_include, 

4523 extensions_to_exclude=fpolicy_extensions_to_exclude) 

4524 

4525 except Exception: 

4526 # NOTE(dviroel): Rollback fpolicy policy and events creation 

4527 # since they won't be linked to the share, which is made by 

4528 # the scope creation. 

4529 

4530 # Delete fpolicy policy 

4531 vserver_client.delete_fpolicy_policy(share_name, policy_name) 

4532 # Delete fpolicy events 

4533 for event in events: 

4534 vserver_client.delete_fpolicy_event(share_name, event) 

4535 

4536 msg = _("Failed to configure a FPolicy resources for share " 

4537 "%(share_id)s. ") % {'share_id': share['id']} 

4538 LOG.exception(msg) 

4539 raise exception.NetAppException(message=msg) 

4540 

4541 # 4. Enable fpolicy policy 

4542 vserver_client.enable_fpolicy_policy(share_name, policy_name, 

4543 available_seq_number) 

4544 

4545 _create_fpolicy_with_lock() 

4546 LOG.debug('A new fpolicy was successfully created and associated to ' 

4547 'share %(share_id)s', {'share_id': share['id']}) 

4548 

4549 def _delete_fpolicy_for_share(self, share, vserver, vserver_client): 

4550 """Delete all associated fpolicy resources from a share.""" 

4551 share_name = self._get_backend_share_name(share['id']) 

4552 

4553 @coordination.synchronized('netapp-fpolicy-%s' % vserver) 

4554 def _delete_fpolicy_with_lock(): 

4555 fpolicy_scopes = vserver_client.get_fpolicy_scopes( 

4556 share_name=share_name, 

4557 shares_to_include=[share_name]) 

4558 

4559 if fpolicy_scopes: 4559 ↛ exitline 4559 didn't return from function '_delete_fpolicy_with_lock' because the condition on line 4559 was always true

4560 shares_to_include = copy.copy( 

4561 fpolicy_scopes[0].get('shares-to-include')) 

4562 shares_to_include.remove(share_name) 

4563 

4564 policy_name = fpolicy_scopes[0].get('policy-name') 

4565 if shares_to_include: 

4566 vserver_client.modify_fpolicy_scope( 

4567 share_name, policy_name, 

4568 shares_to_include=shares_to_include) 

4569 else: 

4570 # Delete an empty fpolicy 

4571 # 1. Disable fpolicy policy 

4572 vserver_client.disable_fpolicy_policy(policy_name) 

4573 # 2. Retrieve fpoliocy info 

4574 fpolicy_policies = vserver_client.get_fpolicy_policies( 

4575 share_name=share_name, 

4576 policy_name=policy_name) 

4577 # 3. Delete fpolicy scope 

4578 vserver_client.delete_fpolicy_scope(policy_name) 

4579 # 4. Delete fpolicy policy 

4580 vserver_client.delete_fpolicy_policy(share_name, 

4581 policy_name) 

4582 # 5. Delete fpolicy events 

4583 for policy in fpolicy_policies: 

4584 events = policy.get('events', []) 

4585 for event in events: 

4586 vserver_client.delete_fpolicy_event(event) 

4587 

4588 _delete_fpolicy_with_lock() 

4589 

4590 @na_utils.trace 

4591 def _initialize_flexgroup_pools(self, cluster_aggr_set): 

4592 """Initialize the FlexGroup pool map.""" 

4593 

4594 flexgroup_pools = self.configuration.safe_get('netapp_flexgroup_pools') 

4595 if not self.configuration.netapp_enable_flexgroup and flexgroup_pools: 

4596 msg = _('Invalid configuration for FlexGroup: ' 

4597 'netapp_enable_flexgroup option must be True to configure ' 

4598 'its custom pools using netapp_flexgroup_pools.') 

4599 raise exception.NetAppException(msg) 

4600 elif not self.configuration.netapp_enable_flexgroup: 

4601 return 

4602 

4603 if not self._client.is_flexgroup_supported(): 

4604 msg = _('FlexGroup pool is only supported with ONTAP version ' 

4605 'greater than or equal to 9.8.') 

4606 raise exception.NetAppException(msg) 

4607 

4608 if flexgroup_pools: 

4609 self._flexgroup_pools = na_utils.parse_flexgroup_pool_config( 

4610 flexgroup_pools, cluster_aggr_set=cluster_aggr_set, check=True) 

4611 self._is_flexgroup_auto = False 

4612 else: 

4613 self._flexgroup_pools[na_utils.FLEXGROUP_DEFAULT_POOL_NAME] = ( 

4614 sorted(cluster_aggr_set)) 

4615 self._is_flexgroup_auto = True 

4616 

4617 @na_utils.trace 

4618 def _get_flexgroup_pool_name(self, aggr_list): 

4619 """Gets the FlexGroup pool name that has the given aggregate list.""" 

4620 

4621 # for FlexGroup auto provisioned is over the same single pool. 

4622 if self._is_flexgroup_auto: 

4623 return na_utils.FLEXGROUP_DEFAULT_POOL_NAME 

4624 

4625 pool_name = '' 

4626 aggr_list.sort() 

4627 for pool, aggr_pool in self._flexgroup_pools.items(): 

4628 if aggr_pool == aggr_list: 

4629 pool_name = pool 

4630 break 

4631 

4632 return pool_name 

4633 

4634 @na_utils.trace 

4635 def _is_flexgroup_pool(self, pool_name): 

4636 """Check if the given pool name is a FlexGroup pool.""" 

4637 

4638 return pool_name in self._flexgroup_pools 

4639 

4640 @na_utils.trace 

4641 def _get_flexgroup_aggregate_list(self, pool_name): 

4642 """Returns the aggregate list of a given FlexGroup pool name.""" 

4643 

4644 return (self._flexgroup_pools[pool_name] 

4645 if self._is_flexgroup_pool(pool_name) else []) 

4646 

4647 @staticmethod 

4648 def _is_flexgroup_share(vserver_client, share_name): 

4649 """Determines if a given share name is a FlexGroup style or not.""" 

4650 

4651 try: 

4652 return vserver_client.is_flexgroup_volume(share_name) 

4653 except (netapp_api.NaApiError, 

4654 exception.StorageResourceNotFound, 

4655 exception.NetAppException): 

4656 msg = _('Could not determine if the volume %s is a FlexGroup or ' 

4657 'a FlexVol style.') 

4658 LOG.exception(msg, share_name) 

4659 raise exception.ShareNotFound(share_id=share_name) 

4660 

4661 @na_utils.trace 

4662 def is_flexvol_pool_configured(self): 

4663 """Determines if the driver has FlexVol pools.""" 

4664 

4665 return (not self.configuration.netapp_enable_flexgroup or 

4666 not self.configuration.netapp_flexgroup_pool_only) 

4667 

4668 @na_utils.trace 

4669 def _get_minimum_flexgroup_size(self, pool_name): 

4670 """Returns the minimum size for a FlexGroup share inside a pool.""" 

4671 

4672 aggr_list = set(self._get_flexgroup_aggregate_list(pool_name)) 

4673 return self.FLEXGROUP_MIN_SIZE_PER_AGGR * len(aggr_list) 

4674 

4675 @na_utils.trace 

4676 def is_flexgroup_destination_host(self, host, dm_session): 

4677 """Returns if the destination host is over a FlexGroup pool.""" 

4678 __, config = dm_session.get_backend_name_and_config_obj(host) 

4679 if not config.safe_get('netapp_enable_flexgroup'): 

4680 return False 

4681 

4682 flexgroup_pools = config.safe_get('netapp_flexgroup_pools') 

4683 pools = {} 

4684 if flexgroup_pools: 

4685 pools = na_utils.parse_flexgroup_pool_config(flexgroup_pools) 

4686 else: 

4687 pools[na_utils.FLEXGROUP_DEFAULT_POOL_NAME] = {} 

4688 

4689 pool_name = share_utils.extract_host(host, level='pool') 

4690 return pool_name in pools 

4691 

4692 @na_utils.trace 

4693 def create_backup(self, context, share_instance, backup, 

4694 share_server=None): 

4695 """Create backup for NetApp share""" 

4696 

4697 src_vserver, src_vserver_client = self._get_vserver( 

4698 share_server=share_server) 

4699 src_cluster = src_vserver_client.get_cluster_name() 

4700 src_vol = self._get_backend_share_name(share_instance['id']) 

4701 backup_options = backup.get('backup_options', {}) 

4702 backup_type = backup_options.get(Backup.BACKUP_TYPE.value) 

4703 

4704 # Check if valid backup type is provided 

4705 if not backup_type: 

4706 raise exception.BackupException("Driver needs a valid backup type" 

4707 " from command line or API.") 

4708 

4709 # check the backend is related to NetApp 

4710 try: 

4711 backup_config = data_motion.get_backup_configuration(backup_type) 

4712 except exception.BadConfigurationException: 

4713 msg = _("Could not find backup_type '%(backup_type)s' stanza" 

4714 " in config file") % {'backup_type': backup_type} 

4715 raise exception.BadConfigurationException(reason=msg) 

4716 backend_name = backup_config.safe_get(Backup.BACKEND_NAME.value) 

4717 try: 

4718 backend_config = data_motion.get_backend_configuration( 

4719 backend_name 

4720 ) 

4721 except exception.BadConfigurationException: 

4722 msg = _("Could not find backend '%(backend_name)s' stanza" 

4723 " in config file") % {'backend_name': backend_name} 

4724 raise exception.BadConfigurationException(reason=msg) 

4725 if (backend_config.safe_get("netapp_storage_family") 

4726 != 'ontap_cluster'): 

4727 err_msg = _("Wrong vendor backend '%s' is provided, provide" 

4728 " only NetApp backend.") % backend_name 

4729 raise exception.BackupException(err_msg) 

4730 

4731 # Check backend has compatible backup type 

4732 if (backend_config.safe_get("netapp_enabled_backup_types") is None or 

4733 backup_type not in backend_config.safe_get( 

4734 "netapp_enabled_backup_types")): 

4735 err_msg = _("Backup type '%(backup_type)s' is not compatible with" 

4736 " backend '%(backend_name)s'.") 

4737 msg_args = { 

4738 'backup_type': backup_type, 

4739 'backend_name': backend_name, 

4740 } 

4741 raise exception.BackupException(err_msg % msg_args) 

4742 

4743 # Verify that both source and destination cluster are peered 

4744 des_cluster_api_client = self._get_api_client_for_backend( 

4745 backend_name) 

4746 des_cluster = des_cluster_api_client.get_cluster_name() 

4747 if src_cluster != des_cluster: 4747 ↛ 4762line 4747 didn't jump to line 4762 because the condition on line 4747 was always true

4748 cluster_peer_info = self._client.get_cluster_peers( 

4749 remote_cluster_name=des_cluster) 

4750 if not cluster_peer_info: 

4751 err_msg = _("Source cluster '%(src_cluster)s' and destination" 

4752 " cluster '%(des_cluster)s' are not peered" 

4753 " backend %(backend_name)s.") 

4754 msg_args = { 

4755 'src_cluster': src_cluster, 

4756 'des_cluster': des_cluster, 

4757 'backend_name': backend_name 

4758 } 

4759 raise exception.NetAppException(err_msg % msg_args) 

4760 

4761 # Get the destination vserver and volume for relationship 

4762 source_path = f"{src_vserver}:{src_vol}" 

4763 snapmirror_info = src_vserver_client.get_snapmirror_destinations( 

4764 source_path=source_path 

4765 ) 

4766 if len(snapmirror_info) > 1: 

4767 msg = _("Source path '%(path)s' has more than one relationships." 

4768 " To create the share backup, delete the all source" 

4769 " volume's SnapMirror relationships using 'snapmirror'" 

4770 " ONTAP CLI or System Manager.") 

4771 msg_args = { 

4772 'path': source_path 

4773 } 

4774 raise exception.NetAppException(msg % msg_args) 

4775 elif len(snapmirror_info) == 1: 

4776 des_vserver, des_volume = self._get_destination_vserver_and_vol( 

4777 src_vserver_client, source_path, False) 

4778 des_vserver_client = self._get_api_client_for_backend( 

4779 backend_name, vserver=des_vserver) 

4780 else: 

4781 if (backup_config.safe_get(Backup.DES_VOLUME.value) and 

4782 not backup_config.safe_get(Backup.DES_VSERVER.value)): 

4783 msg = _("Could not find vserver name under stanza" 

4784 " '%(backup_type)s' in configuration while volume" 

4785 " name is provided.") 

4786 params = {"backup_type": backup_type} 

4787 raise exception.BadConfigurationException(reason=msg % params) 

4788 

4789 des_vserver = self._get_vserver_for_backup( 

4790 backup, share_server=share_server) 

4791 des_vserver_client = self._get_api_client_for_backend( 

4792 backend_name, vserver=des_vserver) 

4793 try: 

4794 des_volume = self._get_volume_for_backup(backup, 

4795 share_instance, 

4796 src_vserver_client, 

4797 des_vserver_client) 

4798 except (netapp_api.NaApiError, exception.NetAppException): 

4799 # Delete the vserver 

4800 if share_server: 4800 ↛ 4801line 4800 didn't jump to line 4801 because the condition on line 4800 was never true

4801 self._delete_backup_vserver(backup, des_vserver) 

4802 

4803 msg = _("Failed to create a volume in vserver '%(des_vserver)s" 

4804 "'") 

4805 msg_args = {'des_vserver': des_vserver} 

4806 raise exception.NetAppException(msg % msg_args) 

4807 

4808 if (src_vserver != des_vserver and 

4809 len(src_vserver_client.get_vserver_peers( 

4810 src_vserver, des_vserver)) == 0): 

4811 src_vserver_client.create_vserver_peer( 

4812 src_vserver, des_vserver, 

4813 peer_cluster_name=des_cluster) 

4814 if des_cluster is not None and src_cluster != des_cluster: 4814 ↛ 4817line 4814 didn't jump to line 4817 because the condition on line 4814 was always true

4815 des_vserver_client.accept_vserver_peer(des_vserver, 

4816 src_vserver) 

4817 des_snapshot_list = (des_vserver_client. 

4818 list_volume_snapshots(des_volume)) 

4819 snap_list_with_backup = [ 

4820 snap for snap in des_snapshot_list if snap.startswith( 

4821 Backup.SM_LABEL.value) 

4822 ] 

4823 if len(snap_list_with_backup) == 1: 4823 ↛ 4824line 4823 didn't jump to line 4824 because the condition on line 4823 was never true

4824 self._set_volume_has_backup_before(True) 

4825 policy_name = f"{Backup.SM_POLICY.value}_{share_instance['id']}" 

4826 try: 

4827 des_vserver_client.create_snapmirror_policy( 

4828 policy_name, 

4829 policy_type="vault", 

4830 discard_network_info=False, 

4831 snapmirror_label=Backup.SM_LABEL.value, 

4832 keep=250) 

4833 except netapp_api.NaApiError as e: 

4834 with excutils.save_and_reraise_exception() as exc_context: 

4835 if 'policy with this name already exists' in e.message: 

4836 exc_context.reraise = False 

4837 try: 

4838 des_vserver_client.create_snapmirror_vol( 

4839 src_vserver, 

4840 src_vol, 

4841 des_vserver, 

4842 des_volume, 

4843 "extended_data_protection", 

4844 policy=policy_name, 

4845 ) 

4846 db_session = data_motion.DataMotionSession() 

4847 db_session.initialize_and_wait_snapmirror_vol( 

4848 des_vserver_client, 

4849 src_vserver, 

4850 src_vol, 

4851 des_vserver, 

4852 des_volume, 

4853 timeout=backup_config.netapp_snapmirror_job_timeout 

4854 ) 

4855 except netapp_api.NaApiError: 

4856 self._resource_cleanup_for_backup(backup, 

4857 share_instance, 

4858 des_vserver, 

4859 des_volume, 

4860 share_server=share_server) 

4861 msg = _("SnapVault relationship creation or initialization" 

4862 " failed between source '%(source_vserver)s:" 

4863 "%(source_volume)s' and destination '%(des_vserver)s:" 

4864 "%(des_volume)s' for share id %(share_id)s.") 

4865 

4866 msg_args = { 

4867 'source_vserver': src_vserver, 

4868 'source_volume': src_vol, 

4869 'des_vserver': des_vserver, 

4870 'des_volume': des_volume, 

4871 'share_id': share_instance['share_id'] 

4872 } 

4873 raise exception.NetAppException(msg % msg_args) 

4874 

4875 snapshot_name = self._get_backup_snapshot_name(backup, 

4876 share_instance['id']) 

4877 src_vserver_client.create_snapshot( 

4878 src_vol, snapshot_name, 

4879 snapmirror_label=Backup.SM_LABEL.value) 

4880 

4881 # Update the SnapMirror relationship 

4882 des_vserver_client.update_snapmirror_vol(src_vserver, 

4883 src_vol, 

4884 des_vserver, 

4885 des_volume) 

4886 LOG.debug("SnapMirror relationship updated successfully.") 

4887 

4888 @na_utils.trace 

4889 def create_backup_continue(self, context, share_instance, backup, 

4890 share_server=None): 

4891 """Keep tracking the status of share backup""" 

4892 

4893 progress_status = {'total_progress': Backup.TOTAL_PROGRESS_ZERO.value} 

4894 src_vserver, src_vserver_client = self._get_vserver( 

4895 share_server=share_server) 

4896 src_vol_name = self._get_backend_share_name(share_instance['id']) 

4897 backend_name = self._get_backend(backup) 

4898 source_path = f"{src_vserver}:{src_vol_name}" 

4899 LOG.debug("SnapMirror source path: %s", source_path) 

4900 backup_type = backup.get(Backup.BACKUP_TYPE.value) 

4901 backup_config = data_motion.get_backup_configuration(backup_type) 

4902 

4903 # Make sure SnapMirror relationship is created 

4904 snapmirror_info = src_vserver_client.get_snapmirror_destinations( 

4905 source_path=source_path, 

4906 ) 

4907 if not snapmirror_info: 

4908 LOG.warning("There is no SnapMirror relationship available for" 

4909 " source path yet %s.", source_path) 

4910 return progress_status 

4911 

4912 des_vserver, des_vol = self._get_destination_vserver_and_vol( 

4913 src_vserver_client, 

4914 source_path, 

4915 ) 

4916 if not des_vserver or not des_vol: 

4917 raise exception.NetAppException("Not able to find vserver " 

4918 " and volume from SnpMirror" 

4919 " relationship.") 

4920 des_path = f"{des_vserver}:{des_vol}" 

4921 LOG.debug("SnapMirror destination path: %s", des_path) 

4922 

4923 des_vserver_client = self._get_api_client_for_backend( 

4924 backend_name, 

4925 vserver=des_vserver, 

4926 ) 

4927 snapmirror_info = des_vserver_client.get_snapmirrors( 

4928 source_path=source_path, dest_path=des_path) 

4929 if not snapmirror_info: 

4930 msg_args = { 

4931 'source_path': source_path, 

4932 'des_path': des_path, 

4933 } 

4934 msg = _("There is no SnapMirror relationship available for" 

4935 " source path '%(source_path)s' and destination path" 

4936 " '%(des_path)s' yet.") 

4937 LOG.warning(msg, msg_args) 

4938 return progress_status 

4939 LOG.debug("SnapMirror details %s:", snapmirror_info) 

4940 progress_status["total_progress"] = (Backup. 

4941 TOTAL_PROGRESS_HUNDRED.value) 

4942 if snapmirror_info[0].get("last-transfer-type") != "update": 

4943 progress_status["total_progress"] = (Backup. 

4944 TOTAL_PROGRESS_ZERO.value) 

4945 return progress_status 

4946 

4947 if snapmirror_info[0].get("relationship-status") != "idle": 

4948 progress_status = self._get_backup_progress_status( 

4949 des_vserver_client, snapmirror_info) 

4950 LOG.debug("Progress status: %(progress_status)s", 

4951 {'progress_status': progress_status}) 

4952 return progress_status 

4953 

4954 if snapmirror_info[0].get("is-healthy") == 'false': 

4955 self._resource_cleanup_for_backup(backup, 

4956 share_instance, 

4957 des_vserver, 

4958 des_vol, 

4959 share_server=share_server) 

4960 msg_args = { 

4961 'source_path': source_path, 

4962 'des_path': des_path, 

4963 } 

4964 msg = _("There is an issue with SnapMirror relationship with" 

4965 " source path '%(source_path)s' and destination path" 

4966 " '%(des_path)s'. Make sure destination volume is or was " 

4967 "not part of any other SnapMirror relationship.") 

4968 raise exception.NetAppException(message=msg % msg_args) 

4969 

4970 # Verify that snapshot is transferred to destination volume 

4971 snap_name = self._get_backup_snapshot_name(backup, 

4972 share_instance['id']) 

4973 self._verify_and_wait_for_snapshot_to_transfer(des_vserver_client, 

4974 des_vol, 

4975 snap_name) 

4976 LOG.debug("Snapshot '%(snap_name)s' transferred successfully to" 

4977 " destination.", {'snap_name': snap_name}) 

4978 # previously if volume was part of some relationship and if we delete 

4979 # all the backup of share then last snapshot will be left on 

4980 # destination volume, and we can't delete that snapshot due to ONTAP 

4981 # restriction. Next time if user create the first backup then we 

4982 # update the destination volume with latest backup and delete the last 

4983 # leftover snapshot 

4984 is_backup_completed = (progress_status["total_progress"] 

4985 == Backup.TOTAL_PROGRESS_HUNDRED.value) 

4986 if backup_config.get(Backup.DES_VOLUME.value) and is_backup_completed: 4986 ↛ 5004line 4986 didn't jump to line 5004 because the condition on line 4986 was always true

4987 snap_list_with_backup = self._get_des_volume_backup_snapshots( 

4988 des_vserver_client, 

4989 des_vol, share_instance['id'] 

4990 ) 

4991 LOG.debug("Snapshot list for backup %(snap_list)s.", 

4992 {'snap_list': snap_list_with_backup}) 

4993 if (self.is_volume_backup_before and 

4994 len(snap_list_with_backup) == 2): 

4995 if snap_name == snap_list_with_backup[0]: 4995 ↛ 4996line 4995 didn't jump to line 4996 because the condition on line 4995 was never true

4996 snap_to_delete = snap_list_with_backup[1] 

4997 else: 

4998 snap_to_delete = snap_list_with_backup[0] 

4999 self._set_volume_has_backup_before(False) 

5000 des_vserver_client.delete_snapshot(des_vol, snap_to_delete, 

5001 True) 

5002 LOG.debug("Previous snapshot %{snap_name}s deleted" 

5003 " successfully.", {'snap_name': snap_to_delete}) 

5004 return progress_status 

5005 

5006 @na_utils.trace 

5007 def restore_backup(self, context, backup, share_instance, 

5008 share_server=None): 

5009 """Restore the share backup""" 

5010 

5011 src_vserver, src_vserver_client = self._get_vserver( 

5012 share_server=share_server, 

5013 ) 

5014 src_vol_name = self._get_backend_share_name(share_instance['id']) 

5015 

5016 source_path = f"{src_vserver}:{src_vol_name}" 

5017 des_vserver, des_vol = self._get_destination_vserver_and_vol( 

5018 src_vserver_client, 

5019 source_path, 

5020 ) 

5021 if not des_vserver or not des_vol: 

5022 raise exception.NetAppException("Not able to find vserver " 

5023 " and volume from SnpMirror" 

5024 " relationship.") 

5025 backend_name = self._get_backend(backup) 

5026 des_vserver_client = self._get_api_client_for_backend( 

5027 backend_name, 

5028 vserver=des_vserver, 

5029 ) 

5030 vserver_client = src_vserver_client 

5031 backend_config = data_motion.get_backend_configuration( 

5032 backend_name) 

5033 if not backend_config.netapp_use_legacy_client: 5033 ↛ 5034line 5033 didn't jump to line 5034 because the condition on line 5033 was never true

5034 vserver_client = des_vserver_client 

5035 snap_name = self._get_backup_snapshot_name(backup, 

5036 share_instance['id']) 

5037 source_path = src_vserver + ":" + src_vol_name 

5038 des_path = des_vserver + ":" + des_vol 

5039 source_cluster = src_vserver_client.get_cluster_name() 

5040 vserver_client.snapmirror_restore_vol(source_path=des_path, 

5041 dest_path=source_path, 

5042 source_snapshot=snap_name, 

5043 des_cluster=source_cluster) 

5044 

5045 @na_utils.trace 

5046 def restore_backup_continue(self, context, backup, 

5047 share_instance, share_server=None): 

5048 """Keep checking the restore operation status""" 

5049 

5050 progress_status = {} 

5051 src_vserver, src_vserver_client = self._get_vserver( 

5052 share_server=share_server) 

5053 src_vol_name = self._get_backend_share_name(share_instance['id']) 

5054 

5055 source_path = f"{src_vserver}:{src_vol_name}" 

5056 snapmirror_info = src_vserver_client.get_snapmirrors( 

5057 dest_path=source_path, 

5058 ) 

5059 if snapmirror_info: 

5060 progress_status = { 

5061 "total_progress": Backup.TOTAL_PROGRESS_ZERO.value 

5062 } 

5063 return progress_status 

5064 LOG.debug("SnapMirror relationship of type RST is deleted.") 

5065 snap_name = self._get_backup_snapshot_name(backup, 

5066 share_instance['id']) 

5067 snapshot_list = src_vserver_client.list_volume_snapshots(src_vol_name) 

5068 for snapshot in snapshot_list: 

5069 if snap_name in snapshot: 

5070 progress_status["total_progress"] = ( 

5071 Backup.TOTAL_PROGRESS_HUNDRED.value) 

5072 return progress_status 

5073 if not progress_status: 5073 ↛ exitline 5073 didn't return from function 'restore_backup_continue' because the condition on line 5073 was always true

5074 err_msg = _("Failed to restore the snapshot %s.") % snap_name 

5075 raise exception.NetAppException(err_msg) 

5076 

5077 @na_utils.trace 

5078 def delete_backup(self, context, backup, share_instance, 

5079 share_server=None): 

5080 """Delete the share backup for netapp share""" 

5081 

5082 try: 

5083 src_vserver, src_vserver_client = self._get_vserver( 

5084 share_server=share_server, 

5085 ) 

5086 except exception.VserverNotFound: 

5087 LOG.warning("Vserver associated with share '%s' was not found.", 

5088 share_instance['id']) 

5089 return 

5090 src_vol_name = self._get_backend_share_name(share_instance['id']) 

5091 backend_name = self._get_backend(backup) 

5092 if backend_name is None: 5092 ↛ 5093line 5092 didn't jump to line 5093 because the condition on line 5092 was never true

5093 return 

5094 

5095 source_path = f"{src_vserver}:{src_vol_name}" 

5096 des_vserver, des_vol = self._get_destination_vserver_and_vol( 

5097 src_vserver_client, 

5098 source_path, 

5099 False, 

5100 ) 

5101 

5102 if not des_vserver or not des_vol: 

5103 LOG.debug("Not able to find vserver and volume from SnpMirror" 

5104 " relationship.") 

5105 return 

5106 des_path = f"{des_vserver}:{des_vol}" 

5107 

5108 # Delete the snapshot from destination volume 

5109 snap_name = self._get_backup_snapshot_name(backup, 

5110 share_instance['id']) 

5111 des_vserver_client = self._get_api_client_for_backend( 

5112 backend_name, 

5113 vserver=des_vserver, 

5114 ) 

5115 try: 

5116 list_snapshots = self._get_des_volume_backup_snapshots( 

5117 des_vserver_client, 

5118 des_vol, 

5119 share_instance['id'], 

5120 ) 

5121 except netapp_api.NaApiError: 

5122 LOG.exception("Failed to get the snapshots from cluster," 

5123 " provide the right backup type or check the" 

5124 " backend details are properly configured in" 

5125 " manila.conf file.") 

5126 return 

5127 

5128 snapmirror_info = des_vserver_client.get_snapmirrors( 

5129 source_path=source_path, 

5130 dest_path=des_path, 

5131 ) 

5132 is_snapshot_deleted = self._is_snapshot_deleted(True) 

5133 if snapmirror_info and len(list_snapshots) == 1: 

5134 self._resource_cleanup_for_backup(backup, 

5135 share_instance, 

5136 des_vserver, 

5137 des_vol, 

5138 share_server=share_server) 

5139 elif len(list_snapshots) > 1: 5139 ↛ 5151line 5139 didn't jump to line 5151 because the condition on line 5139 was always true

5140 try: 

5141 des_vserver_client.delete_snapshot(des_vol, snap_name, True) 

5142 except netapp_api.NaApiError as e: 

5143 with excutils.save_and_reraise_exception() as exc_context: 

5144 if "entry doesn't exist" in e.message: 5144 ↛ 5146line 5144 didn't jump to line 5146

5145 exc_context.reraise = False 

5146 try: 

5147 des_vserver_client.get_snapshot(des_vol, snap_name) 

5148 is_snapshot_deleted = self._is_snapshot_deleted(False) 

5149 except (SnapshotResourceNotFound, netapp_api.NaApiError): 

5150 LOG.debug("Snapshot '%s' deleted successfully.", snap_name) 

5151 if not is_snapshot_deleted: 

5152 err_msg = _("Snapshot '%(snapshot_name)s' is not deleted" 

5153 " successfully on ONTAP." 

5154 % {"snapshot_name": snap_name}) 

5155 LOG.exception(err_msg) 

5156 raise exception.NetAppException(err_msg) 

5157 

5158 @na_utils.trace 

5159 def _set_volume_has_backup_before(self, value): 

5160 self.is_volume_backup_before = value 

5161 

5162 @na_utils.trace 

5163 def _is_snapshot_deleted(self, is_deleted): 

5164 return is_deleted 

5165 

5166 @na_utils.trace 

5167 def _get_backup_snapshot_name(self, backup, share_id): 

5168 backup_id = backup.get('id', "") 

5169 return f"{Backup.SM_LABEL.value}_{share_id}_{backup_id}" 

5170 

5171 @na_utils.trace 

5172 def _get_backend(self, backup): 

5173 backup_type = backup.get(Backup.BACKUP_TYPE.value) 

5174 try: 

5175 backup_config = data_motion.get_backup_configuration(backup_type) 

5176 except Exception: 

5177 LOG.exception("There is some issue while getting the" 

5178 " backup configuration. Make sure correct" 

5179 " backup type is provided while creating the" 

5180 " backup.") 

5181 return None 

5182 return backup_config.safe_get(Backup.BACKEND_NAME.value) 

5183 

5184 @na_utils.trace 

5185 def _get_des_volume_backup_snapshots(self, des_vserver_client, 

5186 des_vol, share_id): 

5187 """Get the list of snapshot from destination volume""" 

5188 

5189 des_snapshot_list = (des_vserver_client. 

5190 list_volume_snapshots(des_vol, 

5191 Backup.SM_LABEL.value)) 

5192 backup_filter = f"{Backup.SM_LABEL.value}_{share_id}" 

5193 snap_list_with_backup = [snap for snap in des_snapshot_list 

5194 if snap.startswith(backup_filter)] 

5195 return snap_list_with_backup 

5196 

5197 @na_utils.trace 

5198 def _get_vserver_for_backup(self, backup, share_server=None): 

5199 """Get the destination vserver 

5200 

5201 if vserver not provided we are creating the new one 

5202 in case of dhss_true 

5203 """ 

5204 backup_type_config = data_motion.get_backup_configuration( 

5205 backup.get(Backup.BACKUP_TYPE.value)) 

5206 if backup_type_config.get(Backup.DES_VSERVER.value): 5206 ↛ 5209line 5206 didn't jump to line 5209 because the condition on line 5206 was always true

5207 return backup_type_config.get(Backup.DES_VSERVER.value) 

5208 else: 

5209 return self._get_backup_vserver(backup, share_server=share_server) 

5210 

5211 @na_utils.trace 

5212 def _get_volume_for_backup(self, backup, share_instance, 

5213 src_vserver_client, des_vserver_client): 

5214 """Get the destination volume 

5215 

5216 if volume is not provided in config file under backup_type stanza 

5217 then create the new one 

5218 """ 

5219 

5220 dm_session = data_motion.DataMotionSession() 

5221 backup_type = backup.get(Backup.BACKUP_TYPE.value) 

5222 backup_type_config = data_motion.get_backup_configuration(backup_type) 

5223 if (backup_type_config.get(Backup.DES_VSERVER.value) and 

5224 backup_type_config.get(Backup.DES_VOLUME.value)): 

5225 return backup_type_config.get(Backup.DES_VOLUME.value) 

5226 else: 

5227 des_aggr = dm_session.get_most_available_aggr_of_vserver( 

5228 des_vserver_client) 

5229 if not des_aggr: 

5230 msg = _("Not able to find any aggregate from ONTAP" 

5231 " to create the volume. Make sure aggregates are" 

5232 " added to destination vserver") 

5233 raise exception.NetAppException(msg) 

5234 src_vol = self._get_backend_share_name(share_instance['id']) 

5235 vol_attr = src_vserver_client.get_volume(src_vol) 

5236 source_vol_size = vol_attr.get('size') 

5237 vol_size_in_gb = int(source_vol_size) / units.Gi 

5238 share_id = share_instance['id'].replace('-', '_') 

5239 des_volume = f"{Backup.DES_VOLUME_PREFIX.value}_{share_id}" 

5240 des_vserver_client.create_volume(des_aggr, des_volume, 

5241 vol_size_in_gb, volume_type='dp') 

5242 return des_volume 

5243 

5244 @na_utils.trace 

5245 def _get_destination_vserver_and_vol(self, src_vserver_client, 

5246 source_path, validate_relation=True): 

5247 """Get Destination vserver and volume from SM relationship""" 

5248 

5249 des_vserver, des_vol = None, None 

5250 snapmirror_info = src_vserver_client.get_snapmirror_destinations( 

5251 source_path=source_path) 

5252 if validate_relation and len(snapmirror_info) != 1: 

5253 msg = _("There are more then one relationship with the source" 

5254 " '%(source_path)s'." % {'source_path': source_path}) 

5255 raise exception.NetAppException(msg) 

5256 if len(snapmirror_info) == 1: 5256 ↛ 5259line 5256 didn't jump to line 5259 because the condition on line 5256 was always true

5257 des_vserver = snapmirror_info[0].get("destination-vserver") 

5258 des_vol = snapmirror_info[0].get("destination-volume") 

5259 return des_vserver, des_vol 

5260 

5261 @na_utils.trace 

5262 def _verify_and_wait_for_snapshot_to_transfer(self, 

5263 des_vserver_client, 

5264 des_vol, 

5265 snap_name, 

5266 timeout=300, 

5267 ): 

5268 """Wait and verify that snapshot is moved to destination""" 

5269 

5270 interval = 5 

5271 retries = (timeout / interval or 1) 

5272 

5273 @manila_utils.retry(retry_param=(netapp_api.NaApiError, 

5274 SnapshotResourceNotFound), 

5275 interval=interval, 

5276 retries=retries, backoff_rate=1) 

5277 def _wait_for_snapshot_to_transfer(): 

5278 des_vserver_client.get_snapshot(des_vol, snap_name) 

5279 try: 

5280 _wait_for_snapshot_to_transfer() 

5281 except (netapp_api.NaApiError, SnapshotResourceNotFound): 

5282 msg = _("Timed out while wait for snapshot to transfer") 

5283 raise exception.NetAppException(message=msg) 

5284 

5285 @na_utils.trace 

5286 def _get_backup_progress_status(self, des_vserver_client, 

5287 snapmirror_details): 

5288 """Calculate percentage of SnapMirror data transferred""" 

5289 

5290 des_vol = snapmirror_details[0].get("destination-volume") 

5291 vol_attr = des_vserver_client.get_volume(des_vol) 

5292 size_used = vol_attr.get('size-used') 

5293 sm_data_transferred = snapmirror_details[0].get( 

5294 "last-transfer-size") 

5295 if size_used and sm_data_transferred: 

5296 progress_status_percent = (int(sm_data_transferred) / int( 

5297 size_used)) * 100 

5298 return str(round(progress_status_percent, 2)) 

5299 else: 

5300 return Backup.TOTAL_PROGRESS_ZERO.value 

5301 

5302 @na_utils.trace 

5303 def _resource_cleanup_for_backup(self, backup, share_instance, 

5304 des_vserver, des_vol, 

5305 share_server=None): 

5306 """Cleanup the created resources 

5307 

5308 cleanup all created ONTAP resources when delete the last backup 

5309 or in case of exception throw while creating the backup. 

5310 """ 

5311 src_vserver, src_vserver_client = self._get_vserver( 

5312 share_server=share_server) 

5313 dm_session = data_motion.DataMotionSession() 

5314 backup_type_config = data_motion.get_backup_configuration( 

5315 backup.get(Backup.BACKUP_TYPE.value)) 

5316 backend_name = backup_type_config.safe_get(Backup.BACKEND_NAME.value) 

5317 des_vserver_client = self._get_api_client_for_backend( 

5318 backend_name, 

5319 vserver=des_vserver, 

5320 ) 

5321 src_vol_name = self._get_backend_share_name(share_instance['id']) 

5322 

5323 # Abort relationship 

5324 try: 

5325 des_vserver_client.abort_snapmirror_vol(src_vserver, 

5326 src_vol_name, 

5327 des_vserver, 

5328 des_vol, 

5329 clear_checkpoint=False) 

5330 except netapp_api.NaApiError: 

5331 pass 

5332 try: 

5333 des_vserver_client.delete_snapmirror_vol(src_vserver, 

5334 src_vol_name, 

5335 des_vserver, 

5336 des_vol) 

5337 except netapp_api.NaApiError as e: 

5338 with excutils.save_and_reraise_exception() as exc_context: 

5339 if (e.code == netapp_api.EOBJECTNOTFOUND or 5339 ↛ 5344line 5339 didn't jump to line 5344

5340 e.code == netapp_api.ESOURCE_IS_DIFFERENT or 

5341 "(entry doesn't exist)" in e.message): 

5342 exc_context.reraise = False 

5343 

5344 dm_session.wait_for_snapmirror_release_vol( 

5345 src_vserver, des_vserver, src_vol_name, 

5346 des_vol, False, src_vserver_client, 

5347 timeout=backup_type_config.netapp_snapmirror_job_timeout) 

5348 

5349 try: 

5350 policy_name = f"{Backup.SM_POLICY.value}_{share_instance['id']}" 

5351 des_vserver_client.delete_snapmirror_policy(policy_name) 

5352 except netapp_api.NaApiError: 

5353 pass 

5354 

5355 # Delete the vserver peering 

5356 try: 

5357 if src_vserver != des_vserver: 5357 ↛ 5364line 5357 didn't jump to line 5364 because the condition on line 5357 was always true

5358 src_vserver_client.delete_vserver_peer(src_vserver, 

5359 des_vserver) 

5360 except netapp_api.NaApiError: 

5361 pass 

5362 

5363 # Delete volume 

5364 if not backup_type_config.safe_get(Backup.DES_VOLUME.value): 

5365 try: 

5366 des_vserver_client.offline_volume(des_vol) 

5367 des_vserver_client.delete_volume(des_vol) 

5368 except netapp_api.NaApiError: 

5369 pass 

5370 

5371 # Delete Vserver 

5372 if share_server is not None: 

5373 self._delete_backup_vserver(backup, des_vserver) 

5374 

5375 @na_utils.trace 

5376 def update_volume_snapshot_policy(self, share, snapshot_policy, 

5377 share_server=None): 

5378 share_name = self._get_backend_share_name(share['id']) 

5379 _, vserver_client = self._get_vserver(share_server=share_server) 

5380 vserver_client.update_volume_snapshot_policy(share_name, 

5381 snapshot_policy) 

5382 

5383 @na_utils.trace 

5384 def update_showmount(self, showmount, share_server=None): 

5385 showmount = showmount.lower() 

5386 if showmount not in ('true', 'false'): 

5387 err_msg = _("Invalid showmount value supplied: %s.") % showmount 

5388 raise exception.NetAppException(err_msg) 

5389 

5390 vserver, vserver_client = self._get_vserver(share_server=share_server) 

5391 vserver_client.update_showmount(showmount) 

5392 

5393 def update_pnfs(self, pnfs, share_server=None): 

5394 pnfs = pnfs.lower() 

5395 if pnfs not in ('true', 'false'): 

5396 err_msg = _("Invalid pnfs value supplied: %s.") % pnfs 

5397 raise exception.NetAppException(err_msg) 

5398 

5399 vserver, vserver_client = self._get_vserver(share_server=share_server) 

5400 vserver_client.update_pnfs(pnfs) 

5401 

5402 @na_utils.trace 

5403 def update_share_from_metadata(self, context, share, metadata, 

5404 share_server=None): 

5405 metadata_update_func_map = { 

5406 "snapshot_policy": "update_volume_snapshot_policy", 

5407 } 

5408 

5409 for k, v in metadata.items(): 

5410 metadata_update_method = ( 

5411 getattr(self, metadata_update_func_map.get(k)) 

5412 if k in metadata_update_func_map.keys() else None) 

5413 

5414 if metadata_update_method: 5414 ↛ 5409line 5414 didn't jump to line 5409 because the condition on line 5414 was always true

5415 metadata_update_method(share, v, share_server=share_server) 

5416 

5417 def update_share_network_subnet_from_metadata(self, context, 

5418 share_network, 

5419 share_network_subnet, 

5420 share_server, metadata): 

5421 metadata_update_func_map = { 

5422 "showmount": "update_showmount", 

5423 "pnfs": "update_pnfs", 

5424 } 

5425 

5426 for k, v in metadata.items(): 

5427 metadata_update_method = ( 

5428 getattr(self, metadata_update_func_map.get(k)) 

5429 if k in metadata_update_func_map.keys() else None) 

5430 

5431 if metadata_update_method: 5431 ↛ 5426line 5431 didn't jump to line 5426 because the condition on line 5431 was always true

5432 metadata_update_method(v, share_server=share_server) 

5433 

5434 @na_utils.trace 

5435 def _get_aggregate_snaplock_type(self, aggr_name): 

5436 if self._have_cluster_creds: 

5437 aggr_attributes = self._client.get_aggregate(aggr_name) 

5438 snaplock_type = aggr_attributes.get('snaplock-type') 

5439 

5440 else: 

5441 snaplock_type = self._client.get_vserver_aggr_snaplock_type( 

5442 aggr_name, 

5443 ) 

5444 return snaplock_type 

5445 

5446 @na_utils.trace 

5447 def _is_snaplock_compatible_for_migration(self, source_pool, des_pool): 

5448 if self._client.features.UNIFIED_AGGR: 

5449 return True 

5450 if (self.configuration.netapp_use_legacy_client 

5451 and self._client.features.SNAPLOCK): 

5452 source_snaplock_type = self._ssc_stats.get(source_pool, {}).get( 

5453 'netapp_snaplock_type') 

5454 des_snaplock_type = self._ssc_stats.get(des_pool, {}).get( 

5455 'netapp_snaplock_type') 

5456 if source_snaplock_type != des_snaplock_type: 5456 ↛ 5457line 5456 didn't jump to line 5457 because the condition on line 5456 was never true

5457 return False 

5458 return True