Coverage for manila/api/v2/share_groups.py: 97%

221 statements  

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

1# Copyright 2015 Alex Meade 

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 

16from http import client as http_client 

17 

18from oslo_log import log 

19from oslo_utils import uuidutils 

20import webob 

21from webob import exc 

22 

23from manila.api import common 

24from manila.api.openstack import api_version_request as api_version 

25from manila.api.openstack import wsgi 

26from manila.api.views import share_groups as share_group_views 

27from manila import db 

28from manila import exception 

29from manila.i18n import _ 

30from manila.share import share_types 

31from manila.share_group import api as share_group_api 

32from manila.share_group import share_group_types 

33 

34 

35LOG = log.getLogger(__name__) 

36SG_GRADUATION_VERSION = '2.55' 

37 

38 

39class ShareGroupController(wsgi.Controller, wsgi.AdminActionsMixin): 

40 """The Share Groups API controller for the OpenStack API.""" 

41 

42 resource_name = 'share_group' 

43 _view_builder_class = share_group_views.ShareGroupViewBuilder 

44 

45 def __init__(self): 

46 super(ShareGroupController, self).__init__() 

47 self.share_group_api = share_group_api.API() 

48 

49 def _get_share_group(self, context, share_group_id): 

50 try: 

51 return self.share_group_api.get(context, share_group_id) 

52 except exception.NotFound: 

53 msg = _("Share group %s not found.") % share_group_id 

54 raise exc.HTTPNotFound(explanation=msg) 

55 

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

57 def _show(self, req, id): 

58 """Return data about the given share group.""" 

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

60 share_group = self._get_share_group(context, id) 

61 return self._view_builder.detail(req, share_group) 

62 

63 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

64 def show(self, req, id): 

65 return self._show(req, id) 

66 

67 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

68 def show(self, req, id): # pylint: disable=function-redefined # noqa F811 

69 return self._show(req, id) 

70 

71 @wsgi.Controller.authorize('delete') 

72 def _delete_share_group(self, req, id): 

73 """Delete a share group.""" 

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

75 

76 LOG.info("Delete share group with id: %s", id, context=context) 

77 share_group = self._get_share_group(context, id) 

78 try: 

79 self.share_group_api.delete(context, share_group) 

80 except exception.InvalidShareGroup as e: 

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

82 return webob.Response(status_int=http_client.ACCEPTED) 

83 

84 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

85 def delete(self, req, id): 

86 return self._delete_share_group(req, id) 

87 

88 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

89 def delete(self, req, id): # pylint: disable=function-redefined # noqa F811 

90 return self._delete_share_group(req, id) 

91 

92 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

93 def index(self, req): 

94 return self._get_share_groups(req, is_detail=False) 

95 

96 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

97 def index(self, req): # pylint: disable=function-redefined # noqa F811 

98 return self._get_share_groups(req, is_detail=False) 

99 

100 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

101 def detail(self, req): 

102 return self._get_share_groups(req, is_detail=True) 

103 

104 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

105 def detail(self, req): # pylint: disable=function-redefined # noqa F811 

106 return self._get_share_groups(req, is_detail=True) 

107 

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

109 def _get_share_groups(self, req, is_detail): 

110 """Returns a summary or detail list of share groups.""" 

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

112 

113 search_opts = {} 

114 search_opts.update(req.GET) 

115 

116 # Remove keys that are not related to share group attrs 

117 search_opts.pop('limit', None) 

118 search_opts.pop('offset', None) 

119 sort_key = search_opts.pop('sort_key', 'created_at') 

120 sort_dir = search_opts.pop('sort_dir', 'desc') 

121 if req.api_version_request < api_version.APIVersionRequest("2.36"): 

122 search_opts.pop('name~', None) 

123 search_opts.pop('description~', None) 

124 if 'group_type_id' in search_opts: 124 ↛ 125line 124 didn't jump to line 125 because the condition on line 124 was never true

125 search_opts['share_group_type_id'] = search_opts.pop( 

126 'group_type_id') 

127 

128 share_groups = self.share_group_api.get_all( 

129 context, detailed=is_detail, search_opts=search_opts, 

130 sort_dir=sort_dir, sort_key=sort_key, 

131 ) 

132 

133 limited_list = common.limited(share_groups, req) 

134 

135 if is_detail: 

