~vishvananda/nova/quotas

« back to all changes in this revision

Viewing changes to nova/api/rackspace/__init__.py

  • Committer: Vishvananda Ishaya
  • Date: 2010-09-21 02:19:28 UTC
  • mfrom: (237.1.47 trunk)
  • Revision ID: vishvananda@yahoo.com-20100921021928-vfawa17sn4s8y06e
merged trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
from nova import wsgi
32
32
from nova.api.rackspace import flavors
33
33
from nova.api.rackspace import images
 
34
from nova.api.rackspace import ratelimiting
34
35
from nova.api.rackspace import servers
35
36
from nova.api.rackspace import sharedipgroups
36
37
from nova.auth import manager
40
41
    """WSGI entry point for all Rackspace API requests."""
41
42
 
42
43
    def __init__(self):
43
 
        app = AuthMiddleware(APIRouter())
 
44
        app = AuthMiddleware(RateLimitingMiddleware(APIRouter()))
44
45
        super(API, self).__init__(app)
45
46
 
46
47
 
65
66
        return self.application
66
67
 
67
68
 
 
69
class RateLimitingMiddleware(wsgi.Middleware):
 
70
    """Rate limit incoming requests according to the OpenStack rate limits."""
 
71
 
 
72
    def __init__(self, application, service_host=None):
 
73
        """Create a rate limiting middleware that wraps the given application.
 
74
 
 
75
        By default, rate counters are stored in memory.  If service_host is
 
76
        specified, the middleware instead relies on the ratelimiting.WSGIApp
 
77
        at the given host+port to keep rate counters.
 
78
        """
 
79
        super(RateLimitingMiddleware, self).__init__(application)
 
80
        if not service_host:
 
81
            #TODO(gundlach): These limits were based on limitations of Cloud 
 
82
            #Servers.  We should revisit them in Nova.
 
83
            self.limiter = ratelimiting.Limiter(limits={
 
84
                    'DELETE': (100, ratelimiting.PER_MINUTE),
 
85
                    'PUT': (10, ratelimiting.PER_MINUTE),
 
86
                    'POST': (10, ratelimiting.PER_MINUTE),
 
87
                    'POST servers': (50, ratelimiting.PER_DAY),
 
88
                    'GET changes-since': (3, ratelimiting.PER_MINUTE),
 
89
                })
 
90
        else:
 
91
            self.limiter = ratelimiting.WSGIAppProxy(service_host)
 
92
 
 
93
    @webob.dec.wsgify
 
94
    def __call__(self, req):
 
95
        """Rate limit the request.
 
96
        
 
97
        If the request should be rate limited, return a 413 status with a 
 
98
        Retry-After header giving the time when the request would succeed.
 
99
        """
 
100
        username = req.headers['X-Auth-User']
 
101
        action_name = self.get_action_name(req)
 
102
        if not action_name: # not rate limited
 
103
            return self.application
 
104
        delay = self.get_delay(action_name, username)
 
105
        if delay:
 
106
            # TODO(gundlach): Get the retry-after format correct.
 
107
            raise webob.exc.HTTPRequestEntityTooLarge(headers={
 
108
                    'Retry-After': time.time() + delay})
 
109
        return self.application
 
110
 
 
111
    def get_delay(self, action_name, username):
 
112
        """Return the delay for the given action and username, or None if
 
113
        the action would not be rate limited.
 
114
        """
 
115
        if action_name == 'POST servers':
 
116
            # "POST servers" is a POST, so it counts against "POST" too.
 
117
            # Attempt the "POST" first, lest we are rate limited by "POST" but
 
118
            # use up a precious "POST servers" call.
 
119
            delay = self.limiter.perform("POST", username=username)
 
120
            if delay:
 
121
                return delay
 
122
        return self.limiter.perform(action_name, username=username)
 
123
 
 
124
    def get_action_name(self, req):
 
125
        """Return the action name for this request."""
 
126
        if req.method == 'GET' and 'changes-since' in req.GET:
 
127
            return 'GET changes-since'
 
128
        if req.method == 'POST' and req.path_info.startswith('/servers'):
 
129
            return 'POST servers'
 
130
        if req.method in ['PUT', 'POST', 'DELETE']:
 
131
            return req.method
 
132
        return None
 
133
 
 
134
 
68
135
class APIRouter(wsgi.Router):
69
136
    """
70
137
    Routes requests on the Rackspace API to the appropriate controller