1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
6 require 'helpers/git_test_helper'
7 require 'helpers/docker_migration_helper'
9 class JobTest < ActiveSupport::TestCase
10 include DockerMigrationHelper
13 BAD_COLLECTION = "#{'f' * 32}+0"
16 set_user_from_auth :active
19 def job_attrs merge_me={}
20 # Default (valid) set of attributes, with given overrides
23 script_version: "master",
24 repository: "active/foo",
28 test "Job without Docker image doesn't get locator" do
29 job = Job.new job_attrs
30 assert job.valid?, job.errors.full_messages.to_s
31 assert_nil job.docker_image_locator
34 { 'name' => [:links, :docker_image_collection_tag, :name],
35 'hash' => [:links, :docker_image_collection_hash, :name],
36 'locator' => [:collections, :docker_image, :portable_data_hash],
37 }.each_pair do |spec_type, (fixture_type, fixture_name, fixture_attr)|
38 test "Job initialized with Docker image #{spec_type} gets locator" do
39 image_spec = send(fixture_type, fixture_name).send(fixture_attr)
40 job = Job.new job_attrs(runtime_constraints:
41 {'docker_image' => image_spec})
42 assert job.valid?, job.errors.full_messages.to_s
43 assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
46 test "Job modified with Docker image #{spec_type} gets locator" do
47 job = Job.new job_attrs
48 assert job.valid?, job.errors.full_messages.to_s
49 assert_nil job.docker_image_locator
50 image_spec = send(fixture_type, fixture_name).send(fixture_attr)
51 job.runtime_constraints['docker_image'] = image_spec
52 assert job.valid?, job.errors.full_messages.to_s
53 assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
57 test "removing a Docker runtime constraint removes the locator" do
58 image_locator = collections(:docker_image).portable_data_hash
59 job = Job.new job_attrs(runtime_constraints:
60 {'docker_image' => image_locator})
61 assert job.valid?, job.errors.full_messages.to_s
62 assert_equal(image_locator, job.docker_image_locator)
63 job.runtime_constraints = {}
64 assert job.valid?, job.errors.full_messages.to_s + "after clearing runtime constraints"
65 assert_nil job.docker_image_locator
68 test "locate a Docker image with a repository + tag" do
69 image_repo, image_tag =
70 links(:docker_image_collection_tag2).name.split(':', 2)
71 job = Job.new job_attrs(runtime_constraints:
72 {'docker_image' => image_repo,
73 'docker_image_tag' => image_tag})
74 assert job.valid?, job.errors.full_messages.to_s
75 assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
78 test "can't locate a Docker image with a nonexistent tag" do
79 image_repo = links(:docker_image_collection_tag).name
80 image_tag = '__nonexistent tag__'
81 job = Job.new job_attrs(runtime_constraints:
82 {'docker_image' => image_repo,
83 'docker_image_tag' => image_tag})
84 assert(job.invalid?, "Job with bad Docker tag valid")
90 ].each do |use_config|
91 test "Job with no Docker image uses default docker image when configuration is set #{use_config}" do
92 default_docker_image = collections(:docker_image)[:portable_data_hash]
93 Rails.configuration.Containers.JobsAPI.DefaultDockerImage = default_docker_image if use_config
95 job = Job.new job_attrs
96 assert job.valid?, job.errors.full_messages.to_s
99 refute_nil job.docker_image_locator
100 assert_equal default_docker_image, job.docker_image_locator
102 assert_nil job.docker_image_locator
107 test "locate a Docker image with a partial hash" do
108 image_hash = links(:docker_image_collection_hash).name[0..24]
109 job = Job.new job_attrs(runtime_constraints:
110 {'docker_image' => image_hash})
111 assert job.valid?, job.errors.full_messages.to_s + " with partial hash #{image_hash}"
112 assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
115 { 'name' => 'arvados_test_nonexistent',
117 'locator' => BAD_COLLECTION,
118 }.each_pair do |spec_type, image_spec|
119 test "Job validation fails with nonexistent Docker image #{spec_type}" do
120 Rails.configuration.RemoteClusters = {}
121 job = Job.new job_attrs(runtime_constraints:
122 {'docker_image' => image_spec})
123 assert(job.invalid?, "nonexistent Docker image #{spec_type} #{image_spec} was valid")
127 test "Job validation fails with non-Docker Collection constraint" do
128 job = Job.new job_attrs(runtime_constraints:
129 {'docker_image' => collections(:foo_file).uuid})
130 assert(job.invalid?, "non-Docker Collection constraint was valid")
133 test "can create Job with Docker image Collection without Docker links" do
134 image_uuid = collections(:unlinked_docker_image).portable_data_hash
135 job = Job.new job_attrs(runtime_constraints: {"docker_image" => image_uuid})
136 assert(job.valid?, "Job created with unlinked Docker image was invalid")
137 assert_equal(image_uuid, job.docker_image_locator)
140 def check_attrs_unset(job, attrs)
141 assert_empty(attrs.each_key.map { |key| job.send(key) }.compact,
142 "job has values for #{attrs.keys}")
145 def check_creation_prohibited(attrs)
147 job = Job.new(job_attrs(attrs))
148 rescue ActiveModel::MassAssignmentSecurity::Error
149 # Test passes - expected attribute protection
151 check_attrs_unset(job, attrs)
155 def check_modification_prohibited(attrs)
156 job = Job.new(job_attrs)
157 attrs.each_pair do |key, value|
158 assert_raises(NoMethodError) { job.send("{key}=".to_sym, value) }
160 check_attrs_unset(job, attrs)
163 test "can't create Job with Docker image locator" do
164 check_creation_prohibited(docker_image_locator: BAD_COLLECTION)
167 test "can't assign Docker image locator to Job" do
168 check_modification_prohibited(docker_image_locator: BAD_COLLECTION)
171 SDK_MASTER = "ca68b24e51992e790f29df5cc4bc54ce1da4a1c2"
172 SDK_TAGGED = "00634b2b8a492d6f121e3cf1d6587b821136a9a7"
174 def sdk_constraint(version)
175 {runtime_constraints: {
176 "arvados_sdk_version" => version,
177 "docker_image" => links(:docker_image_collection_tag).name,
181 def check_job_sdk_version(expected)
184 refute(job.valid?, "job valid with bad Arvados SDK version")
186 assert(job.valid?, "job not valid with good Arvados SDK version")
187 assert_equal(expected, job.arvados_sdk_version)
191 test "can't create job with SDK version assigned directly" do
192 check_creation_prohibited(arvados_sdk_version: SDK_MASTER)
195 test "can't modify job to assign SDK version directly" do
196 check_modification_prohibited(arvados_sdk_version: SDK_MASTER)
199 test 'script_parameters_digest is independent of key order' do
200 j1 = Job.new(job_attrs(script_parameters: {'a' => 'a', 'ddee' => {'d' => 'd', 'e' => 'e'}}))
201 j2 = Job.new(job_attrs(script_parameters: {'ddee' => {'e' => 'e', 'd' => 'd'}, 'a' => 'a'}))
204 assert_equal(j1.script_parameters_digest, j2.script_parameters_digest)
207 test 'job fixtures have correct script_parameters_digest' do
209 d = j.script_parameters_digest
210 assert_equal(j.update_script_parameters_digest, d,
211 "wrong script_parameters_digest for #{j.uuid}")
215 test 'deep_sort_hash on array of hashes' do
216 a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]}
217 b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]}
218 assert_equal Job.deep_sort_hash(a).to_json, Job.deep_sort_hash(b).to_json
221 def try_find_reusable
222 foobar = jobs(:foobar)
224 script_version: foobar.script_version,
225 script: foobar.script,
226 script_parameters: foobar.script_parameters,
227 repository: foobar.repository,
230 # Two matching jobs exist with identical outputs. The older one
232 j = Job.find_reusable(example_attrs, {}, [], [users(:active)])
234 assert_equal foobar.uuid, j.uuid
236 # Two matching jobs exist with different outputs. Neither should
238 Job.where(uuid: jobs(:job_with_latest_version).uuid).
239 update_all(output: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+1')
240 assert_nil Job.find_reusable(example_attrs, {}, [], [users(:active)])
242 # ...unless config says to reuse the earlier job in such cases.
243 Rails.configuration.Containers.JobsAPI.ReuseJobIfOutputsDiffer = true
244 j = Job.find_reusable(example_attrs, {}, [], [users(:active)])
245 assert_equal foobar.uuid, j.uuid
248 test 'enable legacy api configuration option = true' do
249 Rails.configuration.Containers.JobsAPI.Enable = "true"
250 check_enable_legacy_jobs_api
251 assert_equal(Disable_update_jobs_api_method_list, Rails.configuration.API.DisabledAPIs)
254 test 'enable legacy api configuration option = false' do
255 Rails.configuration.Containers.JobsAPI.Enable = "false"
256 check_enable_legacy_jobs_api
257 assert_equal Disable_jobs_api_method_list, Rails.configuration.API.DisabledAPIs
260 test 'enable legacy api configuration option = auto, has jobs' do
261 Rails.configuration.Containers.JobsAPI.Enable = "auto"
263 check_enable_legacy_jobs_api
264 assert_equal(Disable_update_jobs_api_method_list, Rails.configuration.API.DisabledAPIs)
267 test 'enable legacy api configuration option = auto, no jobs' do
268 Rails.configuration.Containers.JobsAPI.Enable = "auto"
269 act_as_system_user do
272 assert_equal 0, Job.count
273 assert_equal({}, Rails.configuration.API.DisabledAPIs)
274 check_enable_legacy_jobs_api
275 assert_equal Disable_jobs_api_method_list, Rails.configuration.API.DisabledAPIs