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