Use memcached for rate-limiting the API calls.
authorS.Çağlar Onur <caglar@cs.princeton.edu>
Tue, 9 Nov 2010 20:40:55 +0000 (15:40 -0500)
committerS.Çağlar Onur <caglar@cs.princeton.edu>
Tue, 9 Nov 2010 20:40:55 +0000 (15:40 -0500)
   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 <module>
    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: <Fault 108: 'Permission denied: Maximum allowed number of API calls exceeded'>

aspects/__init__.py
aspects/ratelimitaspects.py [new file with mode: 0644]

index 1df5a9f..cdab6d6 100644 (file)
@@ -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 (file)
index 0000000..0346567
--- /dev/null
@@ -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