136 share_groups = self._view_builder.detail_list(req, limited_list) 

137 else: 

138 share_groups = self._view_builder.summary_list(req, limited_list) 

139 return share_groups 

140 

141 @wsgi.Controller.authorize('update') 

142 def _update_share_group(self, req, id, body): 

143 """Update a share group.""" 

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

145 

146 if not self.is_valid_body(body, 'share_group'): 

147 msg = _("'share_group' is missing from the request body.") 

148 raise exc.HTTPBadRequest(explanation=msg) 

149 

150 share_group_data = body['share_group'] 

151 valid_update_keys = {'name', 'description'} 

152 invalid_fields = set(share_group_data.keys()) - valid_update_keys 

153 if invalid_fields: 

154 msg = _("The fields %s are invalid or not allowed to be updated.") 

155 raise exc.HTTPBadRequest(explanation=msg % invalid_fields) 

156 

157 share_group = self._get_share_group(context, id) 

158 share_group = self.share_group_api.update( 

159 context, share_group, share_group_data) 

160 return self._view_builder.detail(req, share_group) 

161 

162 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

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

164 return self._update_share_group(req, id, body) 

165 

166 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

167 def update(self, req, id, body): # pylint: disable=function-redefined # noqa F811 

168 return self._update_share_group(req, id, body) 

169 

170 @wsgi.Controller.authorize('create') 

171 def _create(self, req, body): 

172 """Creates a new share group.""" 

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

174 

175 if not self.is_valid_body(body, 'share_group'): 

176 msg = _("'share_group' is missing from the request body.") 

177 raise exc.HTTPBadRequest(explanation=msg) 

178 

179 share_group = body['share_group'] 

180 valid_fields = { 

181 'name', 

182 'description', 

183 'share_types', 

184 'share_group_type_id', 

185 'source_share_group_snapshot_id', 

186 'share_network_id', 

187 'availability_zone', 

188 } 

189 invalid_fields = set(share_group.keys()) - valid_fields 

190 if invalid_fields: 

191 msg = _("The fields %s are invalid.") % invalid_fields 

192 raise exc.HTTPBadRequest(explanation=msg) 

193 

194 if ('share_types' in share_group and 

195 'source_share_group_snapshot_id' in share_group): 

196 msg = _("Cannot supply both 'share_types' and " 

197 "'source_share_group_snapshot_id' attributes.") 

198 raise exc.HTTPBadRequest(explanation=msg) 

199 

200 if not (share_group.get('share_types') or 

201 'source_share_group_snapshot_id' in share_group): 

202 default_share_type = share_types.get_default_share_type() 

203 if default_share_type: 

204 share_group['share_types'] = [default_share_type['id']] 

205 else: 

206 msg = _("Must specify at least one share type as a default " 

207 "share type has not been configured.") 

208 raise exc.HTTPBadRequest(explanation=msg) 

209 

210 kwargs = {} 

211 

212 if 'name' in share_group: 

213 kwargs['name'] = share_group.get('name') 

214 if 'description' in share_group: 

215 kwargs['description'] = share_group.get('description') 

216 

217 _share_types = share_group.get('share_types') 

218 if _share_types: 

219 if not all([uuidutils.is_uuid_like(st) for st in _share_types]): 

220 msg = _("The 'share_types' attribute must be a list of uuids") 

221 raise exc.HTTPBadRequest(explanation=msg) 

222 kwargs['share_type_ids'] = _share_types 

223 

224 if ('share_network_id' in share_group and 

225 'source_share_group_snapshot_id' in share_group): 

226 msg = _("Cannot supply both 'share_network_id' and " 

227 "'source_share_group_snapshot_id' attributes as the share " 

228 "network is inherited from the source.") 

229 raise exc.HTTPBadRequest(explanation=msg) 

230 

231 availability_zone = share_group.get('availability_zone') 

232 if availability_zone: 

233 if 'source_share_group_snapshot_id' in share_group: 

234 msg = _( 

235 "Cannot supply both 'availability_zone' and " 

236 "'source_share_group_snapshot_id' attributes as the " 

237 "availability zone is inherited from the source.") 

238 raise exc.HTTPBadRequest(explanation=msg) 

239 try: 

240 az = db.availability_zone_get(context, availability_zone) 

241 kwargs['availability_zone_id'] = az.id 

242 kwargs['availability_zone'] = az.name 

