Coverage for manila/api/v2/share_backups.py: 84%

177 statements  

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

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

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

3# a copy of the License at 

4# 

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

6# 

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

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

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

10# License for the specific language governing permissions and limitations 

11# under the License. 

12 

13"""The Share Backups API.""" 

14 

15import webob 

16from webob import exc 

17 

18from manila.api import common 

19from manila.api.openstack import wsgi 

20from manila.api.views import share_backups as backup_view 

21from manila import db 

22from manila import exception 

23from manila.i18n import _ 

24from manila import policy 

25from manila import share 

26 

27 

28MIN_SUPPORTED_API_VERSION = '2.80' 

29 

30 

31class ShareBackupController(wsgi.Controller, wsgi.AdminActionsMixin): 

32 """The Share Backup API controller for the OpenStack API.""" 

33 

34 resource_name = 'share_backup' 

35 _view_builder_class = backup_view.BackupViewBuilder 

36 

37 def __init__(self): 

38 super(ShareBackupController, self).__init__() 

39 self.share_api = share.API() 

40 

41 def _update(self, *args, **kwargs): 

42 db.share_backup_update(*args, **kwargs) 

43 

44 def _get(self, *args, **kwargs): 

45 return db.share_backup_get(*args, **kwargs) 

46 

47 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) 

48 def index(self, req): 

49 """Return a summary list of backups.""" 

50 return self._get_backups(req) 

51 

52 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) 

53 def detail(self, req): 

54 """Returns a detailed list of backups.""" 

55 return self._get_backups(req, is_detail=True) 

56 

57 @wsgi.Controller.authorize('get_all') 

58 def _get_backups(self, req, is_detail=False): 

59 """Returns list of backups.""" 

60 context = req.environ['manila.context'] 

61 

62 search_opts = {} 

63 search_opts.update(req.GET) 

64 params = common.get_pagination_params(req) 

65 limit, offset = [params.get('limit'), params.get('offset')] 

66 

67 search_opts.pop('limit', None) 

68 search_opts.pop('offset', None) 

69 sort_key, sort_dir = common.get_sort_params(search_opts) 

70 key_dict = {"name": "display_name", 

71 "description": "display_description"} 

72 for key in key_dict: 

73 if sort_key == key: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true

74 sort_key = key_dict[key] 

75 

76 if 'name' in search_opts: 76 ↛ 77line 76 didn't jump to line 77 because the condition on line 76 was never true

77 search_opts['display_name'] = search_opts.pop('name') 

78 if 'description' in search_opts: 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true

79 search_opts['display_description'] = search_opts.pop( 

80 'description') 

81 

82 # like filter 

83 for key, db_key in (('name~', 'display_name~'), 

84 ('description~', 'display_description~')): 

85 if key in search_opts: 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true

86 search_opts[db_key] = search_opts.pop(key) 

87 

88 common.remove_invalid_options(context, search_opts, 

89 self._get_backups_search_options()) 

90 

91 # Read and remove key 'all_tenants' if was provided 

92 search_opts['project_id'] = context.project_id 

93 all_tenants = search_opts.pop('all_tenants', 

94 search_opts.pop('all_projects', None)) 

95 if all_tenants: 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

96 allowed_to_list_all_tenants = policy.check_policy( 

97 context, 'share_backup', 'get_all_project', do_raise=False) 

98 if allowed_to_list_all_tenants: 

99 search_opts.pop('project_id') 

100 

101 share_id = req.params.get('share_id') 

102 if share_id: 

103 try: 

104 self.share_api.get(context, share_id) 

105 search_opts.update({'share_id': share_id}) 

106 except exception.NotFound: 

107 msg = _("No share exists with ID %s.") 

108 raise exc.HTTPBadRequest(explanation=msg % share_id) 

109 

110 backups = db.share_backups_get_all(context, 

111 filters=search_opts, 

112 limit=limit, 

113 offset=offset, 

114 sort_key=sort_key, 

115 sort_dir=sort_dir) 

116 if is_detail: 

117 backups = self._view_builder.detail_list(req, backups) 

118 else: 

119 backups = self._view_builder.summary_list(req, backups) 

120 

121 return backups 

122 

123 def _get_backups_search_options(self): 

124 """Return share backup search options allowed by non-admin.""" 

125 return ('display_name', 'status', 'share_id', 'topic', 'display_name~', 

126 'display_description~', 'display_description') 

127 

128 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) 

129 @wsgi.Controller.authorize('get') 

130 def show(self, req, id): 

131 """Return data about the given backup.""" 

132 context = req.environ['manila.context'] 

133 

134 try: 

135 backup = db.share_backup_get(context, id) 

136 except exception.ShareBackupNotFound: 

137 msg = _("No backup exists with ID %s.") 

138 raise exc.HTTPNotFound(explanation=msg % id) 

139 

140 return self._view_builder.detail(req, backup) 

141 

142 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) 

143 @wsgi.Controller.authorize 

144 @wsgi.response(202) 

145 def create(self, req, body): 

146 """Add a backup to an existing share.""" 

147 context = req.environ['manila.context'] 

148 

149 if not self.is_valid_body(body, 'share_backup'): 

