Coverage for manila/share/drivers/netapp/dataontap/cluster_mode/data_motion.py: 92%

425 statements  

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

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

2# 

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

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

5# a copy of the License at 

6# 

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

8# 

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

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

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

12# License for the specific language governing permissions and limitations 

13# under the License. 

14""" 

15NetApp Data ONTAP data motion library. 

16 

17This library handles transferring data from a source to a destination. Its 

18responsibility is to handle this as efficiently as possible given the 

19location of the data's source and destination. This includes cloning, 

20SnapMirror, and copy-offload as improvements to brute force data transfer. 

21""" 

22 

23from oslo_config import cfg 

24from oslo_log import log 

25from oslo_utils import excutils 

26 

27from manila import exception 

28from manila.i18n import _ 

29from manila.share import configuration 

30from manila.share import driver 

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

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

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

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

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

36from manila.share import utils as share_utils 

37from manila import utils 

38 

39 

40LOG = log.getLogger(__name__) 

41CONF = cfg.CONF 

42 

43 

44def get_backend_configuration(backend_name): 

45 config = configuration.Configuration(driver.share_opts, 

46 config_group=backend_name) 

47 

48 if config.driver_handles_share_servers is None: 

49 msg = _("Could not find backend stanza %(backend_name)s in " 

50 "configuration which is required for replication or migration " 

51 "workflows with the source backend.") 

52 params = { 

53 "backend_name": backend_name, 

54 } 

55 raise exception.BadConfigurationException(reason=msg % params) 

56 

57 if config.driver_handles_share_servers is True: 57 ↛ 60line 57 didn't jump to line 60 because the condition on line 57 was never true

58 # NOTE(dviroel): avoid using a pre-create vserver on DHSS == True mode 

59 # when retrieving remote backend configuration. 

60 config.netapp_vserver = None 

61 config.append_config_values(na_opts.netapp_cluster_opts) 

62 config.append_config_values(na_opts.netapp_connection_opts) 

63 config.append_config_values(na_opts.netapp_basicauth_opts) 

64 config.append_config_values(na_opts.netapp_certificateauth_opts) 

65 config.append_config_values(na_opts.netapp_transport_opts) 

66 config.append_config_values(na_opts.netapp_support_opts) 

67 config.append_config_values(na_opts.netapp_provisioning_opts) 

68 config.append_config_values(na_opts.netapp_data_motion_opts) 

69 config.append_config_values(na_opts.netapp_proxy_opts) 

70 config.append_config_values(na_opts.netapp_backup_opts) 

71 

72 return config 

73 

74 

75def get_backup_configuration(backup_type): 

76 config_stanzas = CONF.list_all_sections() 

77 if backup_type not in config_stanzas: 77 ↛ 87line 77 didn't jump to line 87 because the condition on line 77 was always true

78 msg = _("Could not find backup_type stanza %(backup_type)s in " 

79 "configuration which is required for backup workflows " 

80 "with the source share. Available stanzas are " 

81 "%(stanzas)s") 

82 params = { 

83 "stanzas": config_stanzas, 

84 "backup_type": backup_type, 

85 } 

86 raise exception.BadConfigurationException(reason=msg % params) 

87 config = configuration.Configuration(driver.share_opts, 

88 config_group=backup_type) 

89 config.append_config_values(na_opts.netapp_backup_opts) 

90 return config 

91 

92 

93def get_client_for_backend(backend_name, vserver_name=None, 

94 force_rest_client=False): 

95 config = get_backend_configuration(backend_name) 

96 if config.netapp_use_legacy_client and not force_rest_client: 96 ↛ 112line 96 didn't jump to line 112 because the condition on line 96 was always true

97 client = client_cmode.NetAppCmodeClient( 

98 transport_type=config.netapp_transport_type, 

99 ssl_cert_path=config.netapp_ssl_cert_path, 

100 username=config.netapp_login, 

101 password=config.netapp_password, 

102 hostname=config.netapp_server_hostname, 

103 port=config.netapp_server_port, 

104 vserver=vserver_name or config.netapp_vserver, 

105 trace=na_utils.TRACE_API, 

106 private_key_file=config.netapp_private_key_file, 

107 certificate_file=config.netapp_certificate_file, 

108 ca_certificate_file=config.netapp_ca_certificate_file, 

109 certificate_host_validation=( 

110 config.netapp_certificate_host_validation)) 

111 else: 

112 client = client_cmode_rest.NetAppRestClient( 

113 transport_type=config.netapp_transport_type, 

114 ssl_cert_path=config.netapp_ssl_cert_path, 

115 username=config.netapp_login, 

116 password=config.netapp_password, 

117 hostname=config.netapp_server_hostname, 

118 port=config.netapp_server_port, 

119 vserver=vserver_name or config.netapp_vserver, 

120 async_rest_timeout=config.netapp_rest_operation_timeout, 

121 trace=na_utils.TRACE_API, 

122 private_key_file=config.netapp_private_key_file, 

123 certificate_file=config.netapp_certificate_file, 

124 ca_certificate_file=config.netapp_ca_certificate_file, 

125 certificate_host_validation=( 

126 config.netapp_certificate_host_validation)) 

127 

128 return client 

129 

130 

131def get_client_for_host(host): 

132 """Returns a cluster client to the desired host.""" 

133 backend_name = share_utils.extract_host(host, level='backend_name') 

134 client = get_client_for_backend(backend_name) 

135 return client 

136 

137 