243 except exception.AvailabilityZoneNotFound as e: 

244 raise exc.HTTPNotFound(explanation=e.msg) 

245 

246 if 'source_share_group_snapshot_id' in share_group: 

247 source_share_group_snapshot_id = share_group.get( 

248 'source_share_group_snapshot_id') 

249 if not uuidutils.is_uuid_like(source_share_group_snapshot_id): 

250 msg = _("The 'source_share_group_snapshot_id' attribute " 

251 "must be a uuid.") 

252 raise exc.HTTPBadRequest(explanation=msg) 

253 kwargs['source_share_group_snapshot_id'] = ( 

254 source_share_group_snapshot_id) 

255 elif 'share_network_id' in share_group: 

256 share_network_id = share_group.get('share_network_id') 

257 if not uuidutils.is_uuid_like(share_network_id): 

258 msg = _("The 'share_network_id' attribute must be a uuid.") 

259 raise exc.HTTPBadRequest(explanation=msg) 

260 kwargs['share_network_id'] = share_network_id 

261 

262 if 'share_group_type_id' in share_group: 

263 share_group_type_id = share_group.get('share_group_type_id') 

264 if not uuidutils.is_uuid_like(share_group_type_id): 264 ↛ 265line 264 didn't jump to line 265 because the condition on line 264 was never true

265 msg = _("The 'share_group_type_id' attribute must be a uuid.") 

266 raise exc.HTTPBadRequest(explanation=msg) 

267 kwargs['share_group_type_id'] = share_group_type_id 

268 else: # get default 

269 def_share_group_type = share_group_types.get_default() 

270 if def_share_group_type: 

271 kwargs['share_group_type_id'] = def_share_group_type['id'] 

272 else: 

273 msg = _("Must specify a share group type as a default " 

274 "share group type has not been configured.") 

275 raise exc.HTTPBadRequest(explanation=msg) 

276 

277 try: 

278 new_share_group = self.share_group_api.create(context, **kwargs) 

279 except exception.InvalidShareGroupSnapshot as e: 

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

281 except (exception.ShareGroupSnapshotNotFound, 

282 exception.InvalidInput) as e: 

283 raise exc.HTTPBadRequest(explanation=str(e)) 

284 

285 return self._view_builder.detail( 

286 req, {k: v for k, v in new_share_group.items()}) 

287 

288 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

289 @wsgi.response(202) 

290 def create(self, req, body): 

291 return self._create(req, body) 

292 

293 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

294 @wsgi.response(202) 

295 def create(self, req, body): # pylint: disable=function-redefined # noqa F811 

296 return self._create(req, body) 

297 

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

299 db.share_group_update(*args, **kwargs) 

300 

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

302 return self.share_group_api.get(*args, **kwargs) 

303 

304 def _delete(self, context, resource, force=True): 

305 # Delete all share group snapshots 

306 for snap in resource['snapshots']: 306 ↛ 307line 306 didn't jump to line 307 because the loop on line 306 never started

307 db.share_group_snapshot_destroy(context, snap['id']) 

308 

309 # Delete all shares in share group 

310 for share in db.get_all_shares_by_share_group(context, resource['id']): 310 ↛ 311line 310 didn't jump to line 311 because the loop on line 310 never started

311 db.share_delete(context, share['id']) 

312 

313 db.share_group_destroy(context.elevated(), resource['id']) 

314 

315 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

316 @wsgi.action('reset_status') 

317 def share_group_reset_status(self, req, id, body): 

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

319 

320 # pylint: disable=function-redefined 

321 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

322 @wsgi.action('reset_status') 

323 def share_group_reset_status(self, req, id, body): # noqa F811 

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

325 

326 # pylint: enable=function-redefined 

327 @wsgi.Controller.api_version('2.31', '2.54', experimental=True) 

328 @wsgi.action('force_delete') 

329 def share_group_force_delete(self, req, id, body): 

330 return self._force_delete(req, id, body) 

331 

332 # pylint: disable=function-redefined 

333 @wsgi.Controller.api_version(SG_GRADUATION_VERSION) # noqa 

334 @wsgi.action('force_delete') 

335 def share_group_force_delete(self, req, id, body): # noqa F811 

336 return self._force_delete(req, id, body) 

337 

338 

339def create_resource(): 

340 return wsgi.Resource(ShareGroupController())