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
=== modified file 'glance/client.py'
--- glance/client.py 2010-12-21 05:30:04 +0000
+++ glance/client.py 2010-12-23 19:24:08 +0000
@@ -255,7 +255,7 @@
255 headers['content-type'] = 'application/octet-stream'255 headers['content-type'] = 'application/octet-stream'
256 else:256 else:
257 body = None257 body = None
258 258
259 res = self.do_request("POST", "/images", body, headers)259 res = self.do_request("POST", "/images", body, headers)
260 data = json.loads(res.read())260 data = json.loads(res.read())
261 return data['image']['id']261 return data['image']['id']
262262
=== modified file 'glance/common/db/__init__.py'
--- glance/common/db/__init__.py 2010-10-01 19:51:29 +0000
+++ glance/common/db/__init__.py 2010-12-23 19:24:08 +0000
@@ -19,4 +19,3 @@
19"""19"""
20DB abstraction for Nova and Glance20DB abstraction for Nova and Glance
21"""21"""
22
2322
=== modified file 'glance/common/wsgi.py'
--- glance/common/wsgi.py 2010-12-18 18:00:21 +0000
+++ glance/common/wsgi.py 2010-12-23 19:24:08 +0000
@@ -21,6 +21,7 @@
21Utility methods for working with WSGI servers21Utility methods for working with WSGI servers
22"""22"""
2323
24import json
24import logging25import logging
25import sys26import sys
26import datetime27import datetime
@@ -115,11 +116,11 @@
115 behavior.116 behavior.
116 """117 """
117118
118 def __init__(self, application): # pylint: disable-msg=W0231119 def __init__(self, application): # pylint: disable-msg=W0231
119 self.application = application120 self.application = application
120121
121 @webob.dec.wsgify122 @webob.dec.wsgify
122 def __call__(self, req): # pylint: disable-msg=W0221123 def __call__(self, req): # pylint: disable-msg=W0221
123 """Override to implement middleware behavior."""124 """Override to implement middleware behavior."""
124 return self.application125 return self.application
125126
@@ -237,7 +238,7 @@
237 arg_dict['req'] = req238 arg_dict['req'] = req
238 result = method(**arg_dict)239 result = method(**arg_dict)
239 if type(result) is dict:240 if type(result) is dict:
240 return self._serialize(result, req) 241 return self._serialize(result, req)
241 else:242 else:
242 return result243 return result
243244
@@ -282,7 +283,6 @@
282 return self._methods.get(mimetype, repr)(data)283 return self._methods.get(mimetype, repr)(data)
283284
284 def _to_json(self, data):285 def _to_json(self, data):
285 import json
286 def sanitizer(obj):286 def sanitizer(obj):
287 if isinstance(obj, datetime.datetime):287 if isinstance(obj, datetime.datetime):
288 return obj.isoformat()288 return obj.isoformat()
@@ -320,7 +320,7 @@
320 else:320 else:
321 node = self._to_xml_node(doc, metadata, k, v)321 node = self._to_xml_node(doc, metadata, k, v)
322 result.appendChild(node)322 result.appendChild(node)
323 else: # atom323 else: # atom
324 node = doc.createTextNode(str(data))324 node = doc.createTextNode(str(data))
325 result.appendChild(node)325 result.appendChild(node)
326 return result326 return result
327327
=== modified file 'glance/registry/__init__.py'
--- glance/registry/__init__.py 2010-12-14 15:10:54 +0000
+++ glance/registry/__init__.py 2010-12-23 19:24:08 +0000
@@ -17,7 +17,7 @@
17# under the License.17# under the License.
1818
19"""19"""
20Registry API 20Registry API
21"""21"""
2222
23from glance.registry import client23from glance.registry import client
2424
=== modified file 'glance/registry/db/sqlalchemy/api.py'
--- glance/registry/db/sqlalchemy/api.py 2010-12-21 15:07:16 +0000
+++ glance/registry/db/sqlalchemy/api.py 2010-12-23 19:24:08 +0000
@@ -52,19 +52,7 @@
5252
5353
54def image_create(_context, values):54def image_create(_context, values):
55 values['size'] = int(values['size'])55 return _image_update(_context, values, None)
56 values['is_public'] = bool(values.get('is_public', False))
57 properties = values.pop('properties', {})
58
59 image_ref = models.Image()
60 image_ref.update(values)
61 image_ref.save()
62
63 for key, value in properties.iteritems():
64 prop_values = {'image_id': image_ref.id, 'key': key, 'value': value}
65 image_property_create(_context, prop_values)
66
67 return image_get(_context, image_ref.id)
6856
6957
70def image_destroy(_context, image_id):58def image_destroy(_context, image_id):
@@ -109,13 +97,47 @@
10997
11098
111def image_update(_context, image_id, values):99def image_update(_context, image_id, values):
100 return _image_update(_context, values, image_id)
101
102
103###################
104
105
106def image_property_create(_context, values):
107 _drop_protected_attrs(models.Image, values)
108 image_property_ref = models.ImageProperty()
109 image_property_ref.update(values)
110 image_property_ref.save()
111 return image_property_ref
112
113
114def _drop_protected_attrs(model_class, values):
115 """Removed protected attributes from values dictionary using the models
116 __protected_attributes__ field.
117 """
118 for attr in model_class.__protected_attributes__:
119 if attr in values:
120 del values[attr]
121
122
123def _image_update(_context, values, image_id):
124 """Used internally by image_create and image_update
125
126 :param image_id: If None, create the image, otherwise, find and update it
127 """
112 session = get_session()128 session = get_session()
113 with session.begin():129 with session.begin():
130 _drop_protected_attrs(models.Image, values)
131
114 values['size'] = int(values['size'])132 values['size'] = int(values['size'])
115 values['is_public'] = bool(values.get('is_public', False))133 values['is_public'] = bool(values.get('is_public', False))
116 properties = values.pop('properties', {})134 properties = values.pop('properties', {})
117135
118 image_ref = models.Image.find(image_id, session=session)136 if image_id:
137 image_ref = models.Image.find(image_id, session=session)
138 else:
139 image_ref = models.Image()
140
119 image_ref.update(values)141 image_ref.update(values)
120 image_ref.save(session=session)142 image_ref.save(session=session)
121143
@@ -123,22 +145,4 @@
123 prop_values = {'image_id': image_ref.id, 'key': key, 'value': value}145 prop_values = {'image_id': image_ref.id, 'key': key, 'value': value}
124 image_property_create(_context, prop_values)146 image_property_create(_context, prop_values)
125147
126148 return image_get(_context, image_ref.id)
127###################
128
129
130def image_file_create(_context, values):
131 image_file_ref = models.ImageFile()
132 image_file_ref.update(values)
133 image_file_ref.save()
134 return image_file_ref
135
136
137###################
138
139
140def image_property_create(_context, values):
141 image_property_ref = models.ImageProperty()
142 image_property_ref.update(values)
143 image_property_ref.save()
144 return image_property_ref
145149
=== modified file 'glance/registry/db/sqlalchemy/models.py'
--- glance/registry/db/sqlalchemy/models.py 2010-12-16 22:04:58 +0000
+++ glance/registry/db/sqlalchemy/models.py 2010-12-23 19:24:08 +0000
@@ -45,6 +45,9 @@
45 __table_args__ = {'mysql_engine': 'InnoDB'}45 __table_args__ = {'mysql_engine': 'InnoDB'}
46 __table_initialized__ = False46 __table_initialized__ = False
47 __prefix__ = 'none'47 __prefix__ = 'none'
48 __protected_attributes__ = set([
49 "created_at", "updated_at", "deleted_at", "deleted"])
50
48 created_at = Column(DateTime, default=datetime.datetime.utcnow)51 created_at = Column(DateTime, default=datetime.datetime.utcnow)
49 updated_at = Column(DateTime, onupdate=datetime.datetime.utcnow)52 updated_at = Column(DateTime, onupdate=datetime.datetime.utcnow)
50 deleted_at = Column(DateTime)53 deleted_at = Column(DateTime)
@@ -160,23 +163,6 @@
160 raise exception.Invalid("Invalid status '%s' for image." % status)163 raise exception.Invalid("Invalid status '%s' for image." % status)
161 return status164 return status
162 165
163 # TODO(sirp): should these be stored as properties?
164 #user_id = Column(String(255))
165 #project_id = Column(String(255))
166 #arch = Column(String(255))
167 #default_kernel_id = Column(String(255))
168 #default_ramdisk_id = Column(String(255))
169 #
170 #@validates('default_kernel_id')
171 #def validate_kernel_id(self, key, val):
172 # if val != 'machine':
173 # assert(val is None)
174 #
175 #@validates('default_ramdisk_id')
176 #def validate_ramdisk_id(self, key, val):
177 # if val != 'machine':
178 # assert(val is None)
179
180166
181class ImageProperty(BASE, ModelBase):167class ImageProperty(BASE, ModelBase):
182 """Represents an image properties in the datastore"""168 """Represents an image properties in the datastore"""
183169
=== modified file 'glance/registry/server.py'
--- glance/registry/server.py 2010-12-18 18:00:21 +0000
+++ glance/registry/server.py 2010-12-23 19:24:08 +0000
@@ -28,12 +28,11 @@
2828
2929
30class ImageController(wsgi.Controller):30class ImageController(wsgi.Controller):
31
32 """Image Controller """31 """Image Controller """
33 32
34 def index(self, req):33 def index(self, req):
35 """Return basic information for all public, non-deleted images34 """Return basic information for all public, non-deleted images
36 35
37 :param req: the Request object coming from the wsgi layer36 :param req: the Request object coming from the wsgi layer
38 :retval a mapping of the following form::37 :retval a mapping of the following form::
3938
@@ -42,7 +41,7 @@
42 Where image_list is a sequence of mappings::41 Where image_list is a sequence of mappings::
4342
44 {'id': image_id, 'name': image_name}43 {'id': image_id, 'name': image_name}
45 44
46 """45 """
47 images = db.image_get_all_public(None)46 images = db.image_get_all_public(None)
48 image_dicts = [dict(id=i['id'], name=i['name']) for i in images]47 image_dicts = [dict(id=i['id'], name=i['name']) for i in images]
@@ -50,7 +49,7 @@
5049
51 def detail(self, req):50 def detail(self, req):
52 """Return detailed information for all public, non-deleted images51 """Return detailed information for all public, non-deleted images
53 52
54 :param req: the Request object coming from the wsgi layer53 :param req: the Request object coming from the wsgi layer
55 :retval a mapping of the following form::54 :retval a mapping of the following form::
5655
@@ -58,7 +57,7 @@
5857
59 Where image_list is a sequence of mappings containing58 Where image_list is a sequence of mappings containing
60 all image model fields.59 all image model fields.
61 60
62 """61 """
63 images = db.image_get_all_public(None)62 images = db.image_get_all_public(None)
64 image_dicts = [make_image_dict(i) for i in images]63 image_dicts = [make_image_dict(i) for i in images]
@@ -70,7 +69,7 @@
70 image = db.image_get(None, id)69 image = db.image_get(None, id)
71 except exception.NotFound:70 except exception.NotFound:
72 raise exc.HTTPNotFound()71 raise exc.HTTPNotFound()
73 72
74 return dict(image=make_image_dict(image))73 return dict(image=make_image_dict(image))
7574
76 def delete(self, req, id):75 def delete(self, req, id):
@@ -153,7 +152,7 @@
153 Create a dict representation of an image which we can use to152 Create a dict representation of an image which we can use to
154 serialize the image.153 serialize the image.
155 """154 """
156 155
157 def _fetch_attrs(d, attrs):156 def _fetch_attrs(d, attrs):
158 return dict([(a, d[a]) for a in attrs157 return dict([(a, d[a]) for a in attrs
159 if a in d.keys()])158 if a in d.keys()])
160159
=== modified file 'glance/server.py'
--- glance/server.py 2010-12-21 05:30:04 +0000
+++ glance/server.py 2010-12-23 19:24:08 +0000
@@ -57,10 +57,10 @@
5757
58 """58 """
59 Main WSGI application controller for Glance.59 Main WSGI application controller for Glance.
60 60
61 The Glance API is a RESTful web service for image data. The API61 The Glance API is a RESTful web service for image data. The API
62 is as follows::62 is as follows::
63 63
64 GET /images -- Returns a set of brief metadata about images64 GET /images -- Returns a set of brief metadata about images
65 GET /images/detail -- Returns a set of detailed metadata about65 GET /images/detail -- Returns a set of detailed metadata about
66 images66 images
@@ -72,7 +72,7 @@
72 image data is immutable once stored)72 image data is immutable once stored)
73 DELETE /images/<ID> -- Delete the image with id <ID>73 DELETE /images/<ID> -- Delete the image with id <ID>
74 """74 """
75 75
76 def index(self, req):76 def index(self, req):
77 """77 """
78 Returns the following information for all public, available images:78 Returns the following information for all public, available images:
@@ -81,7 +81,7 @@
81 * name -- The name of the image81 * name -- The name of the image
82 * size -- Size of image data in bytes82 * size -- Size of image data in bytes
83 * type -- One of 'kernel', 'ramdisk', 'raw', or 'machine'83 * type -- One of 'kernel', 'ramdisk', 'raw', or 'machine'
84 84
85 :param request: The WSGI/Webob Request object85 :param request: The WSGI/Webob Request object
86 :retval The response body is a mapping of the following form::86 :retval The response body is a mapping of the following form::
8787
@@ -98,7 +98,7 @@
98 def detail(self, req):98 def detail(self, req):
99 """99 """
100 Returns detailed information for all public, available images100 Returns detailed information for all public, available images
101 101
102 :param request: The WSGI/Webob Request object102 :param request: The WSGI/Webob Request object
103 :retval The response body is a mapping of the following form::103 :retval The response body is a mapping of the following form::
104104
@@ -225,6 +225,7 @@
225 try:225 try:
226 location = store.add(image_meta['id'], req.body)226 location = store.add(image_meta['id'], req.body)
227 except exception.Duplicate, e:227 except exception.Duplicate, e:
228 logging.error("Error adding image to store: %s", str(e))
228 return HTTPConflict(str(e), request=req)229 return HTTPConflict(str(e), request=req)
229 image_meta['status'] = 'available'230 image_meta['status'] = 'available'
230 image_meta['location'] = location231 image_meta['location'] = location
@@ -233,8 +234,10 @@
233 return dict(image=image_meta)234 return dict(image=image_meta)
234235
235 except exception.Duplicate:236 except exception.Duplicate:
236 return HTTPConflict("An image with identifier %s already exists"237 msg = "An image with identifier %s already exists"\
237 % image_meta['id'], request=req)238 % image_meta['id']
239 logging.error(msg)
240 return HTTPConflict(msg, request=req)
238 except exception.Invalid:241 except exception.Invalid:
239 return HTTPBadRequest()242 return HTTPBadRequest()
240243
241244
=== modified file 'glance/store/filesystem.py'
--- glance/store/filesystem.py 2010-12-21 05:30:04 +0000
+++ glance/store/filesystem.py 2010-12-23 19:24:08 +0000
@@ -66,10 +66,10 @@
66 self.fp = None66 self.fp = None
6767
6868
69
70class FilesystemBackend(glance.store.Backend):69class FilesystemBackend(glance.store.Backend):
71 @classmethod70 @classmethod
72 def get(cls, parsed_uri, opener=lambda p: open(p, "rb"), expected_size=None):71 def get(cls, parsed_uri, opener=lambda p: open(p, "rb"),
72 expected_size=None):
73 """ Filesystem-based backend73 """ Filesystem-based backend
7474
75 file:///path/to/file.tar.gz.075 file:///path/to/file.tar.gz.0
7676
=== modified file 'glance/store/http.py'
--- glance/store/http.py 2010-12-14 01:56:11 +0000
+++ glance/store/http.py 2010-12-23 19:24:08 +0000
@@ -29,7 +29,7 @@
29 """29 """
3030
31 if conn_class:31 if conn_class:
32 pass # use the conn_class passed in32 pass # use the conn_class passed in
33 elif parsed_uri.scheme == "http":33 elif parsed_uri.scheme == "http":
34 conn_class = httplib.HTTPConnection34 conn_class = httplib.HTTPConnection
35 elif parsed_uri.scheme == "https":35 elif parsed_uri.scheme == "https":
3636
=== modified file 'glance/store/swift.py'
--- glance/store/swift.py 2010-12-16 22:04:58 +0000
+++ glance/store/swift.py 2010-12-23 19:24:08 +0000
@@ -114,7 +114,7 @@
114114
115def get_connection_class(conn_class):115def get_connection_class(conn_class):
116 if conn_class:116 if conn_class:
117 pass # Use the provided conn_class117 pass # Use the provided conn_class
118 else:118 else:
119 # NOTE(sirp): A standard import statement won't work here because119 # NOTE(sirp): A standard import statement won't work here because
120 # this file ('swift.py') is shadowing the swift module, and since120 # this file ('swift.py') is shadowing the swift module, and since

Subscribers

People subscribed via source and target branches