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

Proposed by Rick Harris
Status: Merged
Approved by: Jay Pipes
Approved revision: 29
Merged at revision: 28
Proposed branch: lp:~rconradharris/glance/use_datetime_obj
Merge into: lp:~hudson-openstack/glance/trunk
Diff against target: 394 lines (+68/-77)
11 files modified
glance/client.py (+1/-1)
glance/common/db/__init__.py (+0/-1)
glance/common/wsgi.py (+5/-5)
glance/registry/__init__.py (+1/-1)
glance/registry/db/sqlalchemy/api.py (+37/-33)
glance/registry/db/sqlalchemy/models.py (+3/-17)
glance/registry/server.py (+7/-8)
glance/server.py (+10/-7)
glance/store/filesystem.py (+2/-2)
glance/store/http.py (+1/-1)
glance/store/swift.py (+1/-1)
To merge this branch: bzr merge lp:~rconradharris/glance/use_datetime_obj
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Review via email: mp+44493@code.launchpad.net

Description of the change

Converts timestamp attributes to datetime objects before persisting.

Refactors image_update and image_create to use the same basic code.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

Hi!

Good stuff, Rick :) Couple quick comments:

* Please add a test case for this... easy enough to change the stub_out_db_api stuff to have (one or more) of the FIXTURES have a string datetime instead of datetime.datetime.utcnow()...
* Please pep8 check:
** No spaces before docstring comments so """ Used internally by image_create and image_update """
should be """Used internally by image_create and image_update""" or
"""
Used internally by image_create and image_update
"""
** No newlines at end of file
** Two newlines between each module-level object and also after import statements

Cheers!
jay

review: Needs Fixing
29. By Rick Harris

Adding __protected_attributes__, some PEP8 cleanups

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

