Coverage for manila/tests/api/test_common.py: 100%

272 statements  

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

1# Copyright 2010 OpenStack LLC. 

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 

16""" 

17Test suites for 'common' code used throughout the OpenStack HTTP API. 

18""" 

19 

20from unittest import mock 

21 

22import ddt 

23import webob 

24import webob.exc 

25 

26from manila.api import common 

27from manila.db import api as db_api 

28from manila import exception 

29from manila import policy 

30from manila import test 

31from manila.tests.api import fakes 

32from manila.tests.db import fakes as db_fakes 

33 

34 

35class LimiterTest(test.TestCase): 

36 """Unit tests for the `manila.api.common.limited` method. 

37 

38 Takes in a list of items and, depending on the 'offset' and 

39 'limit' GET params, returns a subset or complete set of the given 

40 items. 

41 """ 

42 

43 def setUp(self): 

44 """Run before each test.""" 

45 super(LimiterTest, self).setUp() 

46 self.tiny = list(range(1)) 

47 self.small = list(range(10)) 

48 self.medium = list(range(1000)) 

49 self.large = list(range(10000)) 

50 

51 def test_limiter_offset_zero(self): 

52 """Test offset key works with 0.""" 

53 req = webob.Request.blank('/?offset=0') 

54 self.assertEqual(self.tiny, common.limited(self.tiny, req)) 

55 self.assertEqual(self.small, common.limited(self.small, req)) 

56 self.assertEqual(self.medium, common.limited(self.medium, req)) 

57 self.assertEqual(self.large[:1000], common.limited(self.large, req)) 

58 

59 def test_limiter_offset_medium(self): 

60 """Test offset key works with a medium sized number.""" 

61 req = webob.Request.blank('/?offset=10') 

62 self.assertEqual([], common.limited(self.tiny, req)) 

63 self.assertEqual(self.small[10:], common.limited(self.small, req)) 

64 self.assertEqual(self.medium[10:], common.limited(self.medium, req)) 

65 self.assertEqual(self.large[10:1010], common.limited(self.large, req)) 

66 

67 def test_limiter_offset_over_max(self): 

68 """Test offset key works with a number over 1000 (max_limit).""" 

69 req = webob.Request.blank('/?offset=1001') 

70 self.assertEqual([], common.limited(self.tiny, req)) 

71 self.assertEqual([], common.limited(self.small, req)) 

72 self.assertEqual([], common.limited(self.medium, req)) 

73 self.assertEqual( 

74 self.large[1001:2001], common.limited(self.large, req)) 

75 

76 def test_limiter_offset_blank(self): 

77 """Test offset key works with a blank offset.""" 

78 req = webob.Request.blank('/?offset=') 

79 self.assertRaises( 

80 webob.exc.HTTPBadRequest, common.limited, self.tiny, req) 

81 

82 def test_limiter_offset_bad(self): 

83 """Test offset key works with a BAD offset.""" 

84 req = webob.Request.blank(u'/?offset=\u0020aa') 

85 self.assertRaises( 

86 webob.exc.HTTPBadRequest, common.limited, self.tiny, req) 

87 

88 def test_limiter_nothing(self): 

89 """Test request with no offset or limit.""" 

90 req = webob.Request.blank('/') 

91 self.assertEqual(self.tiny, common.limited(self.tiny, req)) 

92 self.assertEqual(self.small, common.limited(self.small, req)) 

93 self.assertEqual(self.medium, common.limited(self.medium, req)) 

94 self.assertEqual(self.large[:1000], common.limited(self.large, req)) 

95 

96 def test_limiter_limit_zero(self): 

97 """Test limit of zero.""" 

98 req = webob.Request.blank('/?limit=0') 

99 self.assertEqual(self.tiny, common.limited(self.tiny, req)) 

100 self.assertEqual(self.small, common.limited(self.small, req)) 

101 self.assertEqual(self.medium, common.limited(self.medium, req)) 

102 self.assertEqual(self.large[:1000], common.limited(self.large, req)) 

