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

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

16 

17import time 

18 

19from oslo_config import cfg 

20from oslo_log import log 

21 

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 

28 

29 

30LOG = log.getLogger(__name__) 

31 

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] 

44 

45CONF = cfg.CONF 

46CONF.register_opts(migration_opts) 

47 

48 

49class ShareMigrationHelper(object): 

50 

51 def __init__(self, context, db, access_helper): 

52 

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 

58 

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) 

63 

64 def delete_instance_and_wait(self, share_instance): 

65 

66 self.api.delete_instance(self.context, share_instance, True) 

67 

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) 

88 

89 def create_instance_and_wait(self, share, dest_host, new_share_network_id, 

90 new_az_id, new_share_type_id): 

91 

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) 

95 

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) 

124 

125 return new_share_instance 

126 

127 # NOTE(ganso): Cleanup methods do not throw exceptions, since the 

128 # exceptions that should be thrown are the ones that call the cleanup 

129 

130 def cleanup_new_instance(self, new_instance): 

131 

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

137 

138 def cleanup_access_rules(self, share_instances, share_server, 

139 dest_host=None): 

140 

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

146 

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

158 

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) 

169 

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) 

174 

175 def apply_new_access_rules(self, new_share_instance, share_id): 

176 

177 rules = self.db.share_instance_access_copy( 

178 self.context, share_id, new_share_instance['id']) 

179 

180 if rules: 

181 self.api.allow_access_to_instance(self.context, new_share_instance) 

182 

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

188 

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)