Coverage for manila/share/migration.py: 100%
96 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) 2015 Hitachi Data Systems.
2# 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"""Helper class for Share Migration."""
17import time
19from oslo_config import cfg
20from oslo_log import log
22from manila.common import constants
23from manila import exception
24from manila.i18n import _
25from manila.share import api as share_api
26from manila.share import rpcapi as share_rpcapi
27import manila.utils as utils
30LOG = log.getLogger(__name__)
32migration_opts = [
33 cfg.IntOpt(
34 'migration_wait_access_rules_timeout',
35 default=180,
36 help="Time to wait for access rules to be allowed/denied on backends "
37 "when migrating shares using generic approach (seconds)."),
38 cfg.IntOpt(
39 'migration_create_delete_share_timeout',
40 default=300,
41 help='Timeout for creating and deleting share instances '
42 'when performing share migration (seconds).'),
43]
45CONF = cfg.CONF
46CONF.register_opts(migration_opts)
49class ShareMigrationHelper(object):
51 def __init__(self, context, db, access_helper):
53 self.db = db
54 self.context = context
55 self.access_helper = access_helper
56 self.api = share_api.API()
57 self.access_helper = access_helper
59 self.migration_create_delete_share_timeout = (
60 CONF.migration_create_delete_share_timeout)
61 self.migration_wait_access_rules_timeout = (
62 CONF.migration_wait_access_rules_timeout)
64 def delete_instance_and_wait(self, share_instance):
66 self.api.delete_instance(self.context, share_instance, True)
68 # Wait for deletion.
69 starttime = time.time()
70 deadline = starttime + self.migration_create_delete_share_timeout
71 tries = 0
72 instance = "Something not None"
73 while instance is not None:
74 try:
75 instance = self.db.share_instance_get(self.context,
76 share_instance['id'])
77 tries += 1
78 now = time.time()
79 if now > deadline:
80 msg = _("Timeout trying to delete instance "
81 "%s") % share_instance['id']
82 raise exception.ShareMigrationFailed(reason=msg)
83 except exception.NotFound:
84 instance = None
85 else:
86 # 1.414 = square-root of 2
87 time.sleep(1.414 ** tries)
89 def create_instance_and_wait(self, share, dest_host, new_share_network_id,
90 new_az_id, new_share_type_id):
92 new_share_instance = self.api.create_instance(
93 self.context, share, new_share_network_id, dest_host,
94 new_az_id, share_type_id=new_share_type_id)
96 # Wait for new_share_instance to become ready
97 starttime = time.time()
98 deadline = starttime + self.migration_create_delete_share_timeout
99 new_share_instance = self.db.share_instance_get(
100 self.context, new_share_instance['id'], with_share_data=True)
101 tries = 0
102 while new_share_instance['status'] != constants.STATUS_AVAILABLE:
103 tries += 1
104 now = time.time()
105 if new_share_instance['status'] == constants.STATUS_ERROR:
106 msg = _("Failed to create new share instance"
107 " (from %(share_id)s) on "
108 "destination host %(host_name)s") % {
109 'share_id': share['id'], 'host_name': dest_host}
110 self.cleanup_new_instance(new_share_instance)
111 raise exception.ShareMigrationFailed(reason=msg)
112 elif now > deadline:
113 msg = _("Timeout creating new share instance "
114 "(from %(share_id)s) on "
115 "destination host %(host_name)s") % {
116 'share_id': share['id'], 'host_name': dest_host}
117 self.cleanup_new_instance(new_share_instance)
118 raise exception.ShareMigrationFailed(reason=msg)
119 else:
120 # 1.414 = square-root of 2
121 time.sleep(1.414 ** tries)
122 new_share_instance = self.db.share_instance_get(
123 self.context, new_share_instance['id'], with_share_data=True)
125 return new_share_instance
127 # NOTE(ganso): Cleanup methods do not throw exceptions, since the
128 # exceptions that should be thrown are the ones that call the cleanup
130 def cleanup_new_instance(self, new_instance):
132 try:
133 self.delete_instance_and_wait(new_instance)
134 except Exception:
135 LOG.warning("Failed to cleanup new instance during generic "
136 "migration for share %s.", new_instance['share_id'])
138 def cleanup_access_rules(self, share_instances, share_server,
139 dest_host=None):
141 try:
142 self.revert_access_rules(share_instances, share_server, dest_host)
143 except Exception:
144 LOG.warning("Failed to cleanup access rules during generic"
145 " migration.")
147 def revert_access_rules(self, share_instances, share_server,
148 dest_host=None):
149 shares_instance_ids = []
150 for share_instance in share_instances:
151 # Cast all rules to 'queued_to_apply' so that they can be
152 # re-applied.
153 shares_instance_ids.append(share_instance['id'])
154 updates = {'state': constants.ACCESS_STATE_QUEUED_TO_APPLY}
155 self.access_helper.get_and_update_share_instance_access_rules(
156 self.context, updates=updates,
157 share_instance_id=share_instance['id'])
159 if dest_host:
160 rpcapi = share_rpcapi.ShareAPI()
161 rpcapi.update_access_for_instances(self.context, dest_host,
162 shares_instance_ids,
163 share_server)
164 else:
165 for share_instance in share_instances:
166 self.access_helper.update_access_rules(
167 self.context, share_instance['id'],
168 share_server=share_server)
170 for share_instance in share_instances:
171 utils.wait_for_access_update(
172 self.context, self.db, share_instance,
173 self.migration_wait_access_rules_timeout)
175 def apply_new_access_rules(self, new_share_instance, share_id):
177 rules = self.db.share_instance_access_copy(
178 self.context, share_id, new_share_instance['id'])
180 if rules:
181 self.api.allow_access_to_instance(self.context, new_share_instance)
183 utils.wait_for_access_update(
184 self.context, self.db, new_share_instance,
185 self.migration_wait_access_rules_timeout)
186 else:
187 LOG.debug("No access rules to sync to destination share instance.")
189 @utils.retry(retry_param=exception.ShareServerNotReady, retries=8)
190 def wait_for_share_server(self, share_server_id):
191 share_server = self.db.share_server_get(self.context, share_server_id)
192 if share_server['status'] == constants.STATUS_ERROR:
193 raise exception.ShareServerNotCreated(
194 share_server_id=share_server_id)
195 elif share_server['status'] == constants.STATUS_ACTIVE:
196 return share_server
197 else:
198 raise exception.ShareServerNotReady(
199 share_server_id=share_server_id, time=511,
200 state=constants.STATUS_AVAILABLE)