103 

104 def test_limiter_limit_medium(self): 

105 """Test limit of 10.""" 

106 req = webob.Request.blank('/?limit=10') 

107 self.assertEqual(self.tiny, common.limited(self.tiny, req)) 

108 self.assertEqual(self.small, common.limited(self.small, req)) 

109 self.assertEqual(self.medium[:10], common.limited(self.medium, req)) 

110 self.assertEqual(self.large[:10], common.limited(self.large, req)) 

111 

112 def test_limiter_limit_over_max(self): 

113 """Test limit of 3000.""" 

114 req = webob.Request.blank('/?limit=3000') 

115 self.assertEqual(self.tiny, common.limited(self.tiny, req)) 

116 self.assertEqual(self.small, common.limited(self.small, req)) 

117 self.assertEqual(self.medium, common.limited(self.medium, req)) 

118 self.assertEqual(self.large[:1000], common.limited(self.large, req)) 

119 

120 def test_limiter_limit_and_offset(self): 

121 """Test request with both limit and offset.""" 

122 items = list(range(2000)) 

123 req = webob.Request.blank('/?offset=1&limit=3') 

124 self.assertEqual(items[1:4], common.limited(items, req)) 

125 req = webob.Request.blank('/?offset=3&limit=0') 

126 self.assertEqual(items[3:1003], common.limited(items, req)) 

127 req = webob.Request.blank('/?offset=3&limit=1500') 

128 self.assertEqual(items[3:1003], common.limited(items, req)) 

129 req = webob.Request.blank('/?offset=3000&limit=10') 

130 self.assertEqual([], common.limited(items, req)) 

131 

132 def test_limiter_custom_max_limit(self): 

133 """Test a max_limit other than 1000.""" 

134 items = list(range(2000)) 

135 req = webob.Request.blank('/?offset=1&limit=3') 

136 self.assertEqual( 

137 items[1:4], common.limited(items, req, max_limit=2000)) 

138 req = webob.Request.blank('/?offset=3&limit=0') 

139 self.assertEqual( 

140 items[3:], common.limited(items, req, max_limit=2000)) 

141 req = webob.Request.blank('/?offset=3&limit=2500') 

142 self.assertEqual( 

143 items[3:], common.limited(items, req, max_limit=2000)) 

144 req = webob.Request.blank('/?offset=3000&limit=10') 

145 self.assertEqual([], common.limited(items, req, max_limit=2000)) 

146 

147 def test_limiter_negative_limit(self): 

148 """Test a negative limit.""" 

149 req = webob.Request.blank('/?limit=-3000') 

150 self.assertRaises( 

151 webob.exc.HTTPBadRequest, common.limited, self.tiny, req) 

152 

153 def test_limiter_negative_offset(self): 

154 """Test a negative offset.""" 

155 req = webob.Request.blank('/?offset=-30') 

156 self.assertRaises( 

157 webob.exc.HTTPBadRequest, common.limited, self.tiny, req) 

158 

159 

160class PaginationParamsTest(test.TestCase): 

161 """Unit tests for the `manila.api.common.get_pagination_params` method. 

162 

163 Takes in a request object and returns 'marker' and 'limit' GET 

164 params. 

165 """ 

166 

167 def test_no_params(self): 

168 """Test no params.""" 

169 req = webob.Request.blank('/') 

170 self.assertEqual({}, common.get_pagination_params(req)) 

171 

172 def test_valid_marker(self): 

173 """Test valid marker param.""" 

174 req = webob.Request.blank( 

175 '/?marker=263abb28-1de6-412f-b00b-f0ee0c4333c2') 

176 self.assertEqual({'marker': '263abb28-1de6-412f-b00b-f0ee0c4333c2'}, 

177 common.get_pagination_params(req)) 

178 

179 def test_valid_limit(self): 

180 """Test valid limit param.""" 

181 req = webob.Request.blank('/?limit=10') 

182 self.assertEqual({'limit': 10}, common.get_pagination_params(req)) 

