Repository: privacyidea/privacyidea

Issue:
Avoid reloading of Token/Resolver classes and types
Caching the Token/Resolver classes and types improves performance.



Discussion:

Resolving patch:
diff --git a/privacyidea/lib/config.py b/privacyidea/lib/config.py
--- a/privacyidea/lib/config.py
+++ b/privacyidea/lib/config.py
@@ -33,6 +33,7 @@
 The code is tested in tests/test_lib_config
 """
 
+import sys
 import logging
 import inspect
 from flask import current_app, g
@@ -54,6 +55,11 @@
 
 ENCODING = 'utf-8'
 
+# this is a pointer to the module object instance itself.
+this = sys.modules[__name__]
+
+this.config = {}
+
 
 class Singleton(type):
     """
@@ -241,15 +247,12 @@ def get_resolver_types():
     :return: array of resolvertypes like 'passwdresolver'
     :rtype: array
     """
-    resolver_types = []
-    if "pi_resolver_types" in current_app.config:
-        resolver_types = current_app.config["pi_resolver_types"]
-    else:
-        (_r_classes, r_types) = get_resolver_class_dict()
-        resolver_types = r_types.values()
-        current_app.config["pi_resolver_types"] = resolver_types
-    
-    return resolver_types
+    if "pi_resolver_types" not in this.config:
+        (r_classes, r_types) = get_resolver_class_dict()
+        this.config["pi_resolver_classes"] = r_classes
+        this.config["pi_resolver_types"] = r_types
+
+    return this.config["pi_resolver_types"].values()
 
 
 def get_caconnector_types():
@@ -270,15 +273,13 @@ def get_resolver_classes():
     :return: array of resolver classes
     :rtype: array
     """
-    resolver_classes = []
-    if "pi_resolver_classes" in current_app.config:
-        resolver_classes = current_app.config["pi_resolver_classes"]
-    else:
-        (r_classes, _r_types) = get_resolver_class_dict()
-        resolver_classes = r_classes.values()
-        current_app.config["pi_resolver_classes"] = resolver_classes
-    
-    return resolver_classes
+    resolver_classes = {}
+    if "pi_resolver_classes" not in this.config:
+        (r_classes, r_types) = get_resolver_class_dict()
+        this.config["pi_resolver_types"] = r_types
+        this.config["pi_resolver_classes"] = r_classes
+
+    return this.config["pi_resolver_classes"].values()
 
 
 #@cache.cached(key_prefix="classes")
@@ -331,15 +332,13 @@ def get_token_class(tokentype):
     """
     if tokentype.lower() == "hmac":
         tokentype = "hotp"
-    class_dict, type_dict = get_token_class_dict()
-    tokenmodule = ""
+
     tokenclass = None
-    for module, ttype in type_dict.items():
-        if ttype.lower() == tokentype.lower():
-            tokenmodule = module
+    tokenclasses = get_token_classes()
+    for tclass in tokenclasses:
+        if tclass.get_class_type().lower() == tokentype.lower():
+            tokenclass = tclass
             break
-    if tokenmodule:
-        tokenclass = class_dict.get(tokenmodule)
 
     return tokenclass
 
@@ -351,15 +350,12 @@ def get_token_types():
     :return: array of tokentypes like 'hotp', 'totp'...
     :rtype: array
     """
-    tokentypes = []
-    if "pi_token_types" in current_app.config:
-        tokentypes = current_app.config["pi_token_types"]
-    else:
-        (_t_classes, t_types) = get_token_class_dict()
-        tokentypes = t_types.values()
-        current_app.config["pi_token_types"] = tokentypes
+    if "pi_token_types" not in this.config:
+        (t_classes, t_types) = get_token_class_dict()
+        this.config["pi_token_types"] = t_types
+        this.config["pi_token_classes"] = t_classes
 
-    return tokentypes
+    return this.config["pi_token_types"].values()
 
 
 #@cache.cached(key_prefix="prefix")
@@ -396,15 +392,13 @@ def get_token_classes():
     :return: array of token classes
     :rtype: array
     """
-    token_classes = []
-    if "pi_token_classes" in current_app.config:
-        token_classes = current_app.config["pi_token_classes"]
-    else:
-        (t_classes, _t_types) = get_token_class_dict()
-        token_classes = t_classes.values()
-        current_app.config["pi_token_classes"] = token_classes
+    if "pi_token_classes" not in this.config:
+        (t_classes, t_types) = get_token_class_dict()
+        this.config["pi_token_classes"] = t_classes
+        this.config["pi_token_types"] = t_types
+
+    return this.config["pi_token_classes"].values()
 
-    return token_classes
 
 def get_machine_resolver_class_dict():
     """
@@ -498,7 +492,6 @@ def get_resolver_class_dict():
     resolverprefix_dict = {}
 
     modules = get_resolver_module_list()
-    base_class_repr = "privacyidea.lib.resolvers.UserIdResolver.UserIdResolver"
     for module in modules:
         log.debug("module: {0!s}".format(module))
         for name in dir(module):
@@ -560,6 +553,7 @@ def get_resolver_list():
 
     return module_list
 
+
 @log_with(log)
 #@cache.memoize(1)
 def get_machine_resolver_class_list():
@@ -615,7 +609,7 @@ def get_token_list():
 
     #module_list.add(".tokens.tagespassworttoken")
     #module_list.add(".tokens.vascotoken")
-    
+
     # Dynamic Resolver modules
     # TODO: Migration
     # config_modules = config.get("privacyideaResolverModules", '')
