add notification support
authorS.Çağlar Onur <caglar@cs.princeton.edu>
Thu, 2 Dec 2010 20:00:28 +0000 (15:00 -0500)
committerS.Çağlar Onur <caglar@cs.princeton.edu>
Thu, 2 Dec 2010 20:00:28 +0000 (15:00 -0500)
aspects/ratelimitaspects.py

index 84af023..e95764f 100644 (file)
@@ -6,12 +6,19 @@
 from PLC.Config import Config
 from PLC.Faults import PLCPermissionDenied
 
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+from PLC.Sessions import Session, Sessions
+
 from datetime import datetime, timedelta
 
 from pyaspects.meta import MetaAspect
 
 import memcache
 
+import os
+import sys
+
 class BaseRateLimit(object):
 
     def __init__(self):
@@ -31,6 +38,35 @@ class BaseRateLimit(object):
         log.write("%s - %s\n" % (date, line))
         log.flush()
 
+    def mail(self, to):
+        sendmail = os.popen("/usr/sbin/sendmail -N never -t -f%s" % self.config.PLC_MAIL_SUPPORT_ADDRESS, "w")
+
+        subject = "[PLCAPI] Maximum allowed number of API calls exceeded"
+
+        header = {'from': "%s Support <%s>" % (self.config.PLC_NAME, self.config.PLC_MAIL_SUPPORT_ADDRESS),
+               'to': "%s, %s" % (to, self.config.PLC_MAIL_SUPPORT_ADDRESS),
+               'version': sys.version.split(" ")[0],
+               'subject': subject}
+
+        body = "Maximum allowed number of API calls exceeded for the user %s within the last %s minutes." % (to, self.minutes)
+
+        # Write headers
+        sendmail.write(
+"""
+Content-type: text/plain
+From: %(from)s
+Reply-To: %(from)s
+To: %(to)s
+X-Mailer: Python/%(version)s
+Subject: %(subject)s
+
+""".lstrip() % header)
+
+        # Write body
+        sendmail.write(body)
+        # Done
+        sendmail.close()
+
     def before(self, wobj, data, *args, **kwargs):
         # ratelimit_128.112.139.115_201011091532 = 1
         # ratelimit_128.112.139.115_201011091533 = 14
@@ -46,8 +82,17 @@ class BaseRateLimit(object):
         except:
             return
 
+        # decode api_method_caller
         if api_method == "session":
-            api_method_caller = args[0]["session"]
+            api_method_caller = Sessions(wobj.api, {'session_id': args[0]["session"]})
+            if api_method_caller == []:
+                return
+            elif api_method_caller[0]["person_id"] != None:
+                api_method_caller = Persons(wobj.api, api_method_caller[0]["person_id"])[0]["email"]
+            elif api_method_caller[0]["node_id"] != None:
+                api_method_caller = Nodes(wobj.api, api_method_caller[0]["node_id"])[0]["hostname"]
+            else:
+                api_method_caller = args[0]["session"]
         elif api_method == "password" or api_method == "capability":
             api_method_caller = args[0]["Username"]
         elif api_method == "gpg":
@@ -59,34 +104,43 @@ class BaseRateLimit(object):
         else:
             api_method_caller = "unknown"
 
+        # excludes
         if api_method_source == None or api_method_source[0] == self.config.PLC_API_IP or api_method_source[0] in self.whitelist:
             return
 
+        # sanity check
         if api_method_caller == None:
             self.log("%s called from %s with Username = None?" % (api_method_name, api_method_source[0]))
             return
 
+        # normalize unicode string otherwise memcache throws an exception
+        api_method_caller = str(api_method_caller)
+
         mc = memcache.Client(["%s:11211" % self.config.PLC_API_HOST])
         now = datetime.now()
-        current_key = "%s_%s_%s_%s" % (self.prefix, api_method_caller, api_method_source[0], now.strftime("%Y%m%d%H%M"))
 
+        current_key = "%s_%s_%s_%s" % (self.prefix, api_method_caller, api_method_source[0], now.strftime("%Y%m%d%H%M"))
         keys_to_check = ["%s_%s_%s_%s" % (self.prefix, api_method_caller, api_method_source[0], (now - timedelta(minutes = minute)).strftime("%Y%m%d%H%M")) for minute in range(self.minutes + 1)]
 
         try:
-            value = mc.incr(current_key)
+            mc.incr(current_key)
         except ValueError:
-            value = None
-
-        if value == None:
             mc.set(current_key, 1, time=self.expire_after)
 
-        result = mc.get_multi(keys_to_check)
+        results = mc.get_multi(keys_to_check)
         total_requests = 0
-        for i in result:
-            total_requests += result[i]
+        for i in results:
+            total_requests += results[i]
 
         if total_requests > self.requests:
             self.log("%s - %s" % (api_method_source[0], api_method_caller))
+
+            caller_key = "%s_%s" % (self.prefix, api_method_caller)
+            if mc.get(caller_key) == None:
+                mc.set(caller_key, 1, time = self.expire_after)
+                if (api_method == "session" and api_method_caller.__contains__("@")) or (api_method == "password" or api_method == "capability"):
+                    self.mail(api_method_caller)
+
             raise PLCPermissionDenied, "Maximum allowed number of API calls exceeded"
 
     def after(self, wobj, data, *args, **kwargs):