OpenStack Image Management (Glance)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_quota.py 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. # Copyright 2013, Red Hat, Inc.
  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. import uuid
  16. import mock
  17. from mock import patch
  18. from oslo_utils import encodeutils
  19. from oslo_utils import units
  20. # NOTE(jokke): simplified transition to py3, behaves like py2 xrange
  21. from six.moves import range
  22. from glance.common import exception
  23. from glance.common import store_utils
  24. import glance.quota
  25. from glance.tests.unit import utils as unit_test_utils
  26. from glance.tests import utils as test_utils
  27. UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
  28. class FakeContext(object):
  29. owner = 'someone'
  30. is_admin = False
  31. class FakeImage(object):
  32. size = None
  33. image_id = 'someid'
  34. locations = [{'url': 'file:///not/a/path', 'metadata': {}}]
  35. tags = set([])
  36. def set_data(self, data, size=None, backend=None):
  37. self.size = 0
  38. for d in data:
  39. self.size += len(d)
  40. def __init__(self, **kwargs):
  41. self.extra_properties = kwargs.get('extra_properties', {})
  42. class TestImageQuota(test_utils.BaseTestCase):
  43. def setUp(self):
  44. super(TestImageQuota, self).setUp()
  45. def _get_image(self, location_count=1, image_size=10):
  46. context = FakeContext()
  47. db_api = unit_test_utils.FakeDB()
  48. store_api = unit_test_utils.FakeStoreAPI()
  49. store = unit_test_utils.FakeStoreUtils(store_api)
  50. base_image = FakeImage()
  51. base_image.image_id = 'xyz'
  52. base_image.size = image_size
  53. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  54. locations = []
  55. for i in range(location_count):
  56. locations.append({'url': 'file:///g/there/it/is%d' % i,
  57. 'metadata': {}, 'status': 'active'})
  58. image_values = {'id': 'xyz', 'owner': context.owner,
  59. 'status': 'active', 'size': image_size,
  60. 'locations': locations}
  61. db_api.image_create(context, image_values)
  62. return image
  63. def test_quota_allowed(self):
  64. quota = 10
  65. self.config(user_storage_quota=str(quota))
  66. context = FakeContext()
  67. db_api = unit_test_utils.FakeDB()
  68. store_api = unit_test_utils.FakeStoreAPI()
  69. store = unit_test_utils.FakeStoreUtils(store_api)
  70. base_image = FakeImage()
  71. base_image.image_id = 'id'
  72. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  73. data = '*' * quota
  74. base_image.set_data(data, size=None)
  75. image.set_data(data)
  76. self.assertEqual(quota, base_image.size)
  77. def _test_quota_allowed_unit(self, data_length, config_quota):
  78. self.config(user_storage_quota=config_quota)
  79. context = FakeContext()
  80. db_api = unit_test_utils.FakeDB()
  81. store_api = unit_test_utils.FakeStoreAPI()
  82. store = unit_test_utils.FakeStoreUtils(store_api)
  83. base_image = FakeImage()
  84. base_image.image_id = 'id'
  85. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  86. data = '*' * data_length
  87. base_image.set_data(data, size=None)
  88. image.set_data(data)
  89. self.assertEqual(data_length, base_image.size)
  90. def test_quota_allowed_unit_b(self):
  91. self._test_quota_allowed_unit(10, '10B')
  92. def test_quota_allowed_unit_kb(self):
  93. self._test_quota_allowed_unit(10, '1KB')
  94. def test_quota_allowed_unit_mb(self):
  95. self._test_quota_allowed_unit(10, '1MB')
  96. def test_quota_allowed_unit_gb(self):
  97. self._test_quota_allowed_unit(10, '1GB')
  98. def test_quota_allowed_unit_tb(self):
  99. self._test_quota_allowed_unit(10, '1TB')
  100. def _quota_exceeded_size(self, quota, data,
  101. deleted=True, size=None):
  102. self.config(user_storage_quota=quota)
  103. context = FakeContext()
  104. db_api = unit_test_utils.FakeDB()
  105. store_api = unit_test_utils.FakeStoreAPI()
  106. store = unit_test_utils.FakeStoreUtils(store_api)
  107. base_image = FakeImage()
  108. base_image.image_id = 'id'
  109. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  110. if deleted:
  111. with patch.object(store_utils, 'safe_delete_from_backend'):
  112. store_utils.safe_delete_from_backend(
  113. context,
  114. image.image_id,
  115. base_image.locations[0])
  116. self.assertRaises(exception.StorageQuotaFull,
  117. image.set_data,
  118. data,
  119. size=size)
  120. def test_quota_exceeded_no_size(self):
  121. quota = 10
  122. data = '*' * (quota + 1)
  123. # NOTE(jbresnah) When the image size is None it means that it is
  124. # not known. In this case the only time we will raise an
  125. # exception is when there is no room left at all, thus we know
  126. # it will not fit.
  127. # That's why 'get_remaining_quota' is mocked with return_value = 0.
  128. with patch.object(glance.api.common, 'get_remaining_quota',
  129. return_value=0):
  130. self._quota_exceeded_size(str(quota), data)
  131. def test_quota_exceeded_with_right_size(self):
  132. quota = 10
  133. data = '*' * (quota + 1)
  134. self._quota_exceeded_size(str(quota), data, size=len(data),
  135. deleted=False)
  136. def test_quota_exceeded_with_right_size_b(self):
  137. quota = 10
  138. data = '*' * (quota + 1)
  139. self._quota_exceeded_size('10B', data, size=len(data),
  140. deleted=False)
  141. def test_quota_exceeded_with_right_size_kb(self):
  142. quota = units.Ki
  143. data = '*' * (quota + 1)
  144. self._quota_exceeded_size('1KB', data, size=len(data),
  145. deleted=False)
  146. def test_quota_exceeded_with_lie_size(self):
  147. quota = 10
  148. data = '*' * (quota + 1)
  149. self._quota_exceeded_size(str(quota), data, deleted=False,
  150. size=quota - 1)
  151. def test_append_location(self):
  152. new_location = {'url': 'file:///a/path', 'metadata': {},
  153. 'status': 'active'}
  154. image = self._get_image()
  155. pre_add_locations = image.locations[:]
  156. image.locations.append(new_location)
  157. pre_add_locations.append(new_location)
  158. self.assertEqual(image.locations, pre_add_locations)
  159. def test_insert_location(self):
  160. new_location = {'url': 'file:///a/path', 'metadata': {},
  161. 'status': 'active'}
  162. image = self._get_image()
  163. pre_add_locations = image.locations[:]
  164. image.locations.insert(0, new_location)
  165. pre_add_locations.insert(0, new_location)
  166. self.assertEqual(image.locations, pre_add_locations)
  167. def test_extend_location(self):
  168. new_location = {'url': 'file:///a/path', 'metadata': {},
  169. 'status': 'active'}
  170. image = self._get_image()
  171. pre_add_locations = image.locations[:]
  172. image.locations.extend([new_location])
  173. pre_add_locations.extend([new_location])
  174. self.assertEqual(image.locations, pre_add_locations)
  175. def test_iadd_location(self):
  176. new_location = {'url': 'file:///a/path', 'metadata': {},
  177. 'status': 'active'}
  178. image = self._get_image()
  179. pre_add_locations = image.locations[:]
  180. image.locations += [new_location]
  181. pre_add_locations += [new_location]
  182. self.assertEqual(image.locations, pre_add_locations)
  183. def test_set_location(self):
  184. new_location = {'url': 'file:///a/path', 'metadata': {},
  185. 'status': 'active'}
  186. image = self._get_image()
  187. image.locations = [new_location]
  188. self.assertEqual(image.locations, [new_location])
  189. def _make_image_with_quota(self, image_size=10, location_count=2):
  190. quota = image_size * location_count
  191. self.config(user_storage_quota=str(quota))
  192. return self._get_image(image_size=image_size,
  193. location_count=location_count)
  194. def test_exceed_append_location(self):
  195. image = self._make_image_with_quota()
  196. self.assertRaises(exception.StorageQuotaFull,
  197. image.locations.append,
  198. {'url': 'file:///a/path', 'metadata': {},
  199. 'status': 'active'})
  200. def test_exceed_insert_location(self):
  201. image = self._make_image_with_quota()
  202. self.assertRaises(exception.StorageQuotaFull,
  203. image.locations.insert,
  204. 0,
  205. {'url': 'file:///a/path', 'metadata': {},
  206. 'status': 'active'})
  207. def test_exceed_extend_location(self):
  208. image = self._make_image_with_quota()
  209. self.assertRaises(exception.StorageQuotaFull,
  210. image.locations.extend,
  211. [{'url': 'file:///a/path', 'metadata': {},
  212. 'status': 'active'}])
  213. def test_set_location_under(self):
  214. image = self._make_image_with_quota(location_count=1)
  215. image.locations = [{'url': 'file:///a/path', 'metadata': {},
  216. 'status': 'active'}]
  217. def test_set_location_exceed(self):
  218. image = self._make_image_with_quota(location_count=1)
  219. try:
  220. image.locations = [{'url': 'file:///a/path', 'metadata': {},
  221. 'status': 'active'},
  222. {'url': 'file:///a/path2', 'metadata': {},
  223. 'status': 'active'}]
  224. self.fail('Should have raised the quota exception')
  225. except exception.StorageQuotaFull:
  226. pass
  227. def test_iadd_location_exceed(self):
  228. image = self._make_image_with_quota(location_count=1)
  229. try:
  230. image.locations += [{'url': 'file:///a/path', 'metadata': {},
  231. 'status': 'active'}]
  232. self.fail('Should have raised the quota exception')
  233. except exception.StorageQuotaFull:
  234. pass
  235. def test_append_location_for_queued_image(self):
  236. context = FakeContext()
  237. db_api = unit_test_utils.FakeDB()
  238. store_api = unit_test_utils.FakeStoreAPI()
  239. store = unit_test_utils.FakeStoreUtils(store_api)
  240. base_image = FakeImage()
  241. base_image.image_id = str(uuid.uuid4())
  242. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  243. self.assertIsNone(image.size)
  244. self.mock_object(store_api, 'get_size_from_backend',
  245. unit_test_utils.fake_get_size_from_backend)
  246. image.locations.append({'url': 'file:///fake.img.tar.gz',
  247. 'metadata': {}})
  248. self.assertIn({'url': 'file:///fake.img.tar.gz', 'metadata': {}},
  249. image.locations)
  250. def test_insert_location_for_queued_image(self):
  251. context = FakeContext()
  252. db_api = unit_test_utils.FakeDB()
  253. store_api = unit_test_utils.FakeStoreAPI()
  254. store = unit_test_utils.FakeStoreUtils(store_api)
  255. base_image = FakeImage()
  256. base_image.image_id = str(uuid.uuid4())
  257. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  258. self.assertIsNone(image.size)
  259. self.mock_object(store_api, 'get_size_from_backend',
  260. unit_test_utils.fake_get_size_from_backend)
  261. image.locations.insert(0,
  262. {'url': 'file:///fake.img.tar.gz',
  263. 'metadata': {}})
  264. self.assertIn({'url': 'file:///fake.img.tar.gz', 'metadata': {}},
  265. image.locations)
  266. def test_set_location_for_queued_image(self):
  267. context = FakeContext()
  268. db_api = unit_test_utils.FakeDB()
  269. store_api = unit_test_utils.FakeStoreAPI()
  270. store = unit_test_utils.FakeStoreUtils(store_api)
  271. base_image = FakeImage()
  272. base_image.image_id = str(uuid.uuid4())
  273. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  274. self.assertIsNone(image.size)
  275. self.mock_object(store_api, 'get_size_from_backend',
  276. unit_test_utils.fake_get_size_from_backend)
  277. image.locations = [{'url': 'file:///fake.img.tar.gz', 'metadata': {}}]
  278. self.assertEqual([{'url': 'file:///fake.img.tar.gz', 'metadata': {}}],
  279. image.locations)
  280. def test_iadd_location_for_queued_image(self):
  281. context = FakeContext()
  282. db_api = unit_test_utils.FakeDB()
  283. store_api = unit_test_utils.FakeStoreAPI()
  284. store = unit_test_utils.FakeStoreUtils(store_api)
  285. base_image = FakeImage()
  286. base_image.image_id = str(uuid.uuid4())
  287. image = glance.quota.ImageProxy(base_image, context, db_api, store)
  288. self.assertIsNone(image.size)
  289. self.mock_object(store_api, 'get_size_from_backend',
  290. unit_test_utils.fake_get_size_from_backend)
  291. image.locations += [{'url': 'file:///fake.img.tar.gz', 'metadata': {}}]
  292. self.assertIn({'url': 'file:///fake.img.tar.gz', 'metadata': {}},
  293. image.locations)
  294. class TestImagePropertyQuotas(test_utils.BaseTestCase):
  295. def setUp(self):
  296. super(TestImagePropertyQuotas, self).setUp()
  297. self.base_image = FakeImage()
  298. self.image = glance.quota.ImageProxy(self.base_image,
  299. mock.Mock(),
  300. mock.Mock(),
  301. mock.Mock())
  302. self.image_repo_mock = mock.Mock()
  303. self.image_repo_mock.add.return_value = self.base_image
  304. self.image_repo_mock.save.return_value = self.base_image
  305. self.image_repo_proxy = glance.quota.ImageRepoProxy(
  306. self.image_repo_mock,
  307. mock.Mock(),
  308. mock.Mock(),
  309. mock.Mock())
  310. def test_save_image_with_image_property(self):
  311. self.config(image_property_quota=1)
  312. self.image.extra_properties = {'foo': 'bar'}
  313. self.image_repo_proxy.save(self.image)
  314. self.image_repo_mock.save.assert_called_once_with(self.base_image,
  315. from_state=None)
  316. def test_save_image_too_many_image_properties(self):
  317. self.config(image_property_quota=1)
  318. self.image.extra_properties = {'foo': 'bar', 'foo2': 'bar2'}
  319. exc = self.assertRaises(exception.ImagePropertyLimitExceeded,
  320. self.image_repo_proxy.save, self.image)
  321. self.assertIn("Attempted: 2, Maximum: 1",
  322. encodeutils.exception_to_unicode(exc))
  323. def test_save_image_unlimited_image_properties(self):
  324. self.config(image_property_quota=-1)
  325. self.image.extra_properties = {'foo': 'bar'}
  326. self.image_repo_proxy.save(self.image)
  327. self.image_repo_mock.save.assert_called_once_with(self.base_image,
  328. from_state=None)
  329. def test_add_image_with_image_property(self):
  330. self.config(image_property_quota=1)
  331. self.image.extra_properties = {'foo': 'bar'}
  332. self.image_repo_proxy.add(self.image)
  333. self.image_repo_mock.add.assert_called_once_with(self.base_image)
  334. def test_add_image_too_many_image_properties(self):
  335. self.config(image_property_quota=1)
  336. self.image.extra_properties = {'foo': 'bar', 'foo2': 'bar2'}
  337. exc = self.assertRaises(exception.ImagePropertyLimitExceeded,
  338. self.image_repo_proxy.add, self.image)
  339. self.assertIn("Attempted: 2, Maximum: 1",
  340. encodeutils.exception_to_unicode(exc))
  341. def test_add_image_unlimited_image_properties(self):
  342. self.config(image_property_quota=-1)
  343. self.image.extra_properties = {'foo': 'bar'}
  344. self.image_repo_proxy.add(self.image)
  345. self.image_repo_mock.add.assert_called_once_with(self.base_image)
  346. def _quota_exceed_setup(self):
  347. self.config(image_property_quota=2)
  348. self.base_image.extra_properties = {'foo': 'bar', 'spam': 'ham'}
  349. self.image = glance.quota.ImageProxy(self.base_image,
  350. mock.Mock(),
  351. mock.Mock(),
  352. mock.Mock())
  353. def test_modify_image_properties_when_quota_exceeded(self):
  354. self._quota_exceed_setup()
  355. self.config(image_property_quota=1)
  356. self.image.extra_properties = {'foo': 'frob', 'spam': 'eggs'}
  357. self.image_repo_proxy.save(self.image)
  358. self.image_repo_mock.save.assert_called_once_with(self.base_image,
  359. from_state=None)
  360. self.assertEqual('frob', self.base_image.extra_properties['foo'])
  361. self.assertEqual('eggs', self.base_image.extra_properties['spam'])
  362. def test_delete_image_properties_when_quota_exceeded(self):
  363. self._quota_exceed_setup()
  364. self.config(image_property_quota=1)
  365. del self.image.extra_properties['foo']
  366. self.image_repo_proxy.save(self.image)
  367. self.image_repo_mock.save.assert_called_once_with(self.base_image,
  368. from_state=None)
  369. self.assertNotIn('foo', self.base_image.extra_properties)
  370. self.assertEqual('ham', self.base_image.extra_properties['spam'])
  371. def test_invalid_quota_config_parameter(self):
  372. self.config(user_storage_quota='foo')
  373. location = {"url": "file:///fake.img.tar.gz", "metadata": {}}
  374. self.assertRaises(exception.InvalidOptionValue,
  375. self.image.locations.append, location)
  376. def test_exceed_quota_during_patch_operation(self):
  377. self._quota_exceed_setup()
  378. self.image.extra_properties['frob'] = 'baz'
  379. self.image.extra_properties['lorem'] = 'ipsum'
  380. self.assertEqual('bar', self.base_image.extra_properties['foo'])
  381. self.assertEqual('ham', self.base_image.extra_properties['spam'])
  382. self.assertEqual('baz', self.base_image.extra_properties['frob'])
  383. self.assertEqual('ipsum', self.base_image.extra_properties['lorem'])
  384. del self.image.extra_properties['frob']
  385. del self.image.extra_properties['lorem']
  386. self.image_repo_proxy.save(self.image)
  387. call_args = mock.call(self.base_image, from_state=None)
  388. self.assertEqual(call_args, self.image_repo_mock.save.call_args)
  389. self.assertEqual('bar', self.base_image.extra_properties['foo'])
  390. self.assertEqual('ham', self.base_image.extra_properties['spam'])
  391. self.assertNotIn('frob', self.base_image.extra_properties)
  392. self.assertNotIn('lorem', self.base_image.extra_properties)
  393. def test_quota_exceeded_after_delete_image_properties(self):
  394. self.config(image_property_quota=3)
  395. self.base_image.extra_properties = {'foo': 'bar',
  396. 'spam': 'ham',
  397. 'frob': 'baz'}
  398. self.image = glance.quota.ImageProxy(self.base_image,
  399. mock.Mock(),
  400. mock.Mock(),
  401. mock.Mock())
  402. self.config(image_property_quota=1)
  403. del self.image.extra_properties['foo']
  404. self.image_repo_proxy.save(self.image)
  405. self.image_repo_mock.save.assert_called_once_with(self.base_image,
  406. from_state=None)
  407. self.assertNotIn('foo', self.base_image.extra_properties)
  408. self.assertEqual('ham', self.base_image.extra_properties['spam'])
  409. self.assertEqual('baz', self.base_image.extra_properties['frob'])
  410. class TestImageTagQuotas(test_utils.BaseTestCase):
  411. def setUp(self):
  412. super(TestImageTagQuotas, self).setUp()
  413. self.base_image = mock.Mock()
  414. self.base_image.tags = set([])
  415. self.base_image.extra_properties = {}
  416. self.image = glance.quota.ImageProxy(self.base_image,
  417. mock.Mock(),
  418. mock.Mock(),
  419. mock.Mock())
  420. self.image_repo_mock = mock.Mock()
  421. self.image_repo_proxy = glance.quota.ImageRepoProxy(
  422. self.image_repo_mock,
  423. mock.Mock(),
  424. mock.Mock(),
  425. mock.Mock())
  426. def test_replace_image_tag(self):
  427. self.config(image_tag_quota=1)
  428. self.image.tags = ['foo']
  429. self.assertEqual(1, len(self.image.tags))
  430. def test_replace_too_many_image_tags(self):
  431. self.config(image_tag_quota=0)
  432. exc = self.assertRaises(exception.ImageTagLimitExceeded,
  433. setattr, self.image, 'tags', ['foo', 'bar'])
  434. self.assertIn('Attempted: 2, Maximum: 0',
  435. encodeutils.exception_to_unicode(exc))
  436. self.assertEqual(0, len(self.image.tags))
  437. def test_replace_unlimited_image_tags(self):
  438. self.config(image_tag_quota=-1)
  439. self.image.tags = ['foo']
  440. self.assertEqual(1, len(self.image.tags))
  441. def test_add_image_tag(self):
  442. self.config(image_tag_quota=1)
  443. self.image.tags.add('foo')
  444. self.assertEqual(1, len(self.image.tags))
  445. def test_add_too_many_image_tags(self):
  446. self.config(image_tag_quota=1)
  447. self.image.tags.add('foo')
  448. exc = self.assertRaises(exception.ImageTagLimitExceeded,
  449. self.image.tags.add, 'bar')
  450. self.assertIn('Attempted: 2, Maximum: 1',
  451. encodeutils.exception_to_unicode(exc))
  452. def test_add_unlimited_image_tags(self):
  453. self.config(image_tag_quota=-1)
  454. self.image.tags.add('foo')
  455. self.assertEqual(1, len(self.image.tags))
  456. def test_remove_image_tag_while_over_quota(self):
  457. self.config(image_tag_quota=1)
  458. self.image.tags.add('foo')
  459. self.assertEqual(1, len(self.image.tags))
  460. self.config(image_tag_quota=0)
  461. self.image.tags.remove('foo')
  462. self.assertEqual(0, len(self.image.tags))
  463. class TestQuotaImageTagsProxy(test_utils.BaseTestCase):
  464. def setUp(self):
  465. super(TestQuotaImageTagsProxy, self).setUp()
  466. def test_add(self):
  467. proxy = glance.quota.QuotaImageTagsProxy(set([]))
  468. proxy.add('foo')
  469. self.assertIn('foo', proxy)
  470. def test_add_too_many_tags(self):
  471. self.config(image_tag_quota=0)
  472. proxy = glance.quota.QuotaImageTagsProxy(set([]))
  473. exc = self.assertRaises(exception.ImageTagLimitExceeded,
  474. proxy.add, 'bar')
  475. self.assertIn('Attempted: 1, Maximum: 0',
  476. encodeutils.exception_to_unicode(exc))
  477. def test_equals(self):
  478. proxy = glance.quota.QuotaImageTagsProxy(set([]))
  479. self.assertEqual(set([]), proxy)
  480. def test_not_equals(self):
  481. proxy = glance.quota.QuotaImageTagsProxy(set([]))
  482. self.assertNotEqual('foo', proxy)
  483. def test_contains(self):
  484. proxy = glance.quota.QuotaImageTagsProxy(set(['foo']))
  485. self.assertIn('foo', proxy)
  486. def test_len(self):
  487. proxy = glance.quota.QuotaImageTagsProxy(set(['foo',
  488. 'bar',
  489. 'baz',
  490. 'niz']))
  491. self.assertEqual(4, len(proxy))
  492. def test_iter(self):
  493. items = set(['foo', 'bar', 'baz', 'niz'])
  494. proxy = glance.quota.QuotaImageTagsProxy(items.copy())
  495. self.assertEqual(4, len(items))
  496. for item in proxy:
  497. items.remove(item)
  498. self.assertEqual(0, len(items))
  499. class TestImageMemberQuotas(test_utils.BaseTestCase):
  500. def setUp(self):
  501. super(TestImageMemberQuotas, self).setUp()
  502. db_api = unit_test_utils.FakeDB()
  503. store_api = unit_test_utils.FakeStoreAPI()
  504. store = unit_test_utils.FakeStoreUtils(store_api)
  505. context = FakeContext()
  506. self.image = mock.Mock()
  507. self.base_image_member_factory = mock.Mock()
  508. self.image_member_factory = glance.quota.ImageMemberFactoryProxy(
  509. self.base_image_member_factory, context,
  510. db_api, store)
  511. def test_new_image_member(self):
  512. self.config(image_member_quota=1)
  513. self.image_member_factory.new_image_member(self.image,
  514. 'fake_id')
  515. nim = self.base_image_member_factory.new_image_member
  516. nim.assert_called_once_with(self.image, 'fake_id')
  517. def test_new_image_member_unlimited_members(self):
  518. self.config(image_member_quota=-1)
  519. self.image_member_factory.new_image_member(self.image,
  520. 'fake_id')
  521. nim = self.base_image_member_factory.new_image_member
  522. nim.assert_called_once_with(self.image, 'fake_id')
  523. def test_new_image_member_too_many_members(self):
  524. self.config(image_member_quota=0)
  525. self.assertRaises(exception.ImageMemberLimitExceeded,
  526. self.image_member_factory.new_image_member,
  527. self.image, 'fake_id')
  528. class TestImageLocationQuotas(test_utils.BaseTestCase):
  529. def setUp(self):
  530. super(TestImageLocationQuotas, self).setUp()
  531. self.base_image = mock.Mock()
  532. self.base_image.locations = []
  533. self.base_image.size = 1
  534. self.base_image.extra_properties = {}
  535. self.image = glance.quota.ImageProxy(self.base_image,
  536. mock.Mock(),
  537. mock.Mock(),
  538. mock.Mock())
  539. self.image_repo_mock = mock.Mock()
  540. self.image_repo_proxy = glance.quota.ImageRepoProxy(
  541. self.image_repo_mock,
  542. mock.Mock(),
  543. mock.Mock(),
  544. mock.Mock())
  545. def test_replace_image_location(self):
  546. self.config(image_location_quota=1)
  547. self.image.locations = [{"url": "file:///fake.img.tar.gz",
  548. "metadata": {}
  549. }]
  550. self.assertEqual(1, len(self.image.locations))
  551. def test_replace_too_many_image_locations(self):
  552. self.config(image_location_quota=1)
  553. self.image.locations = [{"url": "file:///fake.img.tar.gz",
  554. "metadata": {}}
  555. ]
  556. locations = [
  557. {"url": "file:///fake1.img.tar.gz", "metadata": {}},
  558. {"url": "file:///fake2.img.tar.gz", "metadata": {}},
  559. {"url": "file:///fake3.img.tar.gz", "metadata": {}}
  560. ]
  561. exc = self.assertRaises(exception.ImageLocationLimitExceeded,
  562. setattr, self.image, 'locations', locations)
  563. self.assertIn('Attempted: 3, Maximum: 1',
  564. encodeutils.exception_to_unicode(exc))
  565. self.assertEqual(1, len(self.image.locations))
  566. def test_replace_unlimited_image_locations(self):
  567. self.config(image_location_quota=-1)
  568. self.image.locations = [{"url": "file:///fake.img.tar.gz",
  569. "metadata": {}}
  570. ]
  571. self.assertEqual(1, len(self.image.locations))
  572. def test_add_image_location(self):
  573. self.config(image_location_quota=1)
  574. location = {"url": "file:///fake.img.tar.gz", "metadata": {}}
  575. self.image.locations.append(location)
  576. self.assertEqual(1, len(self.image.locations))
  577. def test_add_too_many_image_locations(self):
  578. self.config(image_location_quota=1)
  579. location1 = {"url": "file:///fake1.img.tar.gz", "metadata": {}}
  580. self.image.locations.append(location1)
  581. location2 = {"url": "file:///fake2.img.tar.gz", "metadata": {}}
  582. exc = self.assertRaises(exception.ImageLocationLimitExceeded,
  583. self.image.locations.append, location2)
  584. self.assertIn('Attempted: 2, Maximum: 1',
  585. encodeutils.exception_to_unicode(exc))
  586. def test_add_unlimited_image_locations(self):
  587. self.config(image_location_quota=-1)
  588. location1 = {"url": "file:///fake1.img.tar.gz", "metadata": {}}
  589. self.image.locations.append(location1)
  590. self.assertEqual(1, len(self.image.locations))
  591. def test_remove_image_location_while_over_quota(self):
  592. self.config(image_location_quota=1)
  593. location1 = {"url": "file:///fake1.img.tar.gz", "metadata": {}}
  594. self.image.locations.append(location1)
  595. self.assertEqual(1, len(self.image.locations))
  596. self.config(image_location_quota=0)
  597. self.image.locations.remove(location1)
  598. self.assertEqual(0, len(self.image.locations))