Commit c5ac8767 authored by Cage, Gregory's avatar Cage, Gregory
Browse files

Merge branch 'stdout-endpoint' into 'dev'

Add console output endpoint

See merge request !67
parents 15852de1 cc57ae84
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ describe("JobInformation/JobInformation.vue", () => {
    beforeEach(() => {
        axiosMock = new MockAdapter(axios);
        axiosMock.onGet(new RegExp(`api/configuration/decode/*`)).reply(200, { decoded_id: 123 });
        axiosMock.onGet("/api/jobs/test_id?full=True&stdout_position=0&stdout_length=50000&stderr_position=0&stderr_length=50000").reply(200, jobResponse);
        axiosMock.onGet("/api/jobs/test_id?full=True").reply(200, jobResponse);
    });

    afterEach(() => {
+34 −10
Original line number Diff line number Diff line
<template>
    <div>
        <job-details-provider auto-refresh :jobId="job_id" :stdout_position=stdout_position :stdout_length=stdout_length :stderr_position=stderr_position :stderr_length=stderr_length @update:result="updateJob"/>
        <job-details-provider auto-refresh :job-id="job_id" @update:result="updateJob"/>
        <JobConsoleOutputProvider
            auto-refresh="True"
            :job-id="job_id"
            :stdout_position="stdout_position"
            :stdout_length="stdout_length"
            :stderr_position="stderr_position"
            :stderr_length="stderr_length"
            @update:result="updateConsoleOutputs"/>
        <h2 class="h-md">Job Information</h2>
        <table id="job-information" class="tabletip info_data_table">
            <tbody>
@@ -80,7 +88,7 @@
import { getAppRoot } from "onload/loadConfig";
import DecodedId from "../DecodedId.vue";
import CodeRow from "./CodeRow.vue";
import { JobDetailsProvider } from "components/providers/JobProvider";
import { JobDetailsProvider, JobConsoleOutputProvider } from "components/providers/JobProvider";
import UtcDate from "components/UtcDate";
import CopyToClipboard from "components/CopyToClipboard";
import JOB_STATES_MODEL from "utils/job-states-model";
@@ -91,6 +99,7 @@ export default {
        CodeRow,
        DecodedId,
        JobDetailsProvider,
        JobConsoleOutputProvider,
        UtcDate,
        CopyToClipboard,
    },
@@ -131,16 +140,31 @@ export default {
        },
        updateJob(job) {
            this.job = job;
            // Keep stdout in memory and only fetch new text via JobProvider
            if (job.tool_stdout != null) {
            // Load stored stdout and stderr
            if (this.jobIsTerminal) {
                if (job.tool_stdout) {
                    this.stdout_text += job.tool_stdout;
                    this.stdout_position += job.tool_stdout.length;
                }
            if (job.tool_stderr != null) {
                if (job.tool_stderr) {
                    this.stderr_text += job.tool_stderr;
                    this.stderr_position += job.tool_stderr.length;
                }
            }
        },
        updateConsoleOutputs(output) {
            // Keep stdout in memory and only fetch new text via JobProvider
            if (output && !this.jobIsTerminal) {
                if (output.stdout != null) {
                    this.stdout_text += output.stdout;
                    this.stdout_position += output.stdout.length;
                }
                if (output.stderr != null) {
                    this.stderr_text += output.stderr;
                    this.stderr_position += output.stderr.length;
                }
            }
        }
    },
};
</script>
+13 −2
Original line number Diff line number Diff line
@@ -5,8 +5,18 @@ import { rethrowSimple } from "utils/simple-error";
import { stateIsTerminal } from "./utils";
import { cleanPaginationParameters } from "./utils";

async function jobDetails({ jobId, stdout_position = 0, stdout_length = 0, stderr_position = 0, stderr_length = 0 }) {
    const url = `${getAppRoot()}api/jobs/${jobId}?full=True&stdout_position=${stdout_position}&stdout_length=${stdout_length}&stderr_position=${stderr_position}&stderr_length=${stderr_length}`;
async function jobDetails({ jobId }) {
    const url = `${getAppRoot()}api/jobs/${jobId}?full=True`;
    try {
        const { data } = await axios.get(url);
        return data;
    } catch (e) {
        rethrowSimple(e);
    }
}

async function jobConsoleOutput({ jobId, stdout_position = 0, stdout_length = 0, stderr_position = 0, stderr_length = 0 }) {
    const url = `${getAppRoot()}api/jobs/${jobId}/console_output?stdout_position=${stdout_position}&stdout_length=${stdout_length}&stderr_position=${stderr_position}&stderr_length=${stderr_length}`;
    try {
        const { data } = await axios.get(url);
        return data;
@@ -26,6 +36,7 @@ async function jobProblems({ jobId }) {
}

export const JobDetailsProvider = SingleQueryProvider(jobDetails, stateIsTerminal);
export const JobConsoleOutputProvider = SingleQueryProvider(jobConsoleOutput, stateIsTerminal);
export const JobProblemProvider = SingleQueryProvider(jobProblems, stateIsTerminal);

export function jobsProvider(ctx, callback, extraParams = {}) {
+16 −8
Original line number Diff line number Diff line
@@ -228,7 +228,7 @@ class JobManager:
        )
        return self.job_lock()

    def get_accessible_job(self, trans, decoded_job_id, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0):
    def get_accessible_job(self, trans, decoded_job_id):
        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()
@@ -245,8 +245,15 @@ class JobManager:
                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)
        return job

    def get_job_console_output(self, trans, job, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0):
        if job is None:
            raise ObjectNotFound()

        # If stdout_length and stdout_position are good values, then load standard out and add it to status
        console_output = dict()
        console_output["state"] = job.state
        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
@@ -256,8 +263,7 @@ class JobManager:
                    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
                    console_output["stdout"] = stdout_file.read(stdout_length)
                except Exception as e:
                    log.error("Could not read STDOUT: %s", e)
            if stderr_length > 0 and stderr_position > -1:
@@ -265,11 +271,13 @@ class JobManager:
                    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
                    console_output["stderr"] = stderr_file.read(stderr_length)
                except Exception as e:
                    log.error("Could not read STDERR: %s", e)
        return job
        else:
            console_output["stdout"] = job.tool_stdout
            console_output["stderr"] = job.tool_stderr
        return console_output

    def stop(self, job, message=None):
        if not job.finished:
+43 −10
Original line number Diff line number Diff line
@@ -178,10 +178,6 @@ class FastAPIJobs:
        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
@@ -189,17 +185,11 @@ class FastAPIJobs:
        Parameters
        - id: ID of job to return
        - full: Return extra information ?
        - stdout_position: The index of the character to begine reading stdout from
        - stdout_length: How many characters of stdout to read
        """
        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")
@@ -364,6 +354,49 @@ class JobController(BaseGalaxyAPIController, UsesVisualizationMixin):
            exceptions.RequestParameterInvalidException(f"Job with id '{job.tool_id}' is not running.")
        return self.__dictify_associations(trans, job.output_datasets, job.output_library_datasets)

    @expose_api
    def console_output(
            self,
            trans: ProvidesUserContext,
            id,
            stdout_position,
            stdout_length,
            stderr_position,
            stderr_length,
            **kwd
    ):
        """
         * GET /api/jobs/{id}/console_output
                Get the stdout and/or stderr of a job.

        :type   id: string
        :param  id: Encoded job id

        :type   stdout_position: int
        :param  stdout_position: The index of the character to begin reading stdout from

        :type   stdout_length: int
        :param  stdout_length: How many characters of stdout to read

        :type   stderr_position: int
        :param  stderr_position: The index of the character to begin reading stderr from

        :type   stderr_length: int
        :param  stderr_length: How many characters of stderr to read

        :rtype:     dict
        :returns:   dict containing stdout and stderr fields
        """
        job = self.__get_job(trans, id)
        return self.job_manager.get_job_console_output(
            trans,
            job,
            int(stdout_position),
            int(stdout_length),
            int(stderr_position),
            int(stderr_length)
        )

    @expose_api_anonymous
    def metrics(self, trans: ProvidesUserContext, **kwd):
        """
Loading