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
« 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.
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"""
23from oslo_config import cfg
24from oslo_log import log
25from oslo_utils import excutils
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
40LOG = log.getLogger(__name__)
41CONF = cfg.CONF
44def get_backend_configuration(backend_name):
45 config = configuration.Configuration(driver.share_opts,
46 config_group=backend_name)
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)
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)
72 return config
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
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))
128 return client
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
138class DataMotionSession(object):
140 def _get_backend_volume_name(self, config, share_obj):
141 """Return the calculated backend name of the share.
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
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('-', '_')}
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('-', '_')})
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')
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)
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
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)
184 return volume_name, vserver, backend_name
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)
192 return client, vserver
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)
200 src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
201 source_share_obj)
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
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.
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)
229 src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
230 source_share_obj)
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)
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)
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)
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.
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)
268 src_volume_name, src_vserver, src_backend = (
269 self.get_backend_info_for_share(source_share_obj))
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
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
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
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)
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)
321 src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
322 source_share_obj)
324 # Update SnapMirror
325 dest_client.update_snapmirror_vol(src_vserver,
326 src_volume_name,
327 dest_vserver,
328 dest_volume_name)
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)
336 # 1. Attempt to quiesce, then abort
337 dest_client.quiesce_snapmirror_svm(source_vserver, dest_vserver)
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
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.")
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)
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)
372 src_volume, src_vserver, __ = self.get_backend_info_for_share(
373 source_share_obj)
375 # 1. Attempt to quiesce, then abort
376 dest_client.quiesce_snapmirror_vol(src_vserver,
377 src_volume,
378 dest_vserver,
379 dest_volume)
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
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.")
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)
410 def break_snapmirror(self, source_share_obj, dest_share_obj, mount=True,
411 quiesce_wait_time=None):
412 """Breaks SnapMirror relationship.
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)
424 src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
425 source_share_obj)
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)
431 # 2. Break SnapMirror
432 dest_client.break_snapmirror_vol(src_vserver,
433 src_volume_name,
434 dest_vserver,
435 dest_volume_name)
437 # 3. Mount the destination volume and create a junction path
438 if mount:
439 dest_client.mount_volume(dest_volume_name)
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)
448 src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
449 source_share_obj)
451 dest_client.resync_snapmirror_vol(src_vserver,
452 src_volume_name,
453 dest_vserver,
454 dest_volume_name)
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)
464 src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
465 source_share_obj)
467 if schedule is None:
468 config = get_backend_configuration(dest_backend)
469 schedule = config.netapp_snapmirror_schedule
471 dest_client.modify_snapmirror_vol(src_vserver,
472 src_volume_name,
473 dest_vserver,
474 dest_volume_name,
475 schedule=schedule)
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)
484 src_volume_name, src_vserver, __ = self.get_backend_info_for_share(
485 source_share_obj)
487 dest_client.resume_snapmirror_vol(src_vserver,
488 src_volume_name,
489 dest_vserver,
490 dest_volume_name)
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.
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 """
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)
511 new_src_volume_name, new_src_vserver, new_src_backend = (
512 self.get_backend_info_for_share(new_source_replica))
514 # 1. delete
515 for other_replica in replica_list:
516 if other_replica['id'] == replica['id']:
517 continue
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)
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()
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)
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)
560 # 4. resync
561 replica_client.resync_snapmirror_vol(new_src_vserver,
562 new_src_volume_name,
563 replica_vserver,
564 replica_volume_name)
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'])
586 def create_snapmirror_svm(self, source_share_server,
587 dest_share_server):
588 """Sets up a SnapMirror relationship between two vServers.
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)
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)
607 # 2. Create SnapMirror relationship
608 dest_client.create_snapmirror_svm(src_vserver,
609 dest_vserver,
610 policy=policy_name,
611 schedule='hourly')
613 # 2. Initialize async transfer of the initial data
614 dest_client.initialize_snapmirror_svm(src_vserver,
615 dest_vserver)
617 def get_snapmirrors_svm(self, source_share_server, dest_share_server):
618 """Get SnapMirrors between two vServers."""
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)
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
631 def get_snapmirror_destinations_svm(self, source_share_server,
632 dest_share_server):
633 """Get SnapMirrors between two vServers."""
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)
639 snapmirrors = dest_client.get_snapmirror_destinations_svm(
640 source_vserver=src_vserver, dest_vserver=dest_vserver)
641 return snapmirrors
643 def update_snapmirror_svm(self, source_share_server, dest_share_server):
644 """Schedule a SnapMirror update to happen on the backend."""
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)
650 # Update SnapMirror
651 dest_client.update_snapmirror_svm(src_vserver, dest_vserver)
653 def quiesce_and_break_snapmirror_svm(self, source_share_server,
654 dest_share_server):
655 """Abort and break a SnapMirror relationship between vServers.
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)
664 # 1. Attempt to quiesce, then abort
665 self.quiesce_then_abort_svm(source_share_server, dest_share_server)
667 # 2. Break SnapMirror
668 dest_client.break_snapmirror_svm(src_vserver, dest_vserver)
670 def cancel_snapmirror_svm(self, source_share_server, dest_share_server):
671 """Cancels SnapMirror relationship between vServers."""
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)
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)
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)
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)
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)
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.
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
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
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)
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)
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
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)
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)
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.')
832 msg = _('Snapmirror release sent to source vserver. We will '
833 'wait for it to be released.')
834 raise exception.NetAppException(vserver=msg)
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)
843 def wait_for_mount_replica(self, vserver_client, share_name, timeout=300):
844 """Mount a replica share that is waiting for snapmirror initialize."""
846 interval = 10
847 retries = (timeout // interval or 1)
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)
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)
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)
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.')
906 msg = _('Snapmirror release sent to source volume. Waiting '
907 'until it has been released.')
908 raise exception.NetAppException(vserver=msg)
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)
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)
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'])
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'])
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
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 )
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)
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)