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