183 

184 def test_invalid_limit(self): 

185 """Test invalid limit param.""" 

186 req = webob.Request.blank('/?limit=-2') 

187 self.assertRaises( 

188 webob.exc.HTTPBadRequest, common.get_pagination_params, req) 

189 

190 def test_valid_limit_and_marker(self): 

191 """Test valid limit and marker parameters.""" 

192 marker = '263abb28-1de6-412f-b00b-f0ee0c4333c2' 

193 req = webob.Request.blank('/?limit=20&marker=%s' % marker) 

194 self.assertEqual({'marker': marker, 'limit': 20}, 

195 common.get_pagination_params(req)) 

196 

197 

198@ddt.ddt 

199class MiscFunctionsTest(test.TestCase): 

200 

201 @ddt.data( 

202 ('http://manila.example.com/v2/b2d18606-2673-4965-885a-4f5a8b955b9b/', 

203 'http://manila.example.com/b2d18606-2673-4965-885a-4f5a8b955b9b/'), 

204 ('http://manila.example.com/v1/', 

205 'http://manila.example.com/'), 

206 ('http://manila.example.com/share/v2.22/', 

207 'http://manila.example.com/share/'), 

208 ('http://manila.example.com/share/v1/' 

209 'b2d18606-2673-4965-885a-4f5a8b955b9b/', 

210 'http://manila.example.com/share/' 

211 'b2d18606-2673-4965-885a-4f5a8b955b9b/'), 

212 ('http://10.10.10.10:3366/v1/', 

213 'http://10.10.10.10:3366/'), 

214 ('http://10.10.10.10:3366/v2/b2d18606-2673-4965-885a-4f5a8b955b9b/', 

215 'http://10.10.10.10:3366/b2d18606-2673-4965-885a-4f5a8b955b9b/'), 

216 ('http://manila.example.com:3366/v1.1/', 

217 'http://manila.example.com:3366/'), 

218 ('http://manila.example.com:3366/v2/' 

219 'b2d18606-2673-4965-885a-4f5a8b955b9b/', 

220 'http://manila.example.com:3366/' 

221 'b2d18606-2673-4965-885a-4f5a8b955b9b/')) 

222 @ddt.unpack 

223 def test_remove_version_from_href(self, fixture, expected): 

224 actual = common.remove_version_from_href(fixture) 

225 self.assertEqual(expected, actual) 

226 

227 @ddt.data('http://manila.example.com/1.1/shares', 

228 'http://manila.example.com/v/shares', 

229 'http://manila.example.com/v1.1shares') 

230 def test_remove_version_from_href_bad_request(self, fixture): 

231 self.assertRaises(ValueError, 

232 common.remove_version_from_href, 

233 fixture) 

234 

235 def test_validate_cephx_id_invalid_with_period(self): 

236 self.assertRaises(webob.exc.HTTPBadRequest, 

237 common.validate_cephx_id, 

238 "client.manila") 

239 

240 def test_validate_cephx_id_invalid_with_non_ascii(self): 

241 self.assertRaises(webob.exc.HTTPBadRequest, 

242 common.validate_cephx_id, 

243 u"bj\u00F6rn") 

244 

245 @ddt.data("alice", "alice_bob", "alice bob") 

246 def test_validate_cephx_id_valid(self, test_id): 

247 common.validate_cephx_id(test_id) 

248 

249 @ddt.data(['ip', '1.1.1.1', False, False], ['user', 'alice', False, False], 

250 ['cert', 'alice', False, False], ['cephx', 'alice', True, False], 

251 ['user', 'alice$', False, False], 

252 ['user', 'test group name', False, False], 

253 ['user', 'group$.-_\'`{}', False, False], 

254 ['ip', '172.24.41.0/24', False, False], 

255 ['ip', '1001::1001', False, True], 

256 ['ip', '1001::1000/120', False, True]) 

257 @ddt.unpack 

258 def test_validate_access(self, access_type, access_to, ceph, enable_ipv6): 