138class DataMotionSession(object): 

139 

140 def _get_backend_volume_name(self, config, share_obj): 

141 """Return the calculated backend name of the share. 

142 

143 Uses the netapp_volume_name_template configuration value for the 

144 backend to calculate the volume name on the array for the share. 

145 """ 

146 volume_name = config.netapp_volume_name_template % { 

147 'share_id': share_obj['id'].replace('-', '_')} 

148 return volume_name 

149 

150 def _get_backend_qos_policy_group_name(self, share): 

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

152 __, config = self.get_backend_name_and_config_obj(share['host']) 

153 return config.netapp_qos_policy_group_name_template % { 

154 'share_id': share['id'].replace('-', '_')} 

155 

156 def _get_backend_snapmirror_policy_name_svm(self, share_server_id, 

157 backend_name): 

158 config = get_backend_configuration(backend_name) 

159 return (config.netapp_snapmirror_policy_name_svm_template 

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

161 

162 def get_vserver_from_share_server(self, share_server): 

163 backend_details = share_server.get('backend_details') 

164 if backend_details: 164 ↛ exitline 164 didn't return from function 'get_vserver_from_share_server' because the condition on line 164 was always true

165 return backend_details.get('vserver_name') 

166 

167 def get_vserver_from_share(self, share_obj): 

168 share_server = share_obj.get('share_server') 

169 if share_server: 

170 return self.get_vserver_from_share_server(share_server) 

171 

172 def get_backend_name_and_config_obj(self, host): 

173 backend_name = share_utils.extract_host(host, level='backend_name') 

174 config = get_backend_configuration(backend_name) 

175 return backend_name, config 

176 

177 def get_backend_info_for_share(self, share_obj): 

178 backend_name, config = self.get_backend_name_and_config_obj( 

179 share_obj['host']) 

180 vserver = (self.get_vserver_from_share(share_obj) or 

181 config.netapp_vserver) 

182 volume_name = self._get_backend_volume_name(config, share_obj) 

183 

184 return volume_name, vserver, backend_name 

185 

186 def get_client_and_vserver_name(self, share_server): 

187 destination_host = share_server.get('host') 

188 vserver = self.get_vserver_from_share_server(share_server) 

189 backend, __ = self.get_backend_name_and_config_obj(destination_host) 

190 client = get_client_for_backend(backend, vserver_name=vserver) 

191 

192 return client, vserver 

193 

194 def get_snapmirrors(self, source_share_obj, dest_share_obj): 

195 dest_volume_name, dest_vserver, dest_backend = ( 

196 self.get_backend_info_for_share(dest_share_obj)) 

197 dest_client = get_client_for_backend(dest_backend, 

198 vserver_name=dest_vserver) 

199 

200 src_volume_name, src_vserver, __ = self.get_backend_info_for_share( 

201 source_share_obj) 

202 

203 snapmirrors = dest_client.get_snapmirrors( 

204 source_vserver=src_vserver, dest_vserver=dest_vserver, 

205 source_volume=src_volume_name, dest_volume=dest_volume_name, 

206 desired_attributes=['relationship-status', 

207 'mirror-state', 

208 'schedule', 

209 'source-vserver', 

210 'source-volume', 

211 'last-transfer-end-timestamp', 

212 'last-transfer-size', 

213 'last-transfer-error']) 

214 return snapmirrors 

215 

216 def create_snapmirror(self, source_share_obj, dest_share_obj, 

217 relationship_type, mount=False): 

218 """Sets up a SnapMirror relationship between two volumes. 

219 

220 1. Create SnapMirror relationship. 

221 2. Initialize data transfer asynchronously. 

222 3. Mount destination volume if requested. 

223 """ 

224 dest_volume_name, dest_vserver, dest_backend = ( 

225 self.get_backend_info_for_share(dest_share_obj)) 

226 dest_client = get_client_for_backend(dest_backend, 

227 vserver_name=dest_vserver) 

228 

229 src_volume_name, src_vserver, __ = self.get_backend_info_for_share( 

230 source_share_obj) 

231 

232 # 1. Create SnapMirror relationship 

233 config = get_backend_configuration(dest_backend) 

234 schedule = config.netapp_snapmirror_schedule 

235 dest_client.create_snapmirror_vol(src_vserver, 

236 src_volume_name, 

237 dest_vserver, 

238 dest_volume_name, 

239 relationship_type, 

240 schedule=schedule) 

241 

242 # 2. Initialize async transfer of the initial data 

243 dest_client.initialize_snapmirror_vol(src_vserver, 

244 src_volume_name, 

245 dest_vserver, 

246 dest_volume_name) 

247 

248 # 3. Mount the destination volume and create a junction path 

249 if mount: 

250 replica_config = get_backend_configuration(dest_backend) 

251 self.wait_for_mount_replica( 

252 dest_client, dest_volume_name, 

253 timeout=replica_config.netapp_mount_replica_timeout) 

254 

255 def delete_snapmirror(self, source_share_obj, dest_share_obj, 

256 release=True, relationship_info_only=False): 

257 """Ensures all information about a SnapMirror relationship is removed. 

258 

259 1. Abort snapmirror 

260 2. Delete the snapmirror 

261 3. Release snapmirror to cleanup snapmirror metadata and snapshots 

262 """ 

263 dest_volume_name, dest_vserver, dest_backend = ( 

264 self.get_backend_info_for_share(dest_share_obj)) 

265 dest_client = get_client_for_backend(dest_backend, 

266 vserver_name=dest_vserver) 

267 

268 src_volume_name, src_vserver, src_backend = ( 

269 self.get_backend_info_for_share(source_share_obj)) 

270 

271 # 1. Abort any ongoing transfers 

272 try: 

273 dest_client.abort_snapmirror_vol(src_vserver, 

274 src_volume_name, 

275 dest_vserver, 

276 dest_volume_name, 

277 clear_checkpoint=False) 

278 except netapp_api.NaApiError: 

279 # Snapmirror is already deleted 

280 pass 

281 

282 # 2. Delete SnapMirror Relationship and cleanup destination snapshots 

283 try: 

284 dest_client.delete_snapmirror_vol(src_vserver, 

285 src_volume_name, 

286 dest_vserver, 

287 dest_volume_name) 

288 except netapp_api.NaApiError as e: 

289 with excutils.save_and_reraise_exception() as exc_context: 

290 if (e.code == netapp_api.EOBJECTNOTFOUND or 290 ↛ 296line 290 didn't jump to line 296

291 e.code == netapp_api.ESOURCE_IS_DIFFERENT or 

292 "(entry doesn't exist)" in e.message): 

293 LOG.info('No snapmirror relationship to delete') 

294 exc_context.reraise = False 

295 

296 if release: 

297 # If the source is unreachable, do not perform the release 

298 try: 

299 src_client = get_client_for_backend(src_backend, 

300 vserver_name=src_vserver) 

301 except Exception: 

302 src_client = None 

303 

304 # 3. Cleanup SnapMirror relationship on source 

305 if src_client: 

306 src_config = get_backend_configuration(src_backend) 

307 release_timeout = ( 

308 src_config.netapp_snapmirror_release_timeout) 

309 self.wait_for_snapmirror_release_vol( 

310 src_vserver, dest_vserver, src_volume_name, 

311 dest_volume_name, relationship_info_only, src_client, 

312 timeout=release_timeout) 

313 

314 def update_snapmirror(self, source_share_obj, dest_share_obj): 

315 """Schedule a snapmirror update to happen on the backend.""" 

316 dest_volume_name, dest_vserver, dest_backend = ( 

317 self.get_backend_info_for_share(dest_share_obj)) 

318 dest_client = get_client_for_backend(dest_backend, 

319 vserver_name=dest_vserver) 

320 

321 src_volume_name, src_vserver, __ = self.get_backend_info_for_share( 

322 source_share_obj) 

323 

324 # Update SnapMirror 

325 dest_client.update_snapmirror_vol(src_vserver, 

326 src_volume_name, 

327 dest_vserver, 

328 dest_volume_name) 

329 

330 def quiesce_then_abort_svm(self, source_share_server, dest_share_server): 

331 source_client, source_vserver = self.get_client_and_vserver_name( 

332 source_share_server) 

333 dest_client, dest_vserver = self.get_client_and_vserver_name( 

334 dest_share_server) 

335 

336 # 1. Attempt to quiesce, then abort 

337 dest_client.quiesce_snapmirror_svm(source_vserver, dest_vserver) 

338 

339 dest_backend = share_utils.extract_host(dest_share_server['host'], 

340 level='backend_name') 

341 config = get_backend_configuration(dest_backend) 

342 retries = config.netapp_snapmirror_quiesce_timeout / 5 

343 

344 @utils.retry(retry_param=exception.ReplicationException, 

345 interval=5, 

346 retries=retries, 

347 backoff_rate=1) 

348 def wait_for_quiesced(): 

349 snapmirror = dest_client.get_snapmirrors_svm( 

350 source_vserver=source_vserver, dest_vserver=dest_vserver, 

351 desired_attributes=['relationship-status', 'mirror-state'] 

352 )[0] 

353 if snapmirror.get('relationship-status') not in ['quiesced', 

354 'paused']: 

355 raise exception.ReplicationException( 

356 reason="Snapmirror relationship is not quiesced.") 

357 

358 try: 

359 wait_for_quiesced() 

360 except exception.ReplicationException: 

361 dest_client.abort_snapmirror_svm(source_vserver, 

362 dest_vserver, 

363 clear_checkpoint=False) 

364 

365 def quiesce_then_abort(self, source_share_obj, dest_share_obj, 

366 quiesce_wait_time=None): 

367 dest_volume, dest_vserver, dest_backend = ( 

368 self.get_backend_info_for_share(dest_share_obj)) 

369 dest_client = get_client_for_backend(dest_backend, 

370 vserver_name=dest_vserver) 

371 

372 src_volume, src_vserver, __ = self.get_backend_info_for_share( 

373 source_share_obj) 

374 

375 # 1. Attempt to quiesce, then abort 

376 dest_client.quiesce_snapmirror_vol(src_vserver, 

377 src_volume, 

378 dest_vserver, 

379 dest_volume) 

380 

381 config = get_backend_configuration(dest_backend) 

382 timeout = ( 

383 quiesce_wait_time or config.netapp_snapmirror_quiesce_timeout) 

384 retries = int(timeout / 5) or 1 

385 

386 @utils.retry(retry_param=exception.ReplicationException, 

387 interval=5, 

388 retries=retries, 

389 backoff_rate=1) 

390 def wait_for_quiesced(): 

391 snapmirror = dest_client.get_snapmirrors( 

392 source_vserver=src_vserver, dest_vserver=dest_vserver, 

393 source_volume=src_volume, dest_volume=dest_volume, 

394 desired_attributes=['relationship-status', 'mirror-state'] 

395 )[0] 

396 if snapmirror.get('relationship-status') not in ['quiesced', 

397 'paused']: 

398 raise exception.ReplicationException( 

399 reason="Snapmirror relationship is not quiesced.") 

400 

401 try: 

402 wait_for_quiesced() 

403 except exception.ReplicationException: 

404 dest_client.abort_snapmirror_vol(src_vserver, 

405 src_volume, 

406 dest_vserver, 

407 dest_volume, 

408 clear_checkpoint=False) 

409 

410 def break_snapmirror(self, source_share_obj, dest_share_obj, mount=True, 

411 quiesce_wait_time=None): 

412 """Breaks SnapMirror relationship. 

413 

414 1. Quiesce any ongoing snapmirror transfers 

415 2. Wait until snapmirror finishes transfers and enters quiesced state 

416 3. Break snapmirror 

417 4. Mount the destination volume so it is exported as a share 

418 """ 

419 dest_volume_name, dest_vserver, dest_backend = ( 

420 self.get_backend_info_for_share(dest_share_obj)) 

421 dest_client = get_client_for_backend(dest_backend, 

422 vserver_name=dest_vserver) 

423 

424 src_volume_name, src_vserver, __ = self.get_backend_info_for_share( 

425 source_share_obj) 

426 

427 # 1. Attempt to quiesce, then abort 

428 self.quiesce_then_abort(source_share_obj, dest_share_obj, 

429 quiesce_wait_time=quiesce_wait_time) 

430 

431 # 2. Break SnapMirror 

432 dest_client.break_snapmirror_vol(src_vserver, 

433 src_volume_name, 

434 dest_vserver, 

435 dest_volume_name) 

436 

437 # 3. Mount the destination volume and create a junction path 

438 if mount: 

439 dest_client.mount_volume(dest_volume_name) 

440 

441 def resync_snapmirror(self, source_share_obj, dest_share_obj): 

442 """Resync SnapMirror relationship. """ 

443 dest_volume_name, dest_vserver, dest_backend = ( 

444 self.get_backend_info_for_share(dest_share_obj)) 

445 dest_client = get_client_for_backend(dest_backend, 

446 vserver_name=dest_vserver) 

447 

448 src_volume_name, src_vserver, __ = self.get_backend_info_for_share( 

449 source_share_obj) 

450 

451 dest_client.resync_snapmirror_vol(src_vserver, 

452 src_volume_name, 

453 dest_vserver, 

454 dest_volume_name) 

455 

456 def modify_snapmirror(self, source_share_obj, dest_share_obj, 

457 schedule=None): 

458 """Modify SnapMirror relationship: set schedule""" 

459 dest_volume_name, dest_vserver, dest_backend = ( 

460 self.get_backend_info_for_share(dest_share_obj)) 

461 dest_client = get_client_for_backend(dest_backend, 

462 vserver_name=dest_vserver) 

463 

464 src_volume_name, src_vserver, __ = self.get_backend_info_for_share( 

465 source_share_obj) 

466 

467 if schedule is None: 

468 config = get_backend_configuration(dest_backend) 

469 schedule = config.netapp_snapmirror_schedule 

470 

471 dest_client.modify_snapmirror_vol(src_vserver, 

472 src_volume_name, 

473 dest_vserver, 

474 dest_volume_name, 

475 schedule=schedule) 

476 

477 def resume_snapmirror(self, source_share_obj, dest_share_obj): 

478 """Resume SnapMirror relationship from a quiesced state.""" 

479 dest_volume_name, dest_vserver, dest_backend = ( 

480 self.get_backend_info_for_share(dest_share_obj)) 

481 dest_client = get_client_for_backend(dest_backend, 

482 vserver_name=dest_vserver) 

483 

484 src_volume_name, src_vserver, __ = self.get_backend_info_for_share( 

485 source_share_obj) 

486 

487 dest_client.resume_snapmirror_vol(src_vserver, 

488 src_volume_name, 

489 dest_vserver, 

490 dest_volume_name) 

491 

492 def change_snapmirror_source(self, replica, 

493 orig_source_replica, 

494 new_source_replica, replica_list, 

495 is_flexgroup=False): 

496 """Creates SnapMirror relationship from the new source to destination. 

497 

498 1. Delete all snapmirrors involving the replica, but maintain 

499 snapmirror metadata and snapshots for efficiency 

500 2. For DHSS=True scenarios, creates a new vserver peer relationship if 

501 it does not exists 

502 3. Ensure a new source -> replica snapmirror exists 

503 4. Resync new source -> replica snapmirror relationship 

504 """ 

505 

506 replica_volume_name, replica_vserver, replica_backend = ( 

507 self.get_backend_info_for_share(replica)) 

508 replica_client = get_client_for_backend(replica_backend, 

509 vserver_name=replica_vserver) 

510 

511 new_src_volume_name, new_src_vserver, new_src_backend = ( 

512 self.get_backend_info_for_share(new_source_replica)) 

513 

514 # 1. delete 

515 for other_replica in replica_list: 

516 if other_replica['id'] == replica['id']: 

517 continue 

518 

519 # deletes all snapmirror relationships involving this replica to 

520 # ensure new relation can be set. For efficient snapmirror, it 

521 # does not remove the snapshots, only releasing the relationship 

522 # info if FlexGroup volume. 

523 self.delete_snapmirror(other_replica, replica, 

524 release=is_flexgroup, 

525 relationship_info_only=is_flexgroup) 

526 self.delete_snapmirror(replica, other_replica, 

527 release=is_flexgroup, 

528 relationship_info_only=is_flexgroup) 

529 

530 # 2. vserver operations when driver handles share servers 

531 replica_config = get_backend_configuration(replica_backend) 

532 if (replica_config.driver_handles_share_servers 

533 and replica_vserver != new_src_vserver): 

534 # create vserver peering if does not exists 

535 if not replica_client.get_vserver_peers(replica_vserver, 535 ↛ 551line 535 didn't jump to line 551 because the condition on line 535 was always true

536 new_src_vserver): 

537 new_src_client = get_client_for_backend( 

538 new_src_backend, vserver_name=new_src_vserver) 

539 # Cluster name is needed for setting up the vserver peering 

540 new_src_cluster_name = new_src_client.get_cluster_name() 

541 replica_cluster_name = replica_client.get_cluster_name() 

542 

543 replica_client.create_vserver_peer( 

544 replica_vserver, new_src_vserver, 

545 peer_cluster_name=new_src_cluster_name) 

546 if new_src_cluster_name != replica_cluster_name: 546 ↛ 551line 546 didn't jump to line 551 because the condition on line 546 was always true

547 new_src_client.accept_vserver_peer(new_src_vserver, 

548 replica_vserver) 

549 

550 # 3. create 

551 relationship_type = na_utils.get_relationship_type(is_flexgroup) 

552 schedule = replica_config.netapp_snapmirror_schedule 

553 replica_client.create_snapmirror_vol(new_src_vserver, 

554 new_src_volume_name, 

555 replica_vserver, 

556 replica_volume_name, 

557 relationship_type, 

558 schedule=schedule) 

559 

560 # 4. resync 

561 replica_client.resync_snapmirror_vol(new_src_vserver, 

562 new_src_volume_name, 

563 replica_vserver, 

564 replica_volume_name) 

565 

566 @na_utils.trace 

567 def remove_qos_on_old_active_replica(self, orig_active_replica): 

568 old_active_replica_qos_policy = ( 

569 self._get_backend_qos_policy_group_name(orig_active_replica) 

570 ) 

571 replica_volume_name, replica_vserver, replica_backend = ( 

572 self.get_backend_info_for_share(orig_active_replica)) 

573 replica_client = get_client_for_backend( 

574 replica_backend, vserver_name=replica_vserver) 

575 try: 

576 replica_client.set_qos_policy_group_for_volume( 

577 replica_volume_name, 'none') 

578 replica_client.mark_qos_policy_group_for_deletion( 

579 old_active_replica_qos_policy) 

580 except exception.StorageCommunicationException: 

581 LOG.exception("Could not communicate with the backend " 

582 "for replica %s to unset QoS policy and mark " 

583 "the QoS policy group for deletion.", 

584 orig_active_replica['id']) 

585 

586 def create_snapmirror_svm(self, source_share_server, 

587 dest_share_server): 

588 """Sets up a SnapMirror relationship between two vServers. 

589 

590 1. Create a SnapMirror policy for SVM DR 

591 2. Create SnapMirror relationship 

592 3. Initialize data transfer asynchronously 

593 """ 

594 dest_client, dest_vserver = self.get_client_and_vserver_name( 

595 dest_share_server) 

596 src_vserver = self.get_vserver_from_share_server(source_share_server) 

597 

598 # 1: Create SnapMirror policy for SVM DR 

599 dest_backend_name = share_utils.extract_host(dest_share_server['host'], 

600 level='backend_name') 

601 policy_name = self._get_backend_snapmirror_policy_name_svm( 

602 dest_share_server['id'], 

603 dest_backend_name, 

604 ) 

605 dest_client.create_snapmirror_policy(policy_name) 

606 

607 # 2. Create SnapMirror relationship 

608 dest_client.create_snapmirror_svm(src_vserver, 

609 dest_vserver, 

610 policy=policy_name, 

611 schedule='hourly') 

612 

613 # 2. Initialize async transfer of the initial data 

614 dest_client.initialize_snapmirror_svm(src_vserver, 

615 dest_vserver) 

616 

617 def get_snapmirrors_svm(self, source_share_server, dest_share_server): 

618 """Get SnapMirrors between two vServers.""" 

619 

620 dest_client, dest_vserver = self.get_client_and_vserver_name( 

621 dest_share_server) 

622 src_vserver = self.get_vserver_from_share_server(source_share_server) 

623 

624 snapmirrors = dest_client.get_snapmirrors_svm( 

625 source_vserver=src_vserver, dest_vserver=dest_vserver, 

626 desired_attributes=['relationship-status', 

627 'mirror-state', 

628 'last-transfer-end-timestamp']) 

629 return snapmirrors 

630 

631 def get_snapmirror_destinations_svm(self, source_share_server, 

632 dest_share_server): 

633 """Get SnapMirrors between two vServers.""" 

634 

635 dest_client, dest_vserver = self.get_client_and_vserver_name( 

636 dest_share_server) 

637 src_vserver = self.get_vserver_from_share_server(source_share_server) 

638 

639 snapmirrors = dest_client.get_snapmirror_destinations_svm( 

640 source_vserver=src_vserver, dest_vserver=dest_vserver) 

641 return snapmirrors 

642 

643 def update_snapmirror_svm(self, source_share_server, dest_share_server): 

644 """Schedule a SnapMirror update to happen on the backend.""" 

645 

646 dest_client, dest_vserver = self.get_client_and_vserver_name( 

647 dest_share_server) 

648 src_vserver = self.get_vserver_from_share_server(source_share_server) 

649 

650 # Update SnapMirror 

651 dest_client.update_snapmirror_svm(src_vserver, dest_vserver) 

652 

653 def quiesce_and_break_snapmirror_svm(self, source_share_server, 

654 dest_share_server): 

655 """Abort and break a SnapMirror relationship between vServers. 

656 

657 1. Quiesce SnapMirror 

658 2. Break SnapMirror 

659 """ 

660 dest_client, dest_vserver = self.get_client_and_vserver_name( 

661 dest_share_server) 

662 src_vserver = self.get_vserver_from_share_server(source_share_server) 

663 

664 # 1. Attempt to quiesce, then abort 

665 self.quiesce_then_abort_svm(source_share_server, dest_share_server) 

666 

667 # 2. Break SnapMirror 

668 dest_client.break_snapmirror_svm(src_vserver, dest_vserver) 

669 

670 def cancel_snapmirror_svm(self, source_share_server, dest_share_server): 

671 """Cancels SnapMirror relationship between vServers.""" 

672 

673 dest_backend = share_utils.extract_host(dest_share_server['host'], 

674 level='backend_name') 

675 dest_config = get_backend_configuration(dest_backend) 

676 server_timeout = ( 

677 dest_config.netapp_server_migration_state_change_timeout) 

678 dest_client, dest_vserver = self.get_client_and_vserver_name( 

679 dest_share_server) 

680 

681 snapmirrors = self.get_snapmirrors_svm(source_share_server, 

682 dest_share_server) 

683 if snapmirrors: 

684 # 1. Attempt to quiesce and break snapmirror 

685 self.quiesce_and_break_snapmirror_svm(source_share_server, 

686 dest_share_server) 

687 

688 # NOTE(dviroel): Lets wait until the destination vserver be 

689 # promoted to 'default' and state 'running', before starting 

690 # shutting down the source 

691 self.wait_for_vserver_state(dest_vserver, dest_client, 

692 subtype='default', state='running', 

693 operational_state='stopped', 

694 timeout=server_timeout) 

695 # 2. Delete SnapMirror 

696 self.delete_snapmirror_svm(source_share_server, dest_share_server) 

697 else: 

698 dest_info = dest_client.get_vserver_info(dest_vserver) 

699 if dest_info is None: 699 ↛ 702line 699 didn't jump to line 702 because the condition on line 699 was never true

700 # NOTE(dviroel): Nothing to cancel since the destination does 

701 # not exist. 

702 return 

703 if dest_info.get('subtype') == 'dp_destination': 

704 # NOTE(dviroel): Can be a corner case where no snapmirror 

705 # relationship was found but the destination vserver is stuck 

706 # in DP mode. We need to convert it to 'default' to release 

707 # its resources later. 

708 self.convert_svm_to_default_subtype(dest_vserver, dest_client, 

709 timeout=server_timeout) 

710 

711 def convert_svm_to_default_subtype(self, vserver_name, client, 

712 is_dest_path=True, timeout=300): 

713 interval = 10 

714 retries = (timeout / interval or 1) 

715 

716 @utils.retry(retry_param=exception.VserverNotReady, 

717 interval=interval, 

718 retries=retries, 

719 backoff_rate=1) 

720 def wait_for_state(): 

721 vserver_info = client.get_vserver_info(vserver_name) 

722 if vserver_info.get('subtype') != 'default': 

723 if is_dest_path: 

724 client.break_snapmirror_svm(dest_vserver=vserver_name) 

725 else: 

726 client.break_snapmirror_svm(source_vserver=vserver_name) 

727 raise exception.VserverNotReady(vserver=vserver_name) 

728 try: 

729 wait_for_state() 

730 except exception.VserverNotReady: 

731 msg = _("Vserver %s did not reach the expected state. Retries " 

732 "exhausted. Aborting.") % vserver_name 

733 raise exception.NetAppException(message=msg) 

734 

735 def delete_snapmirror_svm(self, src_share_server, dest_share_server, 

736 release=True): 

737 """Ensures all information about a SnapMirror relationship is removed. 

738 

739 1. Abort SnapMirror 

740 2. Delete the SnapMirror 

741 3. Release SnapMirror to cleanup SnapMirror metadata and snapshots 

742 """ 

743 src_client, src_vserver = self.get_client_and_vserver_name( 

744 src_share_server) 

745 dest_client, dest_vserver = self.get_client_and_vserver_name( 

746 dest_share_server) 

747 # 1. Abort any ongoing transfers 

748 try: 

749 dest_client.abort_snapmirror_svm(src_vserver, dest_vserver) 

750 except netapp_api.NaApiError: 

751 # SnapMirror is already deleted 

752 pass 

753 

754 # 2. Delete SnapMirror Relationship and cleanup destination snapshots 

755 try: 

756 dest_client.delete_snapmirror_svm(src_vserver, dest_vserver) 

757 except netapp_api.NaApiError as e: 

758 with excutils.save_and_reraise_exception() as exc_context: 

759 if (e.code == netapp_api.EOBJECTNOTFOUND or 759 ↛ 766line 759 didn't jump to line 766

760 e.code == netapp_api.ESOURCE_IS_DIFFERENT or 

761 "(entry doesn't exist)" in e.message): 

762 LOG.info('No snapmirror relationship to delete') 

763 exc_context.reraise = False 

764 

765 # 3. Release SnapMirror 

766 if release: 

767 src_backend = share_utils.extract_host(src_share_server['host'], 

768 level='backend_name') 

769 src_config = get_backend_configuration(src_backend) 

770 release_timeout = ( 

771 src_config.netapp_snapmirror_release_timeout) 

772 self.wait_for_snapmirror_release_svm(src_vserver, 

773 dest_vserver, 

774 src_client, 

775 timeout=release_timeout) 

776 

777 def wait_for_vserver_state(self, vserver_name, client, state=None, 

778 operational_state=None, subtype=None, 

779 timeout=300): 

780 interval = 10 

781 retries = (timeout / interval or 1) 

782 

783 expected = {} 

784 if state: 784 ↛ 786line 784 didn't jump to line 786 because the condition on line 784 was always true

785 expected['state'] = state 

786 if operational_state: 786 ↛ 788line 786 didn't jump to line 788 because the condition on line 786 was always true

787 expected['operational_state'] = operational_state 

788 if subtype: 788 ↛ 791line 788 didn't jump to line 791 because the condition on line 788 was always true

789 expected['subtype'] = subtype 

790 

791 @utils.retry(retry_param=exception.VserverNotReady, 

792 interval=interval, 

793 retries=retries, 

794 backoff_rate=1) 

795 def wait_for_state(): 

796 vserver_info = client.get_vserver_info(vserver_name) 

797 if not all(item in vserver_info.items() for 

798 item in expected.items()): 

799 raise exception.VserverNotReady(vserver=vserver_name) 

800 try: 

801 wait_for_state() 

802 except exception.VserverNotReady: 

803 msg = _("Vserver %s did not reach the expected state. Retries " 

804 "exhausted. Aborting.") % vserver_name 

805 raise exception.NetAppException(message=msg) 

806 

807 def wait_for_snapmirror_release_svm(self, source_vserver, dest_vserver, 

808 src_client, timeout=300): 

809 interval = 10 

810 retries = (timeout / interval or 1) 

811 

812 @utils.retry(retry_param=exception.NetAppException, 

813 interval=interval, 

814 retries=retries, 

815 backoff_rate=1) 

816 def release_snapmirror(): 

817 snapmirrors = src_client.get_snapmirror_destinations_svm( 

818 source_vserver=source_vserver, dest_vserver=dest_vserver) 

819 if not snapmirrors: 

820 LOG.debug("No snapmirrors to be released in source location.") 

821 else: 

822 try: 

823 src_client.release_snapmirror_svm(source_vserver, 

824 dest_vserver) 

825 except netapp_api.NaApiError as e: 

826 if (e.code == netapp_api.EOBJECTNOTFOUND or 826 ↛ 832line 826 didn't jump to line 832 because the condition on line 826 was always true

827 e.code == netapp_api.ESOURCE_IS_DIFFERENT or 

828 "(entry doesn't exist)" in e.message): 

829 LOG.debug('Snapmirror relationship does not exists ' 

830 'anymore.') 

831 

832 msg = _('Snapmirror release sent to source vserver. We will ' 

833 'wait for it to be released.') 

834 raise exception.NetAppException(vserver=msg) 

835 

836 try: 

837 release_snapmirror() 

838 except exception.NetAppException: 

839 msg = _("Unable to release the snapmirror from source vserver %s. " 

840 "Retries exhausted. Aborting") % source_vserver 

841 raise exception.NetAppException(message=msg) 

842 

843 def wait_for_mount_replica(self, vserver_client, share_name, timeout=300): 

844 """Mount a replica share that is waiting for snapmirror initialize.""" 

845 

846 interval = 10 

847 retries = (timeout // interval or 1) 

848 

849 @utils.retry(exception.ShareBusyException, interval=interval, 

850 retries=retries, backoff_rate=1) 

851 def try_mount_volume(): 

852 try: 

853 vserver_client.mount_volume(share_name) 

854 except netapp_api.NaApiError as e: 

855 undergoing_snap_init = 'snapmirror initialize' 

856 msg_args = {'name': share_name} 

857 if (e.code == netapp_api.EAPIERROR and 

858 undergoing_snap_init in e.message): 

859 msg = _('The share %(name)s is undergoing a snapmirror ' 

860 'initialize. Will retry the operation.') % msg_args 

861 LOG.warning(msg) 

862 raise exception.ShareBusyException(reason=msg) 

863 else: 

864 msg = _("Unable to perform mount operation for the share " 

865 "%(name)s. Caught an unexpected error. Not " 

866 "retrying.") % msg_args 

867 raise exception.NetAppException(message=msg) 

868 

869 try: 

870 try_mount_volume() 

871 except exception.ShareBusyException: 

872 msg_args = {'name': share_name} 

873 msg = _("Unable to perform mount operation for the share %(name)s " 

874 "because a snapmirror initialize operation is still in " 

875 "progress. Retries exhausted. Not retrying.") % msg_args 

876 raise exception.NetAppException(message=msg) 

877 

878 def wait_for_snapmirror_release_vol(self, src_vserver, dest_vserver, 

879 src_volume_name, dest_volume_name, 

880 relationship_info_only, src_client, 

881 timeout=300): 

882 interval = 10 

883 retries = (timeout / interval or 1) 

884 

885 @utils.retry(exception.NetAppException, interval=interval, 

886 retries=retries, backoff_rate=1) 

887 def release_snapmirror(): 

888 snapmirrors = src_client.get_snapmirror_destinations( 

889 source_vserver=src_vserver, dest_vserver=dest_vserver, 

890 source_volume=src_volume_name, dest_volume=dest_volume_name) 

891 if not snapmirrors: 

892 LOG.debug("No snapmirrors to be released in source volume.") 

893 else: 

894 try: 

895 src_client.release_snapmirror_vol( 

896 src_vserver, src_volume_name, dest_vserver, 

897 dest_volume_name, 

898 relationship_info_only=relationship_info_only) 

899 except netapp_api.NaApiError as e: 

900 if (e.code == netapp_api.EOBJECTNOTFOUND or 900 ↛ 906line 900 didn't jump to line 906 because the condition on line 900 was always true

901 e.code == netapp_api.ESOURCE_IS_DIFFERENT or 

902 "(entry doesn't exist)" in e.message): 

903 LOG.debug('Snapmirror relationship does not exist ' 

904 'anymore.') 

905 

906 msg = _('Snapmirror release sent to source volume. Waiting ' 

907 'until it has been released.') 

908 raise exception.NetAppException(vserver=msg) 

909 

910 try: 

911 release_snapmirror() 

912 except exception.NetAppException: 

913 msg = _("Unable to release the snapmirror from source volume %s. " 

914 "Retries exhausted. Aborting") % src_volume_name 

915 raise exception.NetAppException(message=msg) 

916 

917 def cleanup_previous_snapmirror_relationships(self, replica, replica_list): 

918 """Cleanup previous snapmirrors relationships for replica.""" 

919 LOG.debug("Cleaning up old snapmirror relationships for replica %s.", 

920 replica['id']) 

921 src_vol_name, src_vserver, src_backend = ( 

922 self.get_backend_info_for_share(replica)) 

923 src_client = get_client_for_backend(src_backend, 

924 vserver_name=src_vserver) 

925 

926 # replica_list may contain the replica we are trying to clean up 

927 destinations = (r for r in replica_list if r['id'] != replica['id']) 

928 

929 for destination in destinations: 

930 dest_vol_name, dest_vserver, _ = ( 

931 self.get_backend_info_for_share(destination)) 

932 try: 

933 src_client.release_snapmirror_vol( 

934 src_vserver, src_vol_name, dest_vserver, dest_vol_name) 

935 except netapp_api.NaApiError as e: 

936 if (e.code == netapp_api.EOBJECTNOTFOUND or 

937 e.code == netapp_api.ESOURCE_IS_DIFFERENT or 

938 "(entry doesn't exist)" in e.message): 

939 LOG.debug( 

940 'Snapmirror destination %s no longer exists for ' 

941 'replica %s.', destination['id'], replica['id']) 

942 else: 

943 LOG.exception( 

944 'Error releasing snapmirror destination %s for ' 

945 'replica %s.', destination['id'], replica['id']) 

946 

947 def get_most_available_aggr_of_vserver(self, vserver_client): 

948 """Get most available aggregate""" 

949 aggrs_space_attr = vserver_client.get_vserver_aggregate_capacities() 

950 if not aggrs_space_attr: 950 ↛ 951line 950 didn't jump to line 951 because the condition on line 950 was never true

951 return None 

952 aggr_list = list(aggrs_space_attr.keys()) 

953 most_available_aggr = aggr_list[0] 

954 for aggr in aggr_list: 

955 if (aggrs_space_attr.get(aggr).get('available') 955 ↛ 958line 955 didn't jump to line 958 because the condition on line 955 was never true

956 > aggrs_space_attr.get( 

957 most_available_aggr).get('available')): 

958 most_available_aggr = aggr 

959 return most_available_aggr 

960 

961 def initialize_and_wait_snapmirror_vol(self, vserver_client, 

962 source_vserver, source_volume, 

963 dest_vserver, dest_volume, 

964 source_snapshot=None, 

965 transfer_priority=None, 

966 timeout=300): 

967 """Initialize and wait for SnapMirror relationship""" 

968 interval = 10 

969 retries = (timeout / interval or 1) 

970 vserver_client.initialize_snapmirror_vol( 

971 source_vserver, 

972 source_volume, 

973 dest_vserver, 

974 dest_volume, 

975 source_snapshot=source_snapshot, 

976 transfer_priority=transfer_priority, 

977 ) 

978 

979 @utils.retry(exception.NetAppException, interval=interval, 

980 retries=retries, backoff_rate=1) 

981 def wait_for_initialization(): 

982 source_path = f"{source_vserver}:{source_volume}" 

983 des_path = f"{dest_vserver}:{dest_volume}" 

984 snapmirror_info = vserver_client.get_snapmirrors( 

985 source_path=source_path, dest_path=des_path) 

986 relationship_status = snapmirror_info[0].get("relationship-status") 

987 if relationship_status == "idle": 987 ↛ 990line 987 didn't jump to line 990 because the condition on line 987 was always true

988 return 

989 else: 

990 msg = (_('Snapmirror relationship status is: %s. Waiting ' 

991 'until it has been initialized.') % 

992 relationship_status) 

993 raise exception.NetAppException(message=msg) 

994 

995 try: 

996 wait_for_initialization() 

997 except exception.NetAppException: 

998 msg = _("Timed out while wait for SnapMirror relationship to " 

999 "be initialized") 

1000 raise exception.NetAppException(message=msg)