~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to nova/api/openstack/ratelimiting/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2011-01-21 11:48:06 UTC
  • mto: This revision was merged to the branch mainline in revision 9.
  • Revision ID: james.westby@ubuntu.com-20110121114806-v8fvnnl6az4m4ohv
Tags: upstream-2011.1~bzr597
ImportĀ upstreamĀ versionĀ 2011.1~bzr597

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2010 OpenStack LLC.
 
4
# All Rights Reserved.
 
5
#
 
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
 
9
#
 
10
#         http://www.apache.org/licenses/LICENSE-2.0
 
11
#
 
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
 
17
 
1
18
"""Rate limiting of arbitrary actions."""
2
19
 
3
20
import httplib
6
23
import webob.dec
7
24
import webob.exc
8
25
 
 
26
from nova import wsgi
 
27
from nova.api.openstack import faults
9
28
 
10
29
# Convenience constants for the limits dictionary passed to Limiter().
11
30
PER_SECOND = 1
14
33
PER_DAY = 60 * 60 * 24
15
34
 
16
35
 
 
36
class RateLimitingMiddleware(wsgi.Middleware):
 
37
    """Rate limit incoming requests according to the OpenStack rate limits."""
 
38
 
 
39
    def __init__(self, application, service_host=None):
 
40
        """Create a rate limiting middleware that wraps the given application.
 
41
 
 
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.
 
45
        """
 
46
        if not service_host:
 
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),
 
55
                })
 
56
        else:
 
57
            self.limiter = WSGIAppProxy(service_host)
 
58
        super(RateLimitingMiddleware, self).__init__(application)
 
59
 
 
60
    @webob.dec.wsgify
 
61
    def __call__(self, req):
 
62
        """Rate limit the request.
 
63
 
 
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.
 
66
        """
 
67
        return self.rate_limited_request(req, self.application)
 
68
 
 
69
    def rate_limited_request(self, req, application):
 
70
        """Rate limit the request.
 
71
 
 
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.
 
74
        """
 
75
        action_name = self.get_action_name(req)
 
76
        if not action_name:
 
77
            # Not rate limited
 
78
            return application
 
79
        delay = self.get_delay(action_name,
 
80
            req.environ['nova.context'].user_id)
 
81
        if delay:
 
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)
 
87
        return application
 
88
 
 
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.
 
92
        """
 
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)
 
98
            if delay:
 
99
                return delay
 
100
        return self.limiter.perform(action_name, username=username)
 
101
 
 
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']:
 
109
            return req.method
 
110
        return None
 
111
 
 
112
 
17
113
class Limiter(object):
18
114
 
19
115
    """Class providing rate limiting of arbitrary actions."""