diff --git a/server/src/uds/REST/methods/servers_management.py b/server/src/uds/REST/methods/servers_management.py index 9e470de6b..c22219c80 100644 --- a/server/src/uds/REST/methods/servers_management.py +++ b/server/src/uds/REST/methods/servers_management.py @@ -37,6 +37,7 @@ from django.utils.translation import gettext_lazy as _ from uds import models from uds.core import consts, types, ui +from uds.core.managers.servers import ServerManager from uds.core.util import net, permissions, ensure from uds.core.util.model import sql_now, process_uuid from uds.core.exceptions.rest import NotFound, RequestError @@ -321,7 +322,7 @@ class ServersServers(DetailHandler): def maintenance(self, parent: 'Model', id: str) -> typing.Any: parent = ensure.is_instance(parent, models.ServerGroup) """ - Custom method that swaps maintenance mode state for a tunnel server + Custom method that swaps maintenance mode state for a server :param item: """ item = models.Server.objects.get(uuid=process_uuid(id)) @@ -403,6 +404,7 @@ class ServersServers(DetailHandler): class ServersGroups(ModelHandler): + custom_methods = [('stats', True)] model = models.ServerGroup model_filter = { 'type__in': [ @@ -506,3 +508,18 @@ class ServersGroups(ModelHandler): item.delete() except self.model.DoesNotExist: raise NotFound('Element do not exists') from None + + def stats(self, item: 'Model') -> typing.Any: + item = ensure.is_instance(item, models.ServerGroup) + + return [ + { + 'stats': s[0].as_dict() if s[0] else None, + 'server': { + 'id': s[1].uuid, + 'name': s[1].hostname, + 'ip': s[1].ip, + }, + } + for s in ServerManager.manager().get_server_stats(item.servers.all()) + ] diff --git a/server/src/uds/core/managers/servers.py b/server/src/uds/core/managers/servers.py index f6a0aeb5c..23c76ce87 100644 --- a/server/src/uds/core/managers/servers.py +++ b/server/src/uds/core/managers/servers.py @@ -101,7 +101,7 @@ class ServerManager(metaclass=singleton.Singleton): counters[uuid] = counters.get(uuid, 0) + 1 def get_server_stats( - self, severs_filter: 'QuerySet[models.Server]' + self, servers_filter: 'QuerySet[models.Server]' ) -> list[tuple[typing.Optional['types.servers.ServerStats'], 'models.Server']]: """ Returns a list of stats for a list of servers @@ -118,8 +118,9 @@ class ServerManager(metaclass=singleton.Singleton): retrieved_stats.append((None, server)) # Retrieve, in parallel, stats for all servers (not restrained) + # Not using a transaction here, as we are only reading data with ThreadPoolExecutor(max_workers=10) as executor: - for server in severs_filter.select_for_update(): + for server in servers_filter.all(): if server.is_restrained(): continue # Skip restrained servers executor.submit(_retrieve_stats, server) @@ -504,15 +505,14 @@ class ServerManager(metaclass=singleton.Singleton): Returns: List of servers sorted by usage """ - with transaction.atomic(): - now = model_utils.sql_now() - fltrs = server_group.servers.filter(maintenance_mode=False) - fltrs = fltrs.filter(Q(locked_until=None) | Q(locked_until__lte=now)) # Only unlocked servers - if excluded_servers_uuids: - fltrs = fltrs.exclude(uuid__in=excluded_servers_uuids) + now = model_utils.sql_now() + fltrs = server_group.servers.filter(maintenance_mode=False) + fltrs = fltrs.filter(Q(locked_until=None) | Q(locked_until__lte=now)) # Only unlocked servers + if excluded_servers_uuids: + fltrs = fltrs.exclude(uuid__in=excluded_servers_uuids) - # Get the stats for all servers, but in parallel - server_stats = self.get_server_stats(fltrs) + # Get the stats for all servers, but in parallel + server_stats = self.get_server_stats(fltrs) # Sort by weight, lower first (lower is better) return [s[1] for s in sorted(server_stats, key=lambda x: x[0].weight() if x[0] else 999999999)]