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
« 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.
16"""
17Test suites for 'common' code used throughout the OpenStack HTTP API.
18"""
20from unittest import mock
22import ddt
23import webob
24import webob.exc
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
35class LimiterTest(test.TestCase):
36 """Unit tests for the `manila.api.common.limited` method.
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 """
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))
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))
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))
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))
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)
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)
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))
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))
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))
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))
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))
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))
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)
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)
160class PaginationParamsTest(test.TestCase):
161 """Unit tests for the `manila.api.common.get_pagination_params` method.
163 Takes in a request object and returns 'marker' and 'limit' GET
164 params.
165 """
167 def test_no_params(self):
168 """Test no params."""
169 req = webob.Request.blank('/')
170 self.assertEqual({}, common.get_pagination_params(req))
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))
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))
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)
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))
198@ddt.ddt
199class MiscFunctionsTest(test.TestCase):
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)
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)
235 def test_validate_cephx_id_invalid_with_period(self):
236 self.assertRaises(webob.exc.HTTPBadRequest,
237 common.validate_cephx_id,
238 "client.manila")
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")
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)
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)
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)
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')
280 actual_params = common.validate_public_share_policy(
281 'fake_context', api_params)
283 self.assertDictEqual(api_params, actual_params)
284 policy.check_policy.assert_not_called()
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')
291 self.assertRaises(exception.InvalidParameterValue,
292 common.validate_public_share_policy,
293 'fake_context',
294 api_params)
295 policy.check_policy.assert_not_called()
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))
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)
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))
314 actual_params = common.validate_public_share_policy(
315 'fake_context', api_params, api='update')
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)
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))
326 actual_params = common.validate_public_share_policy(
327 'fake_context', api_params, api='update')
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)
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)
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))
353 def test_parse_is_public_invalid(self):
354 self.assertRaises(webob.exc.HTTPBadRequest,
355 common.parse_is_public,
356 'fakefakefake')
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))
369 net_id = 'fake_net'
370 context = 'fake_context'
371 res_subnets = common._get_existing_subnets(context, net_id, az)
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)
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))
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)
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)
411 def test_validate_subnet_create_net_not_found(self):
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")))
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)
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')))
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)
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'))
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)
461@ddt.ddt
462class ViewBuilderTest(test.TestCase):
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()
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)
481 actual_resource = self.view_builder.view(req, self.fake_resource)
483 self.assertEqual(set({'id', 'foo', 'fred', 'alice'}),
484 set(actual_resource.keys()))
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')
494 actual_resource = self.view_builder.view(req, self.fake_resource)
496 self.assertEqual(expected_keys, set(actual_resource.keys()))
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)
507 expected_keys = set({'id', 'fred', 'xyzzy', 'alice'})
508 if is_admin:
509 expected_keys.add('spoon')
511 actual_resource = self.view_builder.view(req, self.fake_resource)
513 self.assertEqual(expected_keys, set(actual_resource.keys()))