Coverage for manila/tests/api/v2/test_share_instances.py: 99%

213 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 

13from unittest import mock 

14 

15import ddt 

16from oslo_config import cfg 

17from oslo_serialization import jsonutils 

18from webob import exc as webob_exc 

19 

20from manila.api.openstack import api_version_request 

21from manila.api.v2 import share_instances 

22from manila.common import constants 

23from manila import context 

24from manila import db 

25from manila import exception 

26from manila import policy 

27from manila import test 

28from manila.tests.api import fakes 

29from manila.tests import db_utils 

30 

31CONF = cfg.CONF 

32 

33 

34@ddt.ddt 

35class ShareInstancesAPITest(test.TestCase): 

36 """Share instances API Test.""" 

37 

38 def setUp(self): 

39 super(ShareInstancesAPITest, self).setUp() 

40 self.controller = share_instances.ShareInstancesController() 

41 self.resource_name = self.controller.resource_name 

42 self.mock_policy_check = self.mock_object( 

43 policy, 'check_policy', mock.Mock(return_value=True)) 

44 self.admin_context = context.RequestContext('admin', 'fake', True) 

45 self.member_context = context.RequestContext('fake', 'fake') 

46 

47 def _get_context(self, role): 

48 return getattr(self, '%s_context' % role) 

49 

50 def _setup_share_instance_data(self, instance=None, version='2.7'): 

51 if instance is None: 

52 instance = db_utils.create_share(status=constants.STATUS_AVAILABLE, 

53 size='1').instance 

54 path = '/v2/fake/share_instances/%s/action' % instance['id'] 

55 req = fakes.HTTPRequest.blank(path, script_name=path, version=version) 

56 return instance, req 

57 

58 def _get_request(self, uri, context=None, version="2.3"): 

59 if context is None: 

60 context = self.admin_context 

61 req = fakes.HTTPRequest.blank(uri, version=version) 

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

63 return req 

64 

65 def _validate_ids_in_share_instances_list(self, expected, actual): 

66 self.assertEqual(len(expected), len(actual)) 

67 self.assertEqual([i['id'] for i in expected], 

68 [i['id'] for i in actual]) 

69 

70 @ddt.data("2.3", "2.34", "2.35", "2.69") 

71 def test_index(self, version): 

72 url = '/share_instances' 

73 if (api_version_request.APIVersionRequest(version) >= 

74 api_version_request.APIVersionRequest('2.35')): 

75 url += "?export_location_path=/admin/export/location" 

76 if (api_version_request.APIVersionRequest(version) >= 

77 api_version_request.APIVersionRequest('2.69')): 

78 url += "&is_soft_deleted=true" 

79 req = self._get_request(url, version=version) 

80 req_context = req.environ['manila.context'] 

81 last_instance = [db_utils.create_share(size=1, 

82 is_soft_deleted=True).instance] 

83 share_instances_count = 3 

84 other_instances = [ 

85 db_utils.create_share(size=s + 1).instance 

86 for s in range(0, share_instances_count) 

87 ] 

88 test_instances = other_instances + last_instance 

89 

90 db.export_locations_update( 

91 self.admin_context, test_instances[0]['id'], 

92 '/admin/export/location', False) 

93 

94 actual_result = self.controller.index(req) 

95 

96 if (api_version_request.APIVersionRequest(version) >= 

97 api_version_request.APIVersionRequest('2.69')): 

98 test_instances = [] 

99 elif (api_version_request.APIVersionRequest(version) >= 

100 api_version_request.APIVersionRequest('2.35')): 

101 test_instances = test_instances[:1] 

102 else: 

103 test_instances = other_instances 

104 self._validate_ids_in_share_instances_list( 

105 test_instances, actual_result['share_instances']) 

106 self.mock_policy_check.assert_called_once_with( 

107 req_context, self.resource_name, 'index') 

108 

109 def test_index_with_limit(self): 

110 req = self._get_request('/v2/fake/share_instances') 

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

112 share_instances_count = 3 

113 test_instances = [ 

114 db_utils.create_share(size=s + 1).instance 

115 for s in range(0, share_instances_count) 

116 ] 

117 expect_links = [ 

118 { 

119 'href': ( 

120 'http://localhost/share/v2/fake/share_instances?' 

121 'limit=3&marker=%s' % test_instances[2]['id']), 

122 'rel': 'next', 

123 } 

124 ] 

125 

126 url = '/v2/fake/share_instances?limit=3' 

127 req = self._get_request(url) 

128 actual_result = self.controller.index(req) 

129 

130 self._validate_ids_in_share_instances_list( 

131 test_instances, actual_result['share_instances']) 

132 self.assertEqual(expect_links, actual_result['share_instances_links']) 

133 self.mock_policy_check.assert_called_once_with( 

134 req_context, self.resource_name, 'index') 

135 

136 @ddt.data('2.3', '2.54', '2.71') 

137 def test_show(self, version): 

138 test_instance = db_utils.create_share(size=1).instance 

