Repository: learningequality/kolibri

Issue:
incompatible code between the kolibri source code and kolibri sentry plugin
<!--
Instructions:
 * Fill out the sections below, replace …'s with information about your issue
 * Use the 'preview' function above this text box to verify formatting before submitting
-->

### Observed behavior
<!--
Description of the behavior that was observed, including screenshots or other references when applicable
-->

On kolibri-beta server that uses sentry plugin, the upgrade to the latest beta failed

### Expected behavior
<!--
Description of what behavior was expected but did not occur
-->

upgrade should be successful

### User-facing consequences
<!--
Implications and real-world consequences for learners, coaches, admins, and other users of the application
-->

can't upgrade the beta server to the latest beta

### Errors and logs
<!--
Relevant logs from:
 * the command line
 * ~/.kolibri/logs/kolibri.txt
 * the browser console

Please wrap errors in triple backticks for clean formatting like this:
```
01:10 info: something happened
01:12 error: something bad happened
```
-->

```
Traceback (most recent call last):
  File "/usr/local/bin/kolibri", line 7, in <module>
    from kolibri.utils.cli import main
  File "/usr/local/lib/python2.7/dist-packages/kolibri/utils/cli.py", line 29, in <module>
    from . import sanity_checks
  File "/usr/local/lib/python2.7/dist-packages/kolibri/utils/sanity_checks.py", line 14, in <module>
    from .server import get_status
  File "/usr/local/lib/python2.7/dist-packages/kolibri/utils/server.py", line 18, in <module>
    from kolibri.core.deviceadmin.utils import schedule_vacuum
  File "/usr/local/lib/python2.7/dist-packages/kolibri/core/deviceadmin/utils.py", line 13, in <module>
    from kolibri.core.tasks.main import scheduler
  File "/usr/local/lib/python2.7/dist-packages/kolibri/core/tasks/main.py", line 11, in <module>
    from kolibri.core.tasks.queue import Queue
  File "/usr/local/lib/python2.7/dist-packages/kolibri/core/tasks/queue.py", line 1, in <module>
    from kolibri.core.tasks.job import Job
  File "/usr/local/lib/python2.7/dist-packages/kolibri/core/tasks/job.py", line 7, in <module>
    from kolibri.core.tasks.utils import current_state_tracker
  File "/usr/local/lib/python2.7/dist-packages/kolibri/core/tasks/utils.py", line 123, in <module>
    db_task_write_lock = get_process_lock("db_task_write_lock")
  File "/usr/local/lib/python2.7/dist-packages/kolibri/core/utils/cache.py", line 49, in get_process_lock
    cache = process_cache.cache("locks")
  File "/usr/local/lib/python2.7/dist-packages/kolibri/dist/django/utils/functional.py", line 238, in inner
    self._setup()
  File "/usr/local/lib/python2.7/dist-packages/kolibri/dist/django/utils/functional.py", line 386, in _setup
    self._wrapped = self._setupfunc()
  File "/usr/local/lib/python2.7/dist-packages/kolibri/core/utils/cache.py", line 17, in __get_process_cache
    return caches["process_cache"]
  File "/usr/local/lib/python2.7/dist-packages/kolibri/dist/django/core/cache/__init__.py", line 75, in __getitem__
    if alias not in settings.CACHES:
  File "/usr/local/lib/python2.7/dist-packages/kolibri/dist/django/conf/__init__.py", line 56, in __getattr__
    self._setup(name)
  File "/usr/local/lib/python2.7/dist-packages/kolibri/dist/django/conf/__init__.py", line 41, in _setup
    self._wrapped = Settings(settings_module)
  File "/usr/local/lib/python2.7/dist-packages/kolibri/dist/django/conf/__init__.py", line 110, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/usr/local/lib/python2.7/dist-packages/kolibri/deployment/default/settings/base.py", line 357, in <module>
    apply_settings(sys.modules[__name__])
  File "/usr/local/lib/python2.7/dist-packages/kolibri/plugins/utils/settings.py", line 133, in apply_settings
    plugin_settings_module = plugin_instance.settings_module
  File "/usr/local/lib/python2.7/dist-packages/kolibri/plugins/__init__.py", line 345, in settings_module
    module = self._return_module(self.django_settings)
  File "/usr/local/lib/python2.7/dist-packages/kolibri/plugins/__init__.py", line 245, in _return_module
    return import_module(models_module_name)
  File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/usr/local/lib/python2.7/dist-packages/kolibri_sentry_plugin/settings.py", line 6, in <module>
    from kolibri.utils.server import installation_type
ImportError: cannot import name installation_type
```

