summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCerberus <matt.dietz@rackspace.com>2010-12-16 12:09:38 -0600
committerCerberus <matt.dietz@rackspace.com>2010-12-16 12:09:38 -0600
commite01e6d7976adfd99addf31f4f914c7625a394fda (patch)
treeb2890d869165af78f0b7cb2806f46b246e088f90
parentedff8090714f11305d6dbc091a96eece20f0d1a1 (diff)
downloadnova-e01e6d7976adfd99addf31f4f914c7625a394fda.tar.gz
nova-e01e6d7976adfd99addf31f4f914c7625a394fda.tar.xz
nova-e01e6d7976adfd99addf31f4f914c7625a394fda.zip
Moved implementation specific stuff from the middleware into their respective modules
-rw-r--r--nova/api/openstack/__init__.py83
-rw-r--r--nova/api/openstack/auth.py20
-rw-r--r--nova/api/openstack/common.py17
-rw-r--r--nova/api/openstack/flavors.py3
-rw-r--r--nova/api/openstack/images.py6
-rw-r--r--nova/api/openstack/ratelimiting/__init__.py60
-rw-r--r--nova/api/openstack/servers.py3
7 files changed, 106 insertions, 86 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index b9ecbd9b8..e941694d9 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -49,6 +49,10 @@ flags.DEFINE_string('nova_api_auth',
'nova.api.openstack.auth.BasicApiAuthManager',
'The auth mechanism to use for the OpenStack API implemenation')
+flags.DEFINE_string('os_api_ratelimiting',
+ 'nova.api.openstack.ratelimiting.BasicRateLimiting',
+ 'Default ratelimiting implementation for the Openstack API')
+
flags.DEFINE_bool('allow_admin_api',
False,
'When True, this API service will accept admin operations.')
@@ -81,10 +85,10 @@ class AuthMiddleware(wsgi.Middleware):
@webob.dec.wsgify
def __call__(self, req):
- if 'X-Auth-Token' not in req.headers:
+ if not self.auth_driver.has_authentication(req)
return self.auth_driver.authenticate(req)
- user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"])
+ user = self.auth_driver.get_user_by_authentication(req)
if not user:
return faults.Fault(webob.exc.HTTPUnauthorized())
@@ -104,62 +108,12 @@ class RateLimitingMiddleware(wsgi.Middleware):
at the given host+port to keep rate counters.
"""
super(RateLimitingMiddleware, self).__init__(application)
- if not service_host:
- #TODO(gundlach): These limits were based on limitations of Cloud
- #Servers. We should revisit them in Nova.
- self.limiter = ratelimiting.Limiter(limits={
- 'DELETE': (100, ratelimiting.PER_MINUTE),
- 'PUT': (10, ratelimiting.PER_MINUTE),
- 'POST': (10, ratelimiting.PER_MINUTE),
- 'POST servers': (50, ratelimiting.PER_DAY),
- 'GET changes-since': (3, ratelimiting.PER_MINUTE),
- })
- else:
- self.limiter = ratelimiting.WSGIAppProxy(service_host)
+ self._limiting_driver =
+ utils.import_class(FLAGS.os_api_ratelimiting)(service_host)
@webob.dec.wsgify
def __call__(self, req):
- """Rate limit the request.
-
- If the request should be rate limited, return a 413 status with a
- Retry-After header giving the time when the request would succeed.
- """
- action_name = self.get_action_name(req)
- if not action_name:
- # Not rate limited
- return self.application
- delay = self.get_delay(action_name,
- req.environ['nova.context'].user_id)
- if delay:
- # TODO(gundlach): Get the retry-after format correct.
- exc = webob.exc.HTTPRequestEntityTooLarge(
- explanation='Too many requests.',
- headers={'Retry-After': time.time() + delay})
- raise faults.Fault(exc)
- return self.application
-
- def get_delay(self, action_name, username):
- """Return the delay for the given action and username, or None if
- the action would not be rate limited.
- """
- if action_name == 'POST servers':
- # "POST servers" is a POST, so it counts against "POST" too.
- # Attempt the "POST" first, lest we are rate limited by "POST" but
- # use up a precious "POST servers" call.
- delay = self.limiter.perform("POST", username=username)
- if delay:
- return delay
- return self.limiter.perform(action_name, username=username)
-
- def get_action_name(self, req):
- """Return the action name for this request."""
- if req.method == 'GET' and 'changes-since' in req.GET:
- return 'GET changes-since'
- if req.method == 'POST' and req.path_info.startswith('/servers'):
- return 'POST servers'
- if req.method in ['PUT', 'POST', 'DELETE']:
- return req.method
- return None
+ return self._limiting_driver.limited_request(req)
class APIRouter(wsgi.Router):
@@ -191,22 +145,3 @@ class APIRouter(wsgi.Router):
# TODO: Place routes for admin operations here.
super(APIRouter, self).__init__(mapper)
-
-
-def limited(items, req):
- """Return a slice of items according to requested offset and limit.
-
- items - a sliceable
- req - wobob.Request possibly containing offset and limit GET variables.
- offset is where to start in the list, and limit is the maximum number
- of items to return.
-
- If limit is not specified, 0, or > 1000, defaults to 1000.
- """
- offset = int(req.GET.get('offset', 0))
- limit = int(req.GET.get('limit', 0))
- if not limit:
- limit = 1000
- limit = min(1000, limit)
- range_end = offset + limit
- return items[offset:range_end]
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index fcda97ab1..da8ebcfcd 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -7,6 +7,7 @@ import webob.exc
import webob.dec
from nova import auth
+from nova import context
from nova import db
from nova import flags
from nova import manager
@@ -16,10 +17,6 @@ from nova.api.openstack import faults
FLAGS = flags.FLAGS
-class Context(object):
- pass
-
-
class BasicApiAuthManager(object):
""" Implements a somewhat rudimentary version of OpenStack Auth"""
@@ -28,9 +25,14 @@ class BasicApiAuthManager(object):
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 has_authentication(self, req):
+ return 'X-Auth-Token' in req.headers:
+
+ def get_user_by_authentication(self, req):
+ return self.auth_driver.authorize_token(req.headers["X-Auth-Token"])
+
def authenticate(self, req):
# Unless the request is explicitly made against /<version>/ don't
# honor it
@@ -68,11 +70,12 @@ class BasicApiAuthManager(object):
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)
+ ctxt = context.get_admin_context()
+ token = self.db.auth_get_token(ctxt, token_hash)
if token:
delta = datetime.datetime.now() - token.created_at
if delta.days >= 2:
- self.db.auth_destroy_token(self.context, token)
+ self.db.auth_destroy_token(ctxt, token)
else:
return self.auth.get_user(token.user_id)
return None
@@ -84,6 +87,7 @@ class BasicApiAuthManager(object):
key - string API key
req - webob.Request object
"""
+ ctxt = context.get_admin_context()
user = self.auth.get_user_from_access_key(key)
if user and user.name == username:
token_hash = hashlib.sha1('%s%s%f' % (username, key,
@@ -95,6 +99,6 @@ class BasicApiAuthManager(object):
token_dict['server_management_url'] = req.url
token_dict['storage_url'] = ''
token_dict['user_id'] = user.id
- token = self.db.auth_create_token(self.context, token_dict)
+ token = self.db.auth_create_token(ctxt, token_dict)
return token, user
return None, None
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
new file mode 100644
index 000000000..29e9a8623
--- /dev/null
+++ b/nova/api/openstack/common.py
@@ -0,0 +1,17 @@
+def limited(items, req):
+ """Return a slice of items according to requested offset and limit.
+
+ items - a sliceable
+ req - wobob.Request possibly containing offset and limit GET variables.
+ offset is where to start in the list, and limit is the maximum number
+ of items to return.
+
+ If limit is not specified, 0, or > 1000, defaults to 1000.
+ """
+ offset = int(req.GET.get('offset', 0))
+ limit = int(req.GET.get('limit', 0))
+ if not limit:
+ limit = 1000
+ limit = min(1000, limit)
+ range_end = offset + limit
+ return items[offset:range_end]
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index f23f74fd1..f620d4107 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -18,6 +18,7 @@
from webob import exc
from nova.api.openstack import faults
+from nova.api.openstack import common
from nova.compute import instance_types
from nova import wsgi
import nova.api.openstack
@@ -39,7 +40,7 @@ class Controller(wsgi.Controller):
def detail(self, req):
"""Return all flavors in detail."""
items = [self.show(req, id)['flavor'] for id in self._all_ids()]
- items = nova.api.openstack.limited(items, req)
+ items = common.limited(items, req)
return dict(flavors=items)
def show(self, req, id):
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 4a0a8e6f1..fe8d9d75f 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -22,6 +22,8 @@ from nova import utils
from nova import wsgi
import nova.api.openstack
import nova.image.service
+
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -48,11 +50,11 @@ class Controller(wsgi.Controller):
"""Return all public images in detail."""
try:
images = self._service.detail(req.environ['nova.context'])
- images = nova.api.openstack.limited(images, req)
+ images = common.limited(images, req)
except NotImplementedError:
# Emulate detail() using repeated calls to show()
images = self._service.index(ctxt)
- images = nova.api.openstack.limited(images, req)
+ images = common.limited(images, req)
images = [self._service.show(ctxt, i['id']) for i in images]
return dict(images=images)
diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py
index 918caf055..d1da9afa7 100644
--- a/nova/api/openstack/ratelimiting/__init__.py
+++ b/nova/api/openstack/ratelimiting/__init__.py
@@ -14,6 +14,66 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24
+class BasicRateLimiting(object):
+ """ Implements Rate limits as per the Rackspace CloudServers API spec. """
+
+ def __init__(self, service_host):
+ if not service_host:
+ #TODO(gundlach): These limits were based on limitations of Cloud
+ #Servers. We should revisit them in Nova.
+ self.limiter = ratelimiting.Limiter(limits={
+ 'DELETE': (100, ratelimiting.PER_MINUTE),
+ 'PUT': (10, ratelimiting.PER_MINUTE),
+ 'POST': (10, ratelimiting.PER_MINUTE),
+ 'POST servers': (50, ratelimiting.PER_DAY),
+ 'GET changes-since': (3, ratelimiting.PER_MINUTE),
+ })
+ else:
+ self.limiter = ratelimiting.WSGIAppProxy(service_host)
+
+ def limited_request(self, req):
+ """Rate limit the request.
+
+ If the request should be rate limited, return a 413 status with a
+ Retry-After header giving the time when the request would succeed.
+ """
+ action_name = self.get_action_name(req)
+ if not action_name:
+ # Not rate limited
+ return self.application
+ delay = self.get_delay(action_name,
+ req.environ['nova.context'].user_id)
+ if delay:
+ # TODO(gundlach): Get the retry-after format correct.
+ exc = webob.exc.HTTPRequestEntityTooLarge(
+ explanation='Too many requests.',
+ headers={'Retry-After': time.time() + delay})
+ raise faults.Fault(exc)
+ return self.application
+
+ def get_delay(self, action_name, username):
+ """Return the delay for the given action and username, or None if
+ the action would not be rate limited.
+ """
+ if action_name == 'POST servers':
+ # "POST servers" is a POST, so it counts against "POST" too.
+ # Attempt the "POST" first, lest we are rate limited by "POST" but
+ # use up a precious "POST servers" call.
+ delay = self.limiter.perform("POST", username=username)
+ if delay:
+ return delay
+ return self.limiter.perform(action_name, username=username)
+
+ def get_action_name(self, req):
+ """Return the action name for this request."""
+ if req.method == 'GET' and 'changes-since' in req.GET:
+ return 'GET changes-since'
+ if req.method == 'POST' and req.path_info.startswith('/servers'):
+ return 'POST servers'
+ if req.method in ['PUT', 'POST', 'DELETE']:
+ return req.method
+ return None
+
class Limiter(object):
"""Class providing rate limiting of arbitrary actions."""
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 7704f48f1..9e6047805 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -19,6 +19,7 @@ from webob import exc
from nova import exception
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
from nova.auth import manager as auth_manager
from nova.compute import api as compute_api
@@ -91,7 +92,7 @@ class Controller(wsgi.Controller):
"""
instance_list = self.compute_api.get_instances(
req.environ['nova.context'])
- limited_list = nova.api.openstack.limited(instance_list, req)
+ limited_list = common.limited(instance_list, req)
res = [entity_maker(inst)['server'] for inst in limited_list]
return _entity_list(res)