Merge lp:~gundlach/nova/controllers-in-api into lp:~hudson-openstack/nova/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
Reviewer Review Type Date Requested Status
Eric Day (community) Approve
Review via email: mp+36503@code.launchpad.net

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.
Revision history for this message
Eric Day (eday) wrote :

lgtm, although I'd like vish's (or someone from anso) review before this gets merged.

review: Approve
lp:~gundlach/nova/controllers-in-api updated
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 *