259 common.validate_access(access_type=access_type, access_to=access_to, 

260 enable_ceph=ceph, enable_ipv6=enable_ipv6) 

261 

262 @ddt.data(['ip', 'alice', False], ['ip', '1.1.1.0/10/12', False], 

263 ['ip', '255.255.255.265', False], ['ip', '1.1.1.0/34', False], 

264 ['cert', '', False], ['cephx', 'client.alice', True], 

265 ['group', 'alice', True], ['cephx', 'alice', False], 

266 ['cephx', '', True], ['user', 'bob/', False], 

267 ['user', 'group<>', False], ['user', '+=*?group', False], 

268 ['ip', '1001::1001/256', False], 

269 ['ip', '1001:1001/256', False],) 

270 @ddt.unpack 

271 def test_validate_access_exception(self, access_type, access_to, ceph): 

272 self.assertRaises(webob.exc.HTTPBadRequest, common.validate_access, 

273 access_type=access_type, access_to=access_to, 

274 enable_ceph=ceph) 

275 

276 def test_validate_public_share_policy_no_is_public(self): 

277 api_params = {'foo': 'bar', 'clemson': 'tigers'} 

278 self.mock_object(policy, 'check_policy') 

279 

280 actual_params = common.validate_public_share_policy( 

281 'fake_context', api_params) 

282 

283 self.assertDictEqual(api_params, actual_params) 

284 policy.check_policy.assert_not_called() 

285 

286 @ddt.data('foo', 123, 'all', None) 

287 def test_validate_public_share_policy_invalid_value(self, is_public): 

288 api_params = {'is_public': is_public} 

289 self.mock_object(policy, 'check_policy') 

290 

291 self.assertRaises(exception.InvalidParameterValue, 

292 common.validate_public_share_policy, 

293 'fake_context', 

294 api_params) 

295 policy.check_policy.assert_not_called() 

296 

297 @ddt.data('1', True, 'true', 'yes') 

298 def test_validate_public_share_not_authorized(self, is_public): 

299 api_params = {'is_public': is_public, 'size': '16'} 

300 self.mock_object(policy, 'check_policy', mock.Mock(return_value=False)) 

301 

302 self.assertRaises(exception.NotAuthorized, 

303 common.validate_public_share_policy, 

304 'fake_context', 

305 api_params) 

306 policy.check_policy.assert_called_once_with( 

307 'fake_context', 'share', 'create_public_share', do_raise=False) 

308 

309 @ddt.data('0', False, 'false', 'no') 

310 def test_validate_public_share_is_public_False(self, is_public): 

311 api_params = {'is_public': is_public, 'size': '16'} 

312 self.mock_object(policy, 'check_policy', mock.Mock(return_value=False)) 

313 

314 actual_params = common.validate_public_share_policy( 

315 'fake_context', api_params, api='update') 

316 

317 self.assertDictEqual({'is_public': False, 'size': '16'}, actual_params) 

318 policy.check_policy.assert_called_once_with( 

319 'fake_context', 'share', 'set_public_share', do_raise=False) 

320 

321 @ddt.data('1', True, 'true', 'yes') 

322 def test_validate_public_share_is_public_True(self, is_public): 

323 api_params = {'is_public': is_public, 'size': '16'} 

324 self.mock_object(policy, 'check_policy', mock.Mock(return_value=True)) 

325 

326 actual_params = common.validate_public_share_policy( 

327 'fake_context', api_params, api='update') 

328 

329 self.assertDictEqual({'is_public': True, 'size': '16'}, actual_params) 

330 policy.check_policy.assert_called_once_with( 

331 'fake_context', 'share', 'set_public_share', do_raise=False) 

332 

333 @ddt.data(({}, True), 

334 ({'neutron_net_id': 'fake_nn_id'}, False), 

335 ({'neutron_subnet_id': 'fake_sn_id'}, False), 

336 ({'neutron_net_id': 'fake_nn_id', 

337 'neutron_subnet_id': 'fake_sn_id'}, True)) 

