1 |
prometheanfire 15/01/29 05:39:38 |
2 |
|
3 |
Added: glance-CVE-2014-9623.patch |
4 |
Log: |
5 |
bumping glance for CVE-2014-9623 |
6 |
|
7 |
(Portage version: 2.2.14/cvs/Linux x86_64, signed Manifest commit with key 0x33ED3FD25AFC78BA) |
8 |
|
9 |
Revision Changes Path |
10 |
1.1 app-admin/glance/files/glance-CVE-2014-9623.patch |
11 |
|
12 |
file : http://sources.gentoo.org/viewvc.cgi/gentoo-x86/app-admin/glance/files/glance-CVE-2014-9623.patch?rev=1.1&view=markup |
13 |
plain: http://sources.gentoo.org/viewvc.cgi/gentoo-x86/app-admin/glance/files/glance-CVE-2014-9623.patch?rev=1.1&content-type=text/plain |
14 |
|
15 |
Index: glance-CVE-2014-9623.patch |
16 |
=================================================================== |
17 |
From 7d5d8657fd70b20518610b3c6f8e41e16c72fa31 Mon Sep 17 00:00:00 2001 |
18 |
From: Zhi Yan Liu <zhiyanl@××××××.com> |
19 |
Date: Tue, 30 Dec 2014 22:25:50 +0800 |
20 |
Subject: [PATCH] Cleanup chunks for deleted image that was 'saving' |
21 |
|
22 |
Currently image data cannot be removed synchronously for an image that |
23 |
is in saving state. And when, the upload operation for such an image is |
24 |
completed the operator configured quota can be exceeded. |
25 |
|
26 |
This patch fixes the issue of left over chunks for an image which was |
27 |
deleted from saving status. However, by the limitation of the design we |
28 |
cannot enforce a global quota check for the image in saving status. |
29 |
|
30 |
This change introduces a inconsonance between http response codes of |
31 |
v1 and v2 APIs. The status codes which we will now see after the upload |
32 |
process completes on an image which was deleted mid way are: |
33 |
|
34 |
v1: 412 Precondition Failed |
35 |
v2: 410 Gone |
36 |
|
37 |
SecurityImpact |
38 |
UpgradeImpact |
39 |
APIImpact |
40 |
|
41 |
Closes-Bug: 1383973 |
42 |
Closes-Bug: 1398830 |
43 |
Closes-Bug: 1188532 |
44 |
|
45 |
Conflicts: |
46 |
glance/api/v1/upload_utils.py |
47 |
glance/api/v2/image_data.py |
48 |
glance/tests/unit/test_domain_proxy.py |
49 |
glance/tests/unit/v1/test_api.py |
50 |
|
51 |
Change-Id: I47229b366c25367ec1bd48aec684e0880f3dfe60 |
52 |
Signed-off-by: Zhi Yan Liu <zhiyanl@××××××.com> |
53 |
(cherry picked from commit 0dc8fbb3479a53c5bba8475d14f4c7206904c5ea) |
54 |
--- |
55 |
glance/api/authorization.py | 4 +- |
56 |
glance/api/policy.py | 8 ++-- |
57 |
glance/api/v1/upload_utils.py | 19 +++++--- |
58 |
glance/api/v2/image_data.py | 8 +++- |
59 |
glance/db/__init__.py | 7 +-- |
60 |
glance/domain/proxy.py | 4 +- |
61 |
glance/location.py | 4 +- |
62 |
glance/notifier.py | 4 +- |
63 |
glance/quota/__init__.py | 4 +- |
64 |
glance/tests/unit/test_domain_proxy.py | 14 +++--- |
65 |
glance/tests/unit/test_policy.py | 2 +- |
66 |
glance/tests/unit/test_quota.py | 17 ++++--- |
67 |
glance/tests/unit/test_store_image.py | 2 +- |
68 |
glance/tests/unit/v1/test_api.py | 57 +++++++++++------------- |
69 |
glance/tests/unit/v2/test_image_data_resource.py | 24 +++++----- |
70 |
15 files changed, 98 insertions(+), 80 deletions(-) |
71 |
|
72 |
diff --git a/glance/api/authorization.py b/glance/api/authorization.py |
73 |
index 149ff55..77f7ed3 100644 |
74 |
--- a/glance/api/authorization.py |
75 |
+++ b/glance/api/authorization.py |
76 |
@@ -158,10 +158,10 @@ class ImageMemberRepoProxy(glance.domain.proxy.Repo): |
77 |
raise exception.Forbidden(message |
78 |
% self.image.image_id) |
79 |
|
80 |
- def save(self, image_member): |
81 |
+ def save(self, image_member, from_state=None): |
82 |
if (self.context.is_admin or |
83 |
self.context.owner == image_member.member_id): |
84 |
- self.member_repo.save(image_member) |
85 |
+ self.member_repo.save(image_member, from_state=from_state) |
86 |
else: |
87 |
message = _("You cannot update image member %s") |
88 |
raise exception.Forbidden(message % image_member.member_id) |
89 |
diff --git a/glance/api/policy.py b/glance/api/policy.py |
90 |
index 0bc8d56..e395876 100644 |
91 |
--- a/glance/api/policy.py |
92 |
+++ b/glance/api/policy.py |
93 |
@@ -182,9 +182,9 @@ class ImageRepoProxy(glance.domain.proxy.Repo): |
94 |
self.policy.enforce(self.context, 'get_images', {}) |
95 |
return super(ImageRepoProxy, self).list(*args, **kwargs) |
96 |
|
97 |
- def save(self, image): |
98 |
+ def save(self, image, from_state=None): |
99 |
self.policy.enforce(self.context, 'modify_image', {}) |
100 |
- return super(ImageRepoProxy, self).save(image) |
101 |
+ return super(ImageRepoProxy, self).save(image, from_state=from_state) |
102 |
|
103 |
def add(self, image): |
104 |
self.policy.enforce(self.context, 'add_image', {}) |
105 |
@@ -285,9 +285,9 @@ class ImageMemberRepoProxy(glance.domain.proxy.Repo): |
106 |
self.policy.enforce(self.context, 'get_member', {}) |
107 |
return self.member_repo.get(member_id) |
108 |
|
109 |
- def save(self, member): |
110 |
+ def save(self, member, from_state=None): |
111 |
self.policy.enforce(self.context, 'modify_member', {}) |
112 |
- self.member_repo.save(member) |
113 |
+ self.member_repo.save(member, from_state=from_state) |
114 |
|
115 |
def list(self, *args, **kwargs): |
116 |
self.policy.enforce(self.context, 'get_members', {}) |
117 |
diff --git a/glance/api/v1/upload_utils.py b/glance/api/v1/upload_utils.py |
118 |
index 8a190fc..60c3d3d 100644 |
119 |
--- a/glance/api/v1/upload_utils.py |
120 |
+++ b/glance/api/v1/upload_utils.py |
121 |
@@ -153,12 +153,19 @@ def upload_data_to_store(req, image_meta, image_data, store, notifier): |
122 |
update_data = {'checksum': checksum, |
123 |
'size': size} |
124 |
try: |
125 |
- image_meta = registry.update_image_metadata(req.context, |
126 |
- image_id, |
127 |
- update_data, |
128 |
- from_state='saving') |
129 |
- |
130 |
- except exception.NotFound as e: |
131 |
+ try: |
132 |
+ state = 'saving' |
133 |
+ image_meta = registry.update_image_metadata(req.context, |
134 |
+ image_id, |
135 |
+ update_data, |
136 |
+ from_state=state) |
137 |
+ except exception.Duplicate: |
138 |
+ image = registry.get_image_metadata(req.context, image_id) |
139 |
+ if image['status'] == 'deleted': |
140 |
+ raise exception.NotFound() |
141 |
+ else: |
142 |
+ raise |
143 |
+ except exception.NotFound: |
144 |
msg = _LI("Image %s could not be found after upload. The image may" |
145 |
" have been deleted during the upload.") % image_id |
146 |
LOG.info(msg) |
147 |
diff --git a/glance/api/v2/image_data.py b/glance/api/v2/image_data.py |
148 |
index 430ffc5..cdfa34b 100644 |
149 |
--- a/glance/api/v2/image_data.py |
150 |
+++ b/glance/api/v2/image_data.py |
151 |
@@ -73,8 +73,8 @@ class ImageDataController(object): |
152 |
try: |
153 |
image_repo.save(image) |
154 |
image.set_data(data, size) |
155 |
- image_repo.save(image) |
156 |
- except exception.NotFound as e: |
157 |
+ image_repo.save(image, from_state='saving') |
158 |
+ except (exception.NotFound, exception.Conflict) as e: |
159 |
msg = (_("Image %(id)s could not be found after upload." |
160 |
"The image may have been deleted during the upload: " |
161 |
"%(error)s Cleaning up the chunks uploaded") % |
162 |
@@ -152,6 +152,10 @@ class ImageDataController(object): |
163 |
raise webob.exc.HTTPServiceUnavailable(explanation=msg, |
164 |
request=req) |
165 |
|
166 |
+ except webob.exc.HTTPGone as e: |
167 |
+ with excutils.save_and_reraise_exception(): |
168 |
+ LOG.error(_LE("Failed to upload image data due to HTTP error")) |
169 |
+ |
170 |
except webob.exc.HTTPError as e: |
171 |
with excutils.save_and_reraise_exception(): |
172 |
LOG.error(_LE("Failed to upload image data due to HTTP error")) |
173 |
diff --git a/glance/db/__init__.py b/glance/db/__init__.py |
174 |
index 05db7f8..483ba21 100644 |
175 |
--- a/glance/db/__init__.py |
176 |
+++ b/glance/db/__init__.py |
177 |
@@ -164,7 +164,7 @@ class ImageRepo(object): |
178 |
image.created_at = new_values['created_at'] |
179 |
image.updated_at = new_values['updated_at'] |
180 |
|
181 |
- def save(self, image): |
182 |
+ def save(self, image, from_state=None): |
183 |
image_values = self._format_image_to_db(image) |
184 |
if image_values['size'] > CONF.image_size_cap: |
185 |
raise exception.ImageSizeLimitExceeded |
186 |
@@ -172,7 +172,8 @@ class ImageRepo(object): |
187 |
new_values = self.db_api.image_update(self.context, |
188 |
image.image_id, |
189 |
image_values, |
190 |
- purge_props=True) |
191 |
+ purge_props=True, |
192 |
+ from_state=from_state) |
193 |
except (exception.NotFound, exception.Forbidden): |
194 |
msg = _("No image found with ID %s") % image.image_id |
195 |
raise exception.NotFound(msg) |
196 |
@@ -265,7 +266,7 @@ class ImageMemberRepo(object): |
197 |
msg = _("The specified member %s could not be found") |
198 |
raise exception.NotFound(msg % image_member.id) |
199 |
|
200 |
- def save(self, image_member): |
201 |
+ def save(self, image_member, from_state=None): |
202 |
image_member_values = self._format_image_member_to_db(image_member) |
203 |
try: |
204 |
new_values = self.db_api.image_member_update(self.context, |
205 |
diff --git a/glance/domain/proxy.py b/glance/domain/proxy.py |
206 |
index 5a91d34..09c0fb2 100644 |
207 |
--- a/glance/domain/proxy.py |
208 |
+++ b/glance/domain/proxy.py |
209 |
@@ -94,9 +94,9 @@ class Repo(object): |
210 |
result = self.base.add(base_item) |
211 |
return self.helper.proxy(result) |
212 |
|
213 |
- def save(self, item): |
214 |
+ def save(self, item, from_state=None): |
215 |
base_item = self.helper.unproxy(item) |
216 |
- result = self.base.save(base_item) |
217 |
+ result = self.base.save(base_item, from_state=from_state) |
218 |
return self.helper.proxy(result) |
219 |
|
220 |
def remove(self, item): |
221 |
diff --git a/glance/location.py b/glance/location.py |
222 |
index f83fa7a..b49546d 100644 |
223 |
--- a/glance/location.py |
224 |
+++ b/glance/location.py |
225 |
@@ -60,8 +60,8 @@ class ImageRepoProxy(glance.domain.proxy.Repo): |
226 |
self._set_acls(image) |
227 |
return result |
228 |
|
229 |
- def save(self, image): |
230 |
- result = super(ImageRepoProxy, self).save(image) |
231 |
+ def save(self, image, from_state=None): |
232 |
+ result = super(ImageRepoProxy, self).save(image, from_state=from_state) |
233 |
self._set_acls(image) |
234 |
return result |
235 |
|
236 |
diff --git a/glance/notifier.py b/glance/notifier.py |
237 |
index 5ec0854..21223da 100644 |
238 |
--- a/glance/notifier.py |
239 |
+++ b/glance/notifier.py |
240 |
@@ -122,8 +122,8 @@ class ImageRepoProxy(glance.domain.proxy.Repo): |
241 |
item_proxy_class=ImageProxy, |
242 |
item_proxy_kwargs=proxy_kwargs) |
243 |
|
244 |
- def save(self, image): |
245 |
- super(ImageRepoProxy, self).save(image) |
246 |
+ def save(self, image, from_state=None): |
247 |
+ super(ImageRepoProxy, self).save(image, from_state=from_state) |
248 |
self.notifier.info('image.update', |
249 |
format_image_notification(image)) |
250 |
|
251 |
diff --git a/glance/quota/__init__.py b/glance/quota/__init__.py |
252 |
index 4051992..d628a8c 100644 |
253 |
--- a/glance/quota/__init__.py |
254 |
+++ b/glance/quota/__init__.py |
255 |
@@ -104,10 +104,10 @@ class ImageRepoProxy(glance.domain.proxy.Repo): |
256 |
LOG.debug(six.text_type(exc)) |
257 |
raise exc |
258 |
|
259 |
- def save(self, image): |
260 |
+ def save(self, image, from_state=None): |
261 |
if image.added_new_properties(): |
262 |
self._enforce_image_property_quota(len(image.extra_properties)) |
263 |
- return super(ImageRepoProxy, self).save(image) |
264 |
+ return super(ImageRepoProxy, self).save(image, from_state=from_state) |
265 |
|
266 |
def add(self, image): |
267 |
self._enforce_image_property_quota(len(image.extra_properties)) |
268 |
diff --git a/glance/tests/unit/test_domain_proxy.py b/glance/tests/unit/test_domain_proxy.py |
269 |
index 1bb6863..2b8f792 100644 |
270 |
--- a/glance/tests/unit/test_domain_proxy.py |
271 |
+++ b/glance/tests/unit/test_domain_proxy.py |
272 |
@@ -74,7 +74,7 @@ class TestProxyRepoPlain(test_utils.BaseTestCase): |
273 |
self._test_method('add', 'snuff', 'enough') |
274 |
|
275 |
def test_save(self): |
276 |
- self._test_method('save', 'snuff', 'enough') |
277 |
+ self._test_method('save', 'snuff', 'enough', from_state=None) |
278 |
|
279 |
def test_remove(self): |
280 |
self._test_method('add', None, 'flying') |
281 |
@@ -121,14 +121,14 @@ class TestProxyRepoWrapping(test_utils.BaseTestCase): |
282 |
self.assertEqual(results[i].args, tuple()) |
283 |
self.assertEqual(results[i].kwargs, {'a': 1}) |
284 |
|
285 |
- def _test_method_with_proxied_argument(self, name, result): |
286 |
+ def _test_method_with_proxied_argument(self, name, result, **kwargs): |
287 |
self.fake_repo.result = result |
288 |
item = FakeProxy('snoop') |
289 |
method = getattr(self.proxy_repo, name) |
290 |
proxy_result = method(item) |
291 |
|
292 |
- self.assertEqual(self.fake_repo.args, ('snoop',)) |
293 |
- self.assertEqual(self.fake_repo.kwargs, {}) |
294 |
+ self.assertEqual(('snoop',), self.fake_repo.args) |
295 |
+ self.assertEqual(kwargs, self.fake_repo.kwargs) |
296 |
|
297 |
if result is None: |
298 |
self.assertIsNone(proxy_result) |
299 |
@@ -145,10 +145,12 @@ class TestProxyRepoWrapping(test_utils.BaseTestCase): |
300 |
self._test_method_with_proxied_argument('add', None) |
301 |
|
302 |
def test_save(self): |
303 |
- self._test_method_with_proxied_argument('save', 'dog') |
304 |
+ self._test_method_with_proxied_argument('save', 'dog', |
305 |
+ from_state=None) |
306 |
|
307 |
def test_save_with_no_result(self): |
308 |
- self._test_method_with_proxied_argument('save', None) |
309 |
+ self._test_method_with_proxied_argument('save', None, |
310 |
+ from_state=None) |
311 |
|
312 |
def test_remove(self): |
313 |
self._test_method_with_proxied_argument('remove', 'dog') |
314 |
diff --git a/glance/tests/unit/test_policy.py b/glance/tests/unit/test_policy.py |
315 |
index 5b5e870..44546a0 100644 |
316 |
--- a/glance/tests/unit/test_policy.py |
317 |
+++ b/glance/tests/unit/test_policy.py |
318 |
@@ -78,7 +78,7 @@ class MemberRepoStub(object): |
319 |
def get(self, *args, **kwargs): |
320 |
return 'member_repo_get' |
321 |
|
322 |
- def save(self, image_member): |
323 |
+ def save(self, image_member, from_state=None): |
324 |
image_member.output = 'member_repo_save' |
325 |
|
326 |
def list(self, *args, **kwargs): |
327 |
diff --git a/glance/tests/unit/test_quota.py b/glance/tests/unit/test_quota.py |
328 |
index c12eda2..1c40fb4 100644 |
329 |
--- a/glance/tests/unit/test_quota.py |
330 |
+++ b/glance/tests/unit/test_quota.py |
331 |
@@ -367,7 +367,8 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase): |
332 |
self.image.extra_properties = {'foo': 'bar'} |
333 |
self.image_repo_proxy.save(self.image) |
334 |
|
335 |
- self.image_repo_mock.save.assert_called_once_with(self.base_image) |
336 |
+ self.image_repo_mock.save.assert_called_once_with(self.base_image, |
337 |
+ from_state=None) |
338 |
|
339 |
def test_save_image_too_many_image_properties(self): |
340 |
self.config(image_property_quota=1) |
341 |
@@ -383,7 +384,8 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase): |
342 |
self.image.extra_properties = {'foo': 'bar'} |
343 |
self.image_repo_proxy.save(self.image) |
344 |
|
345 |
- self.image_repo_mock.save.assert_called_once_with(self.base_image) |
346 |
+ self.image_repo_mock.save.assert_called_once_with(self.base_image, |
347 |
+ from_state=None) |
348 |
|
349 |
def test_add_image_with_image_property(self): |
350 |
self.config(image_property_quota=1) |
351 |
@@ -422,7 +424,8 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase): |
352 |
self.config(image_property_quota=1) |
353 |
self.image.extra_properties = {'foo': 'frob', 'spam': 'eggs'} |
354 |
self.image_repo_proxy.save(self.image) |
355 |
- self.image_repo_mock.save.assert_called_once_with(self.base_image) |
356 |
+ self.image_repo_mock.save.assert_called_once_with(self.base_image, |
357 |
+ from_state=None) |
358 |
self.assertEqual('frob', self.base_image.extra_properties['foo']) |
359 |
self.assertEqual('eggs', self.base_image.extra_properties['spam']) |
360 |
|
361 |
@@ -431,7 +434,8 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase): |
362 |
self.config(image_property_quota=1) |
363 |
del self.image.extra_properties['foo'] |
364 |
self.image_repo_proxy.save(self.image) |
365 |
- self.image_repo_mock.save.assert_called_once_with(self.base_image) |
366 |
+ self.image_repo_mock.save.assert_called_once_with(self.base_image, |
367 |
+ from_state=None) |
368 |
self.assertNotIn('foo', self.base_image.extra_properties) |
369 |
self.assertEqual('ham', self.base_image.extra_properties['spam']) |
370 |
|
371 |
@@ -447,7 +451,7 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase): |
372 |
del self.image.extra_properties['frob'] |
373 |
del self.image.extra_properties['lorem'] |
374 |
self.image_repo_proxy.save(self.image) |
375 |
- call_args = mock.call(self.base_image) |
376 |
+ call_args = mock.call(self.base_image, from_state=None) |
377 |
self.assertEqual(call_args, self.image_repo_mock.save.call_args) |
378 |
self.assertEqual('bar', self.base_image.extra_properties['foo']) |
379 |
self.assertEqual('ham', self.base_image.extra_properties['spam']) |
380 |
@@ -466,7 +470,8 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase): |
381 |
self.config(image_property_quota=1) |
382 |
del self.image.extra_properties['foo'] |
383 |
self.image_repo_proxy.save(self.image) |
384 |
- self.image_repo_mock.save.assert_called_once_with(self.base_image) |
385 |
+ self.image_repo_mock.save.assert_called_once_with(self.base_image, |
386 |
+ from_state=None) |
387 |
self.assertNotIn('foo', self.base_image.extra_properties) |
388 |
self.assertEqual('ham', self.base_image.extra_properties['spam']) |
389 |
self.assertEqual('baz', self.base_image.extra_properties['frob']) |
390 |
diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py |
391 |
index 8b334ab..b656454 100644 |
392 |
--- a/glance/tests/unit/test_store_image.py |
393 |
+++ b/glance/tests/unit/test_store_image.py |
394 |
@@ -36,7 +36,7 @@ class ImageRepoStub(object): |
395 |
def add(self, image): |
396 |
return image |
397 |
|
398 |
- def save(self, image): |
399 |
+ def save(self, image, from_state=None): |
400 |
return image |
401 |
|
402 |
|
403 |
diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py |
404 |
index 39e9a44..7dc0737 100644 |
405 |
--- a/glance/tests/unit/v1/test_api.py |
406 |
+++ b/glance/tests/unit/v1/test_api.py |
407 |
@@ -39,7 +39,6 @@ from glance.db.sqlalchemy import api as db_api |
408 |
from glance.db.sqlalchemy import models as db_models |
409 |
from glance.openstack.common import jsonutils |
410 |
from glance.openstack.common import timeutils |
411 |
- |
412 |
import glance.registry.client.v1.api as registry |
413 |
from glance.tests.unit import base |
414 |
import glance.tests.unit.utils as unit_test_utils |
415 |
@@ -1735,8 +1734,7 @@ class TestGlanceAPI(base.IsolatedUnitTest): |
416 |
|
417 |
self.assertEqual(1, mock_store_add_to_backend.call_count) |
418 |
|
419 |
- def test_delete_during_image_upload(self): |
420 |
- req = unit_test_utils.get_fake_request() |
421 |
+ def _check_delete_during_image_upload(self, is_admin=False): |
422 |
|
423 |
fixture_headers = {'x-image-meta-store': 'file', |
424 |
'x-image-meta-disk-format': 'vhd', |
425 |
@@ -1744,8 +1742,8 @@ class TestGlanceAPI(base.IsolatedUnitTest): |
426 |
'x-image-meta-name': 'fake image #3', |
427 |
'x-image-meta-property-key1': 'value1'} |
428 |
|
429 |
- req = webob.Request.blank("/images") |
430 |
- req.method = 'POST' |
431 |
+ req = unit_test_utils.get_fake_request(path="/images", |
432 |
+ is_admin=is_admin) |
433 |
for k, v in six.iteritems(fixture_headers): |
434 |
req.headers[k] = v |
435 |
|
436 |
@@ -1770,31 +1768,18 @@ class TestGlanceAPI(base.IsolatedUnitTest): |
437 |
mock_initiate_deletion) |
438 |
|
439 |
orig_update_image_metadata = registry.update_image_metadata |
440 |
- ctlr = glance.api.v1.controller.BaseController |
441 |
- orig_get_image_meta_or_404 = ctlr.get_image_meta_or_404 |
442 |
- |
443 |
- def mock_update_image_metadata(*args, **kwargs): |
444 |
|
445 |
- if args[2].get('status', None) == 'deleted': |
446 |
+ data = "somedata" |
447 |
|
448 |
- # One shot. |
449 |
- def mock_get_image_meta_or_404(*args, **kwargs): |
450 |
- ret = orig_get_image_meta_or_404(*args, **kwargs) |
451 |
- ret['status'] = 'queued' |
452 |
- self.stubs.Set(ctlr, 'get_image_meta_or_404', |
453 |
- orig_get_image_meta_or_404) |
454 |
- return ret |
455 |
- |
456 |
- self.stubs.Set(ctlr, 'get_image_meta_or_404', |
457 |
- mock_get_image_meta_or_404) |
458 |
+ def mock_update_image_metadata(*args, **kwargs): |
459 |
|
460 |
- req = webob.Request.blank("/images/%s" % image_id) |
461 |
- req.method = 'PUT' |
462 |
- req.headers['Content-Type'] = 'application/octet-stream' |
463 |
- req.body = "somedata" |
464 |
+ if args[2].get('size', None) == len(data): |
465 |
+ path = "/images/%s" % image_id |
466 |
+ req = unit_test_utils.get_fake_request(path=path, |
467 |
+ method='DELETE', |
468 |
+ is_admin=is_admin) |
469 |
res = req.get_response(self.api) |
470 |
- self.assertEqual(res.status_int, 200) |
471 |
- self.assertFalse(res.location) |
472 |
+ self.assertEqual(200, res.status_int) |
473 |
|
474 |
self.stubs.Set(registry, 'update_image_metadata', |
475 |
orig_update_image_metadata) |
476 |
@@ -1804,20 +1789,30 @@ class TestGlanceAPI(base.IsolatedUnitTest): |
477 |
self.stubs.Set(registry, 'update_image_metadata', |
478 |
mock_update_image_metadata) |
479 |
|
480 |
- req = webob.Request.blank("/images/%s" % image_id) |
481 |
- req.method = 'DELETE' |
482 |
+ req = unit_test_utils.get_fake_request(path="/images/%s" % image_id, |
483 |
+ method='PUT') |
484 |
+ req.headers['Content-Type'] = 'application/octet-stream' |
485 |
+ req.body = data |
486 |
res = req.get_response(self.api) |
487 |
- self.assertEqual(res.status_int, 200) |
488 |
+ self.assertEqual(412, res.status_int) |
489 |
+ self.assertFalse(res.location) |
490 |
|
491 |
self.assertTrue(called['initiate_deletion']) |
492 |
|
493 |
- req = webob.Request.blank("/images/%s" % image_id) |
494 |
- req.method = 'HEAD' |
495 |
+ req = unit_test_utils.get_fake_request(path="/images/%s" % image_id, |
496 |
+ method='HEAD', |
497 |
+ is_admin=True) |
498 |
res = req.get_response(self.api) |
499 |
self.assertEqual(res.status_int, 200) |
500 |
self.assertEqual(res.headers['x-image-meta-deleted'], 'True') |
501 |
self.assertEqual(res.headers['x-image-meta-status'], 'deleted') |
502 |
|
503 |
+ def test_delete_during_image_upload_by_normal_user(self): |
504 |
+ self._check_delete_during_image_upload(is_admin=False) |
505 |
+ |
506 |
+ def test_delete_during_image_upload_by_admin(self): |
507 |
+ self._check_delete_during_image_upload(is_admin=True) |
508 |
+ |
509 |
def test_disable_purge_props(self): |
510 |
""" |
511 |
Test the special x-glance-registry-purge-props header controls |
512 |
diff --git a/glance/tests/unit/v2/test_image_data_resource.py b/glance/tests/unit/v2/test_image_data_resource.py |
513 |
index cc8148a..a121e82 100644 |
514 |
--- a/glance/tests/unit/v2/test_image_data_resource.py |
515 |
+++ b/glance/tests/unit/v2/test_image_data_resource.py |
516 |
@@ -81,7 +81,7 @@ class FakeImageRepo(object): |
517 |
else: |
518 |
return self.result |
519 |
|
520 |
- def save(self, image): |
521 |
+ def save(self, image, from_state=None): |
522 |
self.saved_image = image |
523 |
|
524 |
|
525 |
@@ -184,17 +184,21 @@ class TestImagesController(base.StoreClearingUnitTest): |
526 |
request, unit_test_utils.UUID1, 'YYYY', 4) |
527 |
|
528 |
def test_upload_non_existent_image_during_save_initiates_deletion(self): |
529 |
- def fake_save(self): |
530 |
+ def fake_save_not_found(self): |
531 |
raise exception.NotFound() |
532 |
|
533 |
- request = unit_test_utils.get_fake_request() |
534 |
- image = FakeImage('abcd', locations=['http://example.com/image']) |
535 |
- self.image_repo.result = image |
536 |
- self.image_repo.save = fake_save |
537 |
- image.delete = mock.Mock() |
538 |
- self.assertRaises(webob.exc.HTTPGone, self.controller.upload, |
539 |
- request, str(uuid.uuid4()), 'ABC', 3) |
540 |
- self.assertTrue(image.delete.called) |
541 |
+ def fake_save_conflict(self): |
542 |
+ raise exception.Conflict() |
543 |
+ |
544 |
+ for fun in [fake_save_not_found, fake_save_conflict]: |
545 |
+ request = unit_test_utils.get_fake_request() |
546 |
+ image = FakeImage('abcd', locations=['http://example.com/image']) |
547 |
+ self.image_repo.result = image |
548 |
+ self.image_repo.save = fun |
549 |
+ image.delete = mock.Mock() |
550 |
+ self.assertRaises(webob.exc.HTTPGone, self.controller.upload, |
551 |
+ request, str(uuid.uuid4()), 'ABC', 3) |
552 |
+ self.assertTrue(image.delete.called) |
553 |
|
554 |
def test_upload_non_existent_image_raises_not_found_exception(self): |
555 |
def fake_save(self): |
556 |
-- |
557 |
2.0.5 |