summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-09-28 12:29:43 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-09-28 12:29:43 -0700
commitbb1540d7bf2c323d43110738e43e9b99ef49b62c (patch)
tree5588175281a26e774fc580f97f047599455c1373
parentf3698b8da4bd63abfade32c9894ac2095672344e (diff)
parent669cf475d11700064aa16f959077d0512e6b1531 (diff)
downloadnova-bb1540d7bf2c323d43110738e43e9b99ef49b62c.tar.gz
nova-bb1540d7bf2c323d43110738e43e9b99ef49b62c.tar.xz
nova-bb1540d7bf2c323d43110738e43e9b99ef49b62c.zip
merged floating-ips
-rwxr-xr-xbin/nova-api47
-rwxr-xr-xbin/nova-api-new45
-rwxr-xr-xbin/nova-manage3
-rw-r--r--doc/source/auth.rst8
-rw-r--r--nova/api/__init__.py69
-rw-r--r--nova/api/cloudpipe/__init__.py69
-rw-r--r--nova/api/ec2/__init__.py10
-rw-r--r--nova/api/ec2/apirequest.py4
-rw-r--r--nova/api/ec2/metadatarequesthandler.py73
-rw-r--r--nova/api/rackspace/__init__.py31
-rw-r--r--nova/api/rackspace/auth.py98
-rw-r--r--nova/auth/manager.py6
-rw-r--r--nova/cloudpipe/api.py59
-rw-r--r--nova/cloudpipe/pipelib.py6
-rw-r--r--nova/db/api.py38
-rw-r--r--nova/db/sqlalchemy/api.py27
-rw-r--r--nova/db/sqlalchemy/models.py16
-rw-r--r--nova/rpc.py47
-rw-r--r--nova/scheduler/driver.py3
-rw-r--r--nova/tests/access_unittest.py111
-rw-r--r--nova/tests/api/__init__.py30
-rw-r--r--nova/tests/api/rackspace/auth.py106
-rw-r--r--nova/tests/api/rackspace/test_helper.py65
-rw-r--r--nova/tests/api_unittest.py5
-rw-r--r--nova/tests/cloud_unittest.py1
-rw-r--r--run_tests.py3
26 files changed, 711 insertions, 269 deletions
diff --git a/bin/nova-api b/bin/nova-api
index ede09d38c..a5027700b 100755
--- a/bin/nova-api
+++ b/bin/nova-api
@@ -1,31 +1,28 @@
#!/usr/bin/env python
+# pylint: disable-msg=C0103
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
"""
-Tornado daemon for the main API endpoint.
+Nova API daemon.
"""
-import logging
import os
import sys
-from tornado import httpserver
-from tornado import ioloop
# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
@@ -36,28 +33,16 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
from nova import flags
-from nova import server
from nova import utils
-from nova.endpoint import admin
-from nova.endpoint import api
-from nova.endpoint import cloud
+from nova import server
FLAGS = flags.FLAGS
+flags.DEFINE_integer('api_port', 8773, 'API port')
-
-def main(_argv):
- """Load the controllers and start the tornado I/O loop."""
- controllers = {
- 'Cloud': cloud.CloudController(),
- 'Admin': admin.AdminController()}
- _app = api.APIServerApplication(controllers)
-
- io_inst = ioloop.IOLoop.instance()
- http_server = httpserver.HTTPServer(_app)
- http_server.listen(FLAGS.cc_port)
- logging.debug('Started HTTP server on %s', FLAGS.cc_port)
- io_inst.start()
-
+def main(_args):
+ from nova import api
+ from nova import wsgi
+ wsgi.run_server(api.API(), FLAGS.api_port)
if __name__ == '__main__':
utils.default_flagfile()
diff --git a/bin/nova-api-new b/bin/nova-api-new
deleted file mode 100755
index 8625c487f..000000000
--- a/bin/nova-api-new
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-# pylint: disable-msg=C0103
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""
-Nova API daemon.
-"""
-
-import os
-import sys
-
-# If ../nova/__init__.py exists, add ../ to Python search path, so that
-# it will override what happens to be installed in /usr/(local/)lib/python...
-possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
- os.pardir,
- os.pardir))
-if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
- sys.path.insert(0, possible_topdir)
-
-from nova import api
-from nova import flags
-from nova import utils
-from nova import wsgi
-
-FLAGS = flags.FLAGS
-flags.DEFINE_integer('api_port', 8773, 'API port')
-
-if __name__ == '__main__':
- utils.default_flagfile()
- wsgi.run_server(api.API(), FLAGS.api_port)
diff --git a/bin/nova-manage b/bin/nova-manage
index 824e00ac5..baa1cb4db 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -73,7 +73,6 @@ from nova import quota
from nova import utils
from nova.auth import manager
from nova.cloudpipe import pipelib
-from nova.endpoint import cloud
FLAGS = flags.FLAGS
@@ -84,7 +83,7 @@ class VpnCommands(object):
def __init__(self):
self.manager = manager.AuthManager()
- self.pipe = pipelib.CloudPipe(cloud.CloudController())
+ self.pipe = pipelib.CloudPipe()
def list(self):
"""Print a listing of the VPNs for all projects."""
diff --git a/doc/source/auth.rst b/doc/source/auth.rst
index 70aca704a..3fcb309cd 100644
--- a/doc/source/auth.rst
+++ b/doc/source/auth.rst
@@ -172,14 +172,6 @@ Further Challenges
-The :mod:`rbac` Module
---------------------------
-
-.. automodule:: nova.auth.rbac
- :members:
- :undoc-members:
- :show-inheritance:
-
The :mod:`signer` Module
------------------------
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 821f1deea..744abd621 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -23,23 +23,65 @@ Root WSGI middleware for all API controllers.
import routes
import webob.dec
+from nova import flags
from nova import wsgi
+from nova.api import cloudpipe
from nova.api import ec2
from nova.api import rackspace
+from nova.api.ec2 import metadatarequesthandler
+
+
+flags.DEFINE_string('rsapi_subdomain', 'rs',
+ 'subdomain running the RS API')
+flags.DEFINE_string('ec2api_subdomain', 'ec2',
+ 'subdomain running the EC2 API')
+flags.DEFINE_string('FAKE_subdomain', None,
+ 'set to rs or ec2 to fake the subdomain of the host for testing')
+FLAGS = flags.FLAGS
class API(wsgi.Router):
"""Routes top-level requests to the appropriate controller."""
def __init__(self):
+ rsdomain = {'sub_domain': [FLAGS.rsapi_subdomain]}
+ ec2domain = {'sub_domain': [FLAGS.ec2api_subdomain]}
+ # If someone wants to pretend they're hitting the RS subdomain
+ # on their local box, they can set FAKE_subdomain to 'rs', which
+ # removes subdomain restrictions from the RS routes below.
+ if FLAGS.FAKE_subdomain == 'rs':
+ rsdomain = {}
+ elif FLAGS.FAKE_subdomain == 'ec2':
+ ec2domain = {}
mapper = routes.Mapper()
- mapper.connect("/", controller=self.versions)
- mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API())
- mapper.connect("/services/{path_info:.*}", controller=ec2.API())
+ mapper.sub_domains = True
+ mapper.connect("/", controller=self.rsapi_versions,
+ conditions=rsdomain)
+ mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API(),
+ conditions=rsdomain)
+
+ mapper.connect("/", controller=self.ec2api_versions,
+ conditions=ec2domain)
+ mapper.connect("/services/{path_info:.*}", controller=ec2.API(),
+ conditions=ec2domain)
+ mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API())
+ mrh = metadatarequesthandler.MetadataRequestHandler()
+ for s in ['/latest',
+ '/2009-04-04',
+ '/2008-09-01',
+ '/2008-02-01',
+ '/2007-12-15',
+ '/2007-10-10',
+ '/2007-08-29',
+ '/2007-03-01',
+ '/2007-01-19',
+ '/1.0']:
+ mapper.connect('%s/{path_info:.*}' % s, controller=mrh,
+ conditions=ec2domain)
super(API, self).__init__(mapper)
@webob.dec.wsgify
- def versions(self, req):
+ def rsapi_versions(self, req):
"""Respond to a request for all OpenStack API versions."""
response = {
"versions": [
@@ -48,3 +90,22 @@ class API(wsgi.Router):
"application/xml": {
"attributes": dict(version=["status", "id"])}}
return wsgi.Serializer(req.environ, metadata).to_content_type(response)
+
+ @webob.dec.wsgify
+ def ec2api_versions(self, req):
+ """Respond to a request for all EC2 versions."""
+ # available api versions
+ versions = [
+ '1.0',
+ '2007-01-19',
+ '2007-03-01',
+ '2007-08-29',
+ '2007-10-10',
+ '2007-12-15',
+ '2008-02-01',
+ '2008-09-01',
+ '2009-04-04',
+ ]
+ return ''.join('%s\n' % v for v in versions)
+
+
diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py
new file mode 100644
index 000000000..6d40990a8
--- /dev/null
+++ b/nova/api/cloudpipe/__init__.py
@@ -0,0 +1,69 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+REST API Request Handlers for CloudPipe
+"""
+
+import logging
+import urllib
+import webob
+import webob.dec
+import webob.exc
+
+from nova import crypto
+from nova import wsgi
+from nova.auth import manager
+from nova.api.ec2 import cloud
+
+
+_log = logging.getLogger("api")
+_log.setLevel(logging.DEBUG)
+
+
+class API(wsgi.Application):
+
+ def __init__(self):
+ self.controller = cloud.CloudController()
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ if req.method == 'POST':
+ return self.sign_csr(req)
+ _log.debug("Cloudpipe path is %s" % req.path_info)
+ if req.path_info.endswith("/getca/"):
+ return self.send_root_ca(req)
+ return webob.exc.HTTPNotFound()
+
+ def get_project_id_from_ip(self, ip):
+ # TODO(eday): This was removed with the ORM branch, fix!
+ instance = self.controller.get_instance_by_ip(ip)
+ return instance['project_id']
+
+ def send_root_ca(self, req):
+ _log.debug("Getting root ca")
+ project_id = self.get_project_id_from_ip(req.remote_addr)
+ res = webob.Response()
+ res.headers["Content-Type"] = "text/plain"
+ res.body = crypto.fetch_ca(project_id)
+ return res
+
+ def sign_csr(self, req):
+ project_id = self.get_project_id_from_ip(req.remote_addr)
+ cert = self.str_params['cert']
+ return crypto.sign_csr(urllib.unquote(cert), project_id)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index a7b10e428..f0aa57ee4 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -25,6 +25,7 @@ import webob.dec
import webob.exc
from nova import exception
+from nova import flags
from nova import wsgi
from nova.api.ec2 import apirequest
from nova.api.ec2 import context
@@ -33,6 +34,7 @@ from nova.api.ec2 import cloud
from nova.auth import manager
+FLAGS = flags.FLAGS
_log = logging.getLogger("api")
_log.setLevel(logging.DEBUG)
@@ -164,8 +166,8 @@ class Authorizer(wsgi.Middleware):
'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
},
'AdminController': {
- # All actions have the same permission: [] (the default)
- # admins will be allowed to run them
+ # All actions have the same permission: ['none'] (the default)
+ # superusers will be allowed to run them
# all others will get HTTPUnauthorized.
},
}
@@ -175,7 +177,7 @@ class Authorizer(wsgi.Middleware):
context = req.environ['ec2.context']
controller_name = req.environ['ec2.controller'].__class__.__name__
action = req.environ['ec2.action']
- allowed_roles = self.action_roles[controller_name].get(action, [])
+ allowed_roles = self.action_roles[controller_name].get(action, ['none'])
if self._matches_any_role(context, allowed_roles):
return self.application
else:
@@ -183,6 +185,8 @@ class Authorizer(wsgi.Middleware):
def _matches_any_role(self, context, roles):
"""Return True if any role in roles is allowed in context."""
+ if context.user.is_superuser():
+ return True
if 'all' in roles:
return True
if 'none' in roles:
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index a3b20118f..a87c21fb3 100644
--- a/nova/api/ec2/apirequest.py
+++ b/nova/api/ec2/apirequest.py
@@ -68,10 +68,8 @@ class APIRequest(object):
key = _camelcase_to_underscore(parts[0])
if len(parts) > 1:
d = args.get(key, {})
- d[parts[1]] = value[0]
+ d[parts[1]] = value
value = d
- else:
- value = value[0]
args[key] = value
for key in args.keys():
diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py
new file mode 100644
index 000000000..08a8040ca
--- /dev/null
+++ b/nova/api/ec2/metadatarequesthandler.py
@@ -0,0 +1,73 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Metadata request handler."""
+
+import logging
+
+import webob.dec
+import webob.exc
+
+from nova.api.ec2 import cloud
+
+
+class MetadataRequestHandler(object):
+
+ """Serve metadata from the EC2 API."""
+
+ def print_data(self, data):
+ if isinstance(data, dict):
+ output = ''
+ for key in data:
+ if key == '_name':
+ continue
+ output += key
+ if isinstance(data[key], dict):
+ if '_name' in data[key]:
+ output += '=' + str(data[key]['_name'])
+ else:
+ output += '/'
+ output += '\n'
+ return output[:-1] # cut off last \n
+ elif isinstance(data, list):
+ return '\n'.join(data)
+ else:
+ return str(data)
+
+ def lookup(self, path, data):
+ items = path.split('/')
+ for item in items:
+ if item:
+ if not isinstance(data, dict):
+ return data
+ if not item in data:
+ return None
+ data = data[item]
+ return data
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ cc = cloud.CloudController()
+ meta_data = cc.get_metadata(req.remote_addr)
+ if meta_data is None:
+ logging.error('Failed to get metadata for ip: %s' % req.remote_addr)
+ raise webob.exc.HTTPNotFound()
+ data = self.lookup(req.path_info, meta_data)
+ if data is None:
+ raise webob.exc.HTTPNotFound()
+ return self.print_data(data)
diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py
index ac5365310..c24d08585 100644
--- a/nova/api/rackspace/__init__.py
+++ b/nova/api/rackspace/__init__.py
@@ -26,8 +26,10 @@ import time
import routes
import webob.dec
import webob.exc
+import webob
from nova import flags
+from nova import utils
from nova import wsgi
from nova.api.rackspace import flavors
from nova.api.rackspace import images
@@ -37,6 +39,11 @@ from nova.api.rackspace import sharedipgroups
from nova.auth import manager
+FLAGS = flags.FLAGS
+flags.DEFINE_string('nova_api_auth',
+ 'nova.api.rackspace.auth.BasicApiAuthManager',
+ 'The auth mechanism to use for the Rackspace API implemenation')
+
class API(wsgi.Middleware):
"""WSGI entry point for all Rackspace API requests."""
@@ -44,28 +51,26 @@ class API(wsgi.Middleware):
app = AuthMiddleware(RateLimitingMiddleware(APIRouter()))
super(API, self).__init__(app)
-
class AuthMiddleware(wsgi.Middleware):
"""Authorize the rackspace API request or return an HTTP Forbidden."""
- #TODO(gundlach): isn't this the old Nova API's auth? Should it be replaced
- #with correct RS API auth?
+ def __init__(self, application):
+ self.auth_driver = utils.import_class(FLAGS.nova_api_auth)()
+ super(AuthMiddleware, self).__init__(application)
@webob.dec.wsgify
def __call__(self, req):
- context = {}
- if "HTTP_X_AUTH_TOKEN" in req.environ:
- context['user'] = manager.AuthManager().get_user_from_access_key(
- req.environ['HTTP_X_AUTH_TOKEN'])
- if context['user']:
- context['project'] = manager.AuthManager().get_project(
- context['user'].name)
- if "user" not in context:
- return webob.exc.HTTPForbidden()
+ if not req.headers.has_key("X-Auth-Token"):
+ return self.auth_driver.authenticate(req)
+
+ user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"])
+
+ if not user:
+ return webob.exc.HTTPUnauthorized()
+ context = {'user': user}
req.environ['nova.context'] = context
return self.application
-
class RateLimitingMiddleware(wsgi.Middleware):
"""Rate limit incoming requests according to the OpenStack rate limits."""
diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py
new file mode 100644
index 000000000..ce5a967eb
--- /dev/null
+++ b/nova/api/rackspace/auth.py
@@ -0,0 +1,98 @@
+import datetime
+import json
+import time
+import webob.exc
+import webob.dec
+import hashlib
+from nova import flags
+from nova import auth
+from nova import manager
+from nova import db
+from nova import utils
+
+FLAGS = flags.FLAGS
+
+class Context(object):
+ pass
+
+class BasicApiAuthManager(object):
+ """ Implements a somewhat rudimentary version of Rackspace Auth"""
+
+ def __init__(self, host=None, db_driver=None):
+ if not host:
+ host = FLAGS.host
+ self.host = host
+ if not db_driver:
+ db_driver = FLAGS.db_driver
+ self.db = utils.import_object(db_driver)
+ self.auth = auth.manager.AuthManager()
+ self.context = Context()
+ super(BasicApiAuthManager, self).__init__()
+
+ def authenticate(self, req):
+ # Unless the request is explicitly made against /<version>/ don't
+ # honor it
+ path_info = req.path_info
+ if len(path_info) > 1:
+ return webob.exc.HTTPUnauthorized()
+
+ try:
+ username, key = req.headers['X-Auth-User'], \
+ req.headers['X-Auth-Key']
+ except KeyError:
+ return webob.exc.HTTPUnauthorized()
+
+ username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key']
+ token, user = self._authorize_user(username, key)
+ if user and token:
+ res = webob.Response()
+ res.headers['X-Auth-Token'] = token['token_hash']
+ res.headers['X-Server-Management-Url'] = \
+ token['server_management_url']
+ res.headers['X-Storage-Url'] = token['storage_url']
+ res.headers['X-CDN-Management-Url'] = token['cdn_management_url']
+ res.content_type = 'text/plain'
+ res.status = '204'
+ return res
+ else:
+ return webob.exc.HTTPUnauthorized()
+
+ def authorize_token(self, token_hash):
+ """ retrieves user information from the datastore given a token
+
+ If the token has expired, returns None
+ If the token is not found, returns None
+ Otherwise returns the token
+
+ This method will also remove the token if the timestamp is older than
+ 2 days ago.
+ """
+ token = self.db.auth_get_token(self.context, token_hash)
+ if token:
+ delta = datetime.datetime.now() - token['created_at']
+ if delta.days >= 2:
+ self.db.auth_destroy_token(self.context, token)
+ else:
+ user = self.auth.get_user(token['user_id'])
+ return { 'id':user['uid'] }
+ return None
+
+ def _authorize_user(self, username, key):
+ """ Generates a new token and assigns it to a user """
+ user = self.auth.get_user_from_access_key(key)
+ if user and user['name'] == username:
+ token_hash = hashlib.sha1('%s%s%f' % (username, key,
+ time.time())).hexdigest()
+ token = {}
+ token['token_hash'] = token_hash
+ token['cdn_management_url'] = ''
+ token['server_management_url'] = self._get_server_mgmt_url()
+ token['storage_url'] = ''
+ token['user_id'] = user['uid']
+ self.db.auth_create_token(self.context, token)
+ return token, user
+ return None, None
+
+ def _get_server_mgmt_url(self):
+ return 'https://%s/v1.0/' % self.host
+
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index bc3a8a12e..55fbf42aa 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -44,7 +44,7 @@ flags.DEFINE_list('allowed_roles',
# NOTE(vish): a user with one of these roles will be a superuser and
# have access to all api commands
flags.DEFINE_list('superuser_roles', ['cloudadmin'],
- 'Roles that ignore rbac checking completely')
+ 'Roles that ignore authorization checking completely')
# NOTE(vish): a user with one of these roles will have it for every
# project, even if he or she is not a member of the project
@@ -266,7 +266,7 @@ class AuthManager(object):
# NOTE(vish): if we stop using project name as id we need better
# logic to find a default project for user
- if project_id is '':
+ if project_id == '':
project_id = user.name
project = self.get_project(project_id)
@@ -304,7 +304,7 @@ class AuthManager(object):
return "%s:%s" % (user.access, Project.safe_id(project))
def is_superuser(self, user):
- """Checks for superuser status, allowing user to bypass rbac
+ """Checks for superuser status, allowing user to bypass authorization
@type user: User or uid
@param user: User to check.
diff --git a/nova/cloudpipe/api.py b/nova/cloudpipe/api.py
deleted file mode 100644
index 56aa89834..000000000
--- a/nova/cloudpipe/api.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""
-Tornado REST API Request Handlers for CloudPipe
-"""
-
-import logging
-import urllib
-
-import tornado.web
-
-from nova import crypto
-from nova.auth import manager
-
-
-_log = logging.getLogger("api")
-_log.setLevel(logging.DEBUG)
-
-
-class CloudPipeRequestHandler(tornado.web.RequestHandler):
- def get(self, path):
- path = self.request.path
- _log.debug( "Cloudpipe path is %s" % path)
- if path.endswith("/getca/"):
- self.send_root_ca()
- self.finish()
-
- def get_project_id_from_ip(self, ip):
- cc = self.application.controllers['Cloud']
- instance = cc.get_instance_by_ip(ip)
- instance['project_id']
-
- def send_root_ca(self):
- _log.debug( "Getting root ca")
- project_id = self.get_project_id_from_ip(self.request.remote_ip)
- self.set_header("Content-Type", "text/plain")
- self.write(crypto.fetch_ca(project_id))
-
- def post(self, *args, **kwargs):
- project_id = self.get_project_id_from_ip(self.request.remote_ip)
- cert = self.get_argument('cert', '')
- self.write(crypto.sign_csr(urllib.unquote(cert), project_id))
- self.finish()
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
index b13a60292..706a175d9 100644
--- a/nova/cloudpipe/pipelib.py
+++ b/nova/cloudpipe/pipelib.py
@@ -32,6 +32,8 @@ from nova import exception
from nova import flags
from nova import utils
from nova.auth import manager
+# TODO(eday): Eventually changes these to something not ec2-specific
+from nova.api.ec2 import cloud
from nova.api.ec2 import context
@@ -42,8 +44,8 @@ flags.DEFINE_string('boot_script_template',
class CloudPipe(object):
- def __init__(self, cloud_controller):
- self.controller = cloud_controller
+ def __init__(self):
+ self.controller = cloud.CloudController()
self.manager = manager.AuthManager()
def launch_vpn_instance(self, project_id):
diff --git a/nova/db/api.py b/nova/db/api.py
index a7dd52473..e9ca3df97 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -458,6 +458,44 @@ def export_device_create(context, values):
###################
+def auth_destroy_token(context, token):
+ """Destroy an auth token"""
+ return IMPL.auth_destroy_token(context, token)
+
+def auth_get_token(context, token_hash):
+ """Retrieves a token given the hash representing it"""
+ return IMPL.auth_get_token(context, token_hash)
+
+def auth_create_token(context, token):
+ """Creates a new token"""
+ return IMPL.auth_create_token(context, token_hash, token)
+
+
+###################
+
+
+def quota_create(context, values):
+ """Create a quota from the values dictionary."""
+ return IMPL.quota_create(context, values)
+
+
+def quota_get(context, project_id):
+ """Retrieve a quota or raise if it does not exist."""
+ return IMPL.quota_get(context, project_id)
+
+
+def quota_update(context, project_id, values):
+ """Update a quota from the values dictionary."""
+ return IMPL.quota_update(context, project_id, values)
+
+
+def quota_destroy(context, project_id):
+ """Destroy the quota or raise if it does not exist."""
+ return IMPL.quota_destroy(context, project_id)
+
+
+###################
+
def quota_create(context, values):
"""Create a quota from the values dictionary."""
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index daa9e991e..ef1741dba 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -19,6 +19,8 @@
Implementation of SQLAlchemy backend
"""
+import sys
+
from nova import db
from nova import exception
from nova import flags
@@ -348,6 +350,7 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time):
'time': time.isoformat()})
return result.rowcount
+
def fixed_ip_get_by_address(_context, address):
session = get_session()
result = session.query(models.FixedIp
@@ -359,6 +362,7 @@ def fixed_ip_get_by_address(_context, address):
raise exception.NotFound("No model for address %s" % address)
return result
+
def fixed_ip_get_instance(_context, address):
session = get_session()
with session.begin():
@@ -713,6 +717,29 @@ def export_device_create(_context, values):
###################
+def auth_destroy_token(_context, token):
+ session = get_session()
+ session.delete(token)
+
+def auth_get_token(_context, token_hash):
+ session = get_session()
+ tk = session.query(models.AuthToken
+ ).filter_by(token_hash=token_hash)
+ if not tk:
+ raise exception.NotFound('Token %s does not exist' % token_hash)
+ return tk
+
+def auth_create_token(_context, token):
+ tk = models.AuthToken()
+ for k,v in token.iteritems():
+ tk[k] = v
+ tk.save()
+ return tk
+
+
+###################
+
+
def quota_create(_context, values):
quota_ref = models.Quota()
for (key, value) in values.iteritems():
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index f62b79af8..f6ba7953f 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -214,6 +214,7 @@ class Instance(BASE, NovaBase):
image_id = Column(String(255))
kernel_id = Column(String(255))
ramdisk_id = Column(String(255))
+
# image_id = Column(Integer, ForeignKey('images.id'), nullable=True)
# kernel_id = Column(Integer, ForeignKey('images.id'), nullable=True)
# ramdisk_id = Column(Integer, ForeignKey('images.id'), nullable=True)
@@ -396,6 +397,18 @@ class NetworkIndex(BASE, NovaBase):
network = relationship(Network, backref=backref('network_index',
uselist=False))
+class AuthToken(BASE, NovaBase):
+ """Represents an authorization token for all API transactions. Fields
+ are a string representing the actual token and a user id for mapping
+ to the actual user"""
+ __tablename__ = 'auth_tokens'
+ token_hash = Column(String(255), primary_key=True)
+ user_id = Column(Integer)
+ server_manageent_url = Column(String(255))
+ storage_url = Column(String(255))
+ cdn_management_url = Column(String(255))
+
+
# TODO(vish): can these both come from the same baseclass?
class FixedIp(BASE, NovaBase):
@@ -463,7 +476,8 @@ def register_models():
"""Register Models and create metadata"""
from sqlalchemy import create_engine
models = (Service, Instance, Volume, ExportDevice,
- FixedIp, FloatingIp, Network, NetworkIndex) # , Image, Host)
+ FixedIp, FloatingIp, Network, NetworkIndex,
+ AuthToken) # , Image, Host)
engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models:
model.metadata.create_all(engine)
diff --git a/nova/rpc.py b/nova/rpc.py
index 84a9b5590..6363335ea 100644
--- a/nova/rpc.py
+++ b/nova/rpc.py
@@ -46,9 +46,9 @@ LOG.setLevel(logging.DEBUG)
class Connection(carrot_connection.BrokerConnection):
"""Connection instance object"""
@classmethod
- def instance(cls):
+ def instance(cls, new=False):
"""Returns the instance"""
- if not hasattr(cls, '_instance'):
+ if new or not hasattr(cls, '_instance'):
params = dict(hostname=FLAGS.rabbit_host,
port=FLAGS.rabbit_port,
userid=FLAGS.rabbit_userid,
@@ -60,7 +60,10 @@ class Connection(carrot_connection.BrokerConnection):
# NOTE(vish): magic is fun!
# pylint: disable-msg=W0142
- cls._instance = cls(**params)
+ if new:
+ return cls(**params)
+ else:
+ cls._instance = cls(**params)
return cls._instance
@classmethod
@@ -94,8 +97,6 @@ class Consumer(messaging.Consumer):
injected.start()
return injected
- attachToTornado = attach_to_tornado
-
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
"""Wraps the parent fetch with some logic for failed connections"""
# TODO(vish): the logic for failed connections and logging should be
@@ -265,28 +266,32 @@ def call(topic, msg):
msg.update({'_msg_id': msg_id})
LOG.debug("MSG_ID is %s" % (msg_id))
- conn = Connection.instance()
- d = defer.Deferred()
- consumer = DirectConsumer(connection=conn, msg_id=msg_id)
-
- def deferred_receive(data, message):
- """Acks message and callbacks or errbacks"""
- message.ack()
- if data['failure']:
- return d.errback(RemoteError(*data['failure']))
- else:
- return d.callback(data['result'])
+ class WaitMessage(object):
- consumer.register_callback(deferred_receive)
- injected = consumer.attach_to_tornado()
+ def __call__(self, data, message):
+ """Acks message and sets result."""
+ message.ack()
+ if data['failure']:
+ self.result = RemoteError(*data['failure'])
+ else:
+ self.result = data['result']
- # clean up after the injected listened and return x
- d.addCallback(lambda x: injected.stop() and x or x)
+ wait_msg = WaitMessage()
+ conn = Connection.instance(True)
+ consumer = DirectConsumer(connection=conn, msg_id=msg_id)
+ consumer.register_callback(wait_msg)
+ conn = Connection.instance()
publisher = TopicPublisher(connection=conn, topic=topic)
publisher.send(msg)
publisher.close()
- return d
+
+ try:
+ consumer.wait(limit=1)
+ except StopIteration:
+ pass
+ consumer.close()
+ return wait_msg.result
def cast(topic, msg):
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index 2e6a5a835..c89d25a47 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -42,7 +42,8 @@ class Scheduler(object):
def service_is_up(service):
"""Check whether a service is up based on last heartbeat."""
last_heartbeat = service['updated_at'] or service['created_at']
- elapsed = datetime.datetime.now() - last_heartbeat
+ # Timestamps in DB are UTC.
+ elapsed = datetime.datetime.utcnow() - last_heartbeat
return elapsed < datetime.timedelta(seconds=FLAGS.service_down_time)
def hosts_up(self, context, topic):
diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py
index 59e1683db..c8a49d2ca 100644
--- a/nova/tests/access_unittest.py
+++ b/nova/tests/access_unittest.py
@@ -18,12 +18,13 @@
import unittest
import logging
+import webob
from nova import exception
from nova import flags
from nova import test
+from nova.api import ec2
from nova.auth import manager
-from nova.auth import rbac
FLAGS = flags.FLAGS
@@ -72,9 +73,17 @@ class AccessTestCase(test.BaseTestCase):
try:
self.project.add_role(self.testsys, 'sysadmin')
except: pass
- self.context = Context()
- self.context.project = self.project
#user is set in each test
+ def noopWSGIApp(environ, start_response):
+ start_response('200 OK', [])
+ return ['']
+ self.mw = ec2.Authorizer(noopWSGIApp)
+ self.mw.action_roles = {'str': {
+ '_allow_all': ['all'],
+ '_allow_none': [],
+ '_allow_project_manager': ['projectmanager'],
+ '_allow_sys_and_net': ['sysadmin', 'netadmin'],
+ '_allow_sysadmin': ['sysadmin']}}
def tearDown(self):
um = manager.AuthManager()
@@ -87,76 +96,46 @@ class AccessTestCase(test.BaseTestCase):
um.delete_user('testsys')
super(AccessTestCase, self).tearDown()
+ def response_status(self, user, methodName):
+ context = Context()
+ context.project = self.project
+ context.user = user
+ environ = {'ec2.context' : context,
+ 'ec2.controller': 'some string',
+ 'ec2.action': methodName}
+ req = webob.Request.blank('/', environ)
+ resp = req.get_response(self.mw)
+ return resp.status_int
+
+ def shouldAllow(self, user, methodName):
+ self.assertEqual(200, self.response_status(user, methodName))
+
+ def shouldDeny(self, user, methodName):
+ self.assertEqual(401, self.response_status(user, methodName))
+
def test_001_allow_all(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_all(self.context))
- self.context.user = self.testpmsys
- self.assertTrue(self._allow_all(self.context))
- self.context.user = self.testnet
- self.assertTrue(self._allow_all(self.context))
- self.context.user = self.testsys
- self.assertTrue(self._allow_all(self.context))
+ users = [self.testadmin, self.testpmsys, self.testnet, self.testsys]
+ for user in users:
+ self.shouldAllow(user, '_allow_all')
def test_002_allow_none(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_none(self.context))
- self.context.user = self.testpmsys
- self.assertRaises(exception.NotAuthorized, self._allow_none, self.context)
- self.context.user = self.testnet
- self.assertRaises(exception.NotAuthorized, self._allow_none, self.context)
- self.context.user = self.testsys
- self.assertRaises(exception.NotAuthorized, self._allow_none, self.context)
+ self.shouldAllow(self.testadmin, '_allow_none')
+ users = [self.testpmsys, self.testnet, self.testsys]
+ for user in users:
+ self.shouldDeny(user, '_allow_none')
def test_003_allow_project_manager(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_project_manager(self.context))
- self.context.user = self.testpmsys
- self.assertTrue(self._allow_project_manager(self.context))
- self.context.user = self.testnet
- self.assertRaises(exception.NotAuthorized, self._allow_project_manager, self.context)
- self.context.user = self.testsys
- self.assertRaises(exception.NotAuthorized, self._allow_project_manager, self.context)
+ for user in [self.testadmin, self.testpmsys]:
+ self.shouldAllow(user, '_allow_project_manager')
+ for user in [self.testnet, self.testsys]:
+ self.shouldDeny(user, '_allow_project_manager')
def test_004_allow_sys_and_net(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_sys_and_net(self.context))
- self.context.user = self.testpmsys # doesn't have the per project sysadmin
- self.assertRaises(exception.NotAuthorized, self._allow_sys_and_net, self.context)
- self.context.user = self.testnet
- self.assertTrue(self._allow_sys_and_net(self.context))
- self.context.user = self.testsys
- self.assertTrue(self._allow_sys_and_net(self.context))
-
- def test_005_allow_sys_no_pm(self):
- self.context.user = self.testadmin
- self.assertTrue(self._allow_sys_no_pm(self.context))
- self.context.user = self.testpmsys
- self.assertRaises(exception.NotAuthorized, self._allow_sys_no_pm, self.context)
- self.context.user = self.testnet
- self.assertRaises(exception.NotAuthorized, self._allow_sys_no_pm, self.context)
- self.context.user = self.testsys
- self.assertTrue(self._allow_sys_no_pm(self.context))
-
- @rbac.allow('all')
- def _allow_all(self, context):
- return True
-
- @rbac.allow('none')
- def _allow_none(self, context):
- return True
-
- @rbac.allow('projectmanager')
- def _allow_project_manager(self, context):
- return True
-
- @rbac.allow('sysadmin', 'netadmin')
- def _allow_sys_and_net(self, context):
- return True
-
- @rbac.allow('sysadmin')
- @rbac.deny('projectmanager')
- def _allow_sys_no_pm(self, context):
- return True
+ for user in [self.testadmin, self.testnet, self.testsys]:
+ self.shouldAllow(user, '_allow_sys_and_net')
+ # denied because it doesn't have the per project sysadmin
+ for user in [self.testpmsys]:
+ self.shouldDeny(user, '_allow_sys_and_net')
if __name__ == "__main__":
# TODO: Implement use_fake as an option
diff --git a/nova/tests/api/__init__.py b/nova/tests/api/__init__.py
index 4682c094e..fc1ab9ae2 100644
--- a/nova/tests/api/__init__.py
+++ b/nova/tests/api/__init__.py
@@ -25,6 +25,7 @@ import stubout
import webob
import webob.dec
+import nova.exception
from nova import api
from nova.tests.api.test_helper import *
@@ -36,25 +37,46 @@ class Test(unittest.TestCase):
def tearDown(self): # pylint: disable-msg=C0103
self.stubs.UnsetAll()
+ def _request(self, url, subdomain, **kwargs):
+ environ_keys = {'HTTP_HOST': '%s.example.com' % subdomain}
+ environ_keys.update(kwargs)
+ req = webob.Request.blank(url, environ_keys)
+ return req.get_response(api.API())
+
def test_rackspace(self):
self.stubs.Set(api.rackspace, 'API', APIStub)
- result = webob.Request.blank('/v1.0/cloud').get_response(api.API())
+ result = self._request('/v1.0/cloud', 'rs')
self.assertEqual(result.body, "/cloud")
def test_ec2(self):
self.stubs.Set(api.ec2, 'API', APIStub)
- result = webob.Request.blank('/ec2/cloud').get_response(api.API())
+ result = self._request('/services/cloud', 'ec2')
self.assertEqual(result.body, "/cloud")
def test_not_found(self):
self.stubs.Set(api.ec2, 'API', APIStub)
self.stubs.Set(api.rackspace, 'API', APIStub)
- result = webob.Request.blank('/test/cloud').get_response(api.API())
+ result = self._request('/test/cloud', 'ec2')
self.assertNotEqual(result.body, "/cloud")
def test_query_api_versions(self):
- result = webob.Request.blank('/').get_response(api.API())
+ result = self._request('/', 'rs')
self.assertTrue('CURRENT' in result.body)
+ def test_metadata(self):
+ def go(url):
+ result = self._request(url, 'ec2',
+ REMOTE_ADDR='128.192.151.2')
+ # Each should get to the ORM layer and fail to find the IP
+ self.assertRaises(nova.exception.NotFound, go, '/latest/')
+ self.assertRaises(nova.exception.NotFound, go, '/2009-04-04/')
+ self.assertRaises(nova.exception.NotFound, go, '/1.0/')
+
+ def test_ec2_root(self):
+ result = self._request('/', 'ec2')
+ self.assertTrue('2007-12-15\n' in result.body)
+
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/nova/tests/api/rackspace/auth.py b/nova/tests/api/rackspace/auth.py
new file mode 100644
index 000000000..429c22ad2
--- /dev/null
+++ b/nova/tests/api/rackspace/auth.py
@@ -0,0 +1,106 @@
+import webob
+import webob.dec
+import unittest
+import stubout
+import nova.api
+import nova.api.rackspace.auth
+from nova import auth
+from nova.tests.api.rackspace import test_helper
+import datetime
+
+class Test(unittest.TestCase):
+ def setUp(self):
+ self.stubs = stubout.StubOutForTesting()
+ self.stubs.Set(nova.api.rackspace.auth.BasicApiAuthManager,
+ '__init__', test_helper.fake_auth_init)
+ test_helper.FakeAuthManager.auth_data = {}
+ test_helper.FakeAuthDatabase.data = {}
+ self.stubs.Set(nova.api.rackspace, 'RateLimitingMiddleware',
+ test_helper.FakeRateLimiter)
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ test_helper.fake_data_store = {}
+
+ def test_authorize_user(self):
+ f = test_helper.FakeAuthManager()
+ f.add_user('derp', { 'uid': 1, 'name':'herp' } )
+
+ req = webob.Request.blank('/v1.0/')
+ req.headers['X-Auth-User'] = 'herp'
+ req.headers['X-Auth-Key'] = 'derp'
+ result = req.get_response(nova.api.API())
+ self.assertEqual(result.status, '204 No Content')
+ self.assertEqual(len(result.headers['X-Auth-Token']), 40)
+ self.assertEqual(result.headers['X-CDN-Management-Url'],
+ "")
+ self.assertEqual(result.headers['X-Storage-Url'], "")
+
+ def test_authorize_token(self):
+ f = test_helper.FakeAuthManager()
+ f.add_user('derp', { 'uid': 1, 'name':'herp' } )
+
+ req = webob.Request.blank('/v1.0/')
+ req.headers['X-Auth-User'] = 'herp'
+ req.headers['X-Auth-Key'] = 'derp'
+ result = req.get_response(nova.api.API())
+ self.assertEqual(result.status, '204 No Content')
+ self.assertEqual(len(result.headers['X-Auth-Token']), 40)
+ self.assertEqual(result.headers['X-Server-Management-Url'],
+ "https://foo/v1.0/")
+ self.assertEqual(result.headers['X-CDN-Management-Url'],
+ "")
+ self.assertEqual(result.headers['X-Storage-Url'], "")
+
+ token = result.headers['X-Auth-Token']
+ self.stubs.Set(nova.api.rackspace, 'APIRouter',
+ test_helper.FakeRouter)
+ req = webob.Request.blank('/v1.0/fake')
+ req.headers['X-Auth-Token'] = token
+ result = req.get_response(nova.api.API())
+ self.assertEqual(result.status, '200 OK')
+ self.assertEqual(result.headers['X-Test-Success'], 'True')
+
+ def test_token_expiry(self):
+ self.destroy_called = False
+ token_hash = 'bacon'
+
+ def destroy_token_mock(meh, context, token):
+ self.destroy_called = True
+
+ def bad_token(meh, context, token_hash):
+ return { 'token_hash':token_hash,
+ 'created_at':datetime.datetime(1990, 1, 1) }
+
+ self.stubs.Set(test_helper.FakeAuthDatabase, 'auth_destroy_token',
+ destroy_token_mock)
+
+ self.stubs.Set(test_helper.FakeAuthDatabase, 'auth_get_token',
+ bad_token)
+
+ req = webob.Request.blank('/v1.0/')
+ req.headers['X-Auth-Token'] = 'bacon'
+ result = req.get_response(nova.api.API())
+ self.assertEqual(result.status, '401 Unauthorized')
+ self.assertEqual(self.destroy_called, True)
+
+ def test_bad_user(self):
+ req = webob.Request.blank('/v1.0/')
+ req.headers['X-Auth-User'] = 'herp'
+ req.headers['X-Auth-Key'] = 'derp'
+ result = req.get_response(nova.api.API())
+ self.assertEqual(result.status, '401 Unauthorized')
+
+ def test_no_user(self):
+ req = webob.Request.blank('/v1.0/')
+ result = req.get_response(nova.api.API())
+ self.assertEqual(result.status, '401 Unauthorized')
+
+ def test_bad_token(self):
+ req = webob.Request.blank('/v1.0/')
+ req.headers['X-Auth-Token'] = 'baconbaconbacon'
+ result = req.get_response(nova.api.API())
+ self.assertEqual(result.status, '401 Unauthorized')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py
new file mode 100644
index 000000000..be14e2de8
--- /dev/null
+++ b/nova/tests/api/rackspace/test_helper.py
@@ -0,0 +1,65 @@
+import webob
+import webob.dec
+import datetime
+from nova.wsgi import Router
+from nova import auth
+
+class Context(object):
+ pass
+
+class FakeRouter(Router):
+ def __init__(self):
+ pass
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ res = webob.Response()
+ res.status = '200'
+ res.headers['X-Test-Success'] = 'True'
+ return res
+
+def fake_auth_init(self):
+ self.db = FakeAuthDatabase()
+ self.context = Context()
+ self.auth = FakeAuthManager()
+ self.host = 'foo'
+
+class FakeAuthDatabase(object):
+ data = {}
+
+ @staticmethod
+ def auth_get_token(context, token_hash):
+ return FakeAuthDatabase.data.get(token_hash, None)
+
+ @staticmethod
+ def auth_create_token(context, token):
+ token['created_at'] = datetime.datetime.now()
+ FakeAuthDatabase.data[token['token_hash']] = token
+
+ @staticmethod
+ def auth_destroy_token(context, token):
+ if FakeAuthDatabase.data.has_key(token['token_hash']):
+ del FakeAuthDatabase.data['token_hash']
+
+class FakeAuthManager(object):
+ auth_data = {}
+
+ def add_user(self, key, user):
+ FakeAuthManager.auth_data[key] = user
+
+ def get_user(self, uid):
+ for k, v in FakeAuthManager.auth_data.iteritems():
+ if v['uid'] == uid:
+ return v
+ return None
+
+ def get_user_from_access_key(self, key):
+ return FakeAuthManager.auth_data.get(key, None)
+
+class FakeRateLimiter(object):
+ def __init__(self, application):
+ self.application = application
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ return self.application
diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py
index 0732c39bb..c040cdad3 100644
--- a/nova/tests/api_unittest.py
+++ b/nova/tests/api_unittest.py
@@ -25,12 +25,17 @@ import random
import StringIO
import webob
+from nova import flags
from nova import test
from nova import api
from nova.api.ec2 import cloud
from nova.auth import manager
+FLAGS = flags.FLAGS
+FLAGS.FAKE_subdomain = 'ec2'
+
+
class FakeHttplibSocket(object):
"""a fake socket implementation for httplib.HTTPResponse, trivial"""
def __init__(self, response_string):
diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py
index 2f22982eb..756ce519e 100644
--- a/nova/tests/cloud_unittest.py
+++ b/nova/tests/cloud_unittest.py
@@ -22,7 +22,6 @@ from M2Crypto import RSA
import StringIO
import time
-from tornado import ioloop
from twisted.internet import defer
import unittest
from xml.etree import ElementTree
diff --git a/run_tests.py b/run_tests.py
index bea97c0b3..4121f4c06 100644
--- a/run_tests.py
+++ b/run_tests.py
@@ -49,8 +49,7 @@ from nova import datastore
from nova import flags
from nova import twistd
-#TODO(gundlach): rewrite and readd this after merge
-#from nova.tests.access_unittest import *
+from nova.tests.access_unittest import *
from nova.tests.auth_unittest import *
from nova.tests.api_unittest import *
from nova.tests.cloud_unittest import *