Merge lp:~soren/nova/twisted-web-s3-server into lp:~hudson-openstack/nova/trunk

Proposed by Soren Hansen
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
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.

To post a comment you must log in.
Revision history for this message
Vish Ishaya (vishvananda) wrote :

Looks good aside from the merge errors. Have you tested to make sure running instances still works with the twisted version?

review: Needs Fixing
Revision history for this message
Soren Hansen (soren) wrote :

Merge errors? I don't see them, sorry.

Yes, I've tested this and it works great.

Revision history for this message
Soren Hansen (soren) wrote :

Oh, wow, yeah, I see them now. *blush* I'll fix.

Revision history for this message
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.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

checkout newest lp:nova and merge it into your branch and you should see the merge errors

Revision history for this message
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.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

lgtm

review: Approve
lp:~soren/nova/twisted-web-s3-server updated
146. By Soren Hansen

Replace tornado objectstore with twisted web.

Revision history for this message
justinsb (justin-fathomdb) wrote :

Bug in line 537?
images = [i for i in image.Image.all() if i.is_authorized(self.context)]

Shouldn't this be...
images = [i for i in image.Image.all() if i.is_authorized(request.context)]

review: Needs Fixing
Revision history for this message
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.NotAuthorized (401?)

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://www.python.org/dev/peps/pep-3110/

3) There's a pretty big FIXME in there "# FIXME: check signature here!" - presumably we should open a bug for that...

Revision history for this message
justinsb (justin-fathomdb) wrote :

Missing call to request.finish() in render_GET in ImageResource

        request.write(json.dumps([i.metadata for i in images]))
+ request.finish()
        return server.NOT_DONE_YET

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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