X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/627fbeb0406fcdd9cdc9e90a6e9529f48dc37321..02f24a0c4a01a7ab46645c21630b9f48065b4b96:/services/api/app/models/job.rb?ds=sidebyside diff --git a/services/api/app/models/job.rb b/services/api/app/models/job.rb index 7536c3b5c3..7508ead5d5 100644 --- a/services/api/app/models/job.rb +++ b/services/api/app/models/job.rb @@ -1,3 +1,7 @@ +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: AGPL-3.0 + require 'log_reuse_info' require 'safe_json' @@ -29,7 +33,7 @@ class Job < ArvadosModel has_many :commit_ancestors, :foreign_key => :descendant, :primary_key => :script_version has_many(:nodes, foreign_key: :job_uuid, primary_key: :uuid) - class SubmitIdReused < StandardError + class SubmitIdReused < RequestError end api_accessible :user, extend: :common do |t| @@ -245,22 +249,23 @@ class Job < ArvadosModel candidates = candidates.where( 'state = ? or (owner_uuid = ? and state in (?))', Job::Complete, current_user.uuid, [Job::Queued, Job::Running]) - log_reuse_info { "have #{candidates.count} candidates after filtering on job state ((state=Complete) or (state=Queued/Running and (submitted by current user)))" } + log_reuse_info(candidates) { "after filtering on job state ((state=Complete) or (state=Queued/Running and (submitted by current user)))" } digest = Job.sorted_hash_digest(attrs[:script_parameters]) candidates = candidates.where('script_parameters_digest = ?', digest) - log_reuse_info { "have #{candidates.count} candidates after filtering on script_parameters_digest #{digest}" } + log_reuse_info(candidates) { "after filtering on script_parameters_digest #{digest}" } candidates = candidates.where('nondeterministic is distinct from ?', true) - log_reuse_info { "have #{candidates.count} candidates after filtering on !nondeterministic" } + log_reuse_info(candidates) { "after filtering on !nondeterministic" } # prefer Running jobs over Queued candidates = candidates.order('state desc, created_at') candidates = apply_filters candidates, filters - log_reuse_info { "have #{candidates.count} candidates after filtering on repo, script, and custom filters #{filters.inspect}" } + log_reuse_info(candidates) { "after filtering on repo, script, and custom filters #{filters.inspect}" } chosen = nil + chosen_output = nil incomplete_job = nil candidates.each do |j| if j.state != Job::Complete @@ -275,17 +280,25 @@ class Job < ArvadosModel # Ignore: we have already decided not to reuse any completed # job. log_reuse_info { "job #{j.uuid} with output #{j.output} ignored, see above" } + elsif j.output.nil? + log_reuse_info { "job #{j.uuid} has nil output" } + elsif j.log.nil? + log_reuse_info { "job #{j.uuid} has nil log" } elsif Rails.configuration.reuse_job_if_outputs_differ - if Collection.readable_by(current_user).find_by_portable_data_hash(j.output) - log_reuse_info { "job #{j.uuid} with output #{j.output} is reusable; decision is final." } - return j - else - # Ignore: keep locking for an incomplete job or one whose + if !Collection.readable_by(current_user).find_by_portable_data_hash(j.output) + # Ignore: keep looking for an incomplete job or one whose # output is readable. log_reuse_info { "job #{j.uuid} output #{j.output} unavailable to user; continuing search" } + elsif !Collection.readable_by(current_user).find_by_portable_data_hash(j.log) + # Ignore: keep looking for an incomplete job or one whose + # log is readable. + log_reuse_info { "job #{j.uuid} log #{j.log} unavailable to user; continuing search" } + else + log_reuse_info { "job #{j.uuid} with output #{j.output} is reusable; decision is final." } + return j end - elsif chosen - if chosen.output != j.output + elsif chosen_output + if chosen_output != j.output # If two matching jobs produced different outputs, run a new # job (or use one that's already running/queued) instead of # choosing one arbitrarily. @@ -304,9 +317,15 @@ class Job < ArvadosModel # any further investigation of reusable jobs is futile. log_reuse_info { "job #{j.uuid} output #{j.output} is unavailable to user; this means no finished job can be reused (see reuse_job_if_outputs_differ in application.default.yml)" } chosen = false + elsif !Collection.readable_by(current_user).find_by_portable_data_hash(j.log) + # This user cannot read the log of this job, don't try to reuse the + # job but consider if the output is consistent. + log_reuse_info { "job #{j.uuid} log #{j.log} is unavailable to user; continuing search" } + chosen_output = j.output else log_reuse_info { "job #{j.uuid} with output #{j.output} can be reused; continuing search in case other candidates have different outputs" } chosen = j + chosen_output = j.output end end j = chosen || incomplete_job