diff --git a/server/src/server/urls.py b/server/src/server/urls.py index 5b13368b..5915a0e1 100644 --- a/server/src/server/urls.py +++ b/server/src/server/urls.py @@ -2,8 +2,7 @@ """ Url patterns for UDS project (Django) """ -from django.conf.urls import include -from django.urls import path +from django.urls import include, path # Uncomment the next two lines to enable the admin: diff --git a/server/src/uds/core/services/service.py b/server/src/uds/core/services/service.py index 9c7940bd..0cacc069 100644 --- a/server/src/uds/core/services/service.py +++ b/server/src/uds/core/services/service.py @@ -248,6 +248,7 @@ class Service(Module): Returns if this service is reachable (that is, we can operate with it). This is used, for example, to check if a service is "operable" before removing an user service (that is, pass from "waiting for remove" to "removing") By default, this method returns True. + Ideally, availability should be cached for a while, so that we don't have to check it every time. """ return True diff --git a/server/src/uds/core/util/cache.py b/server/src/uds/core/util/cache.py index 28fddc30..26502d47 100644 --- a/server/src/uds/core/util/cache.py +++ b/server/src/uds/core/util/cache.py @@ -48,7 +48,10 @@ class Cache: hits = 0 misses = 0 + # Some aliases DEFAULT_VALIDITY = 60 + SHORT_VALIDITY = 5 + LONG_VALIDITY = 3600 _owner: str _bowner: bytes @@ -98,6 +101,12 @@ class Cache: # logger.debug('Cache inaccesible: %s:%s', skey, e) return defValue + def __getitem__(self, key: typing.Union[str, bytes]) -> typing.Any: + """ + Returns the cached value for the given key using the [] operator + """ + return self.get(key) + def remove(self, skey: typing.Union[str, bytes]) -> bool: """ Removes an stored cached item @@ -112,6 +121,12 @@ class Cache: logger.debug('key not found') return False + def __delitem__(self, key: typing.Union[str, bytes]) -> None: + """ + Removes an stored cached item using the [] operator + """ + self.remove(key) + def clean(self) -> None: Cache.delete(self._owner) @@ -148,6 +163,12 @@ class Cache: except transaction.TransactionManagementError: logger.debug('Transaction in course, cannot store value') + def __setitem__(self, key: typing.Union[str, bytes], value: typing.Any) -> None: + """ + Stores a value in the cache using the [] operator with default validity + """ + self.put(key, value) + def refresh(self, skey: typing.Union[str, bytes]) -> None: # logger.debug('Refreshing key "%s" for cache "%s"' % (skey, self._owner,)) try: diff --git a/server/src/uds/core/util/decorators.py b/server/src/uds/core/util/decorators.py index f8864405..e020ca1a 100644 --- a/server/src/uds/core/util/decorators.py +++ b/server/src/uds/core/util/decorators.py @@ -36,6 +36,7 @@ import inspect import typing from uds.core.util.html import checkBrowser +from uds.core.util.cache import Cache from uds.web.util import errors @@ -115,7 +116,7 @@ def ensureConected(func: typing.Callable[..., RT]) -> typing.Callable[..., RT]: # Decorator that tries to get from cache before executing def allowCache( cachePrefix: str, - cacheTimeout: int, + cacheTimeout: typing.Union[typing.Callable[[], int], int] = Cache.DEFAULT_VALIDITY, cachingArgs: typing.Optional[ typing.Union[typing.List[int], typing.Tuple[int], int] ] = None, @@ -131,6 +132,8 @@ def allowCache( :param cacheTimeout: The cache timeout in seconds :param cachingArgs: The caching args. Can be a single integer or a list. First arg (self) is 0, so normally cachingArgs are 1, or [1,2,..] + :param cachingKWArgs: The caching kwargs. Can be a single string or a list. + :param cachingKeyFnc: A function that receives the args and kwargs and returns the key """ keyFnc = cachingKeyFnc or (lambda x: '') @@ -169,12 +172,17 @@ def allowCache( if 'force' in kwargs: # Remove force key del kwargs['force'] + + # ic cacheTimeout is a function, call it + timeout = cacheTimeout + if callable(timeout): + timeout = timeout() if args[0].cache: # Not in cache and object can cache it data = fnc(*args, **kwargs) try: # Maybe returned data is not serializable. In that case, cache will fail but no harm is done with this - args[0].cache.put(cacheKey, data, cacheTimeout) + args[0].cache.put(cacheKey, data, timeout) except Exception as e: logger.debug( 'Data for %s is not serializable on call to %s, not cached. %s (%s)', diff --git a/server/src/uds/services/OpenGnsys/provider.py b/server/src/uds/services/OpenGnsys/provider.py index 8a179ba2..bf75cac7 100644 --- a/server/src/uds/services/OpenGnsys/provider.py +++ b/server/src/uds/services/OpenGnsys/provider.py @@ -39,6 +39,8 @@ from django.utils.translation import gettext_noop as _ from uds.core.services import ServiceProvider from uds.core.ui import gui from uds.core.util import validators +from uds.core.util.decorators import allowCache +from uds.core.util.cache import Cache from .service import OGService from . import og @@ -283,3 +285,10 @@ class OGProvider(ServiceProvider): def status(self, machineId: str) -> typing.Any: return self.api.status(machineId) + + @allowCache('reachable', Cache.SHORT_VALIDITY) + def isAvailable(self) -> bool: + """ + Check if aws provider is reachable + """ + return self.testConnection()[0] diff --git a/server/src/uds/services/OpenGnsys/service.py b/server/src/uds/services/OpenGnsys/service.py index 244698a4..9e33c0dc 100644 --- a/server/src/uds/services/OpenGnsys/service.py +++ b/server/src/uds/services/OpenGnsys/service.py @@ -213,3 +213,6 @@ class OGService(Service): def isRemovableIfUnavailable(self): return self.startIfUnavailable.isTrue() + + def isAvailable(self) -> bool: + return self.parent().isAvailable() diff --git a/server/src/uds/services/OpenNebula/provider.py b/server/src/uds/services/OpenNebula/provider.py index 343c28c3..893436d2 100644 --- a/server/src/uds/services/OpenNebula/provider.py +++ b/server/src/uds/services/OpenNebula/provider.py @@ -37,9 +37,11 @@ from django.utils.translation import gettext_noop as _ from uds.core.services import ServiceProvider from uds.core.ui import gui from uds.core.util import validators +from uds.core.util.cache import Cache +from uds.core.util.decorators import allowCache -from .service import LiveService from . import on +from .service import LiveService # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: @@ -334,3 +336,10 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me @staticmethod def test(env: 'Environment', data: 'Module.ValuesType') -> typing.List[typing.Any]: return OpenNebulaProvider(env, data).testConnection() + + @allowCache('reachable', Cache.SHORT_VALIDITY) + def isAvailable(self) -> bool: + """ + Check if aws provider is reachable + """ + return self.testConnection()[0] diff --git a/server/src/uds/services/OpenNebula/service.py b/server/src/uds/services/OpenNebula/service.py index ee75e765..912c59d7 100644 --- a/server/src/uds/services/OpenNebula/service.py +++ b/server/src/uds/services/OpenNebula/service.py @@ -320,3 +320,6 @@ class LiveService(Service): self, machineId: str, username: str, password: str, domain: str ) -> typing.Dict[str, typing.Any]: return self.parent().desktopLogin(machineId, username, password, domain) + + def isAvailable(self) -> bool: + return self.parent().isAvailable() diff --git a/server/src/uds/services/OpenStack/provider.py b/server/src/uds/services/OpenStack/provider.py index b875e899..87786287 100644 --- a/server/src/uds/services/OpenStack/provider.py +++ b/server/src/uds/services/OpenStack/provider.py @@ -30,16 +30,18 @@ """ .. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com """ -import typing import logging +import typing from django.utils.translation import gettext_noop as _ from uds.core.services import ServiceProvider from uds.core.ui import gui from uds.core.util import validators +from uds.core.util.cache import Cache +from uds.core.util.decorators import allowCache -from .service import LiveService from . import openstack +from .service import LiveService # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: @@ -290,3 +292,11 @@ class OpenStackProvider(ServiceProvider): """ return OpenStackProvider(env, data).testConnection() + + @allowCache('reachable', Cache.SHORT_VALIDITY) + def isAvailable(self) -> bool: + """ + Check if aws provider is reachable + """ + return self.testConnection()[0] + diff --git a/server/src/uds/services/OpenStack/provider_legacy.py b/server/src/uds/services/OpenStack/provider_legacy.py index 32f9eccc..21dce732 100644 --- a/server/src/uds/services/OpenStack/provider_legacy.py +++ b/server/src/uds/services/OpenStack/provider_legacy.py @@ -39,10 +39,11 @@ from django.utils.translation import gettext_noop as _ from uds.core.services import ServiceProvider from uds.core.ui import gui from uds.core.util import validators +from uds.core.util.cache import Cache +from uds.core.util.decorators import allowCache -from .service import LiveService from . import openstack - +from .service import LiveService # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: @@ -274,3 +275,12 @@ class ProviderLegacy(ServiceProvider): """ return ProviderLegacy(env, data).testConnection() + + @allowCache('reachable', Cache.SHORT_VALIDITY) + def isAvailable(self) -> bool: + """ + Check if aws provider is reachable + """ + return self.testConnection()[0] + + \ No newline at end of file diff --git a/server/src/uds/services/OpenStack/service.py b/server/src/uds/services/OpenStack/service.py index 3b6927a5..260a48d4 100644 --- a/server/src/uds/services/OpenStack/service.py +++ b/server/src/uds/services/OpenStack/service.py @@ -440,3 +440,6 @@ class LiveService(Service): Returns the length of numbers part """ return int(self.lenName.value) + + def isAvailable(self) -> bool: + return self.parent().isAvailable()