very nice.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'glance/client.py'
2--- glance/client.py 2010-12-21 05:30:04 +0000
3+++ glance/client.py 2010-12-23 19:24:08 +0000
4@@ -255,7 +255,7 @@
5 headers['content-type'] = 'application/octet-stream'
6 else:
7 body = None
8-
9+
10 res = self.do_request("POST", "/images", body, headers)
11 data = json.loads(res.read())
12 return data['image']['id']
13
14=== modified file 'glance/common/db/__init__.py'
15--- glance/common/db/__init__.py 2010-10-01 19:51:29 +0000
16+++ glance/common/db/__init__.py 2010-12-23 19:24:08 +0000
17@@ -19,4 +19,3 @@
18 """
19 DB abstraction for Nova and Glance
20 """
21-
22
23=== modified file 'glance/common/wsgi.py'
24--- glance/common/wsgi.py 2010-12-18 18:00:21 +0000
25+++ glance/common/wsgi.py 2010-12-23 19:24:08 +0000
26@@ -21,6 +21,7 @@
27 Utility methods for working with WSGI servers
28 """
29
30+import json
31 import logging
32 import sys
33 import datetime
34@@ -115,11 +116,11 @@
35 behavior.
36 """
37
38- def __init__(self, application): # pylint: disable-msg=W0231
39+ def __init__(self, application): # pylint: disable-msg=W0231
40 self.application = application
41
42 @webob.dec.wsgify
43- def __call__(self, req): # pylint: disable-msg=W0221
44+ def __call__(self, req): # pylint: disable-msg=W0221
45 """Override to implement middleware behavior."""
46 return self.application
47
48@@ -237,7 +238,7 @@
49 arg_dict['req'] = req
50 result = method(**arg_dict)
51 if type(result) is dict:
52- return self._serialize(result, req)
53+ return self._serialize(result, req)
54 else:
55 return result
56
57@@ -282,7 +283,6 @@
58 return self._methods.get(mimetype, repr)(data)
59
60 def _to_json(self, data):
61- import json
62 def sanitizer(obj):
63 if isinstance(obj, datetime.datetime):
64 return obj.isoformat()
65@@ -320,7 +320,7 @@
66 else:
67 node = self._to_xml_node(doc, metadata, k, v)
68 result.appendChild(node)
69- else: # atom
70+ else: # atom
71 node = doc.createTextNode(str(data))
72 result.appendChild(node)
73 return result
74
75=== modified file 'glance/registry/__init__.py'
76--- glance/registry/__init__.py 2010-12-14 15:10:54 +0000
77+++ glance/registry/__init__.py 2010-12-23 19:24:08 +0000
78@@ -17,7 +17,7 @@
79 # under the License.
80
81 """
82-Registry API
83+Registry API
84 """
85
86 from glance.registry import client
87
88=== modified file 'glance/registry/db/sqlalchemy/api.py'
89--- glance/registry/db/sqlalchemy/api.py 2010-12-21 15:07:16 +0000
90+++ glance/registry/db/sqlalchemy/api.py 2010-12-23 19:24:08 +0000
91@@ -52,19 +52,7 @@
92
93
94 def image_create(_context, values):
95- values['size'] = int(values['size'])
96- values['is_public'] = bool(values.get('is_public', False))
97- properties = values.pop('properties', {})
98-
99- image_ref = models.Image()
100- image_ref.update(values)
101- image_ref.save()
102-
103- for key, value in properties.iteritems():
104- prop_values = {'image_id': image_ref.id, 'key': key, 'value': value}
105- image_property_create(_context, prop_values)
106-
107- return image_get(_context, image_ref.id)
108+ return _image_update(_context, values, None)
109
110
111 def image_destroy(_context, image_id):
112@@ -109,13 +97,47 @@
113
114
115 def image_update(_context, image_id, values):
116+ return _image_update(_context, values, image_id)
117+
118+
119+###################
120+
121+
122+def image_property_create(_context, values):
123+ _drop_protected_attrs(models.Image, values)
124+ image_property_ref = models.ImageProperty()
125+ image_property_ref.update(values)
126+ image_property_ref.save()
127+ return image_property_ref
128+
129+
130+def _drop_protected_attrs(model_class, values):
131+ """Removed protected attributes from values dictionary using the models
132+ __protected_attributes__ field.
133+ """
134+ for attr in model_class.__protected_attributes__:
135+ if attr in values:
136+ del values[attr]
137+
138+
139+def _image_update(_context, values, image_id):
140+ """Used internally by image_create and image_update
141+
142+ :param image_id: If None, create the image, otherwise, find and update it
143+ """
144 session = get_session()
145 with session.begin():
146+ _drop_protected_attrs(models.Image, values)
147+
148 values['size'] = int(values['size'])
149 values['is_public'] = bool(values.get('is_public', False))
150 properties = values.pop('properties', {})
151
152- image_ref = models.Image.find(image_id, session=session)
153+ if image_id:
154+ image_ref = models.Image.find(image_id, session=session)
155+ else:
156+ image_ref = models.Image()
157+
158 image_ref.update(values)
159 image_ref.save(session=session)
160
161@@ -123,22 +145,4 @@
162 prop_values = {'image_id': image_ref.id, 'key': key, 'value': value}
163 image_property_create(_context, prop_values)
164
165-
166-###################
167-
168-
169-def image_file_create(_context, values):
170- image_file_ref = models.ImageFile()
171- image_file_ref.update(values)
172- image_file_ref.save()
173- return image_file_ref
174-
175-
176-###################
177-
178-
179-def image_property_create(_context, values):
180- image_property_ref = models.ImageProperty()
181- image_property_ref.update(values)
182- image_property_ref.save()
183- return image_property_ref
184+ return image_get(_context, image_ref.id)
185
186=== modified file 'glance/registry/db/sqlalchemy/models.py'
187--- glance/registry/db/sqlalchemy/models.py 2010-12-16 22:04:58 +0000
188+++ glance/registry/db/sqlalchemy/models.py 2010-12-23 19:24:08 +0000
189@@ -45,6 +45,9 @@
190 __table_args__ = {'mysql_engine': 'InnoDB'}
191 __table_initialized__ = False
192 __prefix__ = 'none'
193+ __protected_attributes__ = set([
194+ "created_at", "updated_at", "deleted_at", "deleted"])
195+
196 created_at = Column(DateTime, default=datetime.datetime.utcnow)
197 updated_at = Column(DateTime, onupdate=datetime.datetime.utcnow)
198 deleted_at = Column(DateTime)
199@@ -160,23 +163,6 @@
200 raise exception.Invalid("Invalid status '%s' for image." % status)
201 return status
202
203- # TODO(sirp): should these be stored as properties?
204- #user_id = Column(String(255))
205- #project_id = Column(String(255))
206- #arch = Column(String(255))
207- #default_kernel_id = Column(String(255))
208- #default_ramdisk_id = Column(String(255))
209- #
210- #@validates('default_kernel_id')
211- #def validate_kernel_id(self, key, val):
212- # if val != 'machine':
213- # assert(val is None)
214- #
215- #@validates('default_ramdisk_id')
216- #def validate_ramdisk_id(self, key, val):
217- # if val != 'machine':
218- # assert(val is None)
219-
220
221 class ImageProperty(BASE, ModelBase):
222 """Represents an image properties in the datastore"""
223
224=== modified file 'glance/registry/server.py'
225--- glance/registry/server.py 2010-12-18 18:00:21 +0000
226+++ glance/registry/server.py 2010-12-23 19:24:08 +0000
227@@ -28,12 +28,11 @@
228
229
230 class ImageController(wsgi.Controller):
231-
232 """Image Controller """
233-
234+
235 def index(self, req):
236 """Return basic information for all public, non-deleted images
237-
238+
239 :param req: the Request object coming from the wsgi layer
240 :retval a mapping of the following form::
241
242@@ -42,7 +41,7 @@
243 Where image_list is a sequence of mappings::
244
245 {'id': image_id, 'name': image_name}
246-
247+
248 """
249 images = db.image_get_all_public(None)
250 image_dicts = [dict(id=i['id'], name=i['name']) for i in images]
251@@ -50,7 +49,7 @@
252
253 def detail(self, req):
254 """Return detailed information for all public, non-deleted images
255-
256+
257 :param req: the Request object coming from the wsgi layer
258 :retval a mapping of the following form::
259
260@@ -58,7 +57,7 @@
261
262 Where image_list is a sequence of mappings containing
263 all image model fields.
264-
265+
266 """
267 images = db.image_get_all_public(None)
268 image_dicts = [make_image_dict(i) for i in images]
269@@ -70,7 +69,7 @@
270 image = db.image_get(None, id)
271 except exception.NotFound:
272 raise exc.HTTPNotFound()
273-
274+
275 return dict(image=make_image_dict(image))
276
277 def delete(self, req, id):
278@@ -153,7 +152,7 @@
279 Create a dict representation of an image which we can use to
280 serialize the image.
281 """
282-
283+
284 def _fetch_attrs(d, attrs):
285 return dict([(a, d[a]) for a in attrs
286 if a in d.keys()])
287
288=== modified file 'glance/server.py'
289--- glance/server.py 2010-12-21 05:30:04 +0000
290+++ glance/server.py 2010-12-23 19:24:08 +0000
291@@ -57,10 +57,10 @@
292
293 """
294 Main WSGI application controller for Glance.
295-
296+
297 The Glance API is a RESTful web service for image data. The API
298 is as follows::
299-
300+
301 GET /images -- Returns a set of brief metadata about images
302 GET /images/detail -- Returns a set of detailed metadata about
303 images
304@@ -72,7 +72,7 @@
305 image data is immutable once stored)
306 DELETE /images/<ID> -- Delete the image with id <ID>
307 """
308-
309+
310 def index(self, req):
311 """
312 Returns the following information for all public, available images:
313@@ -81,7 +81,7 @@
314 * name -- The name of the image
315 * size -- Size of image data in bytes
316 * type -- One of 'kernel', 'ramdisk', 'raw', or 'machine'
317-
318+
319 :param request: The WSGI/Webob Request object
320 :retval The response body is a mapping of the following form::
321
322@@ -98,7 +98,7 @@
323 def detail(self, req):
324 """
325 Returns detailed information for all public, available images
326-
327+
328 :param request: The WSGI/Webob Request object
329 :retval The response body is a mapping of the following form::
330
331@@ -225,6 +225,7 @@
332 try:
333 location = store.add(image_meta['id'], req.body)
334 except exception.Duplicate, e:
335+ logging.error("Error adding image to store: %s", str(e))
336 return HTTPConflict(str(e), request=req)
337 image_meta['status'] = 'available'
338 image_meta['location'] = location
339@@ -233,8 +234,10 @@
340 return dict(image=image_meta)
341
342 except exception.Duplicate:
343- return HTTPConflict("An image with identifier %s already exists"
344- % image_meta['id'], request=req)
345+ msg = "An image with identifier %s already exists"\
346+ % image_meta['id']
347+ logging.error(msg)
348+ return HTTPConflict(msg, request=req)
349 except exception.Invalid:
350 return HTTPBadRequest()
351
352
353=== modified file 'glance/store/filesystem.py'
354--- glance/store/filesystem.py 2010-12-21 05:30:04 +0000
355+++ glance/store/filesystem.py 2010-12-23 19:24:08 +0000
356@@ -66,10 +66,10 @@
357 self.fp = None
358
359
360-
361 class FilesystemBackend(glance.store.Backend):
362 @classmethod
363- def get(cls, parsed_uri, opener=lambda p: open(p, "rb"), expected_size=None):
364+ def get(cls, parsed_uri, opener=lambda p: open(p, "rb"),
365+ expected_size=None):
366 """ Filesystem-based backend
367
368 file:///path/to/file.tar.gz.0
369
370=== modified file 'glance/store/http.py'
371--- glance/store/http.py 2010-12-14 01:56:11 +0000
372+++ glance/store/http.py 2010-12-23 19:24:08 +0000
373@@ -29,7 +29,7 @@
374 """
375
376 if conn_class:
377- pass # use the conn_class passed in
378+ pass # use the conn_class passed in
379 elif parsed_uri.scheme == "http":
380 conn_class = httplib.HTTPConnection
381 elif parsed_uri.scheme == "https":
382
383=== modified file 'glance/store/swift.py'
384--- glance/store/swift.py 2010-12-16 22:04:58 +0000
385+++ glance/store/swift.py 2010-12-23 19:24:08 +0000
386@@ -114,7 +114,7 @@
387
388 def get_connection_class(conn_class):
389 if conn_class:
390- pass # Use the provided conn_class
391+ pass # Use the provided conn_class
392 else:
393 # NOTE(sirp): A standard import statement won't work here because
394 # this file ('swift.py') is shadowing the swift module, and since

Subscribers

People subscribed via source and target branches