Merge lp:~jaypipes/glance/bug704854 into lp:~glance-coresec/glance/cactus-trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Jay Pipes
Approved revision: 82
Merged at revision: 75
Proposed branch: lp:~jaypipes/glance/bug704854
Merge into: lp:~glance-coresec/glance/cactus-trunk
Prerequisite: lp:~jaypipes/glance/add-pidfile-option
Diff against target: 619 lines (+310/-57)
14 files modified
MANIFEST.in (+1/-0)
bin/glance-api (+7/-3)
bin/glance-combined (+7/-3)
bin/glance-control (+8/-0)
bin/glance-manage (+7/-3)
bin/glance-registry (+7/-3)
bin/glance-upload (+5/-3)
glance/client.py (+1/-1)
glance/registry/db/api.py (+30/-0)
glance/registry/db/models.py (+0/-13)
glance/registry/server.py (+14/-4)
glance/server.py (+43/-22)
tests/unit/test_clients.py (+2/-2)
tests/unit/test_misc.py (+178/-0)
To merge this branch: bzr merge lp:~jaypipes/glance/bug704854
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Devin Carlen (community) Approve
Rick Harris (community) Approve
Soren Hansen Pending
Review via email: mp+50773@code.launchpad.net

Description of the change

This adds a test case for LP Bug 704854 -- Exception
raised by Registry server gets eaten by API server.

The test involves spinning up an API server and a registry
server, firing cURL and glance-upload against the API
server with invalid requests, and verifying that appropriate
exception messages are contained in the response.

I should probably rebase this commit considering all the previous
commits weren't actually addressing the issue. The fact that I
had glance-api and glance-registry installed on my local machine
was causing the test runs to improperly return a passing result.

It just so happens that the glance-api and glance-registry that
I had installed to my /usr/local/bin were the very same programs
that were from a previous branch I had locally where I fixed the
root cause of this issue, which was that the sqlalchemy @validates
decorator does NOT fire for *new* objects, only existing ones, which
resulted in image_create() improperly storing NULL data in type, name,
and other non-nullable fields in the database. This then set off
a domino effect which caused the next call from
glance.server._upload_and_activate() to die a horrible death due to
the @validates decorator then firing on the already-created Image
object. This horrible death was improperly being raised from the
glance.client as a BadRequest instead of exception.Invalid, which
caused the API server to ignore the text in the actual Invalid
exception coming from the registry server.

In short, this patch finally fixes the root of the problem by
placing a validate_image() function guard which throws exception.Invalid
for any invalid data coming into the _image_update() method in the db
API. It also adds a bunch of logging statements and ensures that
exceptions throughout the call stack between the API server to the
glance.registry.Client to the Registry server are properly handled and
that the text of those exceptions isn't thrown away willy-nilly.

To post a comment you must log in.
Revision history for this message
Rick Harris (rconradharris) wrote :

Jay-

Sorry I'm late on reviewing this, overall this looks really good. Surprised by the decorator only validating on update (major WTF).

Femto-nits:

> 170 + type = values.get('type', None)

We probably shouldn't be using `type` as a variable name (since it's a builtin). `type_` seems ok, `image_type` seems better. (Was burned by this recently :-)

> 320 + logger.debug("Uploading image data for image %(image_id)s "
> 321 + "to %(store_name)s store"
> 322 + % {'image_id': image_id,
> 323 + 'store_name': image_store})

Certainly not a problem, but is there a reason you didn't use the ` % locals()` idiom here?

+ with tempfile.NamedTemporaryFile() as f:

Rather than just `f`, I think `temp_file` or `tmp` would be a little clearer and help with grep-ability.

(Should we eliminate single-letter variable names entirely since they are usually a bad idea? Should we special case the counting variables `i` and `j` since they are *soo* common/idiomatic?)

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

> Jay-
>
> Sorry I'm late on reviewing this, overall this looks really good. Surprised by
> the decorator only validating on update (major WTF).
>
> Femto-nits:
>
> > 170 + type = values.get('type', None)
>
> We probably shouldn't be using `type` as a variable name (since it's a
> builtin). `type_` seems ok, `image_type` seems better. (Was burned by this
> recently :-)

ya.

