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_store_image.py 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. # Copyright 2012 OpenStack Foundation
  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. from cursive import exception as cursive_exception
  16. from cursive import signature_utils
  17. import glance_store
  18. import mock
  19. from glance.common import exception
  20. import glance.location
  21. from glance.tests.unit import base as unit_test_base
  22. from glance.tests.unit import utils as unit_test_utils
  23. from glance.tests import utils
  24. BASE_URI = 'http://storeurl.com/container'
  25. UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
  26. UUID2 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7'
  27. USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
  28. TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
  29. TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
  30. TENANT3 = '228c6da5-29cd-4d67-9457-ed632e083fc0'
  31. class ImageRepoStub(object):
  32. def add(self, image):
  33. return image
  34. def save(self, image, from_state=None):
  35. return image
  36. class ImageStub(object):
  37. def __init__(self, image_id, status=None, locations=None,
  38. visibility=None, extra_properties=None):
  39. self.image_id = image_id
  40. self.status = status
  41. self.locations = locations or []
  42. self.visibility = visibility
  43. self.size = 1
  44. self.extra_properties = extra_properties or {}
  45. def delete(self):
  46. self.status = 'deleted'
  47. def get_member_repo(self):
  48. return FakeMemberRepo(self, [TENANT1, TENANT2])
  49. class ImageFactoryStub(object):
  50. def new_image(self, image_id=None, name=None, visibility='private',
  51. min_disk=0, min_ram=0, protected=False, owner=None,
  52. disk_format=None, container_format=None,
  53. extra_properties=None, tags=None, **other_args):
  54. return ImageStub(image_id, visibility=visibility,
  55. extra_properties=extra_properties, **other_args)
  56. class FakeMemberRepo(object):
  57. def __init__(self, image, tenants=None):
  58. self.image = image
  59. self.factory = glance.domain.ImageMemberFactory()
  60. self.tenants = tenants or []
  61. def list(self, *args, **kwargs):
  62. return [self.factory.new_image_member(self.image, tenant)
  63. for tenant in self.tenants]
  64. def add(self, member):
  65. self.tenants.append(member.member_id)
  66. def remove(self, member):
  67. self.tenants.remove(member.member_id)
  68. class TestStoreImage(utils.BaseTestCase):
  69. def setUp(self):
  70. locations = [{'url': '%s/%s' % (BASE_URI, UUID1),
  71. 'metadata': {}, 'status': 'active'}]
  72. self.image_stub = ImageStub(UUID1, 'active', locations)
  73. self.store_api = unit_test_utils.FakeStoreAPI()
  74. self.store_utils = unit_test_utils.FakeStoreUtils(self.store_api)
  75. super(TestStoreImage, self).setUp()
  76. def test_image_delete(self):
  77. image = glance.location.ImageProxy(self.image_stub, {},
  78. self.store_api, self.store_utils)
  79. location = image.locations[0]
  80. self.assertEqual('active', image.status)
  81. self.store_api.get_from_backend(location['url'], context={})
  82. image.delete()
  83. self.assertEqual('deleted', image.status)
  84. self.assertRaises(glance_store.NotFound,
  85. self.store_api.get_from_backend, location['url'], {})
  86. def test_image_get_data(self):
  87. image = glance.location.ImageProxy(self.image_stub, {},
  88. self.store_api, self.store_utils)
  89. self.assertEqual('XXX', image.get_data())
  90. def test_image_get_data_from_second_location(self):
  91. def fake_get_from_backend(self, location, offset=0,
  92. chunk_size=None, context=None):
  93. if UUID1 in location:
  94. raise Exception('not allow download from %s' % location)
  95. else:
  96. return self.data[location]
  97. image1 = glance.location.ImageProxy(self.image_stub, {},
  98. self.store_api, self.store_utils)
  99. self.assertEqual('XXX', image1.get_data())
  100. # Multiple location support
  101. context = glance.context.RequestContext(user=USER1)
  102. (image2, image_stub2) = self._add_image(context, UUID2, 'ZZZ', 3)
  103. location_data = image2.locations[0]
  104. image1.locations.append(location_data)
  105. self.assertEqual(2, len(image1.locations))
  106. self.assertEqual(UUID2, location_data['url'])
  107. self.mock_object(unit_test_utils.FakeStoreAPI, 'get_from_backend',
  108. fake_get_from_backend)
  109. # This time, image1.get_data() returns the data wrapped in a
  110. # LimitingReader|CooperativeReader pipeline, so peeking under
  111. # the hood of those objects to get at the underlying string.
  112. self.assertEqual('ZZZ', image1.get_data().data.fd)
  113. image1.locations.pop(0)
  114. self.assertEqual(1, len(image1.locations))
  115. image2.delete()
  116. def test_image_set_data(self):
  117. context = glance.context.RequestContext(user=USER1)
  118. image_stub = ImageStub(UUID2, status='queued', locations=[])
  119. image = glance.location.ImageProxy(image_stub, context,
  120. self.store_api, self.store_utils)
  121. image.set_data('YYYY', 4)
  122. self.assertEqual(4, image.size)
  123. # NOTE(markwash): FakeStore returns image_id for location
  124. self.assertEqual(UUID2, image.locations[0]['url'])
  125. self.assertEqual('Z', image.checksum)
  126. self.assertEqual('active', image.status)
  127. def test_image_set_data_location_metadata(self):
  128. context = glance.context.RequestContext(user=USER1)
  129. image_stub = ImageStub(UUID2, status='queued', locations=[])
  130. loc_meta = {'key': 'value5032'}
  131. store_api = unit_test_utils.FakeStoreAPI(store_metadata=loc_meta)
  132. store_utils = unit_test_utils.FakeStoreUtils(store_api)
  133. image = glance.location.ImageProxy(image_stub, context,
  134. store_api, store_utils)
  135. image.set_data('YYYY', 4)
  136. self.assertEqual(4, image.size)
  137. location_data = image.locations[0]
  138. self.assertEqual(UUID2, location_data['url'])
  139. self.assertEqual(loc_meta, location_data['metadata'])
  140. self.assertEqual('Z', image.checksum)
  141. self.assertEqual('active', image.status)
  142. image.delete()
  143. self.assertEqual(image.status, 'deleted')
  144. self.assertRaises(glance_store.NotFound,
  145. self.store_api.get_from_backend,
  146. image.locations[0]['url'], {})
  147. def test_image_set_data_unknown_size(self):
  148. context = glance.context.RequestContext(user=USER1)
  149. image_stub = ImageStub(UUID2, status='queued', locations=[])
  150. image = glance.location.ImageProxy(image_stub, context,
  151. self.store_api, self.store_utils)
  152. image.set_data('YYYY', None)
  153. self.assertEqual(4, image.size)
  154. # NOTE(markwash): FakeStore returns image_id for location
  155. self.assertEqual(UUID2, image.locations[0]['url'])
  156. self.assertEqual('Z', image.checksum)
  157. self.assertEqual('active', image.status)
  158. image.delete()
  159. self.assertEqual(image.status, 'deleted')
  160. self.assertRaises(glance_store.NotFound,
  161. self.store_api.get_from_backend,
  162. image.locations[0]['url'], context={})
  163. @mock.patch('glance.location.LOG')
  164. def test_image_set_data_valid_signature(self, mock_log):
  165. context = glance.context.RequestContext(user=USER1)
  166. extra_properties = {
  167. 'img_signature_certificate_uuid': 'UUID',
  168. 'img_signature_hash_method': 'METHOD',
  169. 'img_signature_key_type': 'TYPE',
  170. 'img_signature': 'VALID'
  171. }
  172. image_stub = ImageStub(UUID2, status='queued',
  173. extra_properties=extra_properties)
  174. self.mock_object(signature_utils, 'get_verifier',
  175. unit_test_utils.fake_get_verifier)
  176. image = glance.location.ImageProxy(image_stub, context,
  177. self.store_api, self.store_utils)
  178. image.set_data('YYYY', 4)
  179. self.assertEqual('active', image.status)
  180. mock_log.info.assert_called_once_with(
  181. u'Successfully verified signature for image %s',
  182. UUID2)
  183. def test_image_set_data_invalid_signature(self):
  184. context = glance.context.RequestContext(user=USER1)
  185. extra_properties = {
  186. 'img_signature_certificate_uuid': 'UUID',
  187. 'img_signature_hash_method': 'METHOD',
  188. 'img_signature_key_type': 'TYPE',
  189. 'img_signature': 'INVALID'
  190. }
  191. image_stub = ImageStub(UUID2, status='queued',
  192. extra_properties=extra_properties)
  193. self.mock_object(signature_utils, 'get_verifier',
  194. unit_test_utils.fake_get_verifier)
  195. image = glance.location.ImageProxy(image_stub, context,
  196. self.store_api, self.store_utils)
  197. with mock.patch.object(self.store_api,
  198. 'delete_from_backend') as mock_delete:
  199. self.assertRaises(cursive_exception.SignatureVerificationError,
  200. image.set_data,
  201. 'YYYY', 4)
  202. mock_delete.assert_called()
  203. def test_image_set_data_invalid_signature_missing_metadata(self):
  204. context = glance.context.RequestContext(user=USER1)
  205. extra_properties = {
  206. 'img_signature_hash_method': 'METHOD',
  207. 'img_signature_key_type': 'TYPE',
  208. 'img_signature': 'INVALID'
  209. }
  210. image_stub = ImageStub(UUID2, status='queued',
  211. extra_properties=extra_properties)
  212. self.mock_object(signature_utils, 'get_verifier',
  213. unit_test_utils.fake_get_verifier)
  214. image = glance.location.ImageProxy(image_stub, context,
  215. self.store_api, self.store_utils)
  216. image.set_data('YYYY', 4)
  217. self.assertEqual(UUID2, image.locations[0]['url'])
  218. self.assertEqual('Z', image.checksum)
  219. # Image is still active, since invalid signature was ignored
  220. self.assertEqual('active', image.status)
  221. def _add_image(self, context, image_id, data, len):
  222. image_stub = ImageStub(image_id, status='queued', locations=[])
  223. image = glance.location.ImageProxy(image_stub, context,
  224. self.store_api, self.store_utils)
  225. image.set_data(data, len)
  226. self.assertEqual(len, image.size)
  227. # NOTE(markwash): FakeStore returns image_id for location
  228. location = {'url': image_id, 'metadata': {}, 'status': 'active'}
  229. self.assertEqual([location], image.locations)
  230. self.assertEqual([location], image_stub.locations)
  231. self.assertEqual('active', image.status)
  232. return (image, image_stub)
  233. def test_image_change_append_invalid_location_uri(self):
  234. self.assertEqual(2, len(self.store_api.data.keys()))
  235. context = glance.context.RequestContext(user=USER1)
  236. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  237. location_bad = {'url': 'unknown://location', 'metadata': {}}
  238. self.assertRaises(exception.BadStoreUri,
  239. image1.locations.append, location_bad)
  240. image1.delete()
  241. self.assertEqual(2, len(self.store_api.data.keys()))
  242. self.assertNotIn(UUID2, self.store_api.data.keys())
  243. def test_image_change_append_invalid_location_metatdata(self):
  244. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  245. self.assertEqual(2, len(self.store_api.data.keys()))
  246. context = glance.context.RequestContext(user=USER1)
  247. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  248. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  249. # Using only one test rule here is enough to make sure
  250. # 'store.check_location_metadata()' can be triggered
  251. # in Location proxy layer. Complete test rule for
  252. # 'store.check_location_metadata()' testing please
  253. # check below cases within 'TestStoreMetaDataChecker'.
  254. location_bad = {'url': UUID3, 'metadata': b"a invalid metadata"}
  255. self.assertRaises(glance_store.BackendException,
  256. image1.locations.append, location_bad)
  257. image1.delete()
  258. image2.delete()
  259. self.assertEqual(2, len(self.store_api.data.keys()))
  260. self.assertNotIn(UUID2, self.store_api.data.keys())
  261. self.assertNotIn(UUID3, self.store_api.data.keys())
  262. def test_image_change_append_locations(self):
  263. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  264. self.assertEqual(2, len(self.store_api.data.keys()))
  265. context = glance.context.RequestContext(user=USER1)
  266. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  267. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  268. location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
  269. location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
  270. image1.locations.append(location3)
  271. self.assertEqual([location2, location3], image_stub1.locations)
  272. self.assertEqual([location2, location3], image1.locations)
  273. image1.delete()
  274. self.assertEqual(2, len(self.store_api.data.keys()))
  275. self.assertNotIn(UUID2, self.store_api.data.keys())
  276. self.assertNotIn(UUID3, self.store_api.data.keys())
  277. image2.delete()
  278. def test_image_change_pop_location(self):
  279. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  280. self.assertEqual(2, len(self.store_api.data.keys()))
  281. context = glance.context.RequestContext(user=USER1)
  282. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  283. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  284. location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
  285. location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
  286. image1.locations.append(location3)
  287. self.assertEqual([location2, location3], image_stub1.locations)
  288. self.assertEqual([location2, location3], image1.locations)
  289. image1.locations.pop()
  290. self.assertEqual([location2], image_stub1.locations)
  291. self.assertEqual([location2], image1.locations)
  292. image1.delete()
  293. self.assertEqual(2, len(self.store_api.data.keys()))
  294. self.assertNotIn(UUID2, self.store_api.data.keys())
  295. self.assertNotIn(UUID3, self.store_api.data.keys())
  296. image2.delete()
  297. def test_image_change_extend_invalid_locations_uri(self):
  298. self.assertEqual(2, len(self.store_api.data.keys()))
  299. context = glance.context.RequestContext(user=USER1)
  300. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  301. location_bad = {'url': 'unknown://location', 'metadata': {}}
  302. self.assertRaises(exception.BadStoreUri,
  303. image1.locations.extend, [location_bad])
  304. image1.delete()
  305. self.assertEqual(2, len(self.store_api.data.keys()))
  306. self.assertNotIn(UUID2, self.store_api.data.keys())
  307. def test_image_change_extend_invalid_locations_metadata(self):
  308. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  309. self.assertEqual(2, len(self.store_api.data.keys()))
  310. context = glance.context.RequestContext(user=USER1)
  311. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  312. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  313. location_bad = {'url': UUID3, 'metadata': b"a invalid metadata"}
  314. self.assertRaises(glance_store.BackendException,
  315. image1.locations.extend, [location_bad])
  316. image1.delete()
  317. image2.delete()
  318. self.assertEqual(2, len(self.store_api.data.keys()))
  319. self.assertNotIn(UUID2, self.store_api.data.keys())
  320. self.assertNotIn(UUID3, self.store_api.data.keys())
  321. def test_image_change_extend_locations(self):
  322. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  323. self.assertEqual(2, len(self.store_api.data.keys()))
  324. context = glance.context.RequestContext(user=USER1)
  325. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  326. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  327. location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
  328. location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
  329. image1.locations.extend([location3])
  330. self.assertEqual([location2, location3], image_stub1.locations)
  331. self.assertEqual([location2, location3], image1.locations)
  332. image1.delete()
  333. self.assertEqual(2, len(self.store_api.data.keys()))
  334. self.assertNotIn(UUID2, self.store_api.data.keys())
  335. self.assertNotIn(UUID3, self.store_api.data.keys())
  336. image2.delete()
  337. def test_image_change_remove_location(self):
  338. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  339. self.assertEqual(2, len(self.store_api.data.keys()))
  340. context = glance.context.RequestContext(user=USER1)
  341. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  342. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  343. location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
  344. location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
  345. location_bad = {'url': 'unknown://location', 'metadata': {}}
  346. image1.locations.extend([location3])
  347. image1.locations.remove(location2)
  348. self.assertEqual([location3], image_stub1.locations)
  349. self.assertEqual([location3], image1.locations)
  350. self.assertRaises(ValueError,
  351. image1.locations.remove, location_bad)
  352. image1.delete()
  353. image2.delete()
  354. self.assertEqual(2, len(self.store_api.data.keys()))
  355. self.assertNotIn(UUID2, self.store_api.data.keys())
  356. self.assertNotIn(UUID3, self.store_api.data.keys())
  357. def test_image_change_delete_location(self):
  358. self.assertEqual(2, len(self.store_api.data.keys()))
  359. context = glance.context.RequestContext(user=USER1)
  360. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  361. del image1.locations[0]
  362. self.assertEqual([], image_stub1.locations)
  363. self.assertEqual(0, len(image1.locations))
  364. self.assertEqual(2, len(self.store_api.data.keys()))
  365. self.assertNotIn(UUID2, self.store_api.data.keys())
  366. image1.delete()
  367. def test_image_change_insert_invalid_location_uri(self):
  368. self.assertEqual(2, len(self.store_api.data.keys()))
  369. context = glance.context.RequestContext(user=USER1)
  370. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  371. location_bad = {'url': 'unknown://location', 'metadata': {}}
  372. self.assertRaises(exception.BadStoreUri,
  373. image1.locations.insert, 0, location_bad)
  374. image1.delete()
  375. self.assertEqual(2, len(self.store_api.data.keys()))
  376. self.assertNotIn(UUID2, self.store_api.data.keys())
  377. def test_image_change_insert_invalid_location_metadata(self):
  378. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  379. self.assertEqual(2, len(self.store_api.data.keys()))
  380. context = glance.context.RequestContext(user=USER1)
  381. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  382. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  383. location_bad = {'url': UUID3, 'metadata': b"a invalid metadata"}
  384. self.assertRaises(glance_store.BackendException,
  385. image1.locations.insert, 0, location_bad)
  386. image1.delete()
  387. image2.delete()
  388. self.assertEqual(2, len(self.store_api.data.keys()))
  389. self.assertNotIn(UUID2, self.store_api.data.keys())
  390. self.assertNotIn(UUID3, self.store_api.data.keys())
  391. def test_image_change_insert_location(self):
  392. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  393. self.assertEqual(2, len(self.store_api.data.keys()))
  394. context = glance.context.RequestContext(user=USER1)
  395. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  396. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  397. location2 = {'url': UUID2, 'metadata': {}, 'status': 'active'}
  398. location3 = {'url': UUID3, 'metadata': {}, 'status': 'active'}
  399. image1.locations.insert(0, location3)
  400. self.assertEqual([location3, location2], image_stub1.locations)
  401. self.assertEqual([location3, location2], image1.locations)
  402. image1.delete()
  403. self.assertEqual(2, len(self.store_api.data.keys()))
  404. self.assertNotIn(UUID2, self.store_api.data.keys())
  405. self.assertNotIn(UUID3, self.store_api.data.keys())
  406. image2.delete()
  407. def test_image_change_delete_locations(self):
  408. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  409. self.assertEqual(2, len(self.store_api.data.keys()))
  410. context = glance.context.RequestContext(user=USER1)
  411. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  412. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  413. location2 = {'url': UUID2, 'metadata': {}}
  414. location3 = {'url': UUID3, 'metadata': {}}
  415. image1.locations.insert(0, location3)
  416. del image1.locations[0:100]
  417. self.assertEqual([], image_stub1.locations)
  418. self.assertEqual(0, len(image1.locations))
  419. self.assertRaises(exception.BadStoreUri,
  420. image1.locations.insert, 0, location2)
  421. self.assertRaises(exception.BadStoreUri,
  422. image2.locations.insert, 0, location3)
  423. image1.delete()
  424. image2.delete()
  425. self.assertEqual(2, len(self.store_api.data.keys()))
  426. self.assertNotIn(UUID2, self.store_api.data.keys())
  427. self.assertNotIn(UUID3, self.store_api.data.keys())
  428. def test_image_change_adding_invalid_location_uri(self):
  429. self.assertEqual(2, len(self.store_api.data.keys()))
  430. context = glance.context.RequestContext(user=USER1)
  431. image_stub1 = ImageStub('fake_image_id', status='queued', locations=[])
  432. image1 = glance.location.ImageProxy(image_stub1, context,
  433. self.store_api, self.store_utils)
  434. location_bad = {'url': 'unknown://location', 'metadata': {}}
  435. self.assertRaises(exception.BadStoreUri,
  436. image1.locations.__iadd__, [location_bad])
  437. self.assertEqual([], image_stub1.locations)
  438. self.assertEqual([], image1.locations)
  439. image1.delete()
  440. self.assertEqual(2, len(self.store_api.data.keys()))
  441. self.assertNotIn(UUID2, self.store_api.data.keys())
  442. def test_image_change_adding_invalid_location_metadata(self):
  443. self.assertEqual(2, len(self.store_api.data.keys()))
  444. context = glance.context.RequestContext(user=USER1)
  445. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  446. image_stub2 = ImageStub('fake_image_id', status='queued', locations=[])
  447. image2 = glance.location.ImageProxy(image_stub2, context,
  448. self.store_api, self.store_utils)
  449. location_bad = {'url': UUID2, 'metadata': b"a invalid metadata"}
  450. self.assertRaises(glance_store.BackendException,
  451. image2.locations.__iadd__, [location_bad])
  452. self.assertEqual([], image_stub2.locations)
  453. self.assertEqual([], image2.locations)
  454. image1.delete()
  455. image2.delete()
  456. self.assertEqual(2, len(self.store_api.data.keys()))
  457. self.assertNotIn(UUID2, self.store_api.data.keys())
  458. def test_image_change_adding_locations(self):
  459. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  460. self.assertEqual(2, len(self.store_api.data.keys()))
  461. context = glance.context.RequestContext(user=USER1)
  462. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  463. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  464. image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
  465. image3 = glance.location.ImageProxy(image_stub3, context,
  466. self.store_api, self.store_utils)
  467. location2 = {'url': UUID2, 'metadata': {}}
  468. location3 = {'url': UUID3, 'metadata': {}}
  469. image3.locations += [location2, location3]
  470. self.assertEqual([location2, location3], image_stub3.locations)
  471. self.assertEqual([location2, location3], image3.locations)
  472. image3.delete()
  473. self.assertEqual(2, len(self.store_api.data.keys()))
  474. self.assertNotIn(UUID2, self.store_api.data.keys())
  475. self.assertNotIn(UUID3, self.store_api.data.keys())
  476. image1.delete()
  477. image2.delete()
  478. def test_image_get_location_index(self):
  479. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  480. self.assertEqual(2, len(self.store_api.data.keys()))
  481. context = glance.context.RequestContext(user=USER1)
  482. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  483. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  484. image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
  485. image3 = glance.location.ImageProxy(image_stub3, context,
  486. self.store_api, self.store_utils)
  487. location2 = {'url': UUID2, 'metadata': {}}
  488. location3 = {'url': UUID3, 'metadata': {}}
  489. image3.locations += [location2, location3]
  490. self.assertEqual(1, image_stub3.locations.index(location3))
  491. image3.delete()
  492. self.assertEqual(2, len(self.store_api.data.keys()))
  493. self.assertNotIn(UUID2, self.store_api.data.keys())
  494. self.assertNotIn(UUID3, self.store_api.data.keys())
  495. image1.delete()
  496. image2.delete()
  497. def test_image_get_location_by_index(self):
  498. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  499. self.assertEqual(2, len(self.store_api.data.keys()))
  500. context = glance.context.RequestContext(user=USER1)
  501. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  502. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  503. image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
  504. image3 = glance.location.ImageProxy(image_stub3, context,
  505. self.store_api, self.store_utils)
  506. location2 = {'url': UUID2, 'metadata': {}}
  507. location3 = {'url': UUID3, 'metadata': {}}
  508. image3.locations += [location2, location3]
  509. self.assertEqual(1, image_stub3.locations.index(location3))
  510. self.assertEqual(location2, image_stub3.locations[0])
  511. image3.delete()
  512. self.assertEqual(2, len(self.store_api.data.keys()))
  513. self.assertNotIn(UUID2, self.store_api.data.keys())
  514. self.assertNotIn(UUID3, self.store_api.data.keys())
  515. image1.delete()
  516. image2.delete()
  517. def test_image_checking_location_exists(self):
  518. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  519. self.assertEqual(2, len(self.store_api.data.keys()))
  520. context = glance.context.RequestContext(user=USER1)
  521. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  522. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  523. image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
  524. image3 = glance.location.ImageProxy(image_stub3, context,
  525. self.store_api, self.store_utils)
  526. location2 = {'url': UUID2, 'metadata': {}}
  527. location3 = {'url': UUID3, 'metadata': {}}
  528. location_bad = {'url': 'unknown://location', 'metadata': {}}
  529. image3.locations += [location2, location3]
  530. self.assertIn(location3, image_stub3.locations)
  531. self.assertNotIn(location_bad, image_stub3.locations)
  532. image3.delete()
  533. self.assertEqual(2, len(self.store_api.data.keys()))
  534. self.assertNotIn(UUID2, self.store_api.data.keys())
  535. self.assertNotIn(UUID3, self.store_api.data.keys())
  536. image1.delete()
  537. image2.delete()
  538. def test_image_reverse_locations_order(self):
  539. UUID3 = 'a8a61ec4-d7a3-11e2-8c28-000c29c27581'
  540. self.assertEqual(2, len(self.store_api.data.keys()))
  541. context = glance.context.RequestContext(user=USER1)
  542. (image1, image_stub1) = self._add_image(context, UUID2, 'XXXX', 4)
  543. (image2, image_stub2) = self._add_image(context, UUID3, 'YYYY', 4)
  544. location2 = {'url': UUID2, 'metadata': {}}
  545. location3 = {'url': UUID3, 'metadata': {}}
  546. image_stub3 = ImageStub('fake_image_id', status='queued', locations=[])
  547. image3 = glance.location.ImageProxy(image_stub3, context,
  548. self.store_api, self.store_utils)
  549. image3.locations += [location2, location3]
  550. image_stub3.locations.reverse()
  551. self.assertEqual([location3, location2], image_stub3.locations)
  552. self.assertEqual([location3, location2], image3.locations)
  553. image3.delete()
  554. self.assertEqual(2, len(self.store_api.data.keys()))
  555. self.assertNotIn(UUID2, self.store_api.data.keys())
  556. self.assertNotIn(UUID3, self.store_api.data.keys())
  557. image1.delete()
  558. image2.delete()
  559. class TestStoreImageRepo(utils.BaseTestCase):
  560. def setUp(self):
  561. super(TestStoreImageRepo, self).setUp()
  562. self.store_api = unit_test_utils.FakeStoreAPI()
  563. store_utils = unit_test_utils.FakeStoreUtils(self.store_api)
  564. self.image_stub = ImageStub(UUID1)
  565. self.image = glance.location.ImageProxy(self.image_stub, {},
  566. self.store_api, store_utils)
  567. self.image_repo_stub = ImageRepoStub()
  568. self.image_repo = glance.location.ImageRepoProxy(self.image_repo_stub,
  569. {}, self.store_api,
  570. store_utils)
  571. patcher = mock.patch("glance.location._get_member_repo_for_store",
  572. self.get_fake_member_repo)
  573. patcher.start()
  574. self.addCleanup(patcher.stop)
  575. self.fake_member_repo = FakeMemberRepo(self.image, [TENANT1, TENANT2])
  576. self.image_member_repo = glance.location.ImageMemberRepoProxy(
  577. self.fake_member_repo,
  578. self.image,
  579. {}, self.store_api)
  580. def get_fake_member_repo(self, image, context, db_api, store_api):
  581. return FakeMemberRepo(self.image, [TENANT1, TENANT2])
  582. def test_add_updates_acls(self):
  583. self.image_stub.locations = [{'url': 'foo', 'metadata': {},
  584. 'status': 'active'},
  585. {'url': 'bar', 'metadata': {},
  586. 'status': 'active'}]
  587. self.image_stub.visibility = 'public'
  588. self.image_repo.add(self.image)
  589. self.assertTrue(self.store_api.acls['foo']['public'])
  590. self.assertEqual([], self.store_api.acls['foo']['read'])
  591. self.assertEqual([], self.store_api.acls['foo']['write'])
  592. self.assertTrue(self.store_api.acls['bar']['public'])
  593. self.assertEqual([], self.store_api.acls['bar']['read'])
  594. self.assertEqual([], self.store_api.acls['bar']['write'])
  595. def test_add_ignores_acls_if_no_locations(self):
  596. self.image_stub.locations = []
  597. self.image_stub.visibility = 'public'
  598. self.image_repo.add(self.image)
  599. self.assertEqual(0, len(self.store_api.acls))
  600. def test_save_updates_acls(self):
  601. self.image_stub.locations = [{'url': 'foo', 'metadata': {},
  602. 'status': 'active'}]
  603. self.image_repo.save(self.image)
  604. self.assertIn('foo', self.store_api.acls)
  605. def test_add_fetches_members_if_private(self):
  606. self.image_stub.locations = [{'url': 'glue', 'metadata': {},
  607. 'status': 'active'}]
  608. self.image_stub.visibility = 'private'
  609. self.image_repo.add(self.image)
  610. self.assertIn('glue', self.store_api.acls)
  611. acls = self.store_api.acls['glue']
  612. self.assertFalse(acls['public'])
  613. self.assertEqual([], acls['write'])
  614. self.assertEqual([TENANT1, TENANT2], acls['read'])
  615. def test_save_fetches_members_if_private(self):
  616. self.image_stub.locations = [{'url': 'glue', 'metadata': {},
  617. 'status': 'active'}]
  618. self.image_stub.visibility = 'private'
  619. self.image_repo.save(self.image)
  620. self.assertIn('glue', self.store_api.acls)
  621. acls = self.store_api.acls['glue']
  622. self.assertFalse(acls['public'])
  623. self.assertEqual([], acls['write'])
  624. self.assertEqual([TENANT1, TENANT2], acls['read'])
  625. def test_member_addition_updates_acls(self):
  626. self.image_stub.locations = [{'url': 'glug', 'metadata': {},
  627. 'status': 'active'}]
  628. self.image_stub.visibility = 'private'
  629. membership = glance.domain.ImageMembership(
  630. UUID1, TENANT3, None, None, status='accepted')
  631. self.image_member_repo.add(membership)
  632. self.assertIn('glug', self.store_api.acls)
  633. acls = self.store_api.acls['glug']
  634. self.assertFalse(acls['public'])
  635. self.assertEqual([], acls['write'])
  636. self.assertEqual([TENANT1, TENANT2, TENANT3], acls['read'])
  637. def test_member_removal_updates_acls(self):
  638. self.image_stub.locations = [{'url': 'glug', 'metadata': {},
  639. 'status': 'active'}]
  640. self.image_stub.visibility = 'private'
  641. membership = glance.domain.ImageMembership(
  642. UUID1, TENANT1, None, None, status='accepted')
  643. self.image_member_repo.remove(membership)
  644. self.assertIn('glug', self.store_api.acls)
  645. acls = self.store_api.acls['glug']
  646. self.assertFalse(acls['public'])
  647. self.assertEqual([], acls['write'])
  648. self.assertEqual([TENANT2], acls['read'])
  649. class TestImageFactory(unit_test_base.StoreClearingUnitTest):
  650. def setUp(self):
  651. super(TestImageFactory, self).setUp()
  652. store_api = unit_test_utils.FakeStoreAPI()
  653. store_utils = unit_test_utils.FakeStoreUtils(store_api)
  654. self.image_factory = glance.location.ImageFactoryProxy(
  655. ImageFactoryStub(),
  656. glance.context.RequestContext(user=USER1),
  657. store_api,
  658. store_utils)
  659. def test_new_image(self):
  660. image = self.image_factory.new_image()
  661. self.assertIsNone(image.image_id)
  662. self.assertIsNone(image.status)
  663. self.assertEqual('private', image.visibility)
  664. self.assertEqual([], image.locations)
  665. def test_new_image_with_location(self):
  666. locations = [{'url': '%s/%s' % (BASE_URI, UUID1),
  667. 'metadata': {}}]
  668. image = self.image_factory.new_image(locations=locations)
  669. self.assertEqual(locations, image.locations)
  670. location_bad = {'url': 'unknown://location', 'metadata': {}}
  671. self.assertRaises(exception.BadStoreUri,
  672. self.image_factory.new_image,
  673. locations=[location_bad])
  674. class TestStoreMetaDataChecker(utils.BaseTestCase):
  675. def test_empty(self):
  676. glance_store.check_location_metadata({})
  677. def test_unicode(self):
  678. m = {'key': u'somevalue'}
  679. glance_store.check_location_metadata(m)
  680. def test_unicode_list(self):
  681. m = {'key': [u'somevalue', u'2']}
  682. glance_store.check_location_metadata(m)
  683. def test_unicode_dict(self):
  684. inner = {'key1': u'somevalue', 'key2': u'somevalue'}
  685. m = {'topkey': inner}
  686. glance_store.check_location_metadata(m)
  687. def test_unicode_dict_list(self):
  688. inner = {'key1': u'somevalue', 'key2': u'somevalue'}
  689. m = {'topkey': inner, 'list': [u'somevalue', u'2'], 'u': u'2'}
  690. glance_store.check_location_metadata(m)
  691. def test_nested_dict(self):
  692. inner = {'key1': u'somevalue', 'key2': u'somevalue'}
  693. inner = {'newkey': inner}
  694. inner = {'anotherkey': inner}
  695. m = {'topkey': inner}
  696. glance_store.check_location_metadata(m)
  697. def test_simple_bad(self):
  698. m = {'key1': object()}
  699. self.assertRaises(glance_store.BackendException,
  700. glance_store.check_location_metadata,
  701. m)
  702. def test_list_bad(self):
  703. m = {'key1': [u'somevalue', object()]}
  704. self.assertRaises(glance_store.BackendException,
  705. glance_store.check_location_metadata,
  706. m)
  707. def test_nested_dict_bad(self):
  708. inner = {'key1': u'somevalue', 'key2': object()}
  709. inner = {'newkey': inner}
  710. inner = {'anotherkey': inner}
  711. m = {'topkey': inner}
  712. self.assertRaises(glance_store.BackendException,
  713. glance_store.check_location_metadata,
  714. m)