@@ -664,7 +658,7 @@ def get_token_module_list():
             modules.append(module)
         except Exception as exx:  # pragma: no cover
             module = None
-            log.warning('unable to load resolver module : {0!r} ({1!r})'.format(mod_name, exx))
+            log.warning('unable to load token module : {0!r} ({1!r})'.format(mod_name, exx))
 
     return modules
 
@@ -673,7 +667,7 @@ def get_token_module_list():
 def get_resolver_module_list():
     """
     return the list of modules of the available resolver classes
-    like passw, sql, ldap
+    like passwd, sql, ldap
 
     :return: list of resolver modules
     """
diff --git a/privacyidea/lib/resolver.py b/privacyidea/lib/resolver.py
--- a/privacyidea/lib/resolver.py
+++ b/privacyidea/lib/resolver.py
@@ -44,8 +44,7 @@
 
 import logging
 from log import log_with
-from config import (get_resolver_types,
-                     get_resolver_class_dict)
+from config import (get_resolver_types, get_resolver_classes)
 from privacyidea.lib.usercache import delete_user_cache
 from ..models import (Resolver,
                       ResolverConfig)
@@ -293,21 +292,18 @@ def get_resolver_config_description(resolver_type):
 
 #@cache.memoize(10)
 def get_resolver_class(resolver_type):
-    '''
+    """
     return the class object for a resolver type
     :param resolver_type: string specifying the resolver
                           fully qualified or abreviated
     :return: resolver object class
-    '''
+    """
     ret = None
-    
-    (resolver_clazzes, resolver_types) = get_resolver_class_dict()
-
-    if resolver_type in resolver_types.values():
-        for k, v in resolver_types.items():
-            if v == resolver_type:
-                ret = resolver_clazzes.get(k)
-                break
+    resolver_clazzes = get_resolver_classes()
+    for k in resolver_clazzes:
+        if k.getResolverClassType() == resolver_type:
+            ret = k
+            break
     return ret
 
 


Test patch:
diff --git a/tests/test_lib_config.py b/tests/test_lib_config.py
--- a/tests/test_lib_config.py
+++ b/tests/test_lib_config.py
@@ -19,8 +19,8 @@
                                     get_token_types,
                                     get_token_classes, get_token_prefix,
                                     get_machine_resolver_class_dict,
-                                    get_privacyidea_node, get_privacyidea_nodes
-                                    )
+                                    get_privacyidea_node, get_privacyidea_nodes,
+                                    this)
 from privacyidea.lib.resolvers.PasswdIdResolver import IdResolver as PWResolver
 from privacyidea.lib.tokens.hotptoken import HotpTokenClass
 from privacyidea.lib.tokens.totptoken import TotpTokenClass
@@ -79,16 +79,11 @@ def test_01_resolver(self):
 
         self.assertTrue(module in mlist, mlist)
 
-        # At the beginning the resolver classes are not cached.
-        self.assertFalse("pi_resolver_classes" in current_app.config,
-                                current_app.config)
-        r = get_resolver_classes()
-        self.assertTrue(PWResolver in r, r)
-        # Now the resolver classes are cached
-        self.assertTrue("pi_resolver_classes" in current_app.config,
-                                current_app.config)
         r = get_resolver_classes()
         self.assertTrue(PWResolver in r, r)
+        # resolver classes must be available here
+        self.assertTrue("pi_resolver_classes" in this.config, this.config)
+        self.assertTrue("pi_resolver_types" in this.config, this.config)
 
         # Class dict
         (classes, types) = get_resolver_class_dict()
@@ -102,17 +97,11 @@ def test_01_resolver(self):
         self.assertTrue(types.get('privacyidea.lib.resolvers.PasswdIdResolver'
                         '.IdResolver') == "passwdresolver", types)
 
-        # At the start the resolvers are not stored in the current_app.config
-        self.assertFalse("pi_resolver_types" in current_app.config,
-                         current_app.config)
+        # With calling 'get_resolver_classes()' the resolver types will also be cached
+        self.assertTrue("pi_resolver_types" in this.config, this.config)
         # When the resolvers are determined, they are stored
         types = get_resolver_types()
         self.assertTrue("passwdresolver" in types, types)
-        # Now the resolver types are contained.
-        self.assertTrue("pi_resolver_types" in current_app.config,
-                         current_app.config)
-        types = get_resolver_types()
-        self.assertTrue("passwdresolver" in types, types)
 
     def test_02_token(self):
         r = get_token_list()
@@ -154,22 +143,10 @@ def test_02_token(self):
         types = get_token_types()
         self.assertTrue("totp" in types, types)
         self.assertTrue("hotp" in types, types)
-        # Now the resolver types are contained.
-        self.assertTrue("pi_token_types" in current_app.config,
-                        current_app.config)
-        types = get_token_types()
-        self.assertTrue("totp" in types, types)
-        self.assertTrue("hotp" in types, types)
 
-        # At the beginning the token classes are not cached.
-        self.assertFalse("pi_token_classes" in current_app.config,
-                         current_app.config)
-        r = get_token_classes()
-        self.assertTrue(TotpTokenClass in r, r)
-        self.assertTrue(HotpTokenClass in r, r)
-        # Now the token classes are cached
-        self.assertTrue("pi_token_classes" in current_app.config,
-                        current_app.config)
+        # token classes are cached with calling 'get_token_types()'
+        self.assertTrue("pi_token_classes" in this.config, this.config)
+        self.assertTrue("pi_token_types" in this.config, this.config)
         r = get_token_classes()
         self.assertTrue(TotpTokenClass in r, r)
         self.assertTrue(HotpTokenClass in r, r)