> > 320 + logger.debug("Uploading image data for image %(image_id)s
> "
> > 321 + "to %(store_name)s store"
> > 322 + % {'image_id': image_id,
> > 323 + 'store_name': image_store})
>
> Certainly not a problem, but is there a reason you didn't use the ` %
> locals()` idiom here?

nope, will fix. :)

> + with tempfile.NamedTemporaryFile() as f:

Copied from Swift ;) I'll rename to temp_file.

> Rather than just `f`, I think `temp_file` or `tmp` would be a little clearer
> and help with grep-ability.
>
> (Should we eliminate single-letter variable names entirely since they are
> usually a bad idea? Should we special case the counting variables `i` and `j`
> since they are *soo* common/idiomatic?)

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

fixes made. pls re-review.

Revision history for this message
Rick Harris (rconradharris) wrote :

Looks good, thanks for the fixes!

review: Approve
Revision history for this message
Devin Carlen (devcamcar) wrote :

lgtm!

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (4.9 KiB)

The attempt to merge lp:~jaypipes/glance/bug704854 into lp:glance failed. Below is the output from the failed tests.

running test
running egg_info
creating glance.egg-info
writing glance.egg-info/PKG-INFO
writing top-level names to glance.egg-info/top_level.txt
writing dependency_links to glance.egg-info/dependency_links.txt
writing manifest file 'glance.egg-info/SOURCES.txt'
reading manifest file 'glance.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'LICENSE'
warning: no files found matching 'ChangeLog'
warning: no files found matching 'tests/test_data.py'
writing manifest file 'glance.egg-info/SOURCES.txt'
running build_ext

Tests raises BadRequest for invalid store header ... ok
Tests raises BadRequest for invalid store header ... ok
Tests creates a queued image for no body and no loc header ... ok
test_delete_image (tests.unit.test_api.TestGlanceAPI) ... ok
test_delete_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
Test for HEAD /images/<ID> ... ok
test_show_image_basic (tests.unit.test_api.TestGlanceAPI) ... ok
test_show_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
Tests that the /images POST registry API creates the image ... ok
Tests proper exception is raised if a bad status is set ... ok
Tests that the /images DELETE registry API deletes the image ... ok
Tests proper exception is raised if attempt to delete non-existing ... ok
Tests that the /images/detail registry API returns ... ok
Tests that the /images registry API returns list of ... ok
Tests that the root registry API returns "index", ... ok
Tests that the /images PUT registry API updates the image ... ok
Tests proper exception is raised if attempt to update non-existing ... ok
Test ClientConnectionError raised ... ok
Tests proper exception is raised if image with ID already exists ... ok
Tests that we can add image metadata and returns the new id ... ok
Tests a bad status is set to a proper one by server ... ok
Tests BadRequest raised when supplying bad store name in meta ... ok
Tests can add image by passing image data as file ... ok
Tests can add image by passing image data as string ... ok
Tests add image by passing image data as string w/ no size attr ... ok
Tests that we can add image metadata with properties ... ok
Tests client returns image as queued ... ok
Tests that image metadata is deleted properly ... ok
Tests cannot delete non-existing image ... ok
Test a simple file backend retrieval works as expected ... ok
Tests that the detailed info about public images returned ... ok
Test correct set of public image returned ... ok
Tests that the detailed info about an image returned ... ok
Tests that NotFound is raised when getting a non-existing image ... ok
Test retrieval of a non-existing image returns a 404 ... ok
Tests that the /images PUT registry API updates the image ... ok
Tests non existing image update doesn't work ... ok
Tests proper exception is raised if image with ID already exists ... ok
Tests that we can add image metadata and returns the new id ... ok
Tests proper exception is raised if a bad status is set ... ok
Tests that we can add image metadata with properties ... ok
Tests t...

Read more...

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

I'm getting SERIOUSLY annoyed with not being able to reproduce shit that Hudson keeps barfing on. Grr.

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

approving debugging statements in test...

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (5.2 KiB)

The attempt to merge lp:~jaypipes/glance/bug704854 into lp:glance failed. Below is the output from the failed tests.

running test
running egg_info
creating glance.egg-info
writing glance.egg-info/PKG-INFO
writing top-level names to glance.egg-info/top_level.txt
writing dependency_links to glance.egg-info/dependency_links.txt
writing manifest file 'glance.egg-info/SOURCES.txt'
reading manifest file 'glance.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'LICENSE'
warning: no files found matching 'ChangeLog'
warning: no files found matching 'tests/test_data.py'
writing manifest file 'glance.egg-info/SOURCES.txt'
running build_ext

Tests raises BadRequest for invalid store header ... ok
Tests raises BadRequest for invalid store header ... ok
Tests creates a queued image for no body and no loc header ... ok
test_delete_image (tests.unit.test_api.TestGlanceAPI) ... ok
test_delete_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
Test for HEAD /images/<ID> ... ok
test_show_image_basic (tests.unit.test_api.TestGlanceAPI) ... ok
test_show_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
Tests that the /images POST registry API creates the image ... ok
Tests proper exception is raised if a bad status is set ... ok
Tests that the /images DELETE registry API deletes the image ... ok
Tests proper exception is raised if attempt to delete non-existing ... ok
Tests that the /images/detail registry API returns ... ok
Tests that the /images registry API returns list of ... ok
Tests that the root registry API returns "index", ... ok
Tests that the /images PUT registry API updates the image ... ok
Tests proper exception is raised if attempt to update non-existing ... ok
Test ClientConnectionError raised ... ok
Tests proper exception is raised if image with ID already exists ... ok
Tests that we can add image metadata and returns the new id ... ok
Tests a bad status is set to a proper one by server ... ok
Tests BadRequest raised when supplying bad store name in meta ... ok
Tests can add image by passing image data as file ... ok
Tests can add image by passing image data as string ... ok
Tests add image by passing image data as string w/ no size attr ... ok
Tests that we can add image metadata with properties ... ok
Tests client returns image as queued ... ok
Tests that image metadata is deleted properly ... ok
Tests cannot delete non-existing image ... ok
Test a simple file backend retrieval works as expected ... ok
Tests that the detailed info about public images returned ... ok
Test correct set of public image returned ... ok
Tests that the detailed info about an image returned ... ok
Tests that NotFound is raised when getting a non-existing image ... ok
Test retrieval of a non-existing image returns a 404 ... ok
Tests that the /images PUT registry API updates the image ... ok
Tests non existing image update doesn't work ... ok
Tests proper exception is raised if image with ID already exists ... ok
Tests that we can add image metadata and returns the new id ... ok
Tests proper exception is raised if a bad status is set ... ok
Tests that we can add image metadata with properties ... ok
Tests t...

Read more...

Revision history for this message
Vish Ishaya (vishvananda) wrote :
Download full text (5.6 KiB)

argparse isn't in python 2.6, no?
On Feb 23, 2011, at 12:48 PM, OpenStack Hudson wrote:

> The attempt to merge lp:~jaypipes/glance/bug704854 into lp:glance failed. Below is the output from the failed tests.
>
> running test
> running egg_info
> creating glance.egg-info
> writing glance.egg-info/PKG-INFO
> writing top-level names to glance.egg-info/top_level.txt
> writing dependency_links to glance.egg-info/dependency_links.txt
> writing manifest file 'glance.egg-info/SOURCES.txt'
> reading manifest file 'glance.egg-info/SOURCES.txt'
> reading manifest template 'MANIFEST.in'
> warning: no files found matching 'LICENSE'
> warning: no files found matching 'ChangeLog'
> warning: no files found matching 'tests/test_data.py'
> writing manifest file 'glance.egg-info/SOURCES.txt'
> running build_ext
>
> Tests raises BadRequest for invalid store header ... ok
> Tests raises BadRequest for invalid store header ... ok
> Tests creates a queued image for no body and no loc header ... ok
> test_delete_image (tests.unit.test_api.TestGlanceAPI) ... ok
> test_delete_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
> Test for HEAD /images/<ID> ... ok
> test_show_image_basic (tests.unit.test_api.TestGlanceAPI) ... ok
> test_show_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
> Tests that the /images POST registry API creates the image ... ok
> Tests proper exception is raised if a bad status is set ... ok
> Tests that the /images DELETE registry API deletes the image ... ok
> Tests proper exception is raised if attempt to delete non-existing ... ok
> Tests that the /images/detail registry API returns ... ok
> Tests that the /images registry API returns list of ... ok
> Tests that the root registry API returns "index", ... ok
> Tests that the /images PUT registry API updates the image ... ok
> Tests proper exception is raised if attempt to update non-existing ... ok
> Test ClientConnectionError raised ... ok
> Tests proper exception is raised if image with ID already exists ... ok
> Tests that we can add image metadata and returns the new id ... ok
> Tests a bad status is set to a proper one by server ... ok
> Tests BadRequest raised when supplying bad store name in meta ... ok
> Tests can add image by passing image data as file ... ok
> Tests can add image by passing image data as string ... ok
> Tests add image by passing image data as string w/ no size attr ... ok
> Tests that we can add image metadata with properties ... ok
> Tests client returns image as queued ... ok
> Tests that image metadata is deleted properly ... ok
> Tests cannot delete non-existing image ... ok
> Test a simple file backend retrieval works as expected ... ok
> Tests that the detailed info about public images returned ... ok
> Test correct set of public image returned ... ok
> Tests that the detailed info about an image returned ... ok
> Tests that NotFound is raised when getting a non-existing image ... ok
> Test retrieval of a non-existing image returns a 404 ... ok
> Tests that the /images PUT registry API updates the image ... ok
> Tests non existing image update doesn't work ... ok
> Tests proper exception is raised if image with ID already exists ... o...

Read more...

Revision history for this message
Jay Pipes (jaypipes) wrote :
Download full text (5.9 KiB)

No it's not.

On Wed, Feb 23, 2011 at 3:58 PM, Vish Ishaya <email address hidden> wrote:
> argparse isn't in python 2.6, no?
> On Feb 23, 2011, at 12:48 PM, OpenStack Hudson wrote:
>
>> The attempt to merge lp:~jaypipes/glance/bug704854 into lp:glance failed. Below is the output from the failed tests.
>>
>> running test
>> running egg_info
>> creating glance.egg-info
>> writing glance.egg-info/PKG-INFO
>> writing top-level names to glance.egg-info/top_level.txt
>> writing dependency_links to glance.egg-info/dependency_links.txt
>> writing manifest file 'glance.egg-info/SOURCES.txt'
>> reading manifest file 'glance.egg-info/SOURCES.txt'
>> reading manifest template 'MANIFEST.in'
>> warning: no files found matching 'LICENSE'
>> warning: no files found matching 'ChangeLog'
>> warning: no files found matching 'tests/test_data.py'
>> writing manifest file 'glance.egg-info/SOURCES.txt'
>> running build_ext
>>
>> Tests raises BadRequest for invalid store header ... ok
>> Tests raises BadRequest for invalid store header ... ok
>> Tests creates a queued image for no body and no loc header ... ok
>> test_delete_image (tests.unit.test_api.TestGlanceAPI) ... ok
>> test_delete_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
>> Test for HEAD /images/<ID> ... ok
>> test_show_image_basic (tests.unit.test_api.TestGlanceAPI) ... ok
>> test_show_non_exists_image (tests.unit.test_api.TestGlanceAPI) ... ok
>> Tests that the /images POST registry API creates the image ... ok
>> Tests proper exception is raised if a bad status is set ... ok
>> Tests that the /images DELETE registry API deletes the image ... ok
>> Tests proper exception is raised if attempt to delete non-existing ... ok
>> Tests that the /images/detail registry API returns ... ok
>> Tests that the /images registry API returns list of ... ok
>> Tests that the root registry API returns "index", ... ok
>> Tests that the /images PUT registry API updates the image ... ok
>> Tests proper exception is raised if attempt to update non-existing ... ok
>> Test ClientConnectionError raised ... ok
>> Tests proper exception is raised if image with ID already exists ... ok
>> Tests that we can add image metadata and returns the new id ... ok
>> Tests a bad status is set to a proper one by server ... ok
>> Tests BadRequest raised when supplying bad store name in meta ... ok
>> Tests can add image by passing image data as file ... ok
>> Tests can add image by passing image data as string ... ok
>> Tests add image by passing image data as string w/ no size attr ... ok
>> Tests that we can add image metadata with properties ... ok
>> Tests client returns image as queued ... ok
>> Tests that image metadata is deleted properly ... ok
>> Tests cannot delete non-existing image ... ok
>> Test a simple file backend retrieval works as expected ... ok
>> Tests that the detailed info about public images returned ... ok
>> Test correct set of public image returned ... ok
>> Tests that the detailed info about an image returned ... ok
>> Tests that NotFound is raised when getting a non-existing image ... ok
>> Test retrieval of a non-existing image returns a 404 ... ok
>> Tests that the /images PUT registry API updat...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'MANIFEST.in'
2--- MANIFEST.in 2011-02-02 01:43:16 +0000
3+++ MANIFEST.in 2011-02-23 20:37:56 +0000
4@@ -8,4 +8,5 @@
5 include run_tests.py
6 include glance/registry/db/migrate_repo/migrate.cfg
7 graft doc
8+graft etc
9 graft tools
10
11=== modified file 'bin/glance-api'
12--- bin/glance-api 2011-02-09 20:24:59 +0000
13+++ bin/glance-api 2011-02-23 20:37:56 +0000
14@@ -26,9 +26,13 @@
15 import os
16 import sys
17
18-ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19-
20-sys.path.append(ROOT_DIR)
21+# If ../glance/__init__.py exists, add ../ to Python search path, so that
22+# it will override what happens to be installed in /usr/(local/)lib/python...
23+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
24+ os.pardir,
25+ os.pardir))
26+if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
27+ sys.path.insert(0, possible_topdir)
28
29 from glance import version
30 from glance.common import config
31
32=== modified file 'bin/glance-combined'
33--- bin/glance-combined 2011-02-09 20:24:59 +0000
34+++ bin/glance-combined 2011-02-23 20:37:56 +0000
35@@ -28,9 +28,13 @@
36 import os
37 import sys
38
39-ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
40-
41-sys.path.append(ROOT_DIR)
42+# If ../glance/__init__.py exists, add ../ to Python search path, so that
43+# it will override what happens to be installed in /usr/(local/)lib/python...
44+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
45+ os.pardir,
46+ os.pardir))
47+if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
48+ sys.path.insert(0, possible_topdir)
49
50 from glance import version
51 from glance.common import config
52
53=== modified file 'bin/glance-control'
54--- bin/glance-control 2011-02-16 07:43:11 +0000
55+++ bin/glance-control 2011-02-23 20:37:56 +0000
56@@ -31,6 +31,14 @@
57 import sys
58 import time
59
60+# If ../glance/__init__.py exists, add ../ to Python search path, so that
61+# it will override what happens to be installed in /usr/(local/)lib/python...
62+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
63+ os.pardir,
64+ os.pardir))
65+if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
66+ sys.path.insert(0, possible_topdir)
67+
68 from glance import version
69 from glance.common import config
70
71
72=== modified file 'bin/glance-manage'
73--- bin/glance-manage 2011-02-02 18:47:08 +0000
74+++ bin/glance-manage 2011-02-23 20:37:56 +0000
75@@ -30,9 +30,13 @@
76 import os
77 import sys
78
79-ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
80-
81-sys.path.append(ROOT_DIR)
82+# If ../glance/__init__.py exists, add ../ to Python search path, so that
83+# it will override what happens to be installed in /usr/(local/)lib/python...
84+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
85+ os.pardir,
86+ os.pardir))
87+if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
88+ sys.path.insert(0, possible_topdir)
89
90 from glance import version as glance_version
91 from glance.common import config
92
93=== modified file 'bin/glance-registry'
94--- bin/glance-registry 2011-02-09 20:24:59 +0000
95+++ bin/glance-registry 2011-02-23 20:37:56 +0000
96@@ -26,9 +26,13 @@
97 import os
98 import sys
99
100-ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
101-
102-sys.path.append(ROOT_DIR)
103+# If ../glance/__init__.py exists, add ../ to Python search path, so that
104+# it will override what happens to be installed in /usr/(local/)lib/python...
105+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
106+ os.pardir,
107+ os.pardir))
108+if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
109+ sys.path.insert(0, possible_topdir)
110
111 from glance import version
112 from glance.common import config
113
114=== modified file 'bin/glance-upload'
115--- bin/glance-upload 2011-02-02 01:43:16 +0000
116+++ bin/glance-upload 2011-02-23 20:37:56 +0000
117@@ -53,10 +53,12 @@
118 parser.add_argument('filename', help='file to upload into Glance')
119 parser.add_argument('name', help='name of image')
120 parser.add_argument('--host', metavar='HOST', default='127.0.0.1',
121- help='Location of Glance Server (default: 127.0.0.1)')
122+ help='Location of Glance Server (default: %default)')
123+ parser.add_argument('--port', metavar='PORT', type=int, default=9292,
124+ help='Port of Glance Server (default: %default)')
125 parser.add_argument('--type', metavar='TYPE', default='raw',
126 help='Type of Image [kernel, ramdisk, machine, raw] '
127- '(default: raw)')
128+ '(default: %default)')
129 parser.add_argument('--kernel', metavar='KERNEL',
130 help='ID of kernel associated with this machine image')
131 parser.add_argument('--ramdisk', metavar='RAMDISK',
132@@ -77,7 +79,7 @@
133 else:
134 die("kernel and ramdisk required for machine image")
135
136- client = Client(args.host, 9292)
137+ client = Client(args.host, args.port)
138 with open(args.filename) as f:
139 new_meta = client.add_image(meta, f)
140
141
142=== modified file 'glance/client.py'
143--- glance/client.py 2011-02-05 02:32:31 +0000
144+++ glance/client.py 2011-02-23 20:37:56 +0000
145@@ -153,7 +153,7 @@
146 elif status_code == httplib.CONFLICT:
147 raise exception.Duplicate(res.read())
148 elif status_code == httplib.BAD_REQUEST:
149- raise exception.BadInputError(res.read())
150+ raise exception.Invalid(res.read())
151 else:
152 raise Exception("Unknown error occurred! %s" % res.__dict__)
153
154
155=== modified file 'glance/registry/db/api.py'
156--- glance/registry/db/api.py 2011-02-11 00:12:51 +0000
157+++ glance/registry/db/api.py 2011-02-23 20:37:56 +0000
158@@ -144,6 +144,30 @@
159 del values[attr]
160
161
162+def validate_image(values):
163+ """
164+ Validates the incoming data and raises a Invalid exception
165+ if anything is out of order.
166+
167+ :param values: Mapping of image metadata to check
168+ """
169+
170+ image_type = values.get('type', None)
171+ if not image_type:
172+ msg = "Image type is required."
173+ raise exception.Invalid(msg)
174+ if image_type not in ('machine', 'kernel', 'ramdisk', 'raw', 'vhd'):
175+ msg = "Invalid image type '%s' for image." % image_type
176+ raise exception.Invalid(msg)
177+ status = values.get('status', None)
178+ if not status:
179+ msg = "Image status is required."
180+ raise exception.Invalid(msg)
181+ if status not in ('active', 'queued', 'killed', 'saving'):
182+ msg = "Invalid image status '%s' for image." % status
183+ raise exception.Invalid(msg)
184+
185+
186 def _image_update(context, values, image_id):
187 """Used internally by image_create and image_update
188
189@@ -151,6 +175,12 @@
190 :param values: A dict of attributes to set
191 :param image_id: If None, create the image, otherwise, find and update it
192 """
193+
194+ # Validate the attributes before we go any further. From my investigation,
195+ # the @validates decorator does not validate on new records, only on
196+ # existing records, which is, well, idiotic.
197+ validate_image(values)
198+
199 session = get_session()
200 with session.begin():
201 _drop_protected_attrs(models.Image, values)
202
203=== modified file 'glance/registry/db/models.py'
204--- glance/registry/db/models.py 2011-02-17 18:34:28 +0000
205+++ glance/registry/db/models.py 2011-02-23 20:37:56 +0000
206@@ -101,19 +101,6 @@
207 is_public = Column(Boolean, nullable=False, default=False)
208 location = Column(Text)
209
210- @validates('type')
211- def validate_type(self, key, type):
212- if not type in ('machine', 'kernel', 'ramdisk', 'raw', 'vhd'):
213- raise exception.Invalid(
214- "Invalid image type '%s' for image." % type)
215- return type
216-
217- @validates('status')
218- def validate_status(self, key, status):
219- if not status in ('active', 'queued', 'killed', 'saving'):
220- raise exception.Invalid("Invalid status '%s' for image." % status)
221- return status
222-
223
224 class ImageProperty(BASE, ModelBase):
225 """Represents an image properties in the datastore"""
226
227=== modified file 'glance/registry/server.py'
228--- glance/registry/server.py 2011-02-03 15:23:48 +0000
229+++ glance/registry/server.py 2011-02-23 20:37:56 +0000
230@@ -19,6 +19,7 @@
231 """
232
233 import json
234+import logging
235
236 import routes
237 from webob import exc
238@@ -28,6 +29,9 @@
239 from glance.registry.db import api as db_api
240
241
242+logger = logging.getLogger('glance.registry.server')
243+
244+
245 class Controller(wsgi.Controller):
246 """Controller for the reference implementation registry server"""
247
248@@ -118,9 +122,13 @@
249 image_data = db_api.image_create(context, image_data)
250 return dict(image=make_image_dict(image_data))
251 except exception.Duplicate:
252- return exc.HTTPConflict()
253- except exception.Invalid:
254- return exc.HTTPBadRequest()
255+ msg = ("Image with identifier %s already exists!" % id)
256+ logger.error(msg)
257+ return exc.HTTPConflict(msg)
258+ except exception.Invalid, e:
259+ msg = ("Failed to add image metadata. Got error: %(e)s" % locals())
260+ logger.error(msg)
261+ return exc.HTTPBadRequest(msg)
262
263 def update(self, req, id):
264 """Updates an existing image with the registry.
265@@ -140,7 +148,9 @@
266 updated_image = db_api.image_update(context, id, image_data)
267 return dict(image=make_image_dict(updated_image))
268 except exception.NotFound:
269- return exc.HTTPNotFound()
270+ raise exc.HTTPNotFound(body='Image not found',
271+ request=req,
272+ content_type='text/plain')
273
274
275 class API(wsgi.Router):
276
277=== modified file 'glance/server.py'
278--- glance/server.py 2011-02-16 10:01:21 +0000
279+++ glance/server.py 2011-02-23 20:37:56 +0000
280@@ -49,6 +49,9 @@
281 from glance import utils
282
283
284+logger = logging.getLogger('glance.server')
285+
286+
287 class Controller(wsgi.Controller):
288
289 """
290@@ -188,10 +191,13 @@
291 except exception.Duplicate:
292 msg = "An image with identifier %s already exists"\
293 % image_meta['id']
294- logging.error(msg)
295- raise HTTPConflict(msg, request=req)
296- except exception.Invalid:
297- raise HTTPBadRequest()
298+ logger.error(msg)
299+ raise HTTPConflict(msg, request=req, content_type="text/plain")
300+ except exception.Invalid, e:
301+ msg = ("Failed to reserve image. Got error: %(e)s" % locals())
302+ for line in msg.split('\n'):
303+ logger.error(line)
304+ raise HTTPBadRequest(msg, request=req, content_type="text/plain")
305
306 def _upload(self, req, image_meta):
307 """
308@@ -211,17 +217,22 @@
309 raise HTTPBadRequest(
310 "Content-Type must be application/octet-stream")
311
312- image_store = req.headers.get(
313+ store_name = req.headers.get(
314 'x-image-meta-store', self.options['default_store'])
315
316- store = self.get_store_or_400(req, image_store)
317+ store = self.get_store_or_400(req, store_name)
318
319 image_meta['status'] = 'saving'
320+ image_id = image_meta['id']
321+ logger.debug("Updating image metadata for image %s"
322+ % image_id)
323 registry.update_image_metadata(self.options,
324 image_meta['id'],
325 image_meta)
326
327 try:
328+ logger.debug("Uploading image data for image %(image_id)s "
329+ "to %(store_name)s store" % locals())
330 location, size = store.add(image_meta['id'],
331 req.body_file,
332 self.options)
333@@ -230,12 +241,14 @@
334 # the new size of the image
335 if image_meta.get('size', 0) != size:
336 image_meta['size'] = size
337+ logger.debug("Updating image metadata for image %s"
338+ % image_id)
339 registry.update_image_metadata(self.options,
340 image_meta['id'],
341 image_meta)
342 return location
343 except exception.Duplicate, e:
344- logging.error("Error adding image to store: %s", str(e))
345+ logger.error("Error adding image to store: %s", str(e))
346 raise HTTPConflict(str(e), request=req)
347
348 def _activate(self, req, image_meta, location):
349@@ -277,7 +290,7 @@
350 try:
351 self._kill(req, image_meta)
352 except Exception, e:
353- logging.error("Unable to kill image %s: %s",
354+ logger.error("Unable to kill image %s: %s",
355 image_meta['id'], repr(e))
356
357 def _upload_and_activate(self, req, image_meta):
358@@ -367,14 +380,21 @@
359 raise HTTPConflict("Cannot upload to an unqueued image")
360
361 new_image_meta = utils.get_image_meta_from_headers(req)
362- image_meta = registry.update_image_metadata(self.options,
363- id,
364- new_image_meta)
365-
366- if has_body:
367- self._upload_and_activate(req, image_meta)
368-
369- return dict(image=image_meta)
370+ try:
371+ image_meta = registry.update_image_metadata(self.options,
372+ id,
373+ new_image_meta)
374+
375+ if has_body:
376+ self._upload_and_activate(req, image_meta)
377+
378+ return dict(image=image_meta)
379+ except exception.Invalid, e:
380+ msg = ("Failed to update image metadata. Got error: %(e)s"
381+ % locals())
382+ for line in msg.split('\n'):
383+ logger.error(line)
384+ raise HTTPBadRequest(msg, request=req, content_type="text/plain")
385
386 def delete(self, req, id):
387 """
388@@ -407,8 +427,9 @@
389 try:
390 return registry.get_image_metadata(self.options, id)
391 except exception.NotFound:
392- raise HTTPNotFound(body='Image not found',
393- request=request,
394+ msg = "Image with identifier %s not found" % id
395+ logger.debug(msg)
396+ raise HTTPNotFound(msg, request=request,
397 content_type='text/plain')
398
399 def get_store_or_400(self, request, store_name):
400@@ -424,10 +445,10 @@
401 try:
402 return get_backend_class(store_name)
403 except UnsupportedBackend:
404- raise HTTPBadRequest(body='Requested store %s not available '
405- 'for storage on this Glance node'
406- % store_name,
407- request=request,
408+ msg = ("Requested store %s not available on this Glance server"
409+ % store_name)
410+ logger.error(msg)
411+ raise HTTPBadRequest(msg, request=request,
412 content_type='text/plain')
413
414
415
416=== modified file 'tests/unit/test_clients.py'
417--- tests/unit/test_clients.py 2011-01-28 21:54:34 +0000
418+++ tests/unit/test_clients.py 2011-02-23 20:37:56 +0000
419@@ -204,7 +204,7 @@
420 'location': "file:///tmp/glance-tests/2",
421 }
422
423- self.assertRaises(exception.BadInputError,
424+ self.assertRaises(exception.Invalid,
425 self.client.add_image,
426 fixture)
427
428@@ -567,7 +567,7 @@
429
430 image_data_fixture = r"chunk00000remainder"
431
432- self.assertRaises(exception.BadInputError,
433+ self.assertRaises(exception.Invalid,
434 self.client.add_image,
435 fixture,
436 image_data_fixture)
437
438=== added file 'tests/unit/test_misc.py'
439--- tests/unit/test_misc.py 1970-01-01 00:00:00 +0000
440+++ tests/unit/test_misc.py 2011-02-23 20:37:56 +0000
441@@ -0,0 +1,178 @@
442+# vim: tabstop=4 shiftwidth=4 softtabstop=4
443+
444+# Copyright 2011 OpenStack, LLC
445+# All Rights Reserved.
446+#
447+# Licensed under the Apache License, Version 2.0 (the "License"); you may
448+# not use this file except in compliance with the License. You may obtain
449+# a copy of the License at
450+#
451+# http://www.apache.org/licenses/LICENSE-2.0
452+#
453+# Unless required by applicable law or agreed to in writing, software
454+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
455+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
456+# License for the specific language governing permissions and limitations
457+# under the License.
458+
459+import os
460+import shutil
461+import signal
462+import subprocess
463+import tempfile
464+import time
465+import unittest
466+
467+
468+def execute(cmd):
469+ env = os.environ.copy()
470+ # Make sure that we use the programs in the
471+ # current source directory's bin/ directory.
472+ env['PATH'] = os.path.join(os.getcwd(), 'bin') + ':' + env['PATH']
473+ process = subprocess.Popen(cmd,
474+ shell=True,
475+ stdin=subprocess.PIPE,
476+ stdout=subprocess.PIPE,
477+ stderr=subprocess.PIPE,
478+ env=env)
479+ result = process.communicate()
480+ (out, err) = result
481+ exitcode = process.returncode
482+ if process.returncode != 0:
483+ msg = "Command %(cmd)s did not succeed. Returned an exit "\
484+ "code of %(exitcode)d."\
485+ "\n\nSTDOUT: %(out)s"\
486+ "\n\nSTDERR: %(err)s" % locals()
487+ raise RuntimeError(msg)
488+ return exitcode, out, err
489+
490+
491+class TestMiscellaneous(unittest.TestCase):
492+
493+ """Some random tests for various bugs and stuff"""
494+
495+ def tearDown(self):
496+ self._cleanup_test_servers()
497+
498+ def _cleanup_test_servers(self):
499+ # Clean up any leftover test servers...
500+ pid_files = ('glance-api.pid', 'glance-registry.pid')
501+ for pid_file in pid_files:
502+ if os.path.exists(pid_file):
503+ pid = int(open(pid_file).read().strip())
504+ try:
505+ os.killpg(pid, signal.SIGTERM)
506+ except:
507+ pass # Ignore if the process group is dead
508+ os.unlink(pid_file)
509+
510+ def test_exception_not_eaten_from_registry_to_api(self):
511+ """
512+ A test for LP bug #704854 -- Exception thrown by registry
513+ server is consumed by API server.
514+
515+ We start both servers daemonized.
516+
517+ We then use curl to try adding an image that does not
518+ meet validation requirements on the registry server and test
519+ that the error returned from the API server to curl is appropriate
520+
521+ We also fire the glance-upload tool against the API server
522+ and verify that glance-upload doesn't eat the exception either...
523+ """
524+
525+ self._cleanup_test_servers()
526+
527+ # Port numbers hopefully not used by anything...
528+ api_port = 32001
529+ reg_port = 32000
530+ image_dir = "/tmp/test.images.%d" % api_port
531+ if os.path.exists(image_dir):
532+ shutil.rmtree(image_dir)
533+
534+ # A config file to use just for this test...we don't want
535+ # to trample on currently-running Glance servers, now do we?
536+ with tempfile.NamedTemporaryFile() as conf_file:
537+ conf_contents = """[DEFAULT]
538+verbose = True
539+debug = True
540+
541+[app:glance-api]
542+paste.app_factory = glance.server:app_factory
543+filesystem_store_datadir=%(image_dir)s
544+default_store = file
545+bind_host = 0.0.0.0
546+bind_port = %(api_port)s
547+registry_host = 0.0.0.0
548+registry_port = %(reg_port)s
549+
550+[app:glance-registry]
551+paste.app_factory = glance.registry.server:app_factory
552+bind_host = 0.0.0.0
553+bind_port = %(reg_port)s
554+sql_connection = sqlite://
555+sql_idle_timeout = 3600
556+""" % locals()
557+ conf_file.write(conf_contents)
558+ conf_file.flush()
559+ conf_file_name = conf_file.name
560+
561+ venv = ""
562+ if 'VIRTUAL_ENV' in os.environ:
563+ venv = "tools/with_venv.sh "
564+
565+ # Start up the API and default registry server
566+ cmd = venv + "./bin/glance-control api start "\
567+ "%s --pid-file=glance-api.pid" % conf_file_name
568+ exitcode, out, err = execute(cmd)
569+
570+ self.assertEquals(0, exitcode)
571+ self.assertTrue("Starting glance-api with" in out)
572+
573+ cmd = venv + "./bin/glance-control registry start "\
574+ "%s --pid-file=glance-registry.pid" % conf_file_name
575+ exitcode, out, err = execute(cmd)
576+
577+ self.assertEquals(0, exitcode)
578+ self.assertTrue("Starting glance-registry with" in out)
579+
580+ time.sleep(2) # Gotta give some time for spin up...
581+
582+ cmd = "curl -g http://0.0.0.0:%d/images" % api_port
583+
584+ exitcode, out, err = execute(cmd)
585+
586+ self.assertEquals(0, exitcode)
587+ self.assertEquals('{"images": []}', out.strip())
588+
589+ cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
590+ "-dinvalid http://0.0.0.0:%d/images" % api_port
591+ ignored, out, err = execute(cmd)
592+
593+ self.assertTrue('Image type is required' in out,
594+ "Could not find 'Image type is required' "
595+ "in output: %s" % out)
596+
597+ cmd = "./bin/glance-upload --port=%(api_port)d "\
598+ "--type=invalid %(conf_file_name)s 'my image'" % locals()
599+
600+ # Normally, we would simply self.assertRaises() here, but
601+ # we also want to check that the Invalid image type is in
602+ # the exception text...
603+ hit_exception = False
604+ try:
605+ ignored, out, err = execute(cmd)
606+ except RuntimeError, e:
607+ hit_exception = True
608+ self.assertTrue('Invalid image type' in str(e),
609+ "Could not find 'Invalid image type' in "
610+ "result from glance-upload:\n%(e)s" % locals())
611+ self.assertTrue(hit_exception)
612+
613+ # Spin down the API and default registry server
614+ cmd = "./bin/glance-control api stop "\
615+ "%s --pid-file=glance-api.pid" % conf_file_name
616+ ignored, out, err = execute(cmd)
617+ cmd = "./bin/glance-control registry stop "\
618+ "%s --pid-file=glance-registry.pid" % conf_file_name
619+ ignored, out, err = execute(cmd)

Subscribers

People subscribed via source and target branches