From: S.Çağlar Onur Date: Tue, 9 Nov 2010 20:40:55 +0000 (-0500) Subject: Use memcached for rate-limiting the API calls. X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=cdcd6351525acffb778d17658838e05aa4577534;p=plcapi.git Use memcached for rate-limiting the API calls. On every request we work out the keys for the past five minutes and use get_multi to retrieve them. If the sum of those counters exceeds the maximum allowed for that time period, we block the request. Ex; ratelimit_128.112.139.115_201011091532 = 1 ratelimit_128.112.139.115_201011091533 = 14 ratelimit_128.112.139.115_201011091534 = 11 [...] [{'hostname': 'aspect.cs.princeton.edu'}, {'hostname': 'func.cs.princeton.edu'}] 8 [{'hostname': 'aspect.cs.princeton.edu'}, {'hostname': 'func.cs.princeton.edu'}] 9 Traceback (most recent call last): File "test.py", line 16, in local_nodes = plc.GetNodes(auth, {}, ["hostname"]) File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/xmlrpclib.py", line 1199, in __call__ return self.__send(self.__name, args) File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/xmlrpclib.py", line 1489, in __request verbose=self.__verbose File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/xmlrpclib.py", line 1253, in request return self._parse_response(h.getfile(), sock) File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/xmlrpclib.py", line 1392, in _parse_response return u.close() File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/xmlrpclib.py", line 838, in close raise Fault(**self._stack[0]) xmlrpclib.Fault: --- diff --git a/aspects/__init__.py b/aspects/__init__.py index 1df5a9f0..cdab6d60 100644 --- a/aspects/__init__.py +++ b/aspects/__init__.py @@ -1,16 +1,13 @@ - - from pyaspects.weaver import weave_class_method from PLC.Method import Method from aspects.omfaspects import OMFAspect - - +from aspects.ratelimitaspects import RateLimitAspect def apply_omf_aspect(): # track all PLC methods to add OMF hooks weave_class_method(OMFAspect(), Method, "__call__") - + weave_class_method(RateLimitAspect(), Method, "__call__") def apply_debugger_aspect(): # just log all method calls w/ their parameters diff --git a/aspects/ratelimitaspects.py b/aspects/ratelimitaspects.py new file mode 100644 index 00000000..03465675 --- /dev/null +++ b/aspects/ratelimitaspects.py @@ -0,0 +1,66 @@ +from PLC.Config import Config +from PLC.Faults import * + +from datetime import datetime, timedelta +from pyaspects.meta import MetaAspect +import memcache + +class BaseRateLimit(object): + + def __init__(self): + self.config = Config("/etc/planetlab/plc_config") + + self.prefix = "ratelimit" + self.minutes = 3 # The time period + self.requests = 10 # Number of allowed requests in that time period + self.expire_after = (self.minutes + 1) * 60 + + def before(self, wobj, data, *args, **kwargs): + # ratelimit_128.112.139.115_201011091532 = 1 + # ratelimit_128.112.139.115_201011091533 = 14 + # ratelimit_128.112.139.115_201011091534 = 11 + # Now, on every request we work out the keys for the past five minutes and use get_multi to retrieve them. + # If the sum of those counters exceeds the maximum allowed for that time period, we block the request. + + api_method_name = wobj.name + api_method_source = wobj.source + + if api_method_source == None or api_method_source[0] == self.config.PLC_API_HOST: + return + + mc = memcache.Client(['%s:11211'] % self.config.PLC_API_HOST) + now = datetime.now() + current_key = '%s_%s_%s' % (self.prefix, api_method_source[0], now.strftime('%Y%m%d%H%M')) + + keys_to_check = ['%s_%s_%s' % (self.prefix, api_method_source[0], (now - timedelta(minutes = minute)).strftime('%Y%m%d%H%M')) for minute in range(self.minutes + 1)] + + try: + mc.incr(current_key) + except ValueError: + mc.set(current_key, 1, time=self.expire_after) + + result = mc.get_multi(keys_to_check) + total_requests = 0 + for i in result: + total_requests += result[i] + + if total_requests > self.requests: + raise PLCPermissionDenied, "Maximum allowed number of API calls exceeded" + + def after(self, wobj, data, *args, **kwargs): + return + +class RateLimitAspect_class(BaseRateLimit): + __metaclass__ = MetaAspect + name = "ratelimitaspect_class" + + def __init__(self): + BaseRateLimit.__init__(self) + + def before(self, wobj, data, *args, **kwargs): + BaseRateLimit.before(self, wobj, data, *args, **kwargs) + + def after(self, wobj, data, *args, **kwargs): + BaseRateLimit.after(self, wobj, data, *args, **kwargs) + +RateLimitAspect = RateLimitAspect_class