65
66
return self.application
69
class RateLimitingMiddleware(wsgi.Middleware):
70
"""Rate limit incoming requests according to the OpenStack rate limits."""
72
def __init__(self, application, service_host=None):
73
"""Create a rate limiting middleware that wraps the given application.
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.
79
super(RateLimitingMiddleware, self).__init__(application)
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),
91
self.limiter = ratelimiting.WSGIAppProxy(service_host)
94
def __call__(self, req):
95
"""Rate limit the request.
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.
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)
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
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.
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)
122
return self.limiter.perform(action_name, username=username)
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']:
68
135
class APIRouter(wsgi.Router):
70
137
Routes requests on the Rackspace API to the appropriate controller