1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2010 OpenStack LLC.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
16
# under the License.import datetime
1
18
"""Rate limiting of arbitrary actions."""
14
33
PER_DAY = 60 * 60 * 24
36
class RateLimitingMiddleware(wsgi.Middleware):
37
"""Rate limit incoming requests according to the OpenStack rate limits."""
39
def __init__(self, application, service_host=None):
40
"""Create a rate limiting middleware that wraps the given application.
42
By default, rate counters are stored in memory. If service_host is
43
specified, the middleware instead relies on the ratelimiting.WSGIApp
44
at the given host+port to keep rate counters.
47
#TODO(gundlach): These limits were based on limitations of Cloud
48
#Servers. We should revisit them in Nova.
49
self.limiter = Limiter(limits={
50
'DELETE': (100, PER_MINUTE),
51
'PUT': (10, PER_MINUTE),
52
'POST': (10, PER_MINUTE),
53
'POST servers': (50, PER_DAY),
54
'GET changes-since': (3, PER_MINUTE),
57
self.limiter = WSGIAppProxy(service_host)
58
super(RateLimitingMiddleware, self).__init__(application)
61
def __call__(self, req):
62
"""Rate limit the request.
64
If the request should be rate limited, return a 413 status with a
65
Retry-After header giving the time when the request would succeed.
67
return self.rate_limited_request(req, self.application)
69
def rate_limited_request(self, req, application):
70
"""Rate limit the request.
72
If the request should be rate limited, return a 413 status with a
73
Retry-After header giving the time when the request would succeed.
75
action_name = self.get_action_name(req)
79
delay = self.get_delay(action_name,
80
req.environ['nova.context'].user_id)
82
# TODO(gundlach): Get the retry-after format correct.
83
exc = webob.exc.HTTPRequestEntityTooLarge(
84
explanation=('Too many requests.'),
85
headers={'Retry-After': time.time() + delay})
86
raise faults.Fault(exc)
89
def get_delay(self, action_name, username):
90
"""Return the delay for the given action and username, or None if
91
the action would not be rate limited.
93
if action_name == 'POST servers':
94
# "POST servers" is a POST, so it counts against "POST" too.
95
# Attempt the "POST" first, lest we are rate limited by "POST" but
96
# use up a precious "POST servers" call.
97
delay = self.limiter.perform("POST", username=username)
100
return self.limiter.perform(action_name, username=username)
102
def get_action_name(self, req):
103
"""Return the action name for this request."""
104
if req.method == 'GET' and 'changes-since' in req.GET:
105
return 'GET changes-since'
106
if req.method == 'POST' and req.path_info.startswith('/servers'):
107
return 'POST servers'
108
if req.method in ['PUT', 'POST', 'DELETE']:
17
113
class Limiter(object):
19
115
"""Class providing rate limiting of arbitrary actions."""