338 @ddt.unpack 

339 def test__check_net_id_and_subnet_id(self, body, expected): 

340 if not expected: 

341 self.assertRaises(webob.exc.HTTPBadRequest, 

342 common.check_net_id_and_subnet_id, 

343 body) 

344 else: 

345 result = common.check_net_id_and_subnet_id(body) 

346 self.assertIsNone(result) 

347 

348 @ddt.data(None, True, 'true', 'false', 'all') 

349 def test_parse_is_public_valid(self, value): 

350 result = common.parse_is_public(value) 

351 self.assertIn(result, (True, False, None)) 

352 

353 def test_parse_is_public_invalid(self): 

354 self.assertRaises(webob.exc.HTTPBadRequest, 

355 common.parse_is_public, 

356 'fakefakefake') 

357 

358 @ddt.data(None, 'fake_az') 

359 def test__get_existing_subnets(self, az): 

360 default_subnets = 'fake_default_subnets' 

361 mock_get_default_subnets = self.mock_object( 

362 db_api, 'share_network_subnet_get_default_subnets', 

363 mock.Mock(return_value=default_subnets)) 

364 subnets = 'fake_subnets' 

365 mock_get_subnets = self.mock_object( 

366 db_api, 'share_network_subnets_get_all_by_availability_zone_id', 

367 mock.Mock(return_value=subnets)) 

368 

369 net_id = 'fake_net' 

370 context = 'fake_context' 

371 res_subnets = common._get_existing_subnets(context, net_id, az) 

372 

373 if az: 

374 self.assertEqual(subnets, res_subnets) 

375 mock_get_subnets.assert_called_once_with(context, net_id, az, 

376 fallback_to_default=False) 

377 mock_get_default_subnets.assert_not_called() 

378 else: 

379 self.assertEqual(default_subnets, res_subnets) 

380 mock_get_subnets.assert_not_called() 

381 mock_get_default_subnets.assert_called_once_with(context, net_id) 

382 

383 def test_validate_subnet_create(self): 

384 mock_check_net = self.mock_object(common, 'check_net_id_and_subnet_id') 

385 net = 'fake_net' 

386 mock_get_net = self.mock_object(db_api, 'share_network_get', 

387 mock.Mock(return_value=net)) 

388 az_id = 'fake_az_id' 

389 az = {'id': az_id} 

390 mock_get_az = self.mock_object(db_api, 'availability_zone_get', 

391 mock.Mock(return_value=az)) 

392 subnets = 'fake_subnets' 

393 mock_get_subnets = self.mock_object(common, '_get_existing_subnets', 

394 mock.Mock(return_value=subnets)) 

395 

396 net_id = 'fake_net_id' 

397 context = 'fake_context' 

398 az_name = 'fake_az' 

399 data = {'availability_zone': az_name} 

400 res_net, res_subnets = common.validate_subnet_create( 

401 context, net_id, data, True) 

402 

403 self.assertEqual(net, res_net) 

404 self.assertEqual(subnets, res_subnets) 

405 self.assertEqual(data['availability_zone_id'], az_id) 

406 mock_check_net.assert_called_once_with(data) 

407 mock_get_net.assert_called_once_with(context, net_id) 

408 mock_get_az.assert_called_once_with(context, az_name) 

409 mock_get_subnets.assert_called_once_with(context, net_id, az_id) 

410 

411 def test_validate_subnet_create_net_not_found(self): 

412 

413 self.mock_object(common, 'check_net_id_and_subnet_id') 

414 self.mock_object(db_api, 'share_network_get', 

415 mock.Mock(side_effect=exception.ShareNetworkNotFound( 

416 share_network_id="fake_id"))) 

417 

418 net_id = 'fake_net_id' 

419 context = 'fake_context' 

420 az_name = 'fake_az' 

421 data = {'availability_zone': az_name} 

422 self.assertRaises(webob.exc.HTTPNotFound, 

423 common.validate_subnet_create, 

424 context, net_id, data, True) 

