Merge lp:~rconradharris/glance/bug706192 into lp:~hudson-openstack/glance/trunk

Proposed by Rick Harris
Status: Merged
Approved by: Jay Pipes
Approved revision: 50
Merged at revision: 47
Proposed branch: lp:~rconradharris/glance/bug706192
Merge into: lp:~hudson-openstack/glance/trunk
Prerequisite: lp:~rconradharris/glance/bug706174
Diff against target: 249 lines (+136/-61)
5 files modified
bin/glance-upload (+85/-0)
glance/registry/db/sqlalchemy/api.py (+46/-11)
glance/util.py (+3/-2)
setup.py (+2/-1)
tests/test_data.py (+0/-47)
To merge this branch: bzr merge lp:~rconradharris/glance/bug706192
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Devin Carlen (community) Approve
Review via email: mp+47166@code.launchpad.net

Description of the change

This patch:
* Converts dashes to underscores when extracting image-properties from HTTP headers (we already do this for 'regular' image attributes
* Update image_properties on image PUTs rather than trying to create dups

Bonus:
* Remove useless test_data file (no longer needed now that we can actually use Glance API via glance/client.py)
* Add glance_upload.py which we can use to add raw/extra-kernel images (this might be able to go away once we have a full-blown glance-admin tool. However, for now, this is useful for testing Glance <-> Nova integration.

To post a comment you must log in.
lp:~rconradharris/glance/bug706192 updated
49. By Rick Harris

Making glance-upload a first-class binary

50. By Rick Harris

Adding Apache license, fixing long line

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

lgtm except for a localization nit. need to _() this:

79 + die("kernel and ramdisk required for machine image")

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

Hey Devin, you raise a good point.

At this point, Glance, unlike Nova, isn't really using gettext (there seems to be an extraneous reference to it in run_tests, but I think that's just b/c we copied it from Nova).

I'm planning on creating a separate bug to add gettext everywhere.

Would you be okay with this patch merging as-is, and having the localization stuff go in as part of a separate ticket?

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

Yep, that's reasonable.

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

lgtm. agree that glance_upload.py should be removed in Cactus when admin-tools hits trunk, but this is great for now. :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'bin/glance-upload'
2--- bin/glance-upload 1970-01-01 00:00:00 +0000
3+++ bin/glance-upload 2011-01-22 19:45:11 +0000
4@@ -0,0 +1,85 @@
5+#!/usr/bin/env python
6+# vim: tabstop=4 shiftwidth=4 softtabstop=4
7+
8+# Copyright 2011 OpenStack LLC.
9+# All Rights Reserved.
10+#
11+# Licensed under the Apache License, Version 2.0 (the "License"); you may
12+# not use this file except in compliance with the License. You may obtain
13+# a copy of the License at
14+#
15+# http://www.apache.org/licenses/LICENSE-2.0
16+#
17+# Unless required by applicable law or agreed to in writing, software
18+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
20+# License for the specific language governing permissions and limitations
21+# under the License.
22+
23+"""
24+Upload an image into Glance
25+
26+Usage:
27+
28+Raw:
29+
30+ glance-upload <filename> <name>
31+
32+Kernel-outside:
33+
34+ glance-upload --type=kernel <filename> <name>
35+ glance-upload --type=ramdisk <filename> <name>
36+ glance-upload --type=machine --kernel=KERNEL_ID --ramdisk=RAMDISK_ID \
37+ <filename> <name>
38+
39+"""
40+import argparse
41+import pprint
42+import sys
43+
44+from glance.client import Client
45+
46+
47+def die(msg):
48+ print >>sys.stderr, msg
49+ sys.exit(1)
50+
51+
52+def parse_args():
53+ parser = argparse.ArgumentParser(description='Upload an image into Glance')
54+ parser.add_argument('filename', help='file to upload into Glance')
55+ parser.add_argument('name', help='name of image')
56+ parser.add_argument('--host', metavar='HOST', default='127.0.0.1',
57+ help='Location of Glance Server (default: 127.0.0.1)')
58+ parser.add_argument('--type', metavar='TYPE', default='raw',
59+ help='Type of Image [kernel, ramdisk, machine, raw] '
60+ '(default: raw)')
61+ parser.add_argument('--kernel', metavar='KERNEL',
62+ help='ID of kernel associated with this machine image')
63+ parser.add_argument('--ramdisk', metavar='RAMDISK',
64+ help='ID of ramdisk associated with this machine '
65+ 'image')
66+ args = parser.parse_args()
67+ return args
68+
69+
70+def main():
71+ args = parse_args()
72+ meta = {'name': args.name, 'type': args.type, 'is_public': True}
73+
74+ if args.type == 'machine':
75+ if args.kernel and args.ramdisk:
76+ meta['properties'] = {'kernel_id': args.kernel,
77+ 'ramdisk_id': args.ramdisk}
78+ else:
79+ die("kernel and ramdisk required for machine image")
80+
81+ client = Client(args.host, 9292)
82+ with open(args.filename) as f:
83+ new_meta = client.add_image(meta, f)
84+
85+ print 'Stored image. Got identifier: %s' % pprint.pformat(new_meta)
86+
87+
88+if __name__ == "__main__":
89+ main()
90
91=== modified file 'glance/registry/db/sqlalchemy/api.py'
92--- glance/registry/db/sqlalchemy/api.py 2011-01-07 06:13:33 +0000
93+++ glance/registry/db/sqlalchemy/api.py 2011-01-22 19:45:11 +0000
94@@ -103,12 +103,24 @@
95 ###################
96
97
98-def image_property_create(_context, values):
99- _drop_protected_attrs(models.Image, values)
100- image_property_ref = models.ImageProperty()
101- image_property_ref.update(values)
102- image_property_ref.save()
103- return image_property_ref
104+def image_property_create(_context, values, session=None):
105+ """Create an ImageProperty object"""
106+ prop_ref = models.ImageProperty()
107+ return _image_property_update(_context, prop_ref, values, session=session)
108+
109+
110+def image_property_update(_context, prop_ref, values, session=None):
111+ """Update an ImageProperty object"""
112+ return _image_property_update(_context, prop_ref, values, session=session)
113+
114+
115+def _image_property_update(_context, prop_ref, values, session=None):
116+ """Used internally by image_property_create and image_property_update
117+ """
118+ _drop_protected_attrs(models.ImageProperty, values)
119+ prop_ref.update(values)
120+ prop_ref.save(session=session)
121+ return prop_ref
122
123
124 def _drop_protected_attrs(model_class, values):
125@@ -123,6 +135,8 @@
126 def _image_update(_context, values, image_id):
127 """Used internally by image_create and image_update
128
129+ :param _context: Request context
130+ :param values: A dict of attributes to set
131 :param image_id: If None, create the image, otherwise, find and update it
132 """
133 session = get_session()
134@@ -143,10 +157,31 @@
135 image_ref.update(values)
136 image_ref.save(session=session)
137
138- for key, value in properties.iteritems():
139- prop_values = {'image_id': image_ref.id,
140- 'key': key,
141- 'value': value}
142- image_property_create(_context, prop_values)
143+ _set_properties_for_image(_context, image_ref, properties, session)
144
145 return image_get(_context, image_ref.id)
146+
147+
148+def _set_properties_for_image(_context, image_ref, properties, session=None):
149+ """
150+ Create or update a set of image_properties for a given image
151+
152+ :param _context: Request context
153+ :param image_ref: An Image object
154+ :param properties: A dict of properties to set
155+ :param session: A SQLAlchemy session to use (if present)
156+ """
157+ orig_properties = {}
158+ for prop_ref in image_ref.properties:
159+ orig_properties[prop_ref.key] = prop_ref
160+
161+ for key, value in properties.iteritems():
162+ prop_values = {'image_id': image_ref.id,
163+ 'key': key,
164+ 'value': value}
165+ if key in orig_properties:
166+ prop_ref = orig_properties[key]
167+ image_property_update(_context, prop_ref, prop_values,
168+ session=session)
169+ else:
170+ image_property_create(_context, prop_values, session=session)
171
172=== modified file 'glance/util.py'
173--- glance/util.py 2011-01-22 19:45:11 +0000
174+++ glance/util.py 2011-01-22 19:45:11 +0000
175@@ -76,8 +76,9 @@
176 for key, value in headers:
177 key = str(key.lower())
178 if key.startswith('x-image-meta-property-'):
179- properties[key[len('x-image-meta-property-'):]] = value
180- if key.startswith('x-image-meta-'):
181+ field_name = key[len('x-image-meta-property-'):].replace('-', '_')
182+ properties[field_name] = value
183+ elif key.startswith('x-image-meta-'):
184 field_name = key[len('x-image-meta-'):].replace('-', '_')
185 result[field_name] = value
186 result['properties'] = properties
187
188=== modified file 'setup.py'
189--- setup.py 2011-01-21 18:00:15 +0000
190+++ setup.py 2011-01-22 19:45:11 +0000
191@@ -71,4 +71,5 @@
192 ],
193 install_requires=[], # removed for better compat
194 scripts=['bin/glance-api',
195- 'bin/glance-registry'])
196+ 'bin/glance-registry',
197+ 'bin/glance-upload'])
198
199=== removed file 'tests/test_data.py'
200--- tests/test_data.py 2011-01-04 22:00:37 +0000
201+++ tests/test_data.py 1970-01-01 00:00:00 +0000
202@@ -1,47 +0,0 @@
203-# vim: tabstop=4 shiftwidth=4 softtabstop=4
204-
205-# Copyright 2010 OpenStack, LLC
206-# All Rights Reserved.
207-#
208-# Licensed under the Apache License, Version 2.0 (the "License"); you may
209-# not use this file except in compliance with the License. You may obtain
210-# a copy of the License at
211-#
212-# http://www.apache.org/licenses/LICENSE-2.0
213-#
214-# Unless required by applicable law or agreed to in writing, software
215-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
216-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
217-# License for the specific language governing permissions and limitations
218-# under the License.
219-
220-from glance.registry import db
221-
222-
223-def make_swift_image():
224- """Create a real image record """
225-
226- # TODO(sirp): Create a testing account, and define gflags for
227- # test_swift_username and test_swift_api_key
228- USERNAME = "your user name here" # fill these out for testing
229- API_KEY = "your api key here"
230- #IMAGE_CHUNKS = [("filename", 123)] # filename, size in bytes
231- IMAGE_CHUNKS = [("your test chunk here", 12345)]
232-
233- image = db.image_create(
234- None,
235- dict(name="testsnap",
236- state="available",
237- public=True,
238- image_type="raw"))
239-
240- for obj, size in IMAGE_CHUNKS:
241- location = (
242- "swift://%s:%s@auth.api.rackspacecloud.com/v1.0/cloudservers/%s"
243- ) % (USERNAME, API_KEY, obj)
244-
245- db.image_file_create(None,
246- dict(image_id=image.id, location=location, size=size))
247-
248-if __name__ == "__main__":
249- make_swift_image() # NOTE: uncomment if you have a username and api_key

Subscribers

People subscribed via source and target branches