### Steps to reproduce
<!--
Precise steps that someone else can follow in order to see this behavior
-->

…

### Context
<!--
Tell us about your environment, including:
 * Kolibri version
 * Operating system
 * Browser
-->

0.14.0b14


Comments:

Patch:
diff --git a/kolibri/core/tasks/utils.py b/kolibri/core/tasks/utils.py
index 2150613d96..6df4b0d13f 100644
--- a/kolibri/core/tasks/utils.py
+++ b/kolibri/core/tasks/utils.py
@@ -4,7 +4,7 @@ import time
 import uuid
 
 from kolibri.core.tasks import compat
-from kolibri.core.utils.cache import get_process_lock
+from kolibri.core.utils.cache import ProcessLock
 
 
 # An object on which to store data about the current job
@@ -120,4 +120,4 @@ class InfiniteLoopThread(compat.Thread):
         self.stop()
 
 
-db_task_write_lock = get_process_lock("db_task_write_lock")
+db_task_write_lock = ProcessLock("db_task_write_lock")
diff --git a/kolibri/core/utils/cache.py b/kolibri/core/utils/cache.py
index 43c6bf9a5a..110faba1de 100644
--- a/kolibri/core/utils/cache.py
+++ b/kolibri/core/utils/cache.py
@@ -22,32 +22,52 @@ def __get_process_cache():
 process_cache = SimpleLazyObject(__get_process_cache)
 
 
-def get_process_lock(key, expire=None):
-    """
-    Return Lock object that's appropriate given current cache backend
-
-    :param key: The lock key
-    :param expire: The cache key expiration in seconds
-    :type key: str
-    :type expire: int
-    :rtype: redis.lock.Lock|diskcache.recipes.RLock
-    """
-    if OPTIONS["Cache"]["CACHE_BACKEND"] == "redis":
-        expire = expire * 1000 if expire is not None else None
-        # if we're using Redis, be sure we use Redis' locking mechanism which uses
-        # `SET NX` under the hood. See redis.lock.Lock
-        return process_cache.lock(
-            key,
-            timeout=expire,  # milliseconds
-            sleep=0.01,  # seconds
-            blocking_timeout=100,  # seconds
-            thread_local=True,
-        )
-    else:
-        # we can't pass in the `process_cache` because it's an instance of DjangoCache
-        # and we need a Cache instance
-        cache = process_cache.cache("locks")
-        return RLock(cache, key, expire=expire)
+class ProcessLock(object):
+    def __init__(self, key, expire=None):
+        """
+        :param key: The lock key
+        :param expire: The cache key expiration in seconds
+        :type key: str
+        :type expire: int
+        """
+        self.key = key
+        self.expire = expire
+
+        self._lock_object = None
+
+    @property
+    def _lock(self):
+        if self._lock_object is None:
+            if OPTIONS["Cache"]["CACHE_BACKEND"] == "redis":
+                expire = self.expire * 1000 if self.expire is not None else None
+                # if we're using Redis, be sure we use Redis' locking mechanism which uses
+                # `SET NX` under the hood. See redis.lock.Lock
+                # The Django RedisCache backend provide the lock method to proxy this
+                self._lock_object = process_cache.lock(
+                    self.key,
+                    timeout=expire,  # milliseconds
+                    sleep=0.01,  # seconds
+                    blocking_timeout=100,  # seconds
+                    thread_local=True,
+                )
+            else:
+                # we can't pass in the `process_cache` because it's an instance of DjangoCache
+                # and we need a DiskCache Cache instance
+                cache = process_cache.cache("locks")
+                self._lock_object = RLock(cache, self.key, expire=self.expire)
+        return self._lock_object
+
+    def acquire(self):
+        self._lock.acquire()
+
+    def release(self):
+        self._lock.release()
+
+    def __enter__(self):
+        self.acquire()
+
+    def __exit__(self, *exc_info):
+        self.release()
 
 
 class NamespacedCacheProxy(BaseCache):
