Merge lp:~soren/nova/twisted-web-s3-server into lp:~hudson-openstack/nova/trunk
- twisted-web-s3-server
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Monty Taylor |
Approved revision: | no longer in the revision history of the source branch. |
Merged at revision: | 146 |
Proposed branch: | lp:~soren/nova/twisted-web-s3-server |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
618 lines (+225/-259) 7 files modified
bin/nova-objectstore (+13/-14) debian/control (+1/-1) debian/nova-objectstore.install (+0/-1) debian/nova-objectstore.links (+0/-1) debian/nova-objectstore.nginx.conf (+0/-17) nova/flags.py (+0/-1) nova/objectstore/handler.py (+211/-224) |
To merge this branch: | bzr merge lp:~soren/nova/twisted-web-s3-server |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
justinsb (community) | Needs Fixing | ||
Vish Ishaya (community) | Approve | ||
Review via email: mp+30206@code.launchpad.net |
Commit message
Replace tornado objectstore with twisted web.
Description of the change
This branch replaces the tornado driven objectstore with a twisted web based one.
Soren Hansen (soren) wrote : | # |
Merge errors? I don't see them, sorry.
Yes, I've tested this and it works great.
Soren Hansen (soren) wrote : | # |
Oh, wow, yeah, I see them now. *blush* I'll fix.
Soren Hansen (soren) wrote : | # |
Uh... I don't know what's going on, but I seriously don't see those merge problems when I check out the code.
Vish Ishaya (vishvananda) wrote : | # |
checkout newest lp:nova and merge it into your branch and you should see the merge errors
Soren Hansen (soren) wrote : | # |
Apparantly, it's intentional. It shows the merge conflicts that would appear when merging with the target branch. Kind of cool, kind of surprising :) I've merged with the trunk and pushed again. Please re-review.
- 146. By Soren Hansen
-
Replace tornado objectstore with twisted web.
justinsb (justin-fathomdb) wrote : | # |
Bug in line 537?
images = [i for i in image.Image.all() if i.is_authorized
Shouldn't this be...
images = [i for i in image.Image.all() if i.is_authorized
justinsb (justin-fathomdb) wrote : | # |
Inherited from the old code, but this is probably the time to clean it up:
1) If the Authorization header is not set, this throws a 500 error, where it should raise exception.
Presumably the except clause needs to be broader than "except exception.Error, ex:"
2) Should we be using the 'modern' exception syntax "exception exception.Error as ex"? http://
3) There's a pretty big FIXME in there "# FIXME: check signature here!" - presumably we should open a bug for that...
justinsb (justin-fathomdb) wrote : | # |
Missing call to request.finish() in render_GET in ImageResource
+ request.finish()
return server.NOT_DONE_YET
Preview Diff
1 | === modified file 'bin/nova-objectstore' |
2 | --- bin/nova-objectstore 2010-07-15 23:13:48 +0000 |
3 | +++ bin/nova-objectstore 2010-07-18 18:28:46 +0000 |
4 | @@ -18,33 +18,32 @@ |
5 | # under the License. |
6 | |
7 | """ |
8 | - Tornado daemon for nova objectstore. Supports S3 API. |
9 | + Twisted daemon for nova objectstore. Supports S3 API. |
10 | """ |
11 | |
12 | import logging |
13 | -from tornado import httpserver |
14 | -from tornado import ioloop |
15 | |
16 | from nova import flags |
17 | -from nova import server |
18 | from nova import utils |
19 | -from nova.auth import users |
20 | +from nova import twistd |
21 | from nova.objectstore import handler |
22 | |
23 | |
24 | FLAGS = flags.FLAGS |
25 | |
26 | |
27 | -def main(argv): |
28 | +def main(): |
29 | # FIXME: if this log statement isn't here, no logging |
30 | # appears from other files and app won't start daemonized |
31 | - logging.debug('Started HTTP server on %s' % (FLAGS.s3_internal_port)) |
32 | - app = handler.Application(users.UserManager()) |
33 | - server = httpserver.HTTPServer(app) |
34 | - server.listen(FLAGS.s3_internal_port) |
35 | - ioloop.IOLoop.instance().start() |
36 | - |
37 | - |
38 | + logging.debug('Started HTTP server on %s' % (FLAGS.s3_port)) |
39 | + app = handler.get_application() |
40 | + print app |
41 | + return app |
42 | + |
43 | +# NOTE(soren): Stolen from nova-compute |
44 | if __name__ == '__main__': |
45 | + twistd.serve(__file__) |
46 | + |
47 | +if __name__ == '__builtin__': |
48 | utils.default_flagfile() |
49 | - server.serve('nova-objectstore', main) |
50 | + application = main() |
51 | |
52 | === modified file 'debian/control' |
53 | --- debian/control 2010-07-15 15:52:11 +0000 |
54 | +++ debian/control 2010-07-18 18:28:46 +0000 |
55 | @@ -91,7 +91,7 @@ |
56 | |
57 | Package: nova-objectstore |
58 | Architecture: all |
59 | -Depends: nova-common (= ${binary:Version}), nginx, ${python:Depends}, ${misc:Depends} |
60 | +Depends: nova-common (= ${binary:Version}), ${python:Depends}, ${misc:Depends} |
61 | Description: Nova Cloud Computing - object store |
62 | Nova is a cloud computing fabric controller (the main part of an IaaS |
63 | system) built to match the popular AWS EC2 and S3 APIs. It is written in |
64 | |
65 | === modified file 'debian/nova-objectstore.install' |
66 | --- debian/nova-objectstore.install 2010-07-12 15:39:01 +0000 |
67 | +++ debian/nova-objectstore.install 2010-07-18 18:28:46 +0000 |
68 | @@ -1,3 +1,2 @@ |
69 | bin/nova-objectstore usr/bin |
70 | debian/nova-objectstore.conf etc/nova |
71 | -debian/nova-objectstore.nginx.conf etc/nginx/sites-available |
72 | |
73 | === removed file 'debian/nova-objectstore.links' |
74 | --- debian/nova-objectstore.links 2010-07-06 11:34:08 +0000 |
75 | +++ debian/nova-objectstore.links 1970-01-01 00:00:00 +0000 |
76 | @@ -1,1 +0,0 @@ |
77 | -/etc/nginx/sites-available/nova-objectstore.nginx.conf /etc/nginx/sites-enabled/nova-objectstore.nginx.conf |
78 | |
79 | === removed file 'debian/nova-objectstore.nginx.conf' |
80 | --- debian/nova-objectstore.nginx.conf 2010-07-06 11:34:08 +0000 |
81 | +++ debian/nova-objectstore.nginx.conf 1970-01-01 00:00:00 +0000 |
82 | @@ -1,17 +0,0 @@ |
83 | -server { |
84 | - listen 3333 default; |
85 | - server_name localhost; |
86 | - client_max_body_size 10m; |
87 | - |
88 | - access_log /var/log/nginx/localhost.access.log; |
89 | - |
90 | - location ~ /_images/.+ { |
91 | - root /var/lib/nova/images; |
92 | - rewrite ^/_images/(.*)$ /$1 break; |
93 | - } |
94 | - |
95 | - location / { |
96 | - proxy_pass http://localhost:3334/; |
97 | - } |
98 | -} |
99 | - |
100 | |
101 | === modified file 'nova/flags.py' |
102 | --- nova/flags.py 2010-07-15 16:07:40 +0000 |
103 | +++ nova/flags.py 2010-07-18 18:28:46 +0000 |
104 | @@ -37,7 +37,6 @@ |
105 | # http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39 |
106 | |
107 | DEFINE_integer('s3_port', 3333, 's3 port') |
108 | -DEFINE_integer('s3_internal_port', 3334, 's3 port') |
109 | DEFINE_string('s3_host', '127.0.0.1', 's3 host') |
110 | #DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on') |
111 | DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') |
112 | |
113 | === modified file 'nova/objectstore/handler.py' |
114 | --- nova/objectstore/handler.py 2010-07-15 23:13:48 +0000 |
115 | +++ nova/objectstore/handler.py 2010-07-18 18:28:46 +0000 |
116 | @@ -1,10 +1,11 @@ |
117 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
118 | - |
119 | -# Copyright 2010 United States Government as represented by the |
120 | -# Administrator of the National Aeronautics and Space Administration. |
121 | -# All Rights Reserved. |
122 | -# |
123 | -# Copyright 2009 Facebook |
124 | +# |
125 | +# Copyright 2010 OpenStack LLC. |
126 | +# Copyright 2010 United States Government as represented by the |
127 | +# Administrator of the National Aeronautics and Space Administration. |
128 | +# All Rights Reserved. |
129 | +# |
130 | +# Copyright 2009 Facebook |
131 | # |
132 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
133 | # not use this file except in compliance with the License. You may obtain |
134 | @@ -37,15 +38,21 @@ |
135 | """ |
136 | |
137 | import datetime |
138 | -import os |
139 | +import logging |
140 | import json |
141 | -import logging |
142 | import multiprocessing |
143 | -from tornado import escape, web |
144 | +import os |
145 | +from tornado import escape |
146 | import urllib |
147 | |
148 | +from twisted.application import internet, service |
149 | +from twisted.web.resource import Resource |
150 | +from twisted.web import server, static |
151 | + |
152 | + |
153 | from nova import exception |
154 | from nova import flags |
155 | +from nova.auth import users |
156 | from nova.endpoint import api |
157 | from nova.objectstore import bucket |
158 | from nova.objectstore import image |
159 | @@ -53,241 +60,213 @@ |
160 | |
161 | FLAGS = flags.FLAGS |
162 | |
163 | - |
164 | -def catch_nova_exceptions(target): |
165 | - # FIXME: find a way to wrap all handlers in the web.Application.__init__ ? |
166 | - def wrapper(*args, **kwargs): |
167 | - try: |
168 | - return target(*args, **kwargs) |
169 | - except exception.NotFound: |
170 | - raise web.HTTPError(404) |
171 | - except exception.NotAuthorized: |
172 | - raise web.HTTPError(403) |
173 | - |
174 | - return wrapper |
175 | - |
176 | - |
177 | -class Application(web.Application): |
178 | +def render_xml(request, value): |
179 | + assert isinstance(value, dict) and len(value) == 1 |
180 | + request.setHeader("Content-Type", "application/xml; charset=UTF-8") |
181 | + |
182 | + name = value.keys()[0] |
183 | + request.write('<?xml version="1.0" encoding="UTF-8"?>\n') |
184 | + request.write('<' + escape.utf8(name) + |
185 | + ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">') |
186 | + _render_parts(value.values()[0], request.write) |
187 | + request.write('</' + escape.utf8(name) + '>') |
188 | + request.finish() |
189 | + |
190 | +def finish(request, content=None): |
191 | + if content: |
192 | + request.write(content) |
193 | + request.finish() |
194 | + |
195 | +def _render_parts(value, write_cb): |
196 | + if isinstance(value, basestring): |
197 | + write_cb(escape.xhtml_escape(value)) |
198 | + elif isinstance(value, int) or isinstance(value, long): |
199 | + write_cb(str(value)) |
200 | + elif isinstance(value, datetime.datetime): |
201 | + write_cb(value.strftime("%Y-%m-%dT%H:%M:%S.000Z")) |
202 | + elif isinstance(value, dict): |
203 | + for name, subvalue in value.iteritems(): |
204 | + if not isinstance(subvalue, list): |
205 | + subvalue = [subvalue] |
206 | + for subsubvalue in subvalue: |
207 | + write_cb('<' + escape.utf8(name) + '>') |
208 | + _render_parts(subsubvalue, write_cb) |
209 | + write_cb('</' + escape.utf8(name) + '>') |
210 | + else: |
211 | + raise Exception("Unknown S3 value type %r", value) |
212 | + |
213 | +def get_argument(request, key, default_value): |
214 | + if key in request.args: |
215 | + return request.args[key][0] |
216 | + return default_value |
217 | + |
218 | +def get_context(request): |
219 | + try: |
220 | + # Authorization Header format: 'AWS <access>:<secret>' |
221 | + access, sep, secret = request.getHeader('Authorization').split(' ')[1].rpartition(':') |
222 | + um = users.UserManager.instance() |
223 | + print 'um %s' % um |
224 | + (user, project) = um.authenticate(access, secret, {}, request.method, request.host, request.uri, False) |
225 | + # FIXME: check signature here! |
226 | + return api.APIRequestContext(None, user, project) |
227 | + except exception.Error, ex: |
228 | + logging.debug("Authentication Failure: %s" % ex) |
229 | + raise exception.NotAuthorized |
230 | + |
231 | +class S3(Resource): |
232 | """Implementation of an S3-like storage server based on local files.""" |
233 | - def __init__(self, user_manager): |
234 | - web.Application.__init__(self, [ |
235 | - (r"/", RootHandler), |
236 | - (r"/_images/(.+)", ImageDownloadHandler), |
237 | - (r"/_images/", ImageHandler), |
238 | - (r"/([^/]+)/(.+)", ObjectHandler), |
239 | - (r"/([^/]+)/", BucketHandler), |
240 | - ]) |
241 | - self.buckets_path = os.path.abspath(FLAGS.buckets_path) |
242 | - self.images_path = os.path.abspath(FLAGS.images_path) |
243 | - |
244 | - if not os.path.exists(self.buckets_path): |
245 | - raise Exception("buckets_path does not exist") |
246 | - if not os.path.exists(self.images_path): |
247 | - raise Exception("images_path does not exist") |
248 | - self.user_manager = user_manager |
249 | - |
250 | - |
251 | -class BaseRequestHandler(web.RequestHandler): |
252 | - SUPPORTED_METHODS = ("PUT", "GET", "DELETE", "HEAD") |
253 | - |
254 | - @property |
255 | - def context(self): |
256 | - if not hasattr(self, '_context'): |
257 | - try: |
258 | - # Authorization Header format: 'AWS <access>:<secret>' |
259 | - access, sep, secret = self.request.headers['Authorization'].split(' ')[1].rpartition(':') |
260 | - (user, project) = self.application.user_manager.authenticate(access, secret, {}, self.request.method, self.request.host, self.request.path, False) |
261 | - # FIXME: check signature here! |
262 | - self._context = api.APIRequestContext(self, user, project) |
263 | - except exception.Error, ex: |
264 | - logging.debug("Authentication Failure: %s" % ex) |
265 | - raise web.HTTPError(403) |
266 | - return self._context |
267 | - |
268 | - def render_xml(self, value): |
269 | - assert isinstance(value, dict) and len(value) == 1 |
270 | - self.set_header("Content-Type", "application/xml; charset=UTF-8") |
271 | - name = value.keys()[0] |
272 | - parts = [] |
273 | - parts.append('<' + escape.utf8(name) + |
274 | - ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">') |
275 | - self._render_parts(value.values()[0], parts) |
276 | - parts.append('</' + escape.utf8(name) + '>') |
277 | - self.finish('<?xml version="1.0" encoding="UTF-8"?>\n' + |
278 | - ''.join(parts)) |
279 | - |
280 | - def _render_parts(self, value, parts=[]): |
281 | - if isinstance(value, basestring): |
282 | - parts.append(escape.xhtml_escape(value)) |
283 | - elif isinstance(value, int) or isinstance(value, long): |
284 | - parts.append(str(value)) |
285 | - elif isinstance(value, datetime.datetime): |
286 | - parts.append(value.strftime("%Y-%m-%dT%H:%M:%S.000Z")) |
287 | - elif isinstance(value, dict): |
288 | - for name, subvalue in value.iteritems(): |
289 | - if not isinstance(subvalue, list): |
290 | - subvalue = [subvalue] |
291 | - for subsubvalue in subvalue: |
292 | - parts.append('<' + escape.utf8(name) + '>') |
293 | - self._render_parts(subsubvalue, parts) |
294 | - parts.append('</' + escape.utf8(name) + '>') |
295 | + def getChild(self, name, request): |
296 | + request.context = get_context(request) |
297 | + |
298 | + if name == '': |
299 | + return self |
300 | + elif name == '_images': |
301 | + return ImageResource() |
302 | else: |
303 | - raise Exception("Unknown S3 value type %r", value) |
304 | - |
305 | - def head(self, *args, **kwargs): |
306 | - return self.get(*args, **kwargs) |
307 | - |
308 | - |
309 | -class RootHandler(BaseRequestHandler): |
310 | - def get(self): |
311 | - buckets = [b for b in bucket.Bucket.all() if b.is_authorized(self.context)] |
312 | - |
313 | - self.render_xml({"ListAllMyBucketsResult": { |
314 | + return BucketResource(name) |
315 | + |
316 | + def render_GET(self, request): |
317 | + buckets = [b for b in bucket.Bucket.all() if b.is_authorized(request.context)] |
318 | + |
319 | + render_xml(request, {"ListAllMyBucketsResult": { |
320 | "Buckets": {"Bucket": [b.metadata for b in buckets]}, |
321 | }}) |
322 | - |
323 | - |
324 | -class BucketHandler(BaseRequestHandler): |
325 | - @catch_nova_exceptions |
326 | - def get(self, bucket_name): |
327 | - logging.debug("List keys for bucket %s" % (bucket_name)) |
328 | - |
329 | - bucket_object = bucket.Bucket(bucket_name) |
330 | - |
331 | - if not bucket_object.is_authorized(self.context): |
332 | - raise web.HTTPError(403) |
333 | - |
334 | - prefix = self.get_argument("prefix", u"") |
335 | - marker = self.get_argument("marker", u"") |
336 | - max_keys = int(self.get_argument("max-keys", 1000)) |
337 | - terse = int(self.get_argument("terse", 0)) |
338 | + return server.NOT_DONE_YET |
339 | + |
340 | +class BucketResource(Resource): |
341 | + def __init__(self, name): |
342 | + Resource.__init__(self) |
343 | + self.name = name |
344 | + |
345 | + def getChild(self, name, request): |
346 | + if name == '': |
347 | + return self |
348 | + else: |
349 | + return ObjectResource(bucket.Bucket(self.name), name) |
350 | + |
351 | + def render_GET(self, request): |
352 | + logging.debug("List keys for bucket %s" % (self.name)) |
353 | + |
354 | + bucket_object = bucket.Bucket(self.name) |
355 | + |
356 | + if not bucket_object.is_authorized(request.context): |
357 | + raise exception.NotAuthorized |
358 | + |
359 | + prefix = get_argument(request, "prefix", u"") |
360 | + marker = get_argument(request, "marker", u"") |
361 | + max_keys = int(get_argument(request, "max-keys", 1000)) |
362 | + terse = int(get_argument(request, "terse", 0)) |
363 | |
364 | results = bucket_object.list_keys(prefix=prefix, marker=marker, max_keys=max_keys, terse=terse) |
365 | - self.render_xml({"ListBucketResult": results}) |
366 | - |
367 | - @catch_nova_exceptions |
368 | - def put(self, bucket_name): |
369 | - logging.debug("Creating bucket %s" % (bucket_name)) |
370 | - bucket.Bucket.create(bucket_name, self.context) |
371 | - self.finish() |
372 | - |
373 | - @catch_nova_exceptions |
374 | - def delete(self, bucket_name): |
375 | - logging.debug("Deleting bucket %s" % (bucket_name)) |
376 | - bucket_object = bucket.Bucket(bucket_name) |
377 | - |
378 | - if not bucket_object.is_authorized(self.context): |
379 | - raise web.HTTPError(403) |
380 | + render_xml(request, {"ListBucketResult": results}) |
381 | + return server.NOT_DONE_YET |
382 | + |
383 | + def render_PUT(self, request): |
384 | + logging.debug("Creating bucket %s" % (self.name)) |
385 | + try: |
386 | + print 'user is %s' % request.context |
387 | + except Exception, e: |
388 | + logging.exception(e) |
389 | + logging.debug("calling bucket.Bucket.create(%r, %r)" % (self.name, request.context)) |
390 | + bucket.Bucket.create(self.name, request.context) |
391 | + return '' |
392 | + |
393 | + def render_DELETE(self, request): |
394 | + logging.debug("Deleting bucket %s" % (self.name)) |
395 | + bucket_object = bucket.Bucket(self.name) |
396 | + |
397 | + if not bucket_object.is_authorized(request.context): |
398 | + raise exception.NotAuthorized |
399 | |
400 | bucket_object.delete() |
401 | - self.set_status(204) |
402 | - self.finish() |
403 | - |
404 | - |
405 | -class ObjectHandler(BaseRequestHandler): |
406 | - @catch_nova_exceptions |
407 | - def get(self, bucket_name, object_name): |
408 | - logging.debug("Getting object: %s / %s" % (bucket_name, object_name)) |
409 | - |
410 | - bucket_object = bucket.Bucket(bucket_name) |
411 | - |
412 | - if not bucket_object.is_authorized(self.context): |
413 | - raise web.HTTPError(403) |
414 | - |
415 | - obj = bucket_object[urllib.unquote(object_name)] |
416 | - self.set_header("Content-Type", "application/unknown") |
417 | - self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp(obj.mtime)) |
418 | - self.set_header("Etag", '"' + obj.md5 + '"') |
419 | - self.finish(obj.read()) |
420 | - |
421 | - @catch_nova_exceptions |
422 | - def put(self, bucket_name, object_name): |
423 | - logging.debug("Putting object: %s / %s" % (bucket_name, object_name)) |
424 | - bucket_object = bucket.Bucket(bucket_name) |
425 | - |
426 | - if not bucket_object.is_authorized(self.context): |
427 | - raise web.HTTPError(403) |
428 | - |
429 | - key = urllib.unquote(object_name) |
430 | - bucket_object[key] = self.request.body |
431 | - self.set_header("Etag", '"' + bucket_object[key].md5 + '"') |
432 | - self.finish() |
433 | - |
434 | - @catch_nova_exceptions |
435 | - def delete(self, bucket_name, object_name): |
436 | - logging.debug("Deleting object: %s / %s" % (bucket_name, object_name)) |
437 | - bucket_object = bucket.Bucket(bucket_name) |
438 | - |
439 | - if not bucket_object.is_authorized(self.context): |
440 | - raise web.HTTPError(403) |
441 | - |
442 | - del bucket_object[urllib.unquote(object_name)] |
443 | - self.set_status(204) |
444 | - self.finish() |
445 | - |
446 | - |
447 | -class ImageDownloadHandler(BaseRequestHandler): |
448 | - SUPPORTED_METHODS = ("GET", ) |
449 | - |
450 | - @catch_nova_exceptions |
451 | - def get(self, image_id): |
452 | - """ send the decrypted image file |
453 | - |
454 | - streaming content through python is slow and should only be used |
455 | - in development mode. You should serve files via a web server |
456 | - in production. |
457 | - """ |
458 | - |
459 | - self.set_header("Content-Type", "application/octet-stream") |
460 | - |
461 | - READ_SIZE = 64*1024 |
462 | - |
463 | - img = image.Image(image_id) |
464 | - with open(img.image_path, 'rb') as fp: |
465 | - s = fp.read(READ_SIZE) |
466 | - while s: |
467 | - self.write(s) |
468 | - s = fp.read(READ_SIZE) |
469 | - |
470 | - self.finish() |
471 | - |
472 | -class ImageHandler(BaseRequestHandler): |
473 | - SUPPORTED_METHODS = ("POST", "PUT", "GET", "DELETE") |
474 | - |
475 | - @catch_nova_exceptions |
476 | - def get(self): |
477 | + request.setResponseCode(204) |
478 | + return '' |
479 | + |
480 | + |
481 | +class ObjectResource(Resource): |
482 | + def __init__(self, bucket, name): |
483 | + Resource.__init__(self) |
484 | + self.bucket = bucket |
485 | + self.name = name |
486 | + |
487 | + def render_GET(self, request): |
488 | + logging.debug("Getting object: %s / %s" % (self.bucket.name, self.name)) |
489 | + |
490 | + if not self.bucket.is_authorized(request.context): |
491 | + raise exception.NotAuthorized |
492 | + |
493 | + obj = self.bucket[urllib.unquote(self.name)] |
494 | + request.setHeader("Content-Type", "application/unknown") |
495 | + request.setHeader("Last-Modified", datetime.datetime.utcfromtimestamp(obj.mtime)) |
496 | + request.setHeader("Etag", '"' + obj.md5 + '"') |
497 | + return static.File(obj.path).render_GET(request) |
498 | + |
499 | + def render_PUT(self, request): |
500 | + logging.debug("Putting object: %s / %s" % (self.bucket.name, self.name)) |
501 | + |
502 | + if not self.bucket.is_authorized(request.context): |
503 | + raise exception.NotAuthorized |
504 | + |
505 | + key = urllib.unquote(self.name) |
506 | + request.content.seek(0, 0) |
507 | + self.bucket[key] = request.content.read() |
508 | + request.setHeader("Etag", '"' + self.bucket[key].md5 + '"') |
509 | + finish(request) |
510 | + return server.NOT_DONE_YET |
511 | + |
512 | + def render_DELETE(self, request): |
513 | + logging.debug("Deleting object: %s / %s" % (self.bucket.name, self.name)) |
514 | + |
515 | + if not self.bucket.is_authorized(request.context): |
516 | + raise exception.NotAuthorized |
517 | + |
518 | + del self.bucket[urllib.unquote(self.name)] |
519 | + request.setResponseCode(204) |
520 | + return '' |
521 | + |
522 | +class ImageResource(Resource): |
523 | + isLeaf = True |
524 | + |
525 | + def getChild(self, name, request): |
526 | + if name == '': |
527 | + return self |
528 | + else: |
529 | + request.setHeader("Content-Type", "application/octet-stream") |
530 | + img = image.Image(name) |
531 | + return static.File(img.image_path) |
532 | + |
533 | + def render_GET(self, request): |
534 | """ returns a json listing of all images |
535 | that a user has permissions to see """ |
536 | |
537 | images = [i for i in image.Image.all() if i.is_authorized(self.context)] |
538 | |
539 | - self.finish(json.dumps([i.metadata for i in images])) |
540 | + request.write(json.dumps([i.metadata for i in images])) |
541 | + return server.NOT_DONE_YET |
542 | |
543 | - @catch_nova_exceptions |
544 | - def put(self): |
545 | + def render_PUT(self, request): |
546 | """ create a new registered image """ |
547 | |
548 | - image_id = self.get_argument('image_id', u'') |
549 | - image_location = self.get_argument('image_location', u'') |
550 | + image_id = get_argument(request, 'image_id', u'') |
551 | + image_location = get_argument(request, 'image_location', u'') |
552 | |
553 | image_path = os.path.join(FLAGS.images_path, image_id) |
554 | if not image_path.startswith(FLAGS.images_path) or \ |
555 | os.path.exists(image_path): |
556 | - raise web.HTTPError(403) |
557 | + raise exception.NotAuthorized |
558 | |
559 | bucket_object = bucket.Bucket(image_location.split("/")[0]) |
560 | manifest = image_location[len(image_location.split('/')[0])+1:] |
561 | |
562 | - if not bucket_object.is_authorized(self.context): |
563 | - raise web.HTTPError(403) |
564 | + if not bucket_object.is_authorized(request.context): |
565 | + raise exception.NotAuthorized |
566 | |
567 | p = multiprocessing.Process(target=image.Image.register_aws_image, |
568 | - args=(image_id, image_location, self.context)) |
569 | + args=(image_id, image_location, request.context)) |
570 | p.start() |
571 | - self.finish() |
572 | + return '' |
573 | |
574 | - @catch_nova_exceptions |
575 | - def post(self): |
576 | + def render_POST(self, request): |
577 | """ update image attributes: public/private """ |
578 | |
579 | image_id = self.get_argument('image_id', u'') |
580 | @@ -295,22 +274,30 @@ |
581 | |
582 | image_object = image.Image(image_id) |
583 | |
584 | - if not image_object.is_authorized(self.context): |
585 | - raise web.HTTPError(403) |
586 | + if not image.is_authorized(request.context): |
587 | + raise exception.NotAuthorized |
588 | |
589 | image_object.set_public(operation=='add') |
590 | |
591 | - self.finish() |
592 | + return '' |
593 | |
594 | - @catch_nova_exceptions |
595 | - def delete(self): |
596 | + def render_DELETE(self, request): |
597 | """ delete a registered image """ |
598 | image_id = self.get_argument("image_id", u"") |
599 | image_object = image.Image(image_id) |
600 | |
601 | - if not image_object.is_authorized(self.context): |
602 | - raise web.HTTPError(403) |
603 | + if not image.is_authorized(request.context): |
604 | + raise exception.NotAuthorized |
605 | |
606 | image_object.delete() |
607 | |
608 | - self.set_status(204) |
609 | + request.setResponseCode(204) |
610 | + return '' |
611 | + |
612 | +def get_application(): |
613 | + root = S3() |
614 | + factory = server.Site(root) |
615 | + application = service.Application("objectstore") |
616 | + objectStoreService = internet.TCPServer(FLAGS.s3_port, factory) |
617 | + objectStoreService.setServiceParent(application) |
618 | + return application |
Looks good aside from the merge errors. Have you tested to make sure running instances still works with the twisted version?