From 58d24dc6e438bc6d9383a5c0c0a0478a951eca3e Mon Sep 17 00:00:00 2001 From: Gregory Cage Date: Tue, 17 Oct 2023 14:55:36 -0400 Subject: [PATCH 01/24] Add option to load stdout and stderr when requesting job status --- lib/galaxy/managers/jobs.py | 26 +++++++++++++++++++++- lib/galaxy/webapps/galaxy/api/job_files.py | 6 ++++- lib/galaxy/webapps/galaxy/api/jobs.py | 14 +++++++++++- lib/galaxy/webapps/galaxy/services/jobs.py | 13 ++++++++++- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 8b3d0a37024e..2f6225bd851d 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -231,7 +231,7 @@ def update_job_lock(self, job_lock: JobLock): ) return self.job_lock() - def get_accessible_job(self, trans, decoded_job_id): + def get_accessible_job(self, trans, decoded_job_id, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0): job = trans.sa_session.query(trans.app.model.Job).filter(trans.app.model.Job.id == decoded_job_id).first() if job is None: raise ObjectNotFound() @@ -248,6 +248,30 @@ def get_accessible_job(self, trans, decoded_job_id): if not self.dataset_manager.is_accessible(data_assoc.dataset.dataset, trans.user): raise ItemAccessibilityException("You are not allowed to rerun this job.") trans.sa_session.refresh(job) + + # If stdout_length and stdout_position are legitimate values, then return stdout with status. + if job.state == job.states.RUNNING: + working_directory = trans.app.object_store.get_filename( + job, base_dir="job_work", dir_only=True, obj_dir=True + ) + if stdout_length > 0 and stdout_position > -1: + try: + stdout_path = Path(working_directory) / STDOUT_LOCATION + stdout_file = open(stdout_path, "r") + stdout_file.seek(stdout_position) + job.job_stdout = stdout_file.read(stdout_length) + job.tool_stdout = job.job_stdout + except Exception as e: + log.error("Could not read STDOUT: %s", e) + if stderr_length > 0 and stderr_position > -1: + try: + stderr_path = Path(working_directory) / STDERR_LOCATION + stderr_file = open(stderr_path, "r") + stderr_file.seek(stderr_position) + job.job_stderr = stderr_file.read(stderr_length) + job.tool_stderr = job.job_stderr + except Exception as e: + log.error("Could not read STDERR: %s", e) return job def stop(self, job, message=None): diff --git a/lib/galaxy/webapps/galaxy/api/job_files.py b/lib/galaxy/webapps/galaxy/api/job_files.py index ea52c5a59a49..2d6c56e30b30 100644 --- a/lib/galaxy/webapps/galaxy/api/job_files.py +++ b/lib/galaxy/webapps/galaxy/api/job_files.py @@ -104,7 +104,11 @@ def create(self, trans, job_id, payload, **kwargs): target_dir = os.path.dirname(path) util.safe_makedirs(target_dir) try: - shutil.move(input_file.name, path) + if os.path.exists(path): + with open(path, "ab") as destination: + shutil.copyfileobj(open(input_file.name, "rb"), destination) + else: + shutil.move(input_file.name, path) finally: try: input_file.close() diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index 72a565b05ccd..441082287373 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -178,6 +178,10 @@ def show( id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans, full: Optional[bool] = False, + stdout_position: Optional[int] = None, + stdout_length: Optional[int] = None, + stderr_position: Optional[int] = None, + stderr_length: Optional[int] = None, ) -> Dict[str, Any]: """ Return dictionary containing description of job data @@ -186,7 +190,15 @@ def show( - id: ID of job to return - full: Return extra information ? """ - return self.service.show(trans, id, bool(full)) + return self.service.show( + trans, + id, + bool(full), + int(stdout_position) if stdout_position else 0, + int(stdout_length) if stdout_length else 0, + int(stderr_position) if stderr_position else 0, + int(stderr_length) if stderr_length else 0, + ) @router.get("/api/jobs") def index( diff --git a/lib/galaxy/webapps/galaxy/services/jobs.py b/lib/galaxy/webapps/galaxy/services/jobs.py index 98ac5bbb8f9e..363f6c7291dc 100644 --- a/lib/galaxy/webapps/galaxy/services/jobs.py +++ b/lib/galaxy/webapps/galaxy/services/jobs.py @@ -48,8 +48,19 @@ def show( trans: ProvidesUserContext, id: DecodedDatabaseIdField, full: bool = False, + stdout_position: int = 0, + stdout_length: int = 0, + stderr_position: int = 0, + stderr_length: int = 0, ) -> Dict[str, Any]: - job = self.job_manager.get_accessible_job(trans, id) + job = self.job_manager.get_accessible_job( + trans, + id, + stdout_position, + stdout_length, + stderr_position, + stderr_length + ) return view_show_job(trans, job, bool(full)) def index( From 89cc78a682275952ae0d6515368222a1cc6a3869 Mon Sep 17 00:00:00 2001 From: Gregory Cage Date: Fri, 3 Nov 2023 11:20:31 -0400 Subject: [PATCH 02/24] Update api endpoint for jobs --- lib/galaxy/managers/jobs.py | 2 +- lib/galaxy/webapps/galaxy/api/jobs.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index 2f6225bd851d..cc329f3700b2 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -249,7 +249,7 @@ def get_accessible_job(self, trans, decoded_job_id, stdout_position=-1, stdout_l raise ItemAccessibilityException("You are not allowed to rerun this job.") trans.sa_session.refresh(job) - # If stdout_length and stdout_position are legitimate values, then return stdout with status. + # If stdout and stderr parameters are legitimate values, then return with status. if job.state == job.states.RUNNING: working_directory = trans.app.object_store.get_filename( job, base_dir="job_work", dir_only=True, obj_dir=True diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py index 441082287373..9254a412a7a3 100644 --- a/lib/galaxy/webapps/galaxy/api/jobs.py +++ b/lib/galaxy/webapps/galaxy/api/jobs.py @@ -16,7 +16,7 @@ Optional, Union, ) - +from pathlib import Path from fastapi import ( Depends, Query, @@ -189,6 +189,10 @@ def show( Parameters - id: ID of job to return - full: Return extra information ? + - stdout_position: The index of the character to begin reading stdout from + - stdout_length: How many characters of stdout to read + - stderr_position: The index of the character to begin reading stderr from + - stderr_length: How many characters of stderr to read """ return self.service.show( trans, From 870917ba2da6adab21903b3facd16d7357625f6d Mon Sep 17 00:00:00 2001 From: Gregory Cage Date: Fri, 3 Nov 2023 13:48:07 -0400 Subject: [PATCH 03/24] Add missing variables and import --- lib/galaxy/managers/jobs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/galaxy/managers/jobs.py b/lib/galaxy/managers/jobs.py index cc329f3700b2..060f3cc43377 100644 --- a/lib/galaxy/managers/jobs.py +++ b/lib/galaxy/managers/jobs.py @@ -5,6 +5,7 @@ date, datetime, ) +from pathlib import Path from boltons.iterutils import remap from pydantic import ( @@ -72,6 +73,9 @@ log = logging.getLogger(__name__) +STDOUT_LOCATION = "outputs/tool_stdout" +STDERR_LOCATION = "outputs/tool_stderr" + class JobLock(BaseModel): active: bool = Field(title="Job lock status", description="If active, jobs will not dispatch") From 6dfbc7a882818ae511bdc993f9a7d60af135484c Mon Sep 17 00:00:00 2001 From: Gregory Cage Date: Fri, 3 Nov 2023 13:48:55 -0400 Subject: [PATCH 04/24] Add capability for UI to load live job stdout and stderr --- .../src/components/JobInformation/CodeRow.vue | 31 ++++++++++++++----- .../JobInformation/JobInformation.vue | 21 +++++++++++-- .../src/components/providers/JobProvider.js | 5 +-- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/client/src/components/JobInformation/CodeRow.vue b/client/src/components/JobInformation/CodeRow.vue index e5b3e5d8ce82..4146958e9f3f 100644 --- a/client/src/components/JobInformation/CodeRow.vue +++ b/client/src/components/JobInformation/CodeRow.vue @@ -1,10 +1,5 @@