Merge lp:~gundlach/nova/controllers-in-api into lp:~hudson-openstack/nova/trunk
- controllers-in-api
- Merge into trunk
Proposed by
Michael Gundlach
Status: | Merged |
---|---|
Approved by: | Michael Gundlach |
Approved revision: | 314 |
Merged at revision: | 292 |
Proposed branch: | lp:~gundlach/nova/controllers-in-api |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
852 lines (+300/-148) 16 files modified
bin/nova-api (+7/-3) bin/nova-manage (+1/-2) doc/source/auth.rst (+0/-8) nova/api/__init__.py (+65/-4) nova/api/cloudpipe/__init__.py (+35/-25) nova/api/ec2/__init__.py (+7/-3) nova/api/ec2/apirequest.py (+1/-3) nova/api/ec2/metadatarequesthandler.py (+73/-0) nova/auth/manager.py (+2/-2) nova/cloudpipe/pipelib.py (+4/-2) nova/rpc.py (+28/-23) nova/tests/access_unittest.py (+45/-66) nova/tests/api/__init__.py (+26/-4) nova/tests/api_unittest.py (+5/-0) nova/tests/cloud_unittest.py (+0/-1) run_tests.py (+1/-2) |
To merge this branch: | bzr merge lp:~gundlach/nova/controllers-in-api |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Day (community) | Approve | ||
Review via email: mp+36503@code.launchpad.net |
Commit message
Description of the change
Put EC2 API -> eventlet back into trunk, fixing the bits that I missed when I put it into trunk on 9/21.
Note that some of this got into trunk via r291 accidentally because r291 was a branch based off of the trunk that was reverted on 9/22.
To post a comment you must log in.
- 314. By Michael Gundlach
-
Merge vishy's patch: admin-user-
not-admin- role
Revision history for this message
Vish Ishaya (vishvananda) wrote : | # |
lgtm.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file 'bin/nova-api' |
2 | --- bin/nova-api 2010-09-08 08:45:39 +0000 |
3 | +++ bin/nova-api 1970-01-01 00:00:00 +0000 |
4 | @@ -1,64 +0,0 @@ |
5 | -#!/usr/bin/env python |
6 | -# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
7 | - |
8 | -# Copyright 2010 United States Government as represented by the |
9 | -# Administrator of the National Aeronautics and Space Administration. |
10 | -# All Rights Reserved. |
11 | -# |
12 | -# Licensed under the Apache License, Version 2.0 (the "License"); you may |
13 | -# not use this file except in compliance with the License. You may obtain |
14 | -# a copy of the License at |
15 | -# |
16 | -# http://www.apache.org/licenses/LICENSE-2.0 |
17 | -# |
18 | -# Unless required by applicable law or agreed to in writing, software |
19 | -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
20 | -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
21 | -# License for the specific language governing permissions and limitations |
22 | -# under the License. |
23 | - |
24 | -""" |
25 | -Tornado daemon for the main API endpoint. |
26 | -""" |
27 | - |
28 | -import logging |
29 | -import os |
30 | -import sys |
31 | -from tornado import httpserver |
32 | -from tornado import ioloop |
33 | - |
34 | -# If ../nova/__init__.py exists, add ../ to Python search path, so that |
35 | -# it will override what happens to be installed in /usr/(local/)lib/python... |
36 | -possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), |
37 | - os.pardir, |
38 | - os.pardir)) |
39 | -if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): |
40 | - sys.path.insert(0, possible_topdir) |
41 | - |
42 | -from nova import flags |
43 | -from nova import server |
44 | -from nova import utils |
45 | -from nova.endpoint import admin |
46 | -from nova.endpoint import api |
47 | -from nova.endpoint import cloud |
48 | - |
49 | -FLAGS = flags.FLAGS |
50 | - |
51 | - |
52 | -def main(_argv): |
53 | - """Load the controllers and start the tornado I/O loop.""" |
54 | - controllers = { |
55 | - 'Cloud': cloud.CloudController(), |
56 | - 'Admin': admin.AdminController()} |
57 | - _app = api.APIServerApplication(controllers) |
58 | - |
59 | - io_inst = ioloop.IOLoop.instance() |
60 | - http_server = httpserver.HTTPServer(_app) |
61 | - http_server.listen(FLAGS.cc_port) |
62 | - logging.debug('Started HTTP server on %s', FLAGS.cc_port) |
63 | - io_inst.start() |
64 | - |
65 | - |
66 | -if __name__ == '__main__': |
67 | - utils.default_flagfile() |
68 | - server.serve('nova-api', main) |
69 | |
70 | === renamed file 'bin/nova-api-new' => 'bin/nova-api' |
71 | --- bin/nova-api-new 2010-09-08 08:45:39 +0000 |
72 | +++ bin/nova-api 2010-09-23 21:08:44 +0000 |
73 | @@ -32,14 +32,18 @@ |
74 | if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): |
75 | sys.path.insert(0, possible_topdir) |
76 | |
77 | -from nova import api |
78 | from nova import flags |
79 | from nova import utils |
80 | -from nova import wsgi |
81 | +from nova import server |
82 | |
83 | FLAGS = flags.FLAGS |
84 | flags.DEFINE_integer('api_port', 8773, 'API port') |
85 | |
86 | +def main(_args): |
87 | + from nova import api |
88 | + from nova import wsgi |
89 | + wsgi.run_server(api.API(), FLAGS.api_port) |
90 | + |
91 | if __name__ == '__main__': |
92 | utils.default_flagfile() |
93 | - wsgi.run_server(api.API(), FLAGS.api_port) |
94 | + server.serve('nova-api', main) |
95 | |
96 | === modified file 'bin/nova-manage' |
97 | --- bin/nova-manage 2010-09-13 08:15:35 +0000 |
98 | +++ bin/nova-manage 2010-09-23 21:08:44 +0000 |
99 | @@ -73,7 +73,6 @@ |
100 | from nova import utils |
101 | from nova.auth import manager |
102 | from nova.cloudpipe import pipelib |
103 | -from nova.endpoint import cloud |
104 | |
105 | |
106 | FLAGS = flags.FLAGS |
107 | @@ -84,7 +83,7 @@ |
108 | |
109 | def __init__(self): |
110 | self.manager = manager.AuthManager() |
111 | - self.pipe = pipelib.CloudPipe(cloud.CloudController()) |
112 | + self.pipe = pipelib.CloudPipe() |
113 | |
114 | def list(self): |
115 | """Print a listing of the VPNs for all projects.""" |
116 | |
117 | === modified file 'doc/source/auth.rst' |
118 | --- doc/source/auth.rst 2010-07-25 01:06:22 +0000 |
119 | +++ doc/source/auth.rst 2010-09-23 21:08:44 +0000 |
120 | @@ -172,14 +172,6 @@ |
121 | |
122 | |
123 | |
124 | -The :mod:`rbac` Module |
125 | --------------------------- |
126 | - |
127 | -.. automodule:: nova.auth.rbac |
128 | - :members: |
129 | - :undoc-members: |
130 | - :show-inheritance: |
131 | - |
132 | The :mod:`signer` Module |
133 | ------------------------ |
134 | |
135 | |
136 | === modified file 'nova/api/__init__.py' |
137 | --- nova/api/__init__.py 2010-09-20 22:30:35 +0000 |
138 | +++ nova/api/__init__.py 2010-09-23 21:08:44 +0000 |
139 | @@ -23,23 +23,65 @@ |
140 | import routes |
141 | import webob.dec |
142 | |
143 | +from nova import flags |
144 | from nova import wsgi |
145 | +from nova.api import cloudpipe |
146 | from nova.api import ec2 |
147 | from nova.api import rackspace |
148 | +from nova.api.ec2 import metadatarequesthandler |
149 | + |
150 | + |
151 | +flags.DEFINE_string('rsapi_subdomain', 'rs', |
152 | + 'subdomain running the RS API') |
153 | +flags.DEFINE_string('ec2api_subdomain', 'ec2', |
154 | + 'subdomain running the EC2 API') |
155 | +flags.DEFINE_string('FAKE_subdomain', None, |
156 | + 'set to rs or ec2 to fake the subdomain of the host for testing') |
157 | +FLAGS = flags.FLAGS |
158 | |
159 | |
160 | class API(wsgi.Router): |
161 | """Routes top-level requests to the appropriate controller.""" |
162 | |
163 | def __init__(self): |
164 | + rsdomain = {'sub_domain': [FLAGS.rsapi_subdomain]} |
165 | + ec2domain = {'sub_domain': [FLAGS.ec2api_subdomain]} |
166 | + # If someone wants to pretend they're hitting the RS subdomain |
167 | + # on their local box, they can set FAKE_subdomain to 'rs', which |
168 | + # removes subdomain restrictions from the RS routes below. |
169 | + if FLAGS.FAKE_subdomain == 'rs': |
170 | + rsdomain = {} |
171 | + elif FLAGS.FAKE_subdomain == 'ec2': |
172 | + ec2domain = {} |
173 | mapper = routes.Mapper() |
174 | - mapper.connect("/", controller=self.versions) |
175 | - mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API()) |
176 | - mapper.connect("/services/{path_info:.*}", controller=ec2.API()) |
177 | + mapper.sub_domains = True |
178 | + mapper.connect("/", controller=self.rsapi_versions, |
179 | + conditions=rsdomain) |
180 | + mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API(), |
181 | + conditions=rsdomain) |
182 | + |
183 | + mapper.connect("/", controller=self.ec2api_versions, |
184 | + conditions=ec2domain) |
185 | + mapper.connect("/services/{path_info:.*}", controller=ec2.API(), |
186 | + conditions=ec2domain) |
187 | + mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API()) |
188 | + mrh = metadatarequesthandler.MetadataRequestHandler() |
189 | + for s in ['/latest', |
190 | + '/2009-04-04', |
191 | + '/2008-09-01', |
192 | + '/2008-02-01', |
193 | + '/2007-12-15', |
194 | + '/2007-10-10', |
195 | + '/2007-08-29', |
196 | + '/2007-03-01', |
197 | + '/2007-01-19', |
198 | + '/1.0']: |
199 | + mapper.connect('%s/{path_info:.*}' % s, controller=mrh, |
200 | + conditions=ec2domain) |
201 | super(API, self).__init__(mapper) |
202 | |
203 | @webob.dec.wsgify |
204 | - def versions(self, req): |
205 | + def rsapi_versions(self, req): |
206 | """Respond to a request for all OpenStack API versions.""" |
207 | response = { |
208 | "versions": [ |
209 | @@ -48,3 +90,22 @@ |
210 | "application/xml": { |
211 | "attributes": dict(version=["status", "id"])}} |
212 | return wsgi.Serializer(req.environ, metadata).to_content_type(response) |
213 | + |
214 | + @webob.dec.wsgify |
215 | + def ec2api_versions(self, req): |
216 | + """Respond to a request for all EC2 versions.""" |
217 | + # available api versions |
218 | + versions = [ |
219 | + '1.0', |
220 | + '2007-01-19', |
221 | + '2007-03-01', |
222 | + '2007-08-29', |
223 | + '2007-10-10', |
224 | + '2007-12-15', |
225 | + '2008-02-01', |
226 | + '2008-09-01', |
227 | + '2009-04-04', |
228 | + ] |
229 | + return ''.join('%s\n' % v for v in versions) |
230 | + |
231 | + |
232 | |
233 | === added directory 'nova/api/cloudpipe' |
234 | === renamed file 'nova/cloudpipe/api.py' => 'nova/api/cloudpipe/__init__.py' |
235 | --- nova/cloudpipe/api.py 2010-08-16 12:16:21 +0000 |
236 | +++ nova/api/cloudpipe/__init__.py 2010-09-23 21:08:44 +0000 |
237 | @@ -17,43 +17,53 @@ |
238 | # under the License. |
239 | |
240 | """ |
241 | -Tornado REST API Request Handlers for CloudPipe |
242 | +REST API Request Handlers for CloudPipe |
243 | """ |
244 | |
245 | import logging |
246 | import urllib |
247 | - |
248 | -import tornado.web |
249 | +import webob |
250 | +import webob.dec |
251 | +import webob.exc |
252 | |
253 | from nova import crypto |
254 | +from nova import wsgi |
255 | from nova.auth import manager |
256 | +from nova.api.ec2 import cloud |
257 | |
258 | |
259 | _log = logging.getLogger("api") |
260 | _log.setLevel(logging.DEBUG) |
261 | |
262 | |
263 | -class CloudPipeRequestHandler(tornado.web.RequestHandler): |
264 | - def get(self, path): |
265 | - path = self.request.path |
266 | - _log.debug( "Cloudpipe path is %s" % path) |
267 | - if path.endswith("/getca/"): |
268 | - self.send_root_ca() |
269 | - self.finish() |
270 | +class API(wsgi.Application): |
271 | + |
272 | + def __init__(self): |
273 | + self.controller = cloud.CloudController() |
274 | + |
275 | + @webob.dec.wsgify |
276 | + def __call__(self, req): |
277 | + if req.method == 'POST': |
278 | + return self.sign_csr(req) |
279 | + _log.debug("Cloudpipe path is %s" % req.path_info) |
280 | + if req.path_info.endswith("/getca/"): |
281 | + return self.send_root_ca(req) |
282 | + return webob.exc.HTTPNotFound() |
283 | |
284 | def get_project_id_from_ip(self, ip): |
285 | - cc = self.application.controllers['Cloud'] |
286 | - instance = cc.get_instance_by_ip(ip) |
287 | - instance['project_id'] |
288 | - |
289 | - def send_root_ca(self): |
290 | - _log.debug( "Getting root ca") |
291 | - project_id = self.get_project_id_from_ip(self.request.remote_ip) |
292 | - self.set_header("Content-Type", "text/plain") |
293 | - self.write(crypto.fetch_ca(project_id)) |
294 | - |
295 | - def post(self, *args, **kwargs): |
296 | - project_id = self.get_project_id_from_ip(self.request.remote_ip) |
297 | - cert = self.get_argument('cert', '') |
298 | - self.write(crypto.sign_csr(urllib.unquote(cert), project_id)) |
299 | - self.finish() |
300 | + # TODO(eday): This was removed with the ORM branch, fix! |
301 | + instance = self.controller.get_instance_by_ip(ip) |
302 | + return instance['project_id'] |
303 | + |
304 | + def send_root_ca(self, req): |
305 | + _log.debug("Getting root ca") |
306 | + project_id = self.get_project_id_from_ip(req.remote_addr) |
307 | + res = webob.Response() |
308 | + res.headers["Content-Type"] = "text/plain" |
309 | + res.body = crypto.fetch_ca(project_id) |
310 | + return res |
311 | + |
312 | + def sign_csr(self, req): |
313 | + project_id = self.get_project_id_from_ip(req.remote_addr) |
314 | + cert = self.str_params['cert'] |
315 | + return crypto.sign_csr(urllib.unquote(cert), project_id) |
316 | |
317 | === modified file 'nova/api/ec2/__init__.py' |
318 | --- nova/api/ec2/__init__.py 2010-09-20 19:36:45 +0000 |
319 | +++ nova/api/ec2/__init__.py 2010-09-23 21:08:44 +0000 |
320 | @@ -25,6 +25,7 @@ |
321 | import webob.exc |
322 | |
323 | from nova import exception |
324 | +from nova import flags |
325 | from nova import wsgi |
326 | from nova.api.ec2 import apirequest |
327 | from nova.api.ec2 import context |
328 | @@ -33,6 +34,7 @@ |
329 | from nova.auth import manager |
330 | |
331 | |
332 | +FLAGS = flags.FLAGS |
333 | _log = logging.getLogger("api") |
334 | _log.setLevel(logging.DEBUG) |
335 | |
336 | @@ -164,8 +166,8 @@ |
337 | 'ModifyImageAttribute': ['projectmanager', 'sysadmin'], |
338 | }, |
339 | 'AdminController': { |
340 | - # All actions have the same permission: [] (the default) |
341 | - # admins will be allowed to run them |
342 | + # All actions have the same permission: ['none'] (the default) |
343 | + # superusers will be allowed to run them |
344 | # all others will get HTTPUnauthorized. |
345 | }, |
346 | } |
347 | @@ -175,7 +177,7 @@ |
348 | context = req.environ['ec2.context'] |
349 | controller_name = req.environ['ec2.controller'].__class__.__name__ |
350 | action = req.environ['ec2.action'] |
351 | - allowed_roles = self.action_roles[controller_name].get(action, []) |
352 | + allowed_roles = self.action_roles[controller_name].get(action, ['none']) |
353 | if self._matches_any_role(context, allowed_roles): |
354 | return self.application |
355 | else: |
356 | @@ -183,6 +185,8 @@ |
357 | |
358 | def _matches_any_role(self, context, roles): |
359 | """Return True if any role in roles is allowed in context.""" |
360 | + if context.user.is_superuser(): |
361 | + return True |
362 | if 'all' in roles: |
363 | return True |
364 | if 'none' in roles: |
365 | |
366 | === modified file 'nova/api/ec2/apirequest.py' |
367 | --- nova/api/ec2/apirequest.py 2010-09-09 23:23:27 +0000 |
368 | +++ nova/api/ec2/apirequest.py 2010-09-23 21:08:44 +0000 |
369 | @@ -68,10 +68,8 @@ |
370 | key = _camelcase_to_underscore(parts[0]) |
371 | if len(parts) > 1: |
372 | d = args.get(key, {}) |
373 | - d[parts[1]] = value[0] |
374 | + d[parts[1]] = value |
375 | value = d |
376 | - else: |
377 | - value = value[0] |
378 | args[key] = value |
379 | |
380 | for key in args.keys(): |
381 | |
382 | === added file 'nova/api/ec2/metadatarequesthandler.py' |
383 | --- nova/api/ec2/metadatarequesthandler.py 1970-01-01 00:00:00 +0000 |
384 | +++ nova/api/ec2/metadatarequesthandler.py 2010-09-23 21:08:44 +0000 |
385 | @@ -0,0 +1,73 @@ |
386 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
387 | + |
388 | +# Copyright 2010 United States Government as represented by the |
389 | +# Administrator of the National Aeronautics and Space Administration. |
390 | +# All Rights Reserved. |
391 | +# |
392 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
393 | +# not use this file except in compliance with the License. You may obtain |
394 | +# a copy of the License at |
395 | +# |
396 | +# http://www.apache.org/licenses/LICENSE-2.0 |
397 | +# |
398 | +# Unless required by applicable law or agreed to in writing, software |
399 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
400 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
401 | +# License for the specific language governing permissions and limitations |
402 | +# under the License. |
403 | + |
404 | +"""Metadata request handler.""" |
405 | + |
406 | +import logging |
407 | + |
408 | +import webob.dec |
409 | +import webob.exc |
410 | + |
411 | +from nova.api.ec2 import cloud |
412 | + |
413 | + |
414 | +class MetadataRequestHandler(object): |
415 | + |
416 | + """Serve metadata from the EC2 API.""" |
417 | + |
418 | + def print_data(self, data): |
419 | + if isinstance(data, dict): |
420 | + output = '' |
421 | + for key in data: |
422 | + if key == '_name': |
423 | + continue |
424 | + output += key |
425 | + if isinstance(data[key], dict): |
426 | + if '_name' in data[key]: |
427 | + output += '=' + str(data[key]['_name']) |
428 | + else: |
429 | + output += '/' |
430 | + output += '\n' |
431 | + return output[:-1] # cut off last \n |
432 | + elif isinstance(data, list): |
433 | + return '\n'.join(data) |
434 | + else: |
435 | + return str(data) |
436 | + |
437 | + def lookup(self, path, data): |
438 | + items = path.split('/') |
439 | + for item in items: |
440 | + if item: |
441 | + if not isinstance(data, dict): |
442 | + return data |
443 | + if not item in data: |
444 | + return None |
445 | + data = data[item] |
446 | + return data |
447 | + |
448 | + @webob.dec.wsgify |
449 | + def __call__(self, req): |
450 | + cc = cloud.CloudController() |
451 | + meta_data = cc.get_metadata(req.remote_addr) |
452 | + if meta_data is None: |
453 | + logging.error('Failed to get metadata for ip: %s' % req.remote_addr) |
454 | + raise webob.exc.HTTPNotFound() |
455 | + data = self.lookup(req.path_info, meta_data) |
456 | + if data is None: |
457 | + raise webob.exc.HTTPNotFound() |
458 | + return self.print_data(data) |
459 | |
460 | === modified file 'nova/auth/manager.py' |
461 | --- nova/auth/manager.py 2010-09-23 09:06:49 +0000 |
462 | +++ nova/auth/manager.py 2010-09-23 21:08:44 +0000 |
463 | @@ -44,7 +44,7 @@ |
464 | # NOTE(vish): a user with one of these roles will be a superuser and |
465 | # have access to all api commands |
466 | flags.DEFINE_list('superuser_roles', ['cloudadmin'], |
467 | - 'Roles that ignore rbac checking completely') |
468 | + 'Roles that ignore authorization checking completely') |
469 | |
470 | # NOTE(vish): a user with one of these roles will have it for every |
471 | # project, even if he or she is not a member of the project |
472 | @@ -304,7 +304,7 @@ |
473 | return "%s:%s" % (user.access, Project.safe_id(project)) |
474 | |
475 | def is_superuser(self, user): |
476 | - """Checks for superuser status, allowing user to bypass rbac |
477 | + """Checks for superuser status, allowing user to bypass authorization |
478 | |
479 | @type user: User or uid |
480 | @param user: User to check. |
481 | |
482 | === modified file 'nova/cloudpipe/pipelib.py' |
483 | --- nova/cloudpipe/pipelib.py 2010-09-21 16:00:44 +0000 |
484 | +++ nova/cloudpipe/pipelib.py 2010-09-23 21:08:44 +0000 |
485 | @@ -32,6 +32,8 @@ |
486 | from nova import flags |
487 | from nova import utils |
488 | from nova.auth import manager |
489 | +# TODO(eday): Eventually changes these to something not ec2-specific |
490 | +from nova.api.ec2 import cloud |
491 | from nova.api.ec2 import context |
492 | |
493 | |
494 | @@ -42,8 +44,8 @@ |
495 | |
496 | |
497 | class CloudPipe(object): |
498 | - def __init__(self, cloud_controller): |
499 | - self.controller = cloud_controller |
500 | + def __init__(self): |
501 | + self.controller = cloud.CloudController() |
502 | self.manager = manager.AuthManager() |
503 | |
504 | def launch_vpn_instance(self, project_id): |
505 | |
506 | === modified file 'nova/rpc.py' |
507 | --- nova/rpc.py 2010-08-18 15:44:24 +0000 |
508 | +++ nova/rpc.py 2010-09-23 21:08:44 +0000 |
509 | @@ -46,9 +46,9 @@ |
510 | class Connection(carrot_connection.BrokerConnection): |
511 | """Connection instance object""" |
512 | @classmethod |
513 | - def instance(cls): |
514 | + def instance(cls, new=False): |
515 | """Returns the instance""" |
516 | - if not hasattr(cls, '_instance'): |
517 | + if new or not hasattr(cls, '_instance'): |
518 | params = dict(hostname=FLAGS.rabbit_host, |
519 | port=FLAGS.rabbit_port, |
520 | userid=FLAGS.rabbit_userid, |
521 | @@ -60,7 +60,10 @@ |
522 | |
523 | # NOTE(vish): magic is fun! |
524 | # pylint: disable-msg=W0142 |
525 | - cls._instance = cls(**params) |
526 | + if new: |
527 | + return cls(**params) |
528 | + else: |
529 | + cls._instance = cls(**params) |
530 | return cls._instance |
531 | |
532 | @classmethod |
533 | @@ -94,8 +97,6 @@ |
534 | injected.start() |
535 | return injected |
536 | |
537 | - attachToTornado = attach_to_tornado |
538 | - |
539 | def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): |
540 | """Wraps the parent fetch with some logic for failed connections""" |
541 | # TODO(vish): the logic for failed connections and logging should be |
542 | @@ -265,28 +266,32 @@ |
543 | msg.update({'_msg_id': msg_id}) |
544 | LOG.debug("MSG_ID is %s" % (msg_id)) |
545 | |
546 | - conn = Connection.instance() |
547 | - d = defer.Deferred() |
548 | + class WaitMessage(object): |
549 | + |
550 | + def __call__(self, data, message): |
551 | + """Acks message and sets result.""" |
552 | + message.ack() |
553 | + if data['failure']: |
554 | + self.result = RemoteError(*data['failure']) |
555 | + else: |
556 | + self.result = data['result'] |
557 | + |
558 | + wait_msg = WaitMessage() |
559 | + conn = Connection.instance(True) |
560 | consumer = DirectConsumer(connection=conn, msg_id=msg_id) |
561 | - |
562 | - def deferred_receive(data, message): |
563 | - """Acks message and callbacks or errbacks""" |
564 | - message.ack() |
565 | - if data['failure']: |
566 | - return d.errback(RemoteError(*data['failure'])) |
567 | - else: |
568 | - return d.callback(data['result']) |
569 | - |
570 | - consumer.register_callback(deferred_receive) |
571 | - injected = consumer.attach_to_tornado() |
572 | - |
573 | - # clean up after the injected listened and return x |
574 | - d.addCallback(lambda x: injected.stop() and x or x) |
575 | - |
576 | + consumer.register_callback(wait_msg) |
577 | + |
578 | + conn = Connection.instance() |
579 | publisher = TopicPublisher(connection=conn, topic=topic) |
580 | publisher.send(msg) |
581 | publisher.close() |
582 | - return d |
583 | + |
584 | + try: |
585 | + consumer.wait(limit=1) |
586 | + except StopIteration: |
587 | + pass |
588 | + consumer.close() |
589 | + return wait_msg.result |
590 | |
591 | |
592 | def cast(topic, msg): |
593 | |
594 | === modified file 'nova/tests/access_unittest.py' |
595 | --- nova/tests/access_unittest.py 2010-09-08 04:15:22 +0000 |
596 | +++ nova/tests/access_unittest.py 2010-09-23 21:08:44 +0000 |
597 | @@ -18,12 +18,13 @@ |
598 | |
599 | import unittest |
600 | import logging |
601 | +import webob |
602 | |
603 | from nova import exception |
604 | from nova import flags |
605 | from nova import test |
606 | +from nova.api import ec2 |
607 | from nova.auth import manager |
608 | -from nova.auth import rbac |
609 | |
610 | |
611 | FLAGS = flags.FLAGS |
612 | @@ -72,9 +73,17 @@ |
613 | try: |
614 | self.project.add_role(self.testsys, 'sysadmin') |
615 | except: pass |
616 | - self.context = Context() |
617 | - self.context.project = self.project |
618 | #user is set in each test |
619 | + def noopWSGIApp(environ, start_response): |
620 | + start_response('200 OK', []) |
621 | + return [''] |
622 | + self.mw = ec2.Authorizer(noopWSGIApp) |
623 | + self.mw.action_roles = {'str': { |
624 | + '_allow_all': ['all'], |
625 | + '_allow_none': [], |
626 | + '_allow_project_manager': ['projectmanager'], |
627 | + '_allow_sys_and_net': ['sysadmin', 'netadmin'], |
628 | + '_allow_sysadmin': ['sysadmin']}} |
629 | |
630 | def tearDown(self): |
631 | um = manager.AuthManager() |
632 | @@ -87,76 +96,46 @@ |
633 | um.delete_user('testsys') |
634 | super(AccessTestCase, self).tearDown() |
635 | |
636 | + def response_status(self, user, methodName): |
637 | + context = Context() |
638 | + context.project = self.project |
639 | + context.user = user |
640 | + environ = {'ec2.context' : context, |
641 | + 'ec2.controller': 'some string', |
642 | + 'ec2.action': methodName} |
643 | + req = webob.Request.blank('/', environ) |
644 | + resp = req.get_response(self.mw) |
645 | + return resp.status_int |
646 | + |
647 | + def shouldAllow(self, user, methodName): |
648 | + self.assertEqual(200, self.response_status(user, methodName)) |
649 | + |
650 | + def shouldDeny(self, user, methodName): |
651 | + self.assertEqual(401, self.response_status(user, methodName)) |
652 | + |
653 | def test_001_allow_all(self): |
654 | - self.context.user = self.testadmin |
655 | - self.assertTrue(self._allow_all(self.context)) |
656 | - self.context.user = self.testpmsys |
657 | - self.assertTrue(self._allow_all(self.context)) |
658 | - self.context.user = self.testnet |
659 | - self.assertTrue(self._allow_all(self.context)) |
660 | - self.context.user = self.testsys |
661 | - self.assertTrue(self._allow_all(self.context)) |
662 | + users = [self.testadmin, self.testpmsys, self.testnet, self.testsys] |
663 | + for user in users: |
664 | + self.shouldAllow(user, '_allow_all') |
665 | |
666 | def test_002_allow_none(self): |
667 | - self.context.user = self.testadmin |
668 | - self.assertTrue(self._allow_none(self.context)) |
669 | - self.context.user = self.testpmsys |
670 | - self.assertRaises(exception.NotAuthorized, self._allow_none, self.context) |
671 | - self.context.user = self.testnet |
672 | - self.assertRaises(exception.NotAuthorized, self._allow_none, self.context) |
673 | - self.context.user = self.testsys |
674 | - self.assertRaises(exception.NotAuthorized, self._allow_none, self.context) |
675 | + self.shouldAllow(self.testadmin, '_allow_none') |
676 | + users = [self.testpmsys, self.testnet, self.testsys] |
677 | + for user in users: |
678 | + self.shouldDeny(user, '_allow_none') |
679 | |
680 | def test_003_allow_project_manager(self): |
681 | - self.context.user = self.testadmin |
682 | - self.assertTrue(self._allow_project_manager(self.context)) |
683 | - self.context.user = self.testpmsys |
684 | - self.assertTrue(self._allow_project_manager(self.context)) |
685 | - self.context.user = self.testnet |
686 | - self.assertRaises(exception.NotAuthorized, self._allow_project_manager, self.context) |
687 | - self.context.user = self.testsys |
688 | - self.assertRaises(exception.NotAuthorized, self._allow_project_manager, self.context) |
689 | + for user in [self.testadmin, self.testpmsys]: |
690 | + self.shouldAllow(user, '_allow_project_manager') |
691 | + for user in [self.testnet, self.testsys]: |
692 | + self.shouldDeny(user, '_allow_project_manager') |
693 | |
694 | def test_004_allow_sys_and_net(self): |
695 | - self.context.user = self.testadmin |
696 | - self.assertTrue(self._allow_sys_and_net(self.context)) |
697 | - self.context.user = self.testpmsys # doesn't have the per project sysadmin |
698 | - self.assertRaises(exception.NotAuthorized, self._allow_sys_and_net, self.context) |
699 | - self.context.user = self.testnet |
700 | - self.assertTrue(self._allow_sys_and_net(self.context)) |
701 | - self.context.user = self.testsys |
702 | - self.assertTrue(self._allow_sys_and_net(self.context)) |
703 | - |
704 | - def test_005_allow_sys_no_pm(self): |
705 | - self.context.user = self.testadmin |
706 | - self.assertTrue(self._allow_sys_no_pm(self.context)) |
707 | - self.context.user = self.testpmsys |
708 | - self.assertRaises(exception.NotAuthorized, self._allow_sys_no_pm, self.context) |
709 | - self.context.user = self.testnet |
710 | - self.assertRaises(exception.NotAuthorized, self._allow_sys_no_pm, self.context) |
711 | - self.context.user = self.testsys |
712 | - self.assertTrue(self._allow_sys_no_pm(self.context)) |
713 | - |
714 | - @rbac.allow('all') |
715 | - def _allow_all(self, context): |
716 | - return True |
717 | - |
718 | - @rbac.allow('none') |
719 | - def _allow_none(self, context): |
720 | - return True |
721 | - |
722 | - @rbac.allow('projectmanager') |
723 | - def _allow_project_manager(self, context): |
724 | - return True |
725 | - |
726 | - @rbac.allow('sysadmin', 'netadmin') |
727 | - def _allow_sys_and_net(self, context): |
728 | - return True |
729 | - |
730 | - @rbac.allow('sysadmin') |
731 | - @rbac.deny('projectmanager') |
732 | - def _allow_sys_no_pm(self, context): |
733 | - return True |
734 | + for user in [self.testadmin, self.testnet, self.testsys]: |
735 | + self.shouldAllow(user, '_allow_sys_and_net') |
736 | + # denied because it doesn't have the per project sysadmin |
737 | + for user in [self.testpmsys]: |
738 | + self.shouldDeny(user, '_allow_sys_and_net') |
739 | |
740 | if __name__ == "__main__": |
741 | # TODO: Implement use_fake as an option |
742 | |
743 | === modified file 'nova/tests/api/__init__.py' |
744 | --- nova/tests/api/__init__.py 2010-09-15 21:17:20 +0000 |
745 | +++ nova/tests/api/__init__.py 2010-09-23 21:08:44 +0000 |
746 | @@ -25,6 +25,7 @@ |
747 | import webob |
748 | import webob.dec |
749 | |
750 | +import nova.exception |
751 | from nova import api |
752 | from nova.tests.api.test_helper import * |
753 | |
754 | @@ -36,25 +37,46 @@ |
755 | def tearDown(self): # pylint: disable-msg=C0103 |
756 | self.stubs.UnsetAll() |
757 | |
758 | + def _request(self, url, subdomain, **kwargs): |
759 | + environ_keys = {'HTTP_HOST': '%s.example.com' % subdomain} |
760 | + environ_keys.update(kwargs) |
761 | + req = webob.Request.blank(url, environ_keys) |
762 | + return req.get_response(api.API()) |
763 | + |
764 | def test_rackspace(self): |
765 | self.stubs.Set(api.rackspace, 'API', APIStub) |
766 | - result = webob.Request.blank('/v1.0/cloud').get_response(api.API()) |
767 | + result = self._request('/v1.0/cloud', 'rs') |
768 | self.assertEqual(result.body, "/cloud") |
769 | |
770 | def test_ec2(self): |
771 | self.stubs.Set(api.ec2, 'API', APIStub) |
772 | - result = webob.Request.blank('/ec2/cloud').get_response(api.API()) |
773 | + result = self._request('/services/cloud', 'ec2') |
774 | self.assertEqual(result.body, "/cloud") |
775 | |
776 | def test_not_found(self): |
777 | self.stubs.Set(api.ec2, 'API', APIStub) |
778 | self.stubs.Set(api.rackspace, 'API', APIStub) |
779 | - result = webob.Request.blank('/test/cloud').get_response(api.API()) |
780 | + result = self._request('/test/cloud', 'ec2') |
781 | self.assertNotEqual(result.body, "/cloud") |
782 | |
783 | def test_query_api_versions(self): |
784 | - result = webob.Request.blank('/').get_response(api.API()) |
785 | + result = self._request('/', 'rs') |
786 | self.assertTrue('CURRENT' in result.body) |
787 | |
788 | + def test_metadata(self): |
789 | + def go(url): |
790 | + result = self._request(url, 'ec2', |
791 | + REMOTE_ADDR='128.192.151.2') |
792 | + # Each should get to the ORM layer and fail to find the IP |
793 | + self.assertRaises(nova.exception.NotFound, go, '/latest/') |
794 | + self.assertRaises(nova.exception.NotFound, go, '/2009-04-04/') |
795 | + self.assertRaises(nova.exception.NotFound, go, '/1.0/') |
796 | + |
797 | + def test_ec2_root(self): |
798 | + result = self._request('/', 'ec2') |
799 | + self.assertTrue('2007-12-15\n' in result.body) |
800 | + |
801 | + |
802 | + |
803 | if __name__ == '__main__': |
804 | unittest.main() |
805 | |
806 | === modified file 'nova/tests/api_unittest.py' |
807 | --- nova/tests/api_unittest.py 2010-09-21 19:00:43 +0000 |
808 | +++ nova/tests/api_unittest.py 2010-09-23 21:08:44 +0000 |
809 | @@ -25,12 +25,17 @@ |
810 | import StringIO |
811 | import webob |
812 | |
813 | +from nova import flags |
814 | from nova import test |
815 | from nova import api |
816 | from nova.api.ec2 import cloud |
817 | from nova.auth import manager |
818 | |
819 | |
820 | +FLAGS = flags.FLAGS |
821 | +FLAGS.FAKE_subdomain = 'ec2' |
822 | + |
823 | + |
824 | class FakeHttplibSocket(object): |
825 | """a fake socket implementation for httplib.HTTPResponse, trivial""" |
826 | def __init__(self, response_string): |
827 | |
828 | === modified file 'nova/tests/cloud_unittest.py' |
829 | --- nova/tests/cloud_unittest.py 2010-09-21 18:00:17 +0000 |
830 | +++ nova/tests/cloud_unittest.py 2010-09-23 21:08:44 +0000 |
831 | @@ -22,7 +22,6 @@ |
832 | import StringIO |
833 | import time |
834 | |
835 | -from tornado import ioloop |
836 | from twisted.internet import defer |
837 | import unittest |
838 | from xml.etree import ElementTree |
839 | |
840 | === modified file 'run_tests.py' |
841 | --- run_tests.py 2010-09-21 18:00:17 +0000 |
842 | +++ run_tests.py 2010-09-23 21:08:44 +0000 |
843 | @@ -49,8 +49,7 @@ |
844 | from nova import flags |
845 | from nova import twistd |
846 | |
847 | -#TODO(gundlach): rewrite and readd this after merge |
848 | -#from nova.tests.access_unittest import * |
849 | +from nova.tests.access_unittest import * |
850 | from nova.tests.auth_unittest import * |
851 | from nova.tests.api_unittest import * |
852 | from nova.tests.cloud_unittest import * |
lgtm, although I'd like vish's (or someone from anso) review before this gets merged.