diff --git a/server/src/tests/core/services/generics/fixtures.py b/server/src/tests/core/services/generics/fixtures.py index baf1b61ed..5c1236aec 100644 --- a/server/src/tests/core/services/generics/fixtures.py +++ b/server/src/tests/core/services/generics/fixtures.py @@ -526,6 +526,7 @@ class DynamicTestingService(dynamic_service.DynamicService): caller_instance: typing.Optional[dynamic_userservice.DynamicUserService | dynamic_publication.DynamicPublication], vmid: str, ) -> None: + super().set_deleting_state(vmid) # Call parent set_delete_runnign self.mock.remove(caller_instance, vmid) self.machine_running_flag = False diff --git a/server/src/tests/core/services/generics/test_dynamic_publication.py b/server/src/tests/core/services/generics/test_dynamic_publication.py index 2e61ff8b5..5a2838dda 100644 --- a/server/src/tests/core/services/generics/test_dynamic_publication.py +++ b/server/src/tests/core/services/generics/test_dynamic_publication.py @@ -196,6 +196,26 @@ class DynamicPublicationTest(UDSTestCase): self.assertEqual(publication.check_state(), types.states.TaskState.ERROR) self.assertEqual(publication.error_reason(), 'Max retries reached') self.assertEqual(counter, 10) # 4 retries + 5 retries after reset + 1 of the reset itself + + def test_publication_delete(self) -> None: + service = fixtures.create_dynamic_service() + publication = fixtures.create_dynamic_publication(service) + publication._queue = [ + types.services.Operation.NOP, # First check + types.services.Operation.DELETE, # Execute-check + types.services.Operation.FINISH, # Finish + ] + + state = types.states.TaskState.RUNNING + counter = 0 # to be able to use it outside the loop + # Shuold keep running until service notify_delete is called + for counter in limited_iterator(lambda: state == types.states.TaskState.RUNNING, limit=128): + state = publication.check_state() + if counter == 16: # After 16 iterations, we will call notify_deleted + publication.service().notify_deleted(publication._vmid) + + # Counter should be greater than 16 + self.assertGreater(counter, 16) EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[DynamicPublicationIterationInfo]] = [ diff --git a/server/src/tests/core/services/generics/test_dynamic_service.py b/server/src/tests/core/services/generics/test_dynamic_service.py index c4cbd5e4f..203d11496 100644 --- a/server/src/tests/core/services/generics/test_dynamic_service.py +++ b/server/src/tests/core/services/generics/test_dynamic_service.py @@ -319,6 +319,27 @@ class DynamicServiceTest(UDSTestCase): self.assertEqual(userservice.check_state(), types.states.TaskState.ERROR) self.assertEqual(userservice.error_reason(), 'Max retries reached') self.assertEqual(counter, 10) # 4 retries + 5 retries after reset + 1 of the reset itself + + def test_userservice_delete(self) -> None: + service = fixtures.create_dynamic_service() + userservice = fixtures.create_dynamic_userservice(service) + userservice._vmid = 'vmid' + userservice._queue = [ + types.services.Operation.NOP, # First check + types.services.Operation.DELETE, # Execute-check + types.services.Operation.FINISH, # Finish + ] + + state = types.states.TaskState.RUNNING + counter = 0 # to be able to use it outside the loop + # Shuold keep running until service notify_delete is called + for counter in limited_iterator(lambda: state == types.states.TaskState.RUNNING, limit=128): + state = userservice.check_state() + if counter == 16: # After 16 iterations, we will call notify_deleted + userservice.service().notify_deleted(userservice._vmid) + + # Counter should be greater than 16 + self.assertGreater(counter, 16) EXPECTED_DEPLOY_ITERATIONS_INFO: typing.Final[list[DynamicServiceIterationInfo]] = [ diff --git a/server/src/uds/core/services/generics/dynamic/publication.py b/server/src/uds/core/services/generics/dynamic/publication.py index b5b186668..02788e666 100644 --- a/server/src/uds/core/services/generics/dynamic/publication.py +++ b/server/src/uds/core/services/generics/dynamic/publication.py @@ -64,6 +64,8 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable # Extra info, not serializable, to keep information in case of exception and debug it _error_debug_info: typing.Optional[str] = None + # Default queues for publication. Should be enough for most of the cases + # but can be overrided if needed _publish_queue: typing.ClassVar[list[Operation]] = [ Operation.INITIALIZE, Operation.CREATE, @@ -488,7 +490,7 @@ class DynamicPublication(services.Publication, autoserializable.AutoSerializable """ This method is called to check if the service is removed """ - if self.service().is_delete_running(self, self._vmid) is False: + if self.service().check_deleting_status(self, self._vmid) is False: return types.states.TaskState.FINISHED return types.states.TaskState.RUNNING diff --git a/server/src/uds/core/services/generics/dynamic/service.py b/server/src/uds/core/services/generics/dynamic/service.py index 00586e13d..268200eb7 100644 --- a/server/src/uds/core/services/generics/dynamic/service.py +++ b/server/src/uds/core/services/generics/dynamic/service.py @@ -207,16 +207,14 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub """ # Default is to stop "hard" return self.stop(caller_instance, vmid) - + def delete(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> None: """ Removes the machine, or queues it for removal, or whatever :) Use the caller_instance to notify anything if needed, or to identify caller """ # Store the deletion has started for future reference - with self.storage.as_dict() as storage: - # Store deleting vmid - storage[f'deleting_{vmid}'] = True + self.set_deleting_state(vmid) DeferredDeletionWorker.add(self, vmid) @@ -247,7 +245,15 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub with self.storage.as_dict() as storage: del storage[f'deleting_{vmid}'] - def is_delete_running(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> bool: + def set_deleting_state(self, vmid: str) -> None: + """ + Marks a machine as deleting + """ + with self.storage.as_dict() as storage: + # Store deleting vmid + storage[f'deleting_{vmid}'] = True + + def check_deleting_status(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> bool: """ Checks if the deferred deletion of a machine is running Default implementation is return False always diff --git a/server/src/uds/core/services/generics/dynamic/userservice.py b/server/src/uds/core/services/generics/dynamic/userservice.py index d791df170..7481b56a3 100644 --- a/server/src/uds/core/services/generics/dynamic/userservice.py +++ b/server/src/uds/core/services/generics/dynamic/userservice.py @@ -811,7 +811,7 @@ class DynamicUserService(services.UserService, autoserializable.AutoSerializable """ This method is called to check if the service is removed """ - if self.service().is_delete_running(self, self._vmid) is False: + if self.service().check_deleting_status(self, self._vmid) is False: return types.states.TaskState.FINISHED return types.states.TaskState.RUNNING