CORD-1171 Cache authentication results for 10 seconds
Change-Id: I0b3cd617784aa91f6b9a485f20a1c746965819e4
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index f79701f..981610c 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -18,6 +18,7 @@
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
def translate_exceptions(function):
+ """ this decorator translates XOS exceptions to grpc status codes """
def wrapper(*args, **kwargs):
try:
return function(*args, **kwargs)
@@ -36,6 +37,60 @@
raise
return wrapper
+
+bench_tStart = time.time()
+bench_ops = 0
+def benchmark(function):
+ """ this decorator will report gRPC benchmark statistics every 10 seconds """
+ def wrapper(*args, **kwargs):
+ global bench_tStart
+ global bench_ops
+ result = function(*args, **kwargs)
+ bench_ops = bench_ops+1
+ elap = time.time() - bench_tStart
+ if (elap >= 10):
+ print "performance %d" % (bench_ops/elap)
+ bench_ops=0
+ bench_tStart = time.time()
+ return result
+ return wrapper
+
+class CachedAuthenticator(object):
+ """ Django Authentication is very slow (~ 10 ops/second), so cache
+ authentication results and reuse them.
+ """
+
+ def __init__(self):
+ self.cached_creds = {}
+ self.timeout = 10 # keep cache entries around for 10s
+
+ def authenticate(self, username, password):
+ self.trim()
+
+ key = "%s:%s" % (username, password)
+ cred = self.cached_creds.get(key, None)
+ if cred:
+ user = User.objects.filter(id=cred["user_id"])
+ if user:
+ user = user[0]
+ #print "cached authenticated %s:%s as %s" % (username, password, user)
+ return user
+
+ user = django_authenticate(username=username, password=password)
+ if user:
+ #print "django authenticated %s:%s as %s" % (username, password, user)
+ self.cached_creds[key] = {"timeout": time.time() + self.timeout, "user_id": user.id}
+
+ return user
+
+ def trim(self):
+ """ Delete all cache entries that have expired """
+ for (k, v) in list(self.cached_creds.items()):
+ if time.time() > v["timeout"]:
+ del self.cached_creds[k]
+
+cached_authenticator = CachedAuthenticator()
+
class XOSAPIHelperMixin(object):
def __init__(self):
import django.apps
@@ -284,10 +339,9 @@
if (method.lower() == "basic"):
auth = base64.b64decode(auth)
(username, password) = auth.split(":")
- user = django_authenticate(username=username, password=password)
+ user = cached_authenticator.authenticate(username=username, password=password)
if not user:
raise XOSPermissionDenied("failed to authenticate %s:%s" % (username, password))
- print "authenticated %s:%s as %s" % (username, password, user)
return user
elif (k.lower()=="x-xossession"):
s = SessionStore(session_key=v)
diff --git a/xos/coreapi/protos/utility.proto b/xos/coreapi/protos/utility.proto
index d899e41..88dd8f1 100644
--- a/xos/coreapi/protos/utility.proto
+++ b/xos/coreapi/protos/utility.proto
@@ -79,6 +79,13 @@
};
}
+ rpc AuthenticatedNoOp(google.protobuf.Empty) returns (google.protobuf.Empty) {
+ option (google.api.http) = {
+ post: "/xosapi/v1/utility/auth_noop"
+ body: "*"
+ };
+ }
+
rpc SetDirtyModels(ModelFilter) returns (ModelList) {
option (google.api.http) = {
post: "/xosapi/v1/utility/dirty_models"
diff --git a/xos/coreapi/xos_utility_api.py b/xos/coreapi/xos_utility_api.py
index d9c3a92..bce9bf0 100644
--- a/xos/coreapi/xos_utility_api.py
+++ b/xos/coreapi/xos_utility_api.py
@@ -41,6 +41,7 @@
class UtilityService(utility_pb2.utilityServicer, XOSAPIHelperMixin):
def __init__(self, thread_pool):
self.thread_pool = thread_pool
+ XOSAPIHelperMixin.__init__(self)
def stop(self):
pass
@@ -129,6 +130,11 @@
return Empty()
@translate_exceptions
+ def AuthenticatedNoOp(self, request, context):
+ self.authenticate(context, required=True)
+ return Empty()
+
+ @translate_exceptions
def ListDirtyModels(self, request, context):
dirty_models = utility_pb2.ModelList()
diff --git a/xos/xos_client/tests/nopper.py b/xos/xos_client/tests/nopper.py
new file mode 100644
index 0000000..564c84b
--- /dev/null
+++ b/xos/xos_client/tests/nopper.py
@@ -0,0 +1,34 @@
+""" nopper
+
+ Sends NoOp operations to Core API Server at maximum rate and reports
+ performance.
+"""
+
+import sys
+import time
+sys.path.append("..")
+
+from xosapi import xos_grpc_client
+
+def test_callback():
+ print "TEST: nop"
+
+ c = xos_grpc_client.coreclient
+
+ while True:
+ tStart = time.time()
+ count = 0
+ while True:
+ if type(xos_grpc_client.coreclient) == xos_grpc_client.SecureClient:
+ c.utility.AuthenticatedNoOp(xos_grpc_client.Empty())
+ else:
+ c.utility.NoOp(xos_grpc_client.Empty())
+ count = count + 1
+ elap = time.time()-tStart
+ if (elap >= 10):
+ print "nops/second = %d" % int(count/elap)
+ tStart = time.time()
+ count = 0
+
+xos_grpc_client.start_api_parseargs(test_callback)
+