139 id = test_instance['id'] 

140 

141 actual_result = self.controller.show( 

142 self._get_request('fake', version=version), id) 

143 

144 self.assertEqual(id, actual_result['share_instance']['id']) 

145 if (api_version_request.APIVersionRequest(version) >= 

146 api_version_request.APIVersionRequest("2.54")): 

147 self.assertIn("progress", actual_result['share_instance']) 

148 else: 

149 self.assertNotIn("progress", actual_result['share_instance']) 

150 if (api_version_request.APIVersionRequest(version) >= 

151 api_version_request.APIVersionRequest("2.71")): 

152 self.assertIn("updated_at", actual_result['share_instance']) 

153 else: 

154 self.assertNotIn("updated_at", actual_result['share_instance']) 

155 

156 self.mock_policy_check.assert_called_once_with( 

157 self.admin_context, self.resource_name, 'show') 

158 

159 def test_show_with_export_locations(self): 

160 test_instance = db_utils.create_share(size=1).instance 

161 req = self._get_request('fake', version="2.8") 

162 id = test_instance['id'] 

163 

164 actual_result = self.controller.show(req, id) 

165 

166 self.assertEqual(id, actual_result['share_instance']['id']) 

167 self.assertIn("export_location", actual_result['share_instance']) 

168 self.assertIn("export_locations", actual_result['share_instance']) 

169 self.mock_policy_check.assert_called_once_with( 

170 self.admin_context, self.resource_name, 'show') 

171 

172 def test_show_without_export_locations(self): 

173 test_instance = db_utils.create_share(size=1).instance 

174 req = self._get_request('fake', version="2.9") 

175 id = test_instance['id'] 

176 

177 actual_result = self.controller.show(req, id) 

178 

179 self.assertEqual(id, actual_result['share_instance']['id']) 

180 self.assertNotIn("export_location", actual_result['share_instance']) 

181 self.assertNotIn("export_locations", actual_result['share_instance']) 

182 self.mock_policy_check.assert_called_once_with( 

183 self.admin_context, self.resource_name, 'show') 

184 

185 def test_show_with_replica_state(self): 

186 test_instance = db_utils.create_share(size=1).instance 

187 req = self._get_request('fake', version="2.11") 

188 id = test_instance['id'] 

189 

190 actual_result = self.controller.show(req, id) 

191 

192 self.assertEqual(id, actual_result['share_instance']['id']) 

193 self.assertIn("replica_state", actual_result['share_instance']) 

194 self.mock_policy_check.assert_called_once_with( 

195 self.admin_context, self.resource_name, 'show') 

196 

197 @ddt.data("2.3", "2.8", "2.9", "2.11") 

198 def test_get_share_instances(self, version): 

199 test_share = db_utils.create_share(size=1) 

200 id = test_share['id'] 

201 req = self._get_request('fake', version=version) 

202 req_context = req.environ['manila.context'] 

203 share_policy_check_call = mock.call( 

204 req_context, 'share', 'get', mock.ANY, do_raise=False) 

205 get_instances_policy_check_call = mock.call( 

206 req_context, 'share_instance', 'index') 

207 

208 actual_result = self.controller.get_share_instances(req, id) 

209 

210 self._validate_ids_in_share_instances_list( 

211 [test_share.instance], 

212 actual_result['share_instances'] 

213 ) 

214 self.assertEqual(1, len(actual_result.get("share_instances", 0))) 

215 for instance in actual_result["share_instances"]: 

216 if (api_version_request.APIVersionRequest(version) > 

217 api_version_request.APIVersionRequest("2.8")): 

218 assert_method = self.assertNotIn 

219 else: 

220 assert_method = self.assertIn 

221 assert_method("export_location", instance) 

222 assert_method("export_locations", instance) 

223 if (api_version_request.APIVersionRequest(version) > 

224 api_version_request.APIVersionRequest("2.10")): 

225 self.assertIn("replica_state", instance) 

226 self.mock_policy_check.assert_has_calls([ 

227 get_instances_policy_check_call, share_policy_check_call]) 

228 

229 @ddt.data('show', 'get_share_instances') 

230 def test_not_found(self, target_method_name): 

231 method = getattr(self.controller, target_method_name) 

232 action = (target_method_name if target_method_name == 'show' else 

233 'index') 

234 self.assertRaises(webob_exc.HTTPNotFound, method, 

235 self._get_request('fake'), 'fake') 

236 self.mock_policy_check.assert_called_once_with( 

237 self.admin_context, self.resource_name, action) 

238 

239 @ddt.data(('show', 2), ('get_share_instances', 2), ('index', 1)) 

240 @ddt.unpack 

241 def test_access(self, target_method_name, args_count): 

242 user_context = context.RequestContext('fake', 'fake') 

243 req = self._get_request('fake', user_context) 

244 policy_exception = exception.PolicyNotAuthorized( 

245 action=target_method_name) 

246 target_method = getattr(self.controller, target_method_name) 

247 args = [i for i in range(1, args_count)] 

