Merge lp:~jshepher/glance/lp813291 into lp:~hudson-openstack/glance/trunk

Proposed by Justin Shepherd
Status: Merged
Approved by: Brian Waldon
Approved revision: 158
Merged at revision: 156
Proposed branch: lp:~jshepher/glance/lp813291
Merge into: lp:~hudson-openstack/glance/trunk
Diff against target: 79 lines (+53/-1)
2 files modified
glance/registry/db/api.py (+6/-1)
tests/functional/test_httplib2_api.py (+47/-0)
To merge this branch: bzr merge lp:~jshepher/glance/lp813291
Reviewer Review Type Date Requested Status
Brian Waldon (community) Approve
Jay Pipes (community) Approve
Review via email: mp+68485@code.launchpad.net

Description of the change

Added fix for Bug #813291: POST to /images setting x-image-meta-id to an already existing image id causes a 500 error.

I came up with about 3 or 4 different ways of resolving this bug, and all tests pass. Let me know if you think this should be resolved in a different way.

This branch also includes a test in tests/functional/test_httplib2_api.py that shows the 500 error.

To post a comment you must log in.
Revision history for this message
Justin Shepherd (jshepher) wrote :

Here are the other solutions i tried:

#2

=== modified file 'glance/registry/db/api.py'
--- glance/registry/db/api.py 2011-07-20 04:11:54 +0000
+++ glance/registry/db/api.py 2011-07-20 04:14:00 +0000
@@ -92,10 +92,10 @@

 def image_create(context, values):
     """Create an image from the values dictionary."""
+ if 'id' in values:
+ return _image_update(context, values, values['id'], False)
+ else:
+ return _image_update(context, values, None, False)

 def image_update(context, image_id, values, purge_props=False):

====================================================================================================

#3

=== modified file 'glance/registry/db/api.py'
--- glance/registry/db/api.py 2011-07-20 04:11:54 +0000
+++ glance/registry/db/api.py 2011-07-20 04:15:01 +0000
@@ -258,6 +255,9 @@
         # not a dict.
         properties = values.pop('properties', {})

+ if 'id' in values:
+ image_id = values['id']
+
         if image_id:
             image_ref = image_get(context, image_id, session=session)
         else:

lp:~jshepher/glance/lp813291 updated
157. By Justin Shepherd

left in 2 fixes.. removing redundant fix

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Thanks for addressing this bug, Justin. I'm not so sure I agree with this behavior. If you try to POST with an image id that is already taken, the client should get a 400 Bad Request. It seems counterintuitive to me to effectively do a PUT and update an existing image when the creation of a new image was expected. What do you think?

review: Needs Information
Revision history for this message
Justin Shepherd (jshepher) wrote :

There was already existing code meant to address this bug.. Where it would check for an already existing image id and update the existing image instead of responding with a error.

The issue is that it was a triple level method chain where image id gets lost in translation between method calls.

Image create gets the values as a dict, and then them on to _image_update (which expects image_id as its 3rd argument).. however image_create was not explicitly passing image_id.. so the existing logic was getting bypassed.

Now i am not sure this is the right code path.. i initially thought this should throw a 409 (http conflict).. and throw an error about a duplicate image id.

Revision history for this message
Jay Pipes (jaypipes) wrote :

> Now i am not sure this is the right code path.. i initially thought this
> should throw a 409 (http conflict).. and throw an error about a duplicate
> image id.

This is what *should* happen, AFAIK. For POST. For PUT, should update of course...

Shep, are you OK with modifying the code to ensure the above behaviour?

-jay

review: Needs Fixing
lp:~jshepher/glance/lp813291 updated
158. By Justin Shepherd

API is now returning a 409 error on duplicate POST. I also modified the testcase to expect a 409 response.

Revision history for this message
Jay Pipes (jaypipes) wrote :

good work, Shep!

review: Approve
Revision history for this message
Brian Waldon (bcwaldon) wrote :

Awesome, Justin. I functionally tested at the api and registry level and got a 409 both times.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'glance/registry/db/api.py'
2--- glance/registry/db/api.py 2011-07-18 18:22:41 +0000
3+++ glance/registry/db/api.py 2011-07-20 20:53:36 +0000
4@@ -24,6 +24,7 @@
5 import logging
6
7 from sqlalchemy import asc, create_engine, desc
8+from sqlalchemy.exc import IntegrityError
9 from sqlalchemy.ext.declarative import declarative_base
10 from sqlalchemy.orm import exc
11 from sqlalchemy.orm import joinedload
12@@ -273,7 +274,11 @@
13 # idiotic.
14 validate_image(image_ref.to_dict())
15
16- image_ref.save(session=session)
17+ try:
18+ image_ref.save(session=session)
19+ except IntegrityError, e:
20+ raise exception.Duplicate("Image ID %s already exists!"
21+ % values['id'])
22
23 _set_properties_for_image(context, image_ref, properties, purge_props,
24 session)
25
26=== modified file 'tests/functional/test_httplib2_api.py'
27--- tests/functional/test_httplib2_api.py 2011-07-20 03:05:28 +0000
28+++ tests/functional/test_httplib2_api.py 2011-07-20 20:53:36 +0000
29@@ -1029,3 +1029,50 @@
30 self.assertEqual(data['images'][2]['id'], 2)
31
32 self.stop_servers()
33+
34+ def test_duplicate_image_upload(self):
35+ """
36+ Upload initial image, then attempt to upload duplicate image
37+ """
38+ self.cleanup()
39+ self.start_servers()
40+
41+ # 0. GET /images
42+ # Verify no public images
43+ path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
44+ http = httplib2.Http()
45+ response, content = http.request(path, 'GET')
46+ self.assertEqual(response.status, 200)
47+ self.assertEqual(content, '{"images": []}')
48+
49+ # 1. POST /images with public image named Image1
50+ headers = {'Content-Type': 'application/octet-stream',
51+ 'X-Image-Meta-Name': 'Image1',
52+ 'X-Image-Meta-Status': 'active',
53+ 'X-Image-Meta-Container-Format': 'ovf',
54+ 'X-Image-Meta-Disk-Format': 'vdi',
55+ 'X-Image-Meta-Size': '19',
56+ 'X-Image-Meta-Is-Public': 'True'}
57+ path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
58+ http = httplib2.Http()
59+ response, content = http.request(path, 'POST', headers=headers)
60+ self.assertEqual(response.status, 201)
61+
62+ # 2. POST /images with public image named Image1, and ID: 1
63+ headers = {'Content-Type': 'application/octet-stream',
64+ 'X-Image-Meta-Name': 'Image1 Update',
65+ 'X-Image-Meta-Status': 'active',
66+ 'X-Image-Meta-Container-Format': 'ovf',
67+ 'X-Image-Meta-Disk-Format': 'vdi',
68+ 'X-Image-Meta-Size': '19',
69+ 'X-Image-Meta-Id': '1',
70+ 'X-Image-Meta-Is-Public': 'True'}
71+ path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
72+ http = httplib2.Http()
73+ response, content = http.request(path, 'POST', headers=headers)
74+ self.assertEqual(response.status, 409)
75+ expected = "An image with identifier 1 already exists"
76+ self.assertTrue(expected in content,
77+ "Could not find '%s' in '%s'" % (expected, content))
78+
79+ self.stop_servers()

Subscribers

People subscribed via source and target branches