From c21652fe83b20f435f51906aa552ae5f5164677c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Thu, 3 Oct 2024 21:54:35 +0200 Subject: [PATCH] Fixing up unmanaged hosts mesh --- actor | 2 +- .../src/tests/REST/actor/test_initialize.py | 146 ++++++++--------- server/src/tests/REST/actor/test_unmanaged.py | 149 ++++++++++++++++++ server/src/tests/REST/test_system.py | 2 +- .../tests/services/proxmox/test_helpers.py | 2 +- server/src/tests/utils/rest/test.py | 4 +- server/src/uds/REST/methods/actor_v3.py | 52 +++--- server/src/uds/core/types/ui.py | 2 +- server/src/uds/core/ui/user_interface.py | 5 +- server/src/uds/core/util/modfinder.py | 5 + server/src/uds/workers/userservice_cleaner.py | 2 +- 11 files changed, 260 insertions(+), 111 deletions(-) create mode 100644 server/src/tests/REST/actor/test_unmanaged.py diff --git a/actor b/actor index 18a1e4722..6b2f9df24 160000 --- a/actor +++ b/actor @@ -1 +1 @@ -Subproject commit 18a1e4722dda938c545b2c67eddc87279627cacc +Subproject commit 6b2f9df247fc24e57c5c6652c1616aca71b67a07 diff --git a/server/src/tests/REST/actor/test_initialize.py b/server/src/tests/REST/actor/test_initialize.py index 66b9fb92e..997a8a9eb 100644 --- a/server/src/tests/REST/actor/test_initialize.py +++ b/server/src/tests/REST/actor/test_initialize.py @@ -194,138 +194,140 @@ def test_initialize_unmanaged_by_mac(self) -> None: """ Test actor initialize v3 for unmanaged actor """ - user_service = self.user_service_unmanaged + userservice = self.userservice_unmanaged actor_token: str = ( - user_service.deployed_service.service.token if user_service.deployed_service.service else None + userservice.deployed_service.service.token if userservice.deployed_service.service else None ) or '' - - unique_id = user_service.get_unique_id() + + if actor_token == '': + self.fail('Service token not found') success = functools.partial(self.invoke_success, 'unmanaged') failure = functools.partial(self.invoke_failure, 'unmanaged') - TEST_MAC: typing.Final[str] = '00:00:00:00:00:00' + NONEXISTING_MAC: typing.Final[str] = '00:00:00:00:00:00' + USERSERVICE_MAC: typing.Final[str] = userservice.get_unique_id() # This will succeed, but only alias token is returned because MAC is not registered by UDS result = success( actor_token, - mac=TEST_MAC, + mac=NONEXISTING_MAC, ) # Unmanaged host is the response for initialization of unmanaged actor ALWAYS - self.assertIsInstance(result['token'], str) - self.assertEqual(result['token'], result['own_token']) + self.assertIsInstance(result['master_token'], str) self.assertIsNone(result['unique_id']) self.assertIsNone(result['os']) + self.assertIsNone(result['own_token']) + self.assertIsNone(result['token']) - # Store alias token for later tests - alias_token = result['token'] + alias_token = result['master_token'] + # Ensure that the alias returned is on alias db, and it points to the same service as the one we belong to + alias = models.ServiceTokenAlias.objects.get(alias=alias_token) + self.assertEqual(alias.service, userservice.deployed_service.service) + self.assertEqual(alias.unique_id, NONEXISTING_MAC.lower()) # If repeated, same token is returned result = success( actor_token, - mac=TEST_MAC, + mac=NONEXISTING_MAC, ) - self.assertEqual(result['token'], alias_token) - - # Now, invoke a "nice" initialize + self.assertEqual(result['master_token'], alias_token) + + # Now, invoke with a correct mac (Exists os user services) result = success( actor_token, - mac=unique_id, + mac=USERSERVICE_MAC, ) - - token = result['token'] - - self.assertIsInstance(token, str) - self.assertEqual(token, user_service.uuid) - self.assertEqual(token, result['own_token']) - self.assertEqual(result['unique_id'], unique_id) - - # Ensure that the alias returned is on alias db, and it points to the same service as the one we belong to + + # Note that due the change of mac, a new alias is created + alias_token = result['master_token'] alias = models.ServiceTokenAlias.objects.get(alias=alias_token) - self.assertEqual(alias.service, user_service.deployed_service.service) - - # Now, we should be able to "initialize" with valid mac and with original and alias tokens - # If we call initialize and we get "own-token" means that we have already logged in with this data - result = success(alias_token, mac=unique_id) - - self.assertEqual(result['token'], user_service.uuid) - self.assertEqual(result['token'], result['own_token']) - self.assertEqual(result['unique_id'], unique_id) + self.assertEqual(alias.service, userservice.deployed_service.service) + self.assertEqual(alias.unique_id, USERSERVICE_MAC.lower()) + + self.assertEqual(USERSERVICE_MAC, result['unique_id']) + self.assertEqual(result['own_token'], result['token']) + self.assertEqual(result['token'], userservice.uuid) + + # Now, invoke with alias, result shouls be the same + result2 = success( + alias_token, + mac=USERSERVICE_MAC, + ) + # master_token should be the same as the alias token + self.assertEqual(result, result2) # - failure('invalid token', mac=unique_id, expect_forbidden=True) + failure('invalid token', mac=USERSERVICE_MAC, expect_forbidden=True) def test_initialize_unmanaged_by_ip(self) -> None: """ Test actor initialize v3 for unmanaged actor """ - user_service = services_fixtures.create_db_one_assigned_userservice( + userservice = services_fixtures.create_db_one_assigned_userservice( self.provider, self.admins[0], self.groups, 'unmanaged', ) # Set an IP as unique_id - unique_id = '1.2.3.4' - user_service.unique_id = unique_id - user_service.save() + USERSERVICE_IP: typing.Final[str] = '1.2.3.4' + userservice.unique_id = USERSERVICE_IP + userservice.save() actor_token: str = ( - user_service.deployed_service.service.token if user_service.deployed_service.service else None + userservice.deployed_service.service.token if userservice.deployed_service.service else None ) or '' success = functools.partial(self.invoke_success, 'unmanaged', mac='00:00:00:00:00:00') failure = functools.partial(self.invoke_failure, 'unmanaged', mac='00:00:00:00:00:00') - TEST_IP: typing.Final[str] = '00:00:00:00:00:00' + NONEXISTING_IP: typing.Final[str] = '00:00:00:00:00:00' # This will succeed, but only alias token is returned because MAC is not registered by UDS result = success( actor_token, - ip=TEST_IP, + ip=NONEXISTING_IP, ) # Unmanaged host is the response for initialization of unmanaged actor ALWAYS - self.assertIsInstance(result['token'], str) - self.assertEqual(result['token'], result['own_token']) + self.assertIsInstance(result['master_token'], str) self.assertIsNone(result['unique_id']) self.assertIsNone(result['os']) + self.assertIsNone(result['own_token']) + self.assertIsNone(result['token']) - # Store alias token for later tests - alias_token = result['token'] - # If repeated, same token is returned - result = success( - actor_token, - ip=TEST_IP, - ) - self.assertEqual(result['token'], alias_token) + alias_token = result['master_token'] + # Ensure that the alias returned is on alias db, and it points to the same service as the one we belong to + alias = models.ServiceTokenAlias.objects.get(alias=alias_token) + self.assertEqual(alias.service, userservice.deployed_service.service) + self.assertEqual(alias.unique_id, NONEXISTING_IP.lower()) - # Now, invoke a "nice" initialize + # Now, invoke with a correct mac (Exists os user services) result = success( actor_token, - ip=unique_id, + mac=USERSERVICE_IP, ) - - token = result['token'] - - self.assertIsInstance(token, str) - self.assertEqual(token, user_service.uuid) - self.assertEqual(token, result['own_token']) - self.assertEqual(result['unique_id'], unique_id) - - # Ensure that the alias returned is on alias db, and it points to the same service as the one we belong to + + # Note that due the change of mac, a new alias is created + alias_token = result['master_token'] alias = models.ServiceTokenAlias.objects.get(alias=alias_token) - self.assertEqual(alias.service, user_service.deployed_service.service) - - # Now, we should be able to "initialize" with valid mac and with original and alias tokens - # If we call initialize and we get "own-token" means that we have already logged in with this data - result = success(alias_token, ip=unique_id) - - self.assertEqual(result['token'], user_service.uuid) - self.assertEqual(result['token'], result['own_token']) - self.assertEqual(result['unique_id'], unique_id) + self.assertEqual(alias.service, userservice.deployed_service.service) + self.assertEqual(alias.unique_id, USERSERVICE_IP.lower()) + + self.assertEqual(USERSERVICE_IP, result['unique_id']) + self.assertEqual(result['own_token'], result['token']) + self.assertEqual(result['token'], userservice.uuid) + + # Now, invoke with alias, result shouls be the same + result2 = success( + alias_token, + mac=USERSERVICE_IP, + ) + # master_token should be the same as the alias token + self.assertEqual(result, result2) # - failure('invalid token', ip=unique_id, expect_forbidden=True) + failure('invalid token', mac=USERSERVICE_IP, expect_forbidden=True) diff --git a/server/src/tests/REST/actor/test_unmanaged.py b/server/src/tests/REST/actor/test_unmanaged.py new file mode 100644 index 000000000..d2d90a8ae --- /dev/null +++ b/server/src/tests/REST/actor/test_unmanaged.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022 Virtual Cable S.L.U. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +Author: Adolfo Gómez, dkmaster at dkmon dot com +""" +import typing +import logging + + +from uds import models +from uds.core.managers.crypto import CryptoManager +from ...utils import rest + + +logger = logging.getLogger(__name__) + + +class ActorUnmanagedTest(rest.test.RESTActorTestCase): + """ + Test actor functionality + """ + + def invoke_success( + self, + token: str, + *, + mac: typing.Optional[str] = None, + ip: typing.Optional[str] = None, + ) -> dict[str, typing.Any]: + response = self.client.post( + '/uds/rest/actor/v3/unmanaged', + data={ + 'id': [{'mac': mac or '42:AC:11:22:33', 'ip': ip or '1.2.3.4'}], + 'token': token, + 'seecret': 'test_secret', + 'port': 1234, + }, + content_type='application/json', + ) + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertIsInstance(data['result'], dict) + return data['result'] + + def invoke_failure( + self, + token: str, + *, + mac: typing.Optional[str] = None, + ip: typing.Optional[str] = None, + expect_forbidden: bool = False, + ) -> dict[str, typing.Any]: + response = self.client.post( + '/uds/rest/actor/v3/unmanaged', + data={ + 'id': [{'mac': mac or '42:AC:11:22:33', 'ip': ip or '1.2.3.4'}], + 'token': token, + 'seecret': 'test_secret', + 'port': 1234, + }, + content_type='application/json', + ) + self.assertEqual(response.status_code, 200 if not expect_forbidden else 403) + if expect_forbidden: + return {} + + data = response.json() + self.assertIsInstance(data['result'], dict) + return data['result'] + + def test_unmanaged(self) -> None: + """ + Test actor initialize v3 for unmanaged actor + """ + userservice = self.userservice_unmanaged + actor_token: str = ( + userservice.deployed_service.service.token if userservice.deployed_service.service else None + ) or '' + + if actor_token == '': + self.fail('Service token not found') + + TEST_MAC: typing.Final[str] = '00:00:00:00:00:00' + + # This will succeed, but only alias token is returned because MAC is not registered by UDS + result = self.invoke_success( + actor_token, + mac=TEST_MAC, + ) + + # 'private_key': key, # To be removed on 5.0 + # 'key': key, + # 'server_certificate': certificate, # To be removed on 5.0 + # 'certificate': certificate, + # 'password': password, + # 'ciphers': getattr(settings, 'SECURE_CIPHERS', ''), + + self.assertIn('private_key', result) + self.assertIn('key', result) + self.assertIn('server_certificate', result) + self.assertIn('certificate', result) + self.assertIn('password', result) + self.assertIn('ciphers', result) + + # Create a token_alias assiciated with the service + token_alias = CryptoManager.manager().random_string(40) + models.ServiceTokenAlias.objects.create( + alias=token_alias, + unique_id=TEST_MAC, + service=userservice.service_pool.service, + ) + + result2 = self.invoke_success( + actor_token, + mac=TEST_MAC, + ) + + # Keys showld be different + self.assertIn('private_key', result2) + self.assertIn('key', result2) + self.assertIn('server_certificate', result2) + self.assertIn('certificate', result2) + self.assertIn('password', result2) + self.assertEqual(result['ciphers'], result2['ciphers']) diff --git a/server/src/tests/REST/test_system.py b/server/src/tests/REST/test_system.py index 5d8b4f231..219fa2fce 100644 --- a/server/src/tests/REST/test_system.py +++ b/server/src/tests/REST/test_system.py @@ -69,7 +69,7 @@ def test_overview(self) -> None: def test_chart_pool(self) -> None: # First, create fixtures for the pool DAYS = 30 - for pool in [self.user_service_managed, self.user_service_unmanaged]: + for pool in [self.user_service_managed, self.userservice_unmanaged]: stats_counters.create_stats_interval_total( id=pool.deployed_service.id, counter_type=[counters.types.stats.CounterType.ASSIGNED, counters.types.stats.CounterType.INUSE, counters.types.stats.CounterType.CACHED], diff --git a/server/src/tests/services/proxmox/test_helpers.py b/server/src/tests/services/proxmox/test_helpers.py index c60cd2cfd..744a7aa6a 100644 --- a/server/src/tests/services/proxmox/test_helpers.py +++ b/server/src/tests/services/proxmox/test_helpers.py @@ -86,7 +86,7 @@ def test_get_machines(self) -> None: self.assertGreaterEqual(len(choices), 1) for choice in choices: self.assertIsInstance(choice, dict) - self.assertIsInstance(choice['id'], int) + self.assertIsInstance(choice['id'], str) self.assertIsInstance(choice['text'], str) api.get_pool_info.assert_called_once() diff --git a/server/src/tests/utils/rest/test.py b/server/src/tests/utils/rest/test.py index d0a14f11f..40f9c153d 100644 --- a/server/src/tests/utils/rest/test.py +++ b/server/src/tests/utils/rest/test.py @@ -55,7 +55,7 @@ class RESTTestCase(test.UDSTransactionTestCase): provider: models.Provider user_service_managed: models.UserService - user_service_unmanaged: models.UserService + userservice_unmanaged: models.UserService user_services: list[models.UserService] @@ -98,7 +98,7 @@ def setUp(self) -> None: self.groups, 'managed', ) - self.user_service_unmanaged = services_fixtures.create_db_one_assigned_userservice( + self.userservice_unmanaged = services_fixtures.create_db_one_assigned_userservice( self.provider, self.admins[0], self.groups, diff --git a/server/src/uds/REST/methods/actor_v3.py b/server/src/uds/REST/methods/actor_v3.py index 9522f9ea0..84cee3529 100644 --- a/server/src/uds/REST/methods/actor_v3.py +++ b/server/src/uds/REST/methods/actor_v3.py @@ -397,15 +397,16 @@ def action(self) -> dict[str, typing.Any]: alias_token: typing.Optional[str] = None def _initialization_result( - own_token: typing.Optional[str], + token: typing.Optional[str], unique_id: typing.Optional[str], os: typing.Any, - alias_token: typing.Optional[str], + master_token: typing.Optional[str], ) -> dict[str, typing.Any]: return ActorV3Action.actor_result( { - 'own_token': own_token or alias_token, # Compat with old actor versions, TBR on 5.0 - 'token': own_token or alias_token, # New token, will be used from now onwards + 'own_token': token, # Compat with old actor versions, TBR on 5.0 + 'token': token, # New token, will be used from now onwards + 'master_token': master_token, # Master token, to replace on unmanaged machines 'unique_id': unique_id, 'os': os, } @@ -413,6 +414,8 @@ def _initialization_result( try: token = self._params['token'] + list_of_ids = get_list_of_ids(self) + # First, try to locate an user service providing this token. if self._params['type'] == consts.actor.UNMANAGED: # First, try to locate on alias table @@ -427,7 +430,7 @@ def _initialization_result( service = Service.objects.get(token=token) # If exists, create and alias for it # Get first mac and, if not exists, get first ip - unique_id = self._params['id'][0].get('mac', self._params['id'][0].get('ip', '')) + unique_id = self._params['id'][0].get('mac', self._params['id'][0].get('ip', '')).lower() if unique_id is None: raise exceptions.rest.BlockAccess() # If exists, do not create a new one (avoid creating for old 3.x actors lots of aliases...) @@ -440,20 +443,17 @@ def _initialization_result( # Locate an userService that belongs to this service and which # Build the possible ids and make initial filter to match service - list_of_ids = get_list_of_ids(self) dbfilter = UserService.objects.filter(deployed_service__service=service) else: # If not service provided token, use actor tokens if not Server.validate_token(token, server_type=types.servers.ServerType.ACTOR): raise exceptions.rest.BlockAccess() # Build the possible ids and make initial filter to match ANY userservice with provided MAC - list_of_ids = [i['mac'] for i in self._params['id'][:5]] dbfilter = UserService.objects.all() # Valid actor token, now validate access allowed. That is, look for a valid mac from the ones provided. try: # ensure idsLists has upper and lower versions for case sensitive databases - list_of_ids = get_list_of_ids(self) # Set full filter dbfilter = dbfilter.filter( unique_id__in=list_of_ids, @@ -462,7 +462,7 @@ def _initialization_result( userservice: UserService = next(iter(dbfilter)) except Exception as e: - logger.info('Unmanaged host request: %s, %s', self._params, e) + logger.info('Not managed host request: %s, %s', self._params, e) return _initialization_result(None, None, None, alias_token) # Managed by UDS, get initialization data from osmanager and return it @@ -479,11 +479,6 @@ def _initialization_result( if osmanager: os_data = osmanager.actor_data(userservice) - if service and not alias_token: # is an UNMANAGED without already an alias? - # Create a new alias for it, and save - alias_token = CryptoManager().random_string(40) # fix alias with new token - service.aliases.create(alias=alias_token) - return _initialization_result(userservice.uuid, userservice.unique_id, os_data, alias_token) except Service.DoesNotExist: raise exceptions.rest.BlockAccess() from None @@ -775,7 +770,7 @@ def action(self) -> dict[str, typing.Any]: unmanaged method expect a json POST with this fields: * id: List[dict] -> List of dictionary containing ip and mac: * token: str -> Valid Actor "master_token" (if invalid, will return an error). - * secret: Secret for commsUrl for actor (Cu + * secret: Secret for commsUrl for actor * port: port of the listener (normally 43910) This method will also regenerater the public-private key pair for client, that will be needed for the new ip @@ -788,31 +783,28 @@ def action(self) -> dict[str, typing.Any]: logger.debug('Args: %s, Params: %s', self._args, self._params) try: - dbService: Service = Service.objects.get(token=self._params['token']) - service: 'services.Service' = dbService.get_instance() + token = self._params['token'] + if ServiceTokenAlias.objects.filter(alias=token).exists(): + # Retrieve real service from token alias + dbservice = ServiceTokenAlias.objects.get(alias=token).service + else: + dbservice: Service = Service.objects.get(token=token) + service: 'services.Service' = dbservice.get_instance() except Exception: + logger.exception('Unmanaged host request: %s', self._params) return ActorV3Action.actor_result(error='Invalid token') - # Build the possible ids and ask service if it recognizes any of it - # If not recognized, will generate anyway the certificate, but will not be saved - list_of_ids = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10] - valid_id: typing.Optional[str] = service.get_valid_id(list_of_ids) - # ensure idsLists has upper and lower versions for case sensitive databases list_of_ids = get_list_of_ids(self) + valid_id: typing.Optional[str] = service.get_valid_id(list_of_ids) # Check if there is already an assigned user service # To notify it logout userservice: typing.Optional[UserService] try: - db_filter = UserService.objects.filter( - unique_id__in=list_of_ids, - state__in=[State.USABLE, State.PREPARING], - ) - userservice = next( iter( - db_filter.filter( + UserService.objects.filter( unique_id__in=list_of_ids, state__in=[State.USABLE, State.PREPARING], ) @@ -824,7 +816,7 @@ def action(self) -> dict[str, typing.Any]: # Try to infer the ip from the valid id (that could be an IP or a MAC) ip: str try: - ip = next(x['ip'] for x in self._params['id'] if valid_id in (x['ip'], x['mac'])) + ip = next(x['ip'] for x in self._params['id'] if valid_id and valid_id.lower() in (x['ip'].lower(), x['mac'].lower())) except StopIteration: ip = self._params['id'][0]['ip'] # Get first IP if no valid ip found @@ -839,7 +831,7 @@ def action(self) -> dict[str, typing.Any]: # If it is not assgined to an user service, notify service service.notify_initialization(valid_id) - # Store certificate, secret & port with service if validId + # Store certificate, secret & port with service if service recognized the id service.store_id_info( valid_id, { diff --git a/server/src/uds/core/types/ui.py b/server/src/uds/core/types/ui.py index b42ab0e44..3f8be70b4 100644 --- a/server/src/uds/core/types/ui.py +++ b/server/src/uds/core/types/ui.py @@ -121,7 +121,7 @@ class CallbackResultItem(typing.TypedDict): class Filler(typing.TypedDict): - callback_name: str + callback_name: typing.NotRequired[str] parameters: list[str] function: typing.NotRequired[collections.abc.Callable[..., CallbackResultType]] diff --git a/server/src/uds/core/ui/user_interface.py b/server/src/uds/core/ui/user_interface.py index 768322beb..a54efe51d 100644 --- a/server/src/uds/core/ui/user_interface.py +++ b/server/src/uds/core/ui/user_interface.py @@ -51,7 +51,7 @@ from uds.core import consts, exceptions, types from uds.core.managers.crypto import UDSK, CryptoManager -from uds.core.util import serializer, validators, ensure +from uds.core.util import modfinder, serializer, validators, ensure logger = logging.getLogger(__name__) @@ -1121,8 +1121,9 @@ def __init__( self._field_info.choices = gui.as_choices(choices) # if has fillers, set them if fills: - if 'function' not in fills or 'callback_name' not in fills: + if 'function' not in fills: raise ValueError('Invalid fills parameters') + fills['callback_name'] = fills.get('callback_name', modfinder.callable_path(fills['function'])) fnc = fills['function'] fills.pop('function') self._field_info.fills = fills diff --git a/server/src/uds/core/util/modfinder.py b/server/src/uds/core/util/modfinder.py index 8a051a049..d545cdbd1 100644 --- a/server/src/uds/core/util/modfinder.py +++ b/server/src/uds/core/util/modfinder.py @@ -199,3 +199,8 @@ def _checker(cls: type[T]) -> bool: module_name, checker=_checker, ) + + +# Given a callable, return the full path to it as a string +def callable_path(callable_: collections.abc.Callable[..., typing.Any]) -> str: + return f'{callable_.__module__}.{callable_.__name__}' diff --git a/server/src/uds/workers/userservice_cleaner.py b/server/src/uds/workers/userservice_cleaner.py index 4decf0953..ff59d2f3a 100644 --- a/server/src/uds/workers/userservice_cleaner.py +++ b/server/src/uds/workers/userservice_cleaner.py @@ -51,7 +51,7 @@ class UserServiceInfoItemsCleaner(Job): - frecuency = 3600 # Constant time, every hour will check for old info items + frecuency = 600 # Constant time, every hour will check for old info items # frecuency_cfg = ( # GlobalConfig.KEEP_INFO_TIME # ) # Request run cache "info" cleaner every configured seconds. If config value is changed, it will be used at next reload