248 

249 with mock.patch.object(policy, 'check_policy', mock.Mock( 

250 side_effect=policy_exception)): 

251 self.assertRaises( 

252 webob_exc.HTTPForbidden, target_method, req, *args) 

253 

254 def _reset_status(self, ctxt, model, req, db_access_method, 

255 valid_code, valid_status=None, body=None, version='2.7'): 

256 if float(version) > 2.6: 

257 action_name = 'reset_status' 

258 else: 

259 action_name = 'os-reset_status' 

260 if body is None: 

261 body = {action_name: {'status': constants.STATUS_ERROR}} 

262 req.method = 'POST' 

263 req.headers['content-type'] = 'application/json' 

264 req.headers['X-Openstack-Manila-Api-Version'] = version 

265 req.body = jsonutils.dumps(body).encode("utf-8") 

266 req.environ['manila.context'] = ctxt 

267 

268 with mock.patch.object( 

269 policy, 'check_policy', fakes.mock_fake_admin_check): 

270 resp = req.get_response(fakes.app()) 

271 

272 # validate response code and model status 

273 self.assertEqual(valid_code, resp.status_int) 

274 

275 actual_model = db_access_method(ctxt, model['id']) 

276 self.assertEqual(valid_status, actual_model['status']) 

277 

278 @ddt.data(*fakes.fixture_reset_status_with_different_roles) 

279 @ddt.unpack 

280 def test_share_instances_reset_status_with_different_roles(self, role, 

281 valid_code, 

282 valid_status, 

283 version): 

284 ctxt = self._get_context(role) 

285 instance, req = self._setup_share_instance_data(version=version) 

286 

287 self._reset_status(ctxt, instance, req, db.share_instance_get, 

288 valid_code, valid_status, version=version) 

289 

290 @ddt.data(*fakes.fixture_valid_reset_status_body) 

291 @ddt.unpack 

292 def test_share_instance_reset_status(self, body, version): 

293 instance, req = self._setup_share_instance_data() 

294 req.headers['X-Openstack-Manila-Api-Version'] = version 

295 

296 if float(version) > 2.6: 

297 state = body['reset_status']['status'] 

298 else: 

299 state = body['os-reset_status']['status'] 

300 self._reset_status(self.admin_context, instance, req, 

301 db.share_instance_get, 202, 

302 state, body, version=version) 

303 

304 @ddt.data( 

305 ({'os-reset_status': {'x-status': 'bad'}}, '2.6'), 

306 ({'os-reset_status': {'status': 'invalid'}}, '2.6'), 

307 ({'reset_status': {'x-status': 'bad'}}, '2.7'), 

308 ({'reset_status': {'status': 'invalid'}}, '2.7'), 

309 ) 

310 @ddt.unpack 

311 def test_share_instance_invalid_reset_status_body(self, body, version): 

312 instance, req = self._setup_share_instance_data() 

313 req.headers['X-Openstack-Manila-Api-Version'] = version 

314 

315 self._reset_status(self.admin_context, instance, req, 

316 db.share_instance_get, 400, 

317 constants.STATUS_AVAILABLE, body, version=version) 

318 

319 def _force_delete(self, ctxt, model, req, db_access_method, valid_code, 

320 check_model_in_db=False, version='2.7'): 

321 if float(version) > 2.6: 

322 action_name = 'force_delete' 

323 else: 

324 action_name = 'os-force_delete' 

325 body = {action_name: {'status': constants.STATUS_ERROR}} 

326 req.method = 'POST' 

327 req.headers['content-type'] = 'application/json' 

328 req.headers['X-Openstack-Manila-Api-Version'] = version 

329 req.body = jsonutils.dumps(body).encode("utf-8") 

330 req.environ['manila.context'] = ctxt 

331 

332 with mock.patch.object( 

333 policy, 'check_policy', fakes.mock_fake_admin_check): 

334 resp = req.get_response(fakes.app()) 

335 

336 # validate response 

337 self.assertEqual(valid_code, resp.status_int) 

338 

339 if valid_code == 202 and check_model_in_db: 339 ↛ 340line 339 didn't jump to line 340 because the condition on line 339 was never true

340 self.assertRaises(exception.NotFound, 

341 db_access_method, 

342 ctxt, 

343 model['id']) 

344 

345 @ddt.data(*fakes.fixture_force_delete_with_different_roles) 

346 @ddt.unpack 

347 def test_instance_force_delete_with_different_roles(self, role, resp_code, 

348 version): 

349 instance, req = self._setup_share_instance_data(version=version) 

350 ctxt = self._get_context(role) 

351 

352 self._force_delete(ctxt, instance, req, db.share_instance_get, 

353 resp_code, version=version) 

354 

355 def test_instance_force_delete_missing(self): 

356 instance, req = self._setup_share_instance_data( 

357 instance={'id': 'fake'}) 

358 ctxt = self._get_context('admin') 

359 

360 self._force_delete(ctxt, instance, req, db.share_instance_get, 404)