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
« 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.
13"""The Share Backups API."""
15import webob
16from webob import exc
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
28MIN_SUPPORTED_API_VERSION = '2.80'
31class ShareBackupController(wsgi.Controller, wsgi.AdminActionsMixin):
32 """The Share Backup API controller for the OpenStack API."""
34 resource_name = 'share_backup'
35 _view_builder_class = backup_view.BackupViewBuilder
37 def __init__(self):
38 super(ShareBackupController, self).__init__()
39 self.share_api = share.API()
41 def _update(self, *args, **kwargs):
42 db.share_backup_update(*args, **kwargs)
44 def _get(self, *args, **kwargs):
45 return db.share_backup_get(*args, **kwargs)
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)
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)
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']
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')]
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]
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')
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)
88 common.remove_invalid_options(context, search_opts,
89 self._get_backups_search_options())
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')
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)
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)
121 return backups
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')
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']
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)
140 return self._view_builder.detail(req, backup)
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']
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)
153 backup = body.get('share_backup')
154 share_id = backup.get('share_id')
156 if not share_id:
157 msg = _("'share_id' is missing from the request body.")
158 raise exc.HTTPBadRequest(explanation=msg)
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)
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)
178 return self._view_builder.detail(req, backup)
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']
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)
192 try:
193 self.share_api.delete_share_backup(context, backup)
194 except exception.InvalidBackup as e:
195 raise exc.HTTPBadRequest(explanation=e.msg)
197 return webob.Response(status_int=202)
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)
208 target_share_id = None
209 if body and 'restore' in body:
210 target_share_id = body.get('restore')
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)
218 retval = self._view_builder.restore_summary(req, restored)
219 return retval
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)
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)
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']
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)
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)
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'))
263 backup = self.share_api.update_share_backup(context, backup,
264 update_dict)
265 return self._view_builder.detail(req, backup)
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)
273def create_resource():
274 return wsgi.Resource(ShareBackupController())