diff --git a/server/src/uds/core/services/generics/dynamic_machine/__init__.py b/server/src/uds/core/services/generics/dynamic/__init__.py similarity index 100% rename from server/src/uds/core/services/generics/dynamic_machine/__init__.py rename to server/src/uds/core/services/generics/dynamic/__init__.py diff --git a/server/src/uds/core/services/generics/dynamic_machine/dynamic_publication.py b/server/src/uds/core/services/generics/dynamic/publication.py similarity index 87% rename from server/src/uds/core/services/generics/dynamic_machine/dynamic_publication.py rename to server/src/uds/core/services/generics/dynamic/publication.py index a25d2b468..f4f6421eb 100644 --- a/server/src/uds/core/services/generics/dynamic_machine/dynamic_publication.py +++ b/server/src/uds/core/services/generics/dynamic/publication.py @@ -10,6 +10,7 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com import abc import collections.abc +import functools import logging import time import typing @@ -20,11 +21,23 @@ from uds.core.types.services import Operation from uds.core.util import autoserializable if typing.TYPE_CHECKING: - from .dynamic_service import DynamicService + from .service import DynamicService logger = logging.getLogger(__name__) +# Decorator that tests that _vmid is not empty +# Used by some default methods that require a vmid to work +def must_have_vmid(fnc: typing.Callable[[typing.Any], None]) -> typing.Callable[['DynamicPublication'], None]: + @functools.wraps(fnc) + def wrapper(self: 'DynamicPublication') -> None: + if self._vmid == '': + raise Exception(f'No machine id on {self._name} for {fnc}') + return fnc(self) + + return wrapper + + class DynamicPublication(services.Publication, autoserializable.AutoSerializable, abc.ABC): # Very simmilar to DynamicUserService, but with some differences suggested_delay = 20 # For publications, we can check every 20 seconds @@ -102,12 +115,6 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ return True - def publish(self) -> types.states.TaskState: - """ """ - self._queue = self._publish_queue.copy() - self._debug('publish') - return self._execute_queue() - def _execute_queue(self) -> types.states.TaskState: self._debug('execute_queue') op = self._current_op() @@ -135,6 +142,14 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable logger.exception('Unexpected FixedUserService exception: %s', e) return self._error(str(e)) + @typing.final + def publish(self) -> types.states.TaskState: + """ """ + self._queue = self._publish_queue.copy() + self._debug('publish') + return self._execute_queue() + + @typing.final def check_state(self) -> types.states.TaskState: """ Check what operation is going on, and acts acordly to it @@ -246,6 +261,7 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ pass + @must_have_vmid def op_start(self) -> None: """ This method is called when the service is started @@ -258,6 +274,7 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ pass + @must_have_vmid def op_stop(self) -> None: """ This method is called for stopping the service @@ -270,6 +287,7 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ pass + @must_have_vmid def op_shutdown(self) -> None: """ This method is called for shutdown the service @@ -282,31 +300,6 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ pass - def op_suspend(self) -> None: - """ - This method is called for suspend the service - """ - # Note that by default suspend is "shutdown" and not "stop" because we - self.service().suspend_machine(self, self._vmid) - - def op_suspend_completed(self) -> None: - """ - This method is called when the service suspension is completed - """ - pass - - def op_reset(self) -> None: - """ - This method is called when the service is reset - """ - pass - - def op_reset_completed(self) -> None: - """ - This method is called when the service reset is completed - """ - self.service().reset_machine(self, self._vmid) - def op_remove(self) -> None: """ This method is called when the service is removed @@ -320,14 +313,6 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ pass - def op_wait(self) -> None: - """ - This method is called when the service is waiting - Basically, will stop the execution of the queue until something external changes it (i.e. poping from the queue) - Executor does nothing - """ - pass - def op_nop(self) -> None: """ This method is called when the service is doing nothing @@ -397,24 +382,6 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ return types.states.TaskState.FINISHED - def op_suspend_checker(self) -> types.states.TaskState: - """ - This method is called to check if the service is suspended - """ - return types.states.TaskState.FINISHED - - def op_suspend_completed_checker(self) -> types.states.TaskState: - """ - This method is called to check if the service suspension is completed - """ - return types.states.TaskState.FINISHED - - def op_reset_checker(self) -> types.states.TaskState: - """ - This method is called to check if the service is reset - """ - return types.states.TaskState.FINISHED - def op_remove_checker(self) -> types.states.TaskState: """ This method is called to check if the service is removed @@ -427,12 +394,6 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ return types.states.TaskState.FINISHED - def op_wait_checker(self) -> types.states.TaskState: - """ - Wait will remain in the same state until something external changes it (i.e. poping from the queue) - """ - return types.states.TaskState.RUNNING - def op_nop_checker(self) -> types.states.TaskState: """ This method is called to check if the service is doing nothing @@ -446,6 +407,14 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable return types.states.TaskState.FINISHED # ERROR, FINISH and UNKNOWN are not here, as they are final states not needing to be checked + + # We use same operation type for Publication and UserService. We add "unsupported" to + # cover not defined operations (will raise an exception) + def op_unsupported(self) -> None: + raise Exception('Operation not defined') + + def op_unsupported_checker(self) -> types.states.TaskState: + raise Exception('Operation not defined') @staticmethod def _op2str(op: Operation) -> str: @@ -480,11 +449,11 @@ _EXECUTORS: typing.Final[ Operation.STOP_COMPLETED: DynamicPublication.op_stop_completed, Operation.SHUTDOWN: DynamicPublication.op_shutdown, Operation.SHUTDOWN_COMPLETED: DynamicPublication.op_shutdown_completed, - Operation.SUSPEND: DynamicPublication.op_suspend, - Operation.SUSPEND_COMPLETED: DynamicPublication.op_suspend_completed, + Operation.SUSPEND: DynamicPublication.op_unsupported, + Operation.SUSPEND_COMPLETED: DynamicPublication.op_unsupported, Operation.REMOVE: DynamicPublication.op_remove, Operation.REMOVE_COMPLETED: DynamicPublication.op_remove_completed, - Operation.WAIT: DynamicPublication.op_wait, + Operation.WAIT: DynamicPublication.op_unsupported, Operation.NOP: DynamicPublication.op_nop, } @@ -501,10 +470,10 @@ _CHECKERS: typing.Final[ Operation.STOP_COMPLETED: DynamicPublication.op_stop_completed_checker, Operation.SHUTDOWN: DynamicPublication.op_shutdown_checker, Operation.SHUTDOWN_COMPLETED: DynamicPublication.op_shutdown_completed_checker, - Operation.SUSPEND: DynamicPublication.op_suspend_checker, - Operation.SUSPEND_COMPLETED: DynamicPublication.op_suspend_completed_checker, + Operation.SUSPEND: DynamicPublication.op_unsupported_checker, + Operation.SUSPEND_COMPLETED: DynamicPublication.op_unsupported_checker, Operation.REMOVE: DynamicPublication.op_remove_checker, Operation.REMOVE_COMPLETED: DynamicPublication.op_remove_completed_checker, - Operation.WAIT: DynamicPublication.op_wait_checker, + Operation.WAIT: DynamicPublication.op_unsupported_checker, Operation.NOP: DynamicPublication.op_nop_checker, } diff --git a/server/src/uds/core/services/generics/dynamic_machine/dynamic_service.py b/server/src/uds/core/services/generics/dynamic/service.py similarity index 92% rename from server/src/uds/core/services/generics/dynamic_machine/dynamic_service.py rename to server/src/uds/core/services/generics/dynamic/service.py index c7678f2e6..0953eced0 100644 --- a/server/src/uds/core/services/generics/dynamic_machine/dynamic_service.py +++ b/server/src/uds/core/services/generics/dynamic/service.py @@ -39,8 +39,8 @@ from uds.core.util import fields, validators # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: - from .dynamic_userservice import DynamicUserService - from .dynamic_publication import DynamicPublication + from .userservice import DynamicUserService + from .publication import DynamicPublication logger = logging.getLogger(__name__) @@ -149,7 +149,7 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub @abc.abstractmethod def start_machine( self, caller_instance: 'DynamicUserService | DynamicPublication', machine_id: str - ) -> typing.Any: + ) -> None: """ Starts the machine Can return a task, or None if no task is returned @@ -157,7 +157,7 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub ... @abc.abstractmethod - def stop_machine(self, caller_instance: 'DynamicUserService | DynamicPublication', machine_id: str) -> typing.Any: + def stop_machine(self, caller_instance: 'DynamicUserService | DynamicPublication', machine_id: str) -> None: """ Stops the machine Can return a task, or None if no task is returned @@ -166,7 +166,7 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub def shutdown_machine( self, caller_instance: 'DynamicUserService | DynamicPublication', machine_id: str - ) -> typing.Any: + ) -> None: """ Shutdowns the machine Defaults to stop_machine @@ -176,7 +176,7 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub def reset_machine( self, caller_instance: 'DynamicUserService | DynamicPublication', machine_id: str - ) -> typing.Any: + ) -> None: """ Resets the machine Can return a task, or None if no task is returned @@ -186,7 +186,7 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub def suspend_machine( self, caller_instance: 'DynamicUserService | DynamicPublication', machine_id: str - ) -> typing.Any: + ) -> None: """ Suspends the machine Defaults to shutdown_machine. @@ -197,7 +197,7 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub @abc.abstractmethod def remove_machine( self, caller_instance: 'DynamicUserService | DynamicPublication', machine_id: str - ) -> typing.Any: + ) -> None: """ Removes the machine, or queues it for removal, or whatever :) """ @@ -207,7 +207,17 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub if self.has_field('maintain_on_error'): # If has been defined on own class... return self.maintain_on_error.value return False - + + def can_clean_errored_userservices(self) -> bool: + """ + Returns if this service can clean errored services. This is used to check if a service can be cleaned + from the stuck cleaner job, for example. By default, this method returns True. + """ + if self.has_field('maintain_on_error'): + return not self.maintain_on_error.value + + return True + def try_graceful_shutdown(self) -> bool: if self.has_field('try_soft_shutdown'): return self.try_soft_shutdown.value diff --git a/server/src/uds/core/services/generics/dynamic_machine/dynamic_userservice.py b/server/src/uds/core/services/generics/dynamic/userservice.py similarity index 97% rename from server/src/uds/core/services/generics/dynamic_machine/dynamic_userservice.py rename to server/src/uds/core/services/generics/dynamic/userservice.py index 1362df50d..b96cf1214 100644 --- a/server/src/uds/core/services/generics/dynamic_machine/dynamic_userservice.py +++ b/server/src/uds/core/services/generics/dynamic/userservice.py @@ -44,7 +44,7 @@ from uds.core.util.model import sql_stamp_seconds # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: from uds import models - from . import dynamic_service + from . import service logger = logging.getLogger(__name__) @@ -52,7 +52,6 @@ logger = logging.getLogger(__name__) # Decorator that tests that _vmid is not empty # Used by some default methods that require a vmid to work def must_have_vmid(fnc: typing.Callable[[typing.Any], None]) -> typing.Callable[['DynamicUserService'], None]: - @functools.wraps(fnc) def wrapper(self: 'DynamicUserService') -> None: if self._vmid == '': @@ -71,8 +70,8 @@ class DynamicUserService(services.UserService, autoserializable.AutoSerializable suggested_delay = 8 # Some customization fields - # If ip can be manually overriden - can_set_ip: typing.ClassVar[bool] = False + # If ip can be manually overriden, normally True... (set by actor, for example) + can_set_ip: typing.ClassVar[bool] = True # How many times we will check for a state before giving up max_state_checks: typing.ClassVar[int] = 20 # If keep_state_sets_error is true, and an error occurs, the machine is set to FINISHED instead of ERROR @@ -223,8 +222,8 @@ class DynamicUserService(services.UserService, autoserializable.AutoSerializable # Utility overrides for type checking... # Probably, overriden again on child classes - def service(self) -> 'dynamic_service.DynamicService': - return typing.cast('dynamic_service.DynamicService', super().service()) + def service(self) -> 'service.DynamicService': + return typing.cast('service.DynamicService', super().service()) @typing.final def get_name(self) -> str: @@ -253,14 +252,14 @@ class DynamicUserService(services.UserService, autoserializable.AutoSerializable @typing.final def get_ip(self) -> str: - # Provide self to the service, so it can some of our methods to generate the unique id - try: - if self._vmid: - return self.service().get_machine_ip(self, self._vmid) - except Exception: - logger.warning('Error obtaining IP for %s: %s', self.__class__.__name__, self._vmid, exc_info=True) - pass - return '' + if self._ip == '': + try: + if self._vmid: + # Provide self to the service, so it can use some of our methods for whaterever it needs + self._ip = self.service().get_machine_ip(self, self._vmid) + except Exception: + logger.warning('Error obtaining IP for %s: %s', self.__class__.__name__, self._vmid, exc_info=True) + return self._ip @typing.final def deploy_for_user(self, user: 'models.User') -> types.states.TaskState: @@ -337,6 +336,7 @@ class DynamicUserService(services.UserService, autoserializable.AutoSerializable logger.exception('Unexpected FixedUserService exception: %s', e) return self._error(e) + @typing.final def check_state(self) -> types.states.TaskState: """ Check what operation is going on, and acts acordly to it diff --git a/server/src/uds/core/services/generics/fixed_machine/__init__.py b/server/src/uds/core/services/generics/fixed/__init__.py similarity index 100% rename from server/src/uds/core/services/generics/fixed_machine/__init__.py rename to server/src/uds/core/services/generics/fixed/__init__.py diff --git a/server/src/uds/core/services/generics/fixed_machine/fixed_service.py b/server/src/uds/core/services/generics/fixed/service.py similarity index 99% rename from server/src/uds/core/services/generics/fixed_machine/fixed_service.py rename to server/src/uds/core/services/generics/fixed/service.py index 4a26d94f3..c04d12f37 100644 --- a/server/src/uds/core/services/generics/fixed_machine/fixed_service.py +++ b/server/src/uds/core/services/generics/fixed/service.py @@ -41,7 +41,7 @@ from uds.core.ui import gui # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: from uds import models - from .fixed_userservice import FixedUserService + from .userservice import FixedUserService logger = logging.getLogger(__name__) diff --git a/server/src/uds/core/services/generics/fixed_machine/fixed_userservice.py b/server/src/uds/core/services/generics/fixed/userservice.py similarity index 99% rename from server/src/uds/core/services/generics/fixed_machine/fixed_userservice.py rename to server/src/uds/core/services/generics/fixed/userservice.py index ac3712fe6..fdf305a1d 100644 --- a/server/src/uds/core/services/generics/fixed_machine/fixed_userservice.py +++ b/server/src/uds/core/services/generics/fixed/userservice.py @@ -42,7 +42,7 @@ from uds.core.util import log, autoserializable # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: from uds import models - from . import fixed_service + from . import service logger = logging.getLogger(__name__) @@ -136,8 +136,8 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable, # Utility overrides for type checking... # Probably, overriden again on child classes - def service(self) -> 'fixed_service.FixedService': - return typing.cast('fixed_service.FixedService', super().service()) + def service(self) -> 'service.FixedService': + return typing.cast('service.FixedService', super().service()) @typing.final def get_name(self) -> str: diff --git a/server/src/uds/services/OpenStack/deployment_fixed.py b/server/src/uds/services/OpenStack/deployment_fixed.py index b86919be6..c395ba073 100644 --- a/server/src/uds/services/OpenStack/deployment_fixed.py +++ b/server/src/uds/services/OpenStack/deployment_fixed.py @@ -34,7 +34,7 @@ import logging import typing from uds.core import types -from uds.core.services.generics.fixed_machine.fixed_userservice import FixedUserService, Operation +from uds.core.services.generics.fixed.userservice import FixedUserService, Operation from uds.core.util import autoserializable from .openstack import types as openstack_types diff --git a/server/src/uds/services/OpenStack/service_fixed.py b/server/src/uds/services/OpenStack/service_fixed.py index e97e5ad71..030db077d 100644 --- a/server/src/uds/services/OpenStack/service_fixed.py +++ b/server/src/uds/services/OpenStack/service_fixed.py @@ -35,8 +35,8 @@ import typing from django.utils.translation import gettext_noop as _ from uds.core import services, types -from uds.core.services.generics.fixed_machine.fixed_service import FixedService -from uds.core.services.generics.fixed_machine.fixed_userservice import FixedUserService +from uds.core.services.generics.fixed.service import FixedService +from uds.core.services.generics.fixed.userservice import FixedUserService from uds.core.ui import gui from uds.core.util import log diff --git a/server/src/uds/services/Proxmox/deployment_fixed.py b/server/src/uds/services/Proxmox/deployment_fixed.py index 607effac5..e8c90e733 100644 --- a/server/src/uds/services/Proxmox/deployment_fixed.py +++ b/server/src/uds/services/Proxmox/deployment_fixed.py @@ -34,7 +34,7 @@ import logging import typing from uds.core import types -from uds.core.services.generics.fixed_machine.fixed_userservice import FixedUserService, Operation +from uds.core.services.generics.fixed.userservice import FixedUserService, Operation from uds.core.util import autoserializable from . import client diff --git a/server/src/uds/services/Proxmox/deployment_linked.py b/server/src/uds/services/Proxmox/deployment_linked.py index b9db9fc0d..672123aa2 100644 --- a/server/src/uds/services/Proxmox/deployment_linked.py +++ b/server/src/uds/services/Proxmox/deployment_linked.py @@ -36,7 +36,7 @@ import logging import typing from uds.core import types, consts -from uds.core.services.generics.dynamic_machine.dynamic_userservice import DynamicUserService, Operation +from uds.core.services.generics.dynamic.userservice import DynamicUserService, Operation from uds.core.managers.userservice import UserServiceManager from uds.core.util import autoserializable diff --git a/server/src/uds/services/Proxmox/publication.py b/server/src/uds/services/Proxmox/publication.py index a3210a5cb..930a4166f 100644 --- a/server/src/uds/services/Proxmox/publication.py +++ b/server/src/uds/services/Proxmox/publication.py @@ -35,7 +35,7 @@ import typing from django.utils.translation import gettext as _ from uds.core import types -from uds.core.services.generics.dynamic_machine.dynamic_publication import DynamicPublication +from uds.core.services.generics.dynamic.publication import DynamicPublication from uds.core.util import autoserializable # Not imported at runtime, just for type checking diff --git a/server/src/uds/services/Proxmox/service_fixed.py b/server/src/uds/services/Proxmox/service_fixed.py index a3072f642..b7e90d59e 100644 --- a/server/src/uds/services/Proxmox/service_fixed.py +++ b/server/src/uds/services/Proxmox/service_fixed.py @@ -35,8 +35,8 @@ import typing from django.utils.translation import gettext_noop as _ from uds.core import services, types -from uds.core.services.generics.fixed_machine.fixed_service import FixedService -from uds.core.services.generics.fixed_machine.fixed_userservice import FixedUserService +from uds.core.services.generics.fixed.service import FixedService +from uds.core.services.generics.fixed.userservice import FixedUserService from uds.core.ui import gui from uds.core.util import log diff --git a/server/src/uds/services/Proxmox/service_linked.py b/server/src/uds/services/Proxmox/service_linked.py index 47cd4523e..39d54bf50 100644 --- a/server/src/uds/services/Proxmox/service_linked.py +++ b/server/src/uds/services/Proxmox/service_linked.py @@ -34,9 +34,9 @@ import typing from django.utils.translation import gettext_noop as _ from uds.core import types -from uds.core.services.generics.dynamic_machine.dynamic_publication import DynamicPublication -from uds.core.services.generics.dynamic_machine.dynamic_service import DynamicService -from uds.core.services.generics.dynamic_machine.dynamic_userservice import DynamicUserService +from uds.core.services.generics.dynamic.publication import DynamicPublication +from uds.core.services.generics.dynamic.service import DynamicService +from uds.core.services.generics.dynamic.userservice import DynamicUserService from uds.core.ui import gui from uds.core.util import validators, log, fields @@ -48,9 +48,9 @@ from .publication import ProxmoxPublication if typing.TYPE_CHECKING: from . import client from .provider import ProxmoxProvider - from uds.core.services.generics.dynamic_machine.dynamic_publication import DynamicPublication - from uds.core.services.generics.dynamic_machine.dynamic_service import DynamicService - from uds.core.services.generics.dynamic_machine.dynamic_userservice import DynamicUserService + from uds.core.services.generics.dynamic.publication import DynamicPublication + from uds.core.services.generics.dynamic.service import DynamicService + from uds.core.services.generics.dynamic.userservice import DynamicUserService logger = logging.getLogger(__name__) diff --git a/server/src/uds/services/Xen/deployment_fixed.py b/server/src/uds/services/Xen/deployment_fixed.py index 9b123e504..008db0617 100644 --- a/server/src/uds/services/Xen/deployment_fixed.py +++ b/server/src/uds/services/Xen/deployment_fixed.py @@ -34,7 +34,7 @@ import logging import typing from uds.core import types -from uds.core.services.generics.fixed_machine.fixed_userservice import FixedUserService, Operation +from uds.core.services.generics.fixed.userservice import FixedUserService, Operation from uds.core.util import log, autoserializable from . import xen_client diff --git a/server/src/uds/services/Xen/service_fixed.py b/server/src/uds/services/Xen/service_fixed.py index 0d789e44e..0d1be1a16 100644 --- a/server/src/uds/services/Xen/service_fixed.py +++ b/server/src/uds/services/Xen/service_fixed.py @@ -35,8 +35,8 @@ import collections.abc from django.utils.translation import gettext_noop as _ from uds.core import consts, services, types -from uds.core.services.generics.fixed_machine.fixed_service import FixedService -from uds.core.services.generics.fixed_machine.fixed_userservice import FixedUserService +from uds.core.services.generics.fixed.service import FixedService +from uds.core.services.generics.fixed.userservice import FixedUserService from uds.core.ui import gui from uds.core.util import log from uds.core.util.decorators import cached diff --git a/server/tests/core/services/generics/test_fixedservice.py b/server/tests/core/services/generics/test_fixedservice.py index 7797e45a2..172db8934 100644 --- a/server/tests/core/services/generics/test_fixedservice.py +++ b/server/tests/core/services/generics/test_fixedservice.py @@ -36,9 +36,9 @@ from unittest import mock from uds import models from uds.core import services, types -from uds.core.services.generics.fixed_machine import ( - fixed_service, - fixed_userservice, +from uds.core.services.generics.fixed import ( + service, + userservice, ) from uds.core.ui.user_interface import gui @@ -47,7 +47,7 @@ from ....utils.test import UDSTestCase @dataclasses.dataclass class FixedServiceIterationInfo: - queue: list[fixed_userservice.Operation] + queue: list[userservice.Operation] service_calls: list[mock._Call] = dataclasses.field(default_factory=list) user_service_calls: list[mock._Call] = dataclasses.field(default_factory=list) state: str = types.states.TaskState.RUNNING @@ -60,11 +60,11 @@ EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[FixedServiceIterationInfo]] = # Initial state for queue FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.CREATE, - fixed_userservice.Operation.SNAPSHOT_CREATE, - fixed_userservice.Operation.PROCESS_TOKEN, - fixed_userservice.Operation.START, - fixed_userservice.Operation.FINISH, + userservice.Operation.CREATE, + userservice.Operation.SNAPSHOT_CREATE, + userservice.Operation.PROCESS_TOKEN, + userservice.Operation.START, + userservice.Operation.FINISH, ], service_calls=[ mock.call.get_and_assign_machine(), @@ -76,12 +76,12 @@ EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[FixedServiceIterationInfo]] = # (this is what our testing example does, but maybe it's not the best approach for all services) FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.NOP, - fixed_userservice.Operation.STOP, - fixed_userservice.Operation.SNAPSHOT_CREATE, - fixed_userservice.Operation.PROCESS_TOKEN, - fixed_userservice.Operation.START, - fixed_userservice.Operation.FINISH, + userservice.Operation.NOP, + userservice.Operation.STOP, + userservice.Operation.SNAPSHOT_CREATE, + userservice.Operation.PROCESS_TOKEN, + userservice.Operation.START, + userservice.Operation.FINISH, ], service_calls=[ mock.call.process_snapshot(False, mock.ANY), @@ -89,21 +89,21 @@ EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[FixedServiceIterationInfo]] = ), FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.STOP, - fixed_userservice.Operation.SNAPSHOT_CREATE, - fixed_userservice.Operation.PROCESS_TOKEN, - fixed_userservice.Operation.START, - fixed_userservice.Operation.FINISH, + userservice.Operation.STOP, + userservice.Operation.SNAPSHOT_CREATE, + userservice.Operation.PROCESS_TOKEN, + userservice.Operation.START, + userservice.Operation.FINISH, ], user_service_calls=[mock.call._stop_machine()], ), # The current operation is snapshot, so check previous operation (Finished) and then process snapshot FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.SNAPSHOT_CREATE, - fixed_userservice.Operation.PROCESS_TOKEN, - fixed_userservice.Operation.START, - fixed_userservice.Operation.FINISH, + userservice.Operation.SNAPSHOT_CREATE, + userservice.Operation.PROCESS_TOKEN, + userservice.Operation.START, + userservice.Operation.FINISH, ], service_calls=[ mock.call.process_snapshot(False, mock.ANY), @@ -112,16 +112,16 @@ EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[FixedServiceIterationInfo]] = ), FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.PROCESS_TOKEN, - fixed_userservice.Operation.START, - fixed_userservice.Operation.FINISH, + userservice.Operation.PROCESS_TOKEN, + userservice.Operation.START, + userservice.Operation.FINISH, ], user_service_calls=[mock.call.db_obj()], ), FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.START, - fixed_userservice.Operation.FINISH, + userservice.Operation.START, + userservice.Operation.FINISH, ], user_service_calls=[mock.call._start_machine()], ), @@ -129,7 +129,7 @@ EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[FixedServiceIterationInfo]] = # (or if queue is empty, but that's not the case here) FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.FINISH, + userservice.Operation.FINISH, ], user_service_calls=[mock.call._start_checker()], state=types.states.TaskState.FINISHED, @@ -139,22 +139,22 @@ EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[FixedServiceIterationInfo]] = EXPECTED_REMOVAL_ITERATIONS_INFO: typing.Final[list[FixedServiceIterationInfo]] = [ FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.REMOVE, - fixed_userservice.Operation.SNAPSHOT_RECOVER, - fixed_userservice.Operation.FINISH, + userservice.Operation.REMOVE, + userservice.Operation.SNAPSHOT_RECOVER, + userservice.Operation.FINISH, ], service_calls=[mock.call.remove_and_free_machine('assigned')], ), FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.SNAPSHOT_RECOVER, - fixed_userservice.Operation.FINISH, + userservice.Operation.SNAPSHOT_RECOVER, + userservice.Operation.FINISH, ], service_calls=[mock.call.process_snapshot(True, mock.ANY)], ), FixedServiceIterationInfo( queue=[ - fixed_userservice.Operation.FINISH, + userservice.Operation.FINISH, ], state=types.states.TaskState.FINISHED, ), @@ -222,11 +222,11 @@ class FixedServiceTest(UDSTestCase): service.mock.reset_mock() userservice.mock.reset_mock() - def test_fixed_service_deploy(self) -> None: + def test_service_deploy(self) -> None: _prov, service, userservice = self.create_elements() self.check_iterations(service, userservice, EXPECTED_DEPLOY_ITERATIONS_INFO, removal=False) - def test_fixed_service_deploy_no_machine(self) -> None: + def test_service_deploy_no_machine(self) -> None: _prov, service, userservice = self.create_elements() service.available_machines_number = 2 self.deploy_service(service, userservice) # Should be deployed without issues @@ -234,7 +234,7 @@ class FixedServiceTest(UDSTestCase): # And now, should fail to deploy again self.assertRaises(Exception, self.deploy_service, service, userservice) - def test_fixed_service_removal(self) -> None: + def test_service_removal(self) -> None: _prov, service, userservice = self.create_elements() # Ensure fully deployed state for userservice @@ -244,7 +244,7 @@ class FixedServiceTest(UDSTestCase): self.check_iterations(service, userservice, EXPECTED_REMOVAL_ITERATIONS_INFO, removal=True) -class FixedTestingUserService(fixed_userservice.FixedUserService): +class FixedTestingUserService(userservice.FixedUserService): mock: 'mock.Mock' = mock.MagicMock() def start_machine(self) -> None: @@ -266,14 +266,14 @@ class FixedTestingUserService(fixed_userservice.FixedUserService): return None -class FixedTestingService(fixed_service.FixedService): +class FixedTestingService(service.FixedService): type_name = 'Fixed Service' type_type = 'FixedService' type_description = 'Fixed Service description' - token = fixed_service.FixedService.token - snapshot_type = fixed_service.FixedService.snapshot_type - machines = fixed_service.FixedService.machines + token = service.FixedService.token + snapshot_type = service.FixedService.snapshot_type + machines = service.FixedService.machines user_service_type = FixedTestingUserService first_process_called = False @@ -281,15 +281,15 @@ class FixedTestingService(fixed_service.FixedService): mock: 'mock.Mock' = mock.MagicMock() - def process_snapshot(self, remove: bool, userservice_instance: fixed_userservice.FixedUserService) -> None: + def process_snapshot(self, remove: bool, userservice_instance: userservice.FixedUserService) -> None: self.mock.process_snapshot(remove, userservice_instance) if not remove and not self.first_process_called: # We want to call start, then snapshot, again # As we have snapshot on top of queue, we need to insert NOP -> STOP # This way, NOP will be consumed right now, then start will be called and then # this will be called again - userservice_instance._push_front_op(fixed_userservice.Operation.STOP) - userservice_instance._push_front_op(fixed_userservice.Operation.NOP) + userservice_instance._push_front_op(userservice.Operation.STOP) + userservice_instance._push_front_op(userservice.Operation.NOP) self.first_process_called = True def get_machine_name(self, vmid: str) -> str: diff --git a/server/tests/core/util/test_cache.py b/server/tests/core/util/test_cache.py index f555b117b..3078c9934 100644 --- a/server/tests/core/util/test_cache.py +++ b/server/tests/core/util/test_cache.py @@ -34,7 +34,6 @@ # We use commit/rollback from ...utils.test import UDSTransactionTestCase from uds.core.util.cache import Cache -from uds.core.util.decorators import cached import time # Some random chars, that include unicode non-ascci chars diff --git a/server/tests/services/proxmox/test_userservice_fixed.py b/server/tests/services/proxmox/test_userservice_fixed.py index 91445707b..f6495c24b 100644 --- a/server/tests/services/proxmox/test_userservice_fixed.py +++ b/server/tests/services/proxmox/test_userservice_fixed.py @@ -42,7 +42,7 @@ from ...utils.generators import limited_iterator # We use transactions on some related methods (storage access, etc...) -class TestProxmovLinkedService(UDSTransactionTestCase): +class TestProxmoxFixedUserService(UDSTransactionTestCase): def setUp(self) -> None: fixtures.set_all_vm_state('stopped') diff --git a/server/tests/services/proxmox/test_userservice_linked.py b/server/tests/services/proxmox/test_userservice_linked.py index b928c62e1..16441f323 100644 --- a/server/tests/services/proxmox/test_userservice_linked.py +++ b/server/tests/services/proxmox/test_userservice_linked.py @@ -43,7 +43,7 @@ from ...utils.generators import limited_iterator # We use transactions on some related methods (storage access, etc...) -class TestProxmovLinkedService(UDSTransactionTestCase): +class TestProxmovLinkedUserService(UDSTransactionTestCase): def setUp(self) -> None: fixtures.set_all_vm_state('stopped') @@ -237,14 +237,14 @@ class TestProxmovLinkedService(UDSTransactionTestCase): self.assertEqual(state, types.states.TaskState.RUNNING) # Ensure DESTROY_VALIDATOR is in the queue self.assertIn(types.services.Operation.DESTROY_VALIDATOR, userservice._queue) - + for _ in limited_iterator(lambda: state == types.states.TaskState.RUNNING, limit=128): state = userservice.check_state() - + # Now, should be finished without any problem, no call to api should have been done self.assertEqual(state, types.states.TaskState.FINISHED) self.assertEqual(len(api.mock_calls), 0) - + # Now again, but process check_queue a couple of times before cancel # we we have an _vmid state = userservice.deploy_for_user(models.User()) @@ -282,3 +282,9 @@ class TestProxmovLinkedService(UDSTransactionTestCase): api.shutdown_machine.assert_called() else: api.stop_machine.assert_called() + + def test_userservice_basics(self) -> None: + with fixtures.patch_provider_api() as _api: + userservice = fixtures.create_userservice_linked() + userservice.set_ip('1.2.3.4') + self.assertEqual(userservice.get_ip(), '1.2.3.4')