@@ -64,7 +84,7 @@ class NamespacedCacheProxy(BaseCache):
         params.update(KEY_PREFIX=namespace)
         super(NamespacedCacheProxy, self).__init__(params)
         self.cache = cache
-        self._lock = get_process_lock("namespaced_cache_{}".format(namespace))
+        self._lock = ProcessLock("namespaced_cache_{}".format(namespace))
 
     def _get_keys(self):
         """
diff --git a/kolibri/plugins/__init__.py b/kolibri/plugins/__init__.py
index 7fbf8c6831..95dfa117e9 100644
--- a/kolibri/plugins/__init__.py
+++ b/kolibri/plugins/__init__.py
@@ -242,7 +242,15 @@ class KolibriPluginBase(with_metaclass(SingletonMeta)):
     def _return_module(self, module_name):
         if module_has_submodule(sys.modules[self.module_path], module_name):
             models_module_name = "%s.%s" % (self.module_path, module_name)
-            return import_module(models_module_name)
+            try:
+                return import_module(models_module_name)
+            except Exception as e:
+                logging.warn(
+                    "Tried to import module {module_name} from {plugin} but an error was raised".format(
+                        plugin=self.module_path, module_name=module_name,
+                    )
+                )
+                logging.exception(e)
 
         return None
 
diff --git a/kolibri/utils/server.py b/kolibri/utils/server.py
index cd7f28a54b..86ebd1ca68 100644
--- a/kolibri/utils/server.py
+++ b/kolibri/utils/server.py
@@ -11,7 +11,6 @@ from django.conf import settings
 from zeroconf import get_all_addresses
 
 import kolibri
-from .conf import OPTIONS
 from .system import kill_pid
 from .system import pid_exists
 from kolibri.core.content.utils import paths
@@ -87,7 +86,7 @@ class ServicesPlugin(SimplePlugin):
         # Initialize the iceqube scheduler to handle scheduled tasks
         scheduler.clear_scheduler()
 
-        if not OPTIONS["Deployment"]["DISABLE_PING"]:
+        if not conf.OPTIONS["Deployment"]["DISABLE_PING"]:
 
             # schedule the pingback job
             from kolibri.core.analytics.utils import schedule_ping


Test patch:
diff --git a/kolibri/core/test/test_utils.py b/kolibri/core/test/test_utils.py
index e83d427301..acaa654430 100644
--- a/kolibri/core/test/test_utils.py
+++ b/kolibri/core/test/test_utils.py
@@ -4,8 +4,8 @@ from django.core.cache.backends.base import BaseCache
 from django.test import TestCase
 from redis import Redis
 
-from kolibri.core.utils.cache import get_process_lock
 from kolibri.core.utils.cache import NamespacedCacheProxy
+from kolibri.core.utils.cache import ProcessLock
 from kolibri.core.utils.cache import RedisSettingsHelper
 
 
@@ -21,7 +21,7 @@ class GetProcessLockTestCase(TestCase):
         self.setup_opts(options, redis=True)
         lock = mock.Mock()
         process_cache.lock.return_value = lock
-        self.assertEqual(lock, get_process_lock("test_key", expire=2))
+        self.assertEqual(lock, ProcessLock("test_key", expire=2)._lock)
         process_cache.lock.assert_called_once_with(
             "test_key",
             timeout=2000,
@@ -34,15 +34,15 @@ class GetProcessLockTestCase(TestCase):
         self.setup_opts(options, redis=False)
         sub_cache = mock.Mock()
         process_cache.cache.return_value = sub_cache
-        lock = get_process_lock("test_key", expire=2)
-        self.assertIsInstance(lock, RLock)
+        lock = ProcessLock("test_key", expire=2)
+        self.assertIsInstance(lock._lock, RLock)
         process_cache.cache.assert_called_once_with("locks")
-        self.assertEqual(sub_cache, lock._cache)
-        self.assertEqual("test_key", lock._key)
-        self.assertEqual(2, lock._expire)
+        self.assertEqual(sub_cache, lock._lock._cache)
+        self.assertEqual("test_key", lock._lock._key)
+        self.assertEqual(2, lock._lock._expire)
 
 
-@mock.patch("kolibri.core.utils.cache.get_process_lock")
+@mock.patch("kolibri.core.utils.cache.ProcessLock")
 class NamespacedCacheProxyTestCase(TestCase):
     def setUp(self):
         self.lock = mock.MagicMock(spec=RLock)