425 

426 def test_validate_subnet_create_az_not_found(self): 

427 self.mock_object(common, 'check_net_id_and_subnet_id') 

428 self.mock_object(db_api, 'share_network_get', 

429 mock.Mock(return_value='fake_net')) 

430 self.mock_object( 

431 db_api, 'availability_zone_get', 

432 mock.Mock(side_effect=exception.AvailabilityZoneNotFound( 

433 id='fake_id'))) 

434 

435 net_id = 'fake_net_id' 

436 context = 'fake_context' 

437 az_name = 'fake_az' 

438 data = {'availability_zone': az_name} 

439 self.assertRaises(webob.exc.HTTPBadRequest, 

440 common.validate_subnet_create, 

441 context, net_id, data, True) 

442 

443 def test_validate_subnet_create_multiple_subnet_not_support(self): 

444 self.mock_object(common, 'check_net_id_and_subnet_id') 

445 self.mock_object(db_api, 'share_network_get', 

446 mock.Mock(return_value='fake_net')) 

447 self.mock_object(db_api, 'availability_zone_get', 

448 mock.Mock(return_value={'id': 'fake_az_id'})) 

449 self.mock_object(common, '_get_existing_subnets', 

450 mock.Mock(return_value='fake_subnets')) 

451 

452 net_id = 'fake_net_id' 

453 context = 'fake_context' 

454 az_name = 'fake_az' 

455 data = {'availability_zone': az_name} 

456 self.assertRaises(webob.exc.HTTPConflict, 

457 common.validate_subnet_create, 

458 context, net_id, data, False) 

459 

460 

461@ddt.ddt 

462class ViewBuilderTest(test.TestCase): 

463 

464 def setUp(self): 

465 super(ViewBuilderTest, self).setUp() 

466 self.expected_resource_dict = { 

467 'id': 'fake_resource_id', 

468 'foo': 'quz', 

469 'fred': 'bob', 

470 'alice': 'waldo', 

471 'spoon': 'spam', 

472 'xyzzy': 'qwerty', 

473 } 

474 self.fake_resource = db_fakes.FakeModel(self.expected_resource_dict) 

475 self.view_builder = fakes.FakeResourceViewBuilder() 

476 

477 @ddt.data('1.0', '1.40') 

478 def test_versioned_method_no_updates(self, version): 

479 req = fakes.HTTPRequest.blank('/my_resource', version=version) 

480 

481 actual_resource = self.view_builder.view(req, self.fake_resource) 

482 

483 self.assertEqual(set({'id', 'foo', 'fred', 'alice'}), 

484 set(actual_resource.keys())) 

485 

486 @ddt.data(True, False) 

487 def test_versioned_method_v1_6(self, is_admin): 

488 req = fakes.HTTPRequest.blank('/my_resource', version='1.6', 

489 use_admin_context=is_admin) 

490 expected_keys = set({'id', 'foo', 'fred', 'alice'}) 

491 if is_admin: 

492 expected_keys.add('spoon') 

493 

494 actual_resource = self.view_builder.view(req, self.fake_resource) 

495 

496 self.assertEqual(expected_keys, set(actual_resource.keys())) 

497 

498 @ddt.unpack 

499 @ddt.data({'is_admin': True, 'version': '3.14'}, 

500 {'is_admin': False, 'version': '3.14'}, 

501 {'is_admin': False, 'version': '6.2'}, 

502 {'is_admin': True, 'version': '6.2'}) 

503 def test_versioned_method_all_match(self, is_admin, version): 

504 req = fakes.HTTPRequest.blank( 

505 '/my_resource', version=version, use_admin_context=is_admin) 

506 

507 expected_keys = set({'id', 'fred', 'xyzzy', 'alice'}) 

508 if is_admin: 

509 expected_keys.add('spoon') 

510 

511 actual_resource = self.view_builder.view(req, self.fake_resource) 

512 

513 self.assertEqual(expected_keys, set(actual_resource.keys()))