diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py index f4786c41bce1..8f4b9c428e82 100644 --- a/lib/galaxy/managers/users.py +++ b/lib/galaxy/managers/users.py @@ -40,6 +40,7 @@ ) from galaxy.managers.base import combine_lists from galaxy.model import ( + Job, User, UserAddress, UserQuotaUsage, @@ -161,6 +162,21 @@ def delete(self, user, flush=True): "The configuration of this Galaxy instance does not allow admins to delete users." ) super().delete(user, flush=flush) + self._stop_all_jobs_from_user(user) + + def _stop_all_jobs_from_user(self, user): + active_jobs = self._get_all_active_jobs_from_user(user) + session = self.session() + for job in active_jobs: + job.mark_deleted(self.app.config.track_jobs_in_database) + with transaction(session): + session.commit() + + def _get_all_active_jobs_from_user(self, user: User) -> List[Job]: + """Get all jobs that are not ready yet and belong to the given user.""" + stmt = select(Job).where(and_(Job.user_id == user.id, Job.state.in_(Job.non_ready_states))) + jobs = self.session().scalars(stmt) + return jobs def undelete(self, user, flush=True): """Remove the deleted flag for the given user.""" diff --git a/lib/galaxy_test/api/test_users.py b/lib/galaxy_test/api/test_users.py index a92e13769a8e..fd39e99a1473 100644 --- a/lib/galaxy_test/api/test_users.py +++ b/lib/galaxy_test/api/test_users.py @@ -2,13 +2,18 @@ from galaxy_test.base.api_asserts import assert_object_id_error from galaxy_test.base.decorators import ( requires_admin, + requires_new_history, requires_new_user, ) -from galaxy_test.base.populators import skip_without_tool +from galaxy_test.base.populators import ( + DatasetPopulator, + skip_without_tool, +) TEST_USER_EMAIL = "user_for_users_index_test@bx.psu.edu" TEST_USER_EMAIL_INDEX_DELETED = "user_for_users_index_deleted_test@bx.psu.edu" TEST_USER_EMAIL_DELETE = "user_for_delete_test@bx.psu.edu" +TEST_USER_EMAIL_DELETE_CANCEL_JOBS = "user_for_delete_cancel_jobs_test@bx.psu.edu" TEST_USER_EMAIL_PURGE = "user_for_purge_test@bx.psu.edu" TEST_USER_EMAIL_UNDELETE = "user_for_undelete_test@bx.psu.edu" TEST_USER_EMAIL_SHOW = "user_for_show_test@bx.psu.edu" @@ -129,6 +134,45 @@ def test_undelete_user(self): undeleted_user = self._get(f"users/{user['id']}", admin=True).json() assert undeleted_user["deleted"] is False, undeleted_user + @requires_admin + @requires_new_user + @requires_new_history + @skip_without_tool("cat_data_and_sleep") + def test_delete_user_cancel_all_jobs(self): + dataset_populator = DatasetPopulator(self.galaxy_interactor) + with self._different_user(TEST_USER_EMAIL_DELETE_CANCEL_JOBS): + user_id = self._get_current_user_id() + history_id = dataset_populator.new_history() + hda_id = dataset_populator.new_dataset(history_id)["id"] + + inputs = { + "input1": {"src": "hda", "id": hda_id}, + "sleep_time": 6000, + } + run_response = dataset_populator.run_tool_raw( + "cat_data_and_sleep", + inputs, + history_id, + ) + self._assert_status_code_is_ok(run_response) + + job_id = run_response.json()["jobs"][0]["id"] + + # Wait a bit for the job to be ready + expected_job_states = ["new", "queued", "running"] + dataset_populator.wait_for_job(job_id, ok_states=expected_job_states) + + # Get the job state + job_response = self._get(f"jobs/{job_id}").json() + assert job_response["state"] in expected_job_states, job_response + + # Delete user will cancel all jobs + self._delete(f"users/{user_id}", admin=True) + + # Get the job state again (this time as admin), it should be deleting + job_response = self._get(f"jobs/{job_id}", admin=True).json() + assert job_response["state"] == "deleting", job_response + @requires_new_user def test_information(self): user = self._setup_user(TEST_USER_EMAIL)