150 msg = _("Body does not contain 'share_backup' information.") 

151 raise exc.HTTPUnprocessableEntity(explanation=msg) 

152 

153 backup = body.get('share_backup') 

154 share_id = backup.get('share_id') 

155 

156 if not share_id: 

157 msg = _("'share_id' is missing from the request body.") 

158 raise exc.HTTPBadRequest(explanation=msg) 

159 

160 try: 

161 share = self.share_api.get(context, share_id) 

162 except exception.NotFound: 

163 msg = _("No share exists with ID %s.") 

164 raise exc.HTTPBadRequest(explanation=msg % share_id) 

165 if share.get('is_soft_deleted'): 165 ↛ 166line 165 didn't jump to line 166 because the condition on line 165 was never true

166 msg = _("Backup can not be created for share '%s' " 

167 "since it has been soft deleted.") % share_id 

168 raise exc.HTTPForbidden(explanation=msg) 

169 

170 try: 

171 backup = self.share_api.create_share_backup(context, share, backup) 

172 except (exception.InvalidBackup, 

173 exception.InvalidShare) as e: 

174 raise exc.HTTPBadRequest(explanation=e.msg) 

175 except exception.ShareBusyException as e: 

176 raise exc.HTTPConflict(explanation=e.msg) 

177 

178 return self._view_builder.detail(req, backup) 

179 

180 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) 

181 @wsgi.Controller.authorize 

182 def delete(self, req, id): 

183 """Delete a backup.""" 

184 context = req.environ['manila.context'] 

185 

186 try: 

187 backup = db.share_backup_get(context, id) 

188 except exception.ShareBackupNotFound: 

189 msg = _("No backup exists with ID %s.") 

190 raise exc.HTTPNotFound(explanation=msg % id) 

191 

192 try: 

193 self.share_api.delete_share_backup(context, backup) 

194 except exception.InvalidBackup as e: 

195 raise exc.HTTPBadRequest(explanation=e.msg) 

196 

197 return webob.Response(status_int=202) 

198 

199 def _restore(self, req, id, body): 

200 """common logic for share backup restore microversion methods""" 

201 context = req.environ['manila.context'] 

202 try: 

203 backup = db.share_backup_get(context, id) 

204 except exception.ShareBackupNotFound: 

205 msg = _("No backup exists with ID %s.") 

206 raise exc.HTTPNotFound(explanation=msg % id) 

207 

208 target_share_id = None 

209 if body and 'restore' in body: 

210 target_share_id = body.get('restore') 

211 

212 try: 

213 restored = self.share_api.restore_share_backup( 

214 context, backup, target_share_id) 

215 except (exception.InvalidShare, exception.InvalidBackup) as e: 

216 raise exc.HTTPBadRequest(explanation=e.msg) 

217 

218 retval = self._view_builder.restore_summary(req, restored) 

219 return retval 

220 

221 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, '2.90', 

222 experimental=True) 

223 @wsgi.action('restore') 

224 @wsgi.Controller.authorize 

225 @wsgi.response(202) 

226 def restore(self, req, id, body): 

227 """Restore an existing backup to a source share.""" 

228 return self._restore(req, id, None) 

229 

230 @wsgi.Controller.api_version('2.91', experimental=True) 

231 @wsgi.action('restore') 

232 @wsgi.Controller.authorize 

233 @wsgi.response(202) 

234 def restore(self, req, id, body): # noqa F811 

235 """Restore an existing backup to a source or target share.""" 

236 return self._restore(req, id, body) 

237 

238 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) 

239 @wsgi.Controller.authorize 

240 @wsgi.response(200) 

241 def update(self, req, id, body): 

242 """Update a backup.""" 

243 context = req.environ['manila.context'] 

244 

245 if not self.is_valid_body(body, 'share_backup'): 245 ↛ 246line 245 didn't jump to line 246 because the condition on line 245 was never true

246 msg = _("Body does not contain 'share_backup' information.") 

247 raise exc.HTTPUnprocessableEntity(explanation=msg) 

248 

249 try: 

250 backup = db.share_backup_get(context, id) 

251 except exception.ShareBackupNotFound: 

252 msg = _("No backup exists with ID %s.") 

253 raise exc.HTTPNotFound(explanation=msg % id) 

254 

255 backup_update = body.get('share_backup') 

256 update_dict = {} 

257 if 'name' in backup_update: 257 ↛ 259line 257 didn't jump to line 259 because the condition on line 257 was always true

258 update_dict['display_name'] = backup_update.pop('name') 

259 if 'description' in backup_update: 259 ↛ 260line 259 didn't jump to line 260 because the condition on line 259 was never true

260 update_dict['display_description'] = ( 

261 backup_update.pop('description')) 

262 

263 backup = self.share_api.update_share_backup(context, backup, 

264 update_dict) 

265 return self._view_builder.detail(req, backup) 

266 

267 @wsgi.Controller.api_version(MIN_SUPPORTED_API_VERSION, experimental=True) 

268 @wsgi.action('reset_status') 

269 def backup_reset_status(self, req, id, body): 

270 return self._reset_status(req, id, body) 

271 

272 

273def create_resource(): 

274 return wsgi.Resource(ShareBackupController())