2 require 'helpers/git_test_helper'
4 class Arvados::V1::JobReuseControllerTest < ActionController::TestCase
5 fixtures :repositories, :users, :jobs, :links, :collections
7 # See git_setup.rb for the commit log for test.git.tar
11 @controller = Arvados::V1::JobsController.new
12 authorize_with :active
15 test "reuse job with no_reuse=false" do
19 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
22 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
26 assert_response :success
27 assert_not_nil assigns(:object)
28 new_job = JSON.parse(@response.body)
29 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
30 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
33 test "reuse job with find_or_create=true" do
37 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
40 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
46 assert_response :success
47 assert_not_nil assigns(:object)
48 new_job = JSON.parse(@response.body)
49 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
50 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
53 test "reuse job with symbolic script_version" do
57 script_version: "tag1",
60 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
66 assert_response :success
67 assert_not_nil assigns(:object)
68 new_job = JSON.parse(@response.body)
69 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
70 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
73 test "do not reuse job because no_reuse=true" do
78 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
81 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
86 assert_response :success
87 assert_not_nil assigns(:object)
88 new_job = JSON.parse(@response.body)
89 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
90 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
93 [false, "false"].each do |whichfalse|
94 test "do not reuse job because find_or_create=#{whichfalse.inspect}" do
98 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
101 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
105 find_or_create: whichfalse
107 assert_response :success
108 assert_not_nil assigns(:object)
109 new_job = JSON.parse(@response.body)
110 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
111 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
115 test "do not reuse job because output is not readable by user" do
116 authorize_with :job_reader
120 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
123 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
129 assert_response :success
130 assert_not_nil assigns(:object)
131 new_job = JSON.parse(@response.body)
132 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
133 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
136 test "test_cannot_reuse_job_no_output" do
140 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
143 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
147 assert_response :success
148 assert_not_nil assigns(:object)
149 new_job = JSON.parse(@response.body)
150 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykppp', new_job['uuid']
153 test "test_reuse_job_range" do
157 minimum_script_version: "tag1",
158 script_version: "master",
161 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
165 assert_response :success
166 assert_not_nil assigns(:object)
167 new_job = JSON.parse(@response.body)
168 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
169 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
172 test "cannot_reuse_job_no_minimum_given_so_must_use_specified_commit" do
176 script_version: "master",
179 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
183 assert_response :success
184 assert_not_nil assigns(:object)
185 new_job = JSON.parse(@response.body)
186 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
187 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
190 test "test_cannot_reuse_job_different_input" do
194 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
197 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
201 assert_response :success
202 assert_not_nil assigns(:object)
203 new_job = JSON.parse(@response.body)
204 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
205 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
208 test "test_cannot_reuse_job_different_version" do
212 script_version: "master",
215 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
219 assert_response :success
220 assert_not_nil assigns(:object)
221 new_job = JSON.parse(@response.body)
222 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
223 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
226 test "test_can_reuse_job_submitted_nondeterministic" do
230 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
233 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
236 nondeterministic: true
238 assert_response :success
239 assert_not_nil assigns(:object)
240 new_job = JSON.parse(@response.body)
241 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
242 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
245 test "test_cannot_reuse_job_past_nondeterministic" do
249 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
252 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
256 assert_response :success
257 assert_not_nil assigns(:object)
258 new_job = JSON.parse(@response.body)
259 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykyyy', new_job['uuid']
260 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
263 test "test_cannot_reuse_job_no_permission" do
264 authorize_with :spectator
268 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
271 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
275 assert_response :success
276 assert_not_nil assigns(:object)
277 new_job = JSON.parse(@response.body)
278 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
279 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
282 test "test_cannot_reuse_job_excluded" do
286 minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
287 script_version: "master",
289 exclude_script_versions: ["tag1"],
291 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
295 assert_response :success
296 assert_not_nil assigns(:object)
297 new_job = JSON.parse(@response.body)
298 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
299 assert_not_equal('4fe459abe02d9b365932b8f5dc419439ab4e2577',
300 new_job['script_version'])
303 test "cannot reuse job with find_or_create but excluded version" do
307 script_version: "master",
310 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
314 find_or_create: true,
315 minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
316 exclude_script_versions: ["tag1"],
318 assert_response :success
319 assert_not_nil assigns(:object)
320 new_job = JSON.parse(@response.body)
321 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
322 assert_not_equal('4fe459abe02d9b365932b8f5dc419439ab4e2577',
323 new_job['script_version'])
327 'repository' => ['=', 'foo'],
328 'script' => ['=', 'hash'],
329 'script_version' => ['in git', 'master'],
330 'docker_image_locator' => ['=', nil],
331 'arvados_sdk_version' => ['=', nil],
334 def filters_from_hash(hash)
335 hash.each_pair.map { |name, filter| [name] + filter }
338 test "can reuse a Job based on filters" do
339 filters_hash = BASE_FILTERS.
340 merge('script_version' => ['in git', 'tag1'])
344 script_version: "master",
347 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
351 filters: filters_from_hash(filters_hash),
352 find_or_create: true,
354 assert_response :success
355 assert_not_nil assigns(:object)
356 new_job = JSON.parse(@response.body)
357 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
358 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
361 test "can not reuse a Job based on filters" do
362 filters = filters_from_hash(BASE_FILTERS
363 .reject { |k| k == 'script_version' })
364 filters += [["script_version", "in git",
365 "31ce37fe365b3dc204300a3e4c396ad333ed0556"],
366 ["script_version", "not in git", ["tag1"]]]
370 script_version: "master",
373 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
378 find_or_create: true,
380 assert_response :success
381 assert_not_nil assigns(:object)
382 new_job = JSON.parse(@response.body)
383 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
384 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
387 test "can not reuse a Job based on arbitrary filters" do
388 filters_hash = BASE_FILTERS.
389 merge("created_at" => ["<", "2010-01-01T00:00:00Z"])
393 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
396 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
400 filters: filters_from_hash(filters_hash),
401 find_or_create: true,
403 assert_response :success
404 assert_not_nil assigns(:object)
405 new_job = JSON.parse(@response.body)
406 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
407 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
410 test "can reuse a Job with a Docker image" do
414 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
417 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
420 runtime_constraints: {
421 docker_image: 'arvados/apitestfixture',
424 find_or_create: true,
426 assert_response :success
427 new_job = assigns(:object)
428 assert_not_nil new_job
429 target_job = jobs(:previous_docker_job_run)
430 [:uuid, :script_version, :docker_image_locator].each do |attr|
431 assert_equal(target_job.send(attr), new_job.send(attr))
435 test "can reuse a Job with a Docker image hash filter" do
436 filters_hash = BASE_FILTERS.
437 merge("script_version" =>
438 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
439 "docker_image_locator" =>
440 ["in docker", links(:docker_image_collection_hash).name])
444 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
447 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
451 filters: filters_from_hash(filters_hash),
452 find_or_create: true,
454 assert_response :success
455 new_job = assigns(:object)
456 assert_not_nil new_job
457 target_job = jobs(:previous_docker_job_run)
458 [:uuid, :script_version, :docker_image_locator].each do |attr|
459 assert_equal(target_job.send(attr), new_job.send(attr))
463 test "reuse Job with Docker image repo+tag" do
464 filters_hash = BASE_FILTERS.
465 merge("script_version" =>
466 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
467 "docker_image_locator" =>
468 ["in docker", links(:docker_image_collection_tag2).name])
472 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
475 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
479 filters: filters_from_hash(filters_hash),
480 find_or_create: true,
482 assert_response :success
483 new_job = assigns(:object)
484 assert_not_nil new_job
485 target_job = jobs(:previous_docker_job_run)
486 [:uuid, :script_version, :docker_image_locator].each do |attr|
487 assert_equal(target_job.send(attr), new_job.send(attr))
491 test "new job with unknown Docker image filter" do
492 filters_hash = BASE_FILTERS.
493 merge("docker_image_locator" => ["in docker", "_nonesuchname_"])
497 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
500 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
504 filters: filters_from_hash(filters_hash),
505 find_or_create: true,
507 assert_response :success
508 new_job = assigns(:object)
509 assert_not_nil new_job
510 assert_not_equal(jobs(:previous_docker_job_run).uuid, new_job.uuid)
513 ["repository", "script"].each do |skip_key|
514 test "missing #{skip_key} filter raises an error" do
515 filters = filters_from_hash(BASE_FILTERS.reject { |k| k == skip_key })
519 script_version: "master",
522 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
527 find_or_create: true,
529 assert_includes(405..599, @response.code.to_i,
530 "bad status code with missing #{skip_key} filter")
534 test "find Job with script version range" do
535 get :index, filters: [["repository", "=", "foo"],
536 ["script", "=", "hash"],
537 ["script_version", "in git", "tag1"]]
538 assert_response :success
539 assert_not_nil assigns(:objects)
540 assert_includes(assigns(:objects).map { |job| job.uuid },
541 jobs(:previous_job_run).uuid)
544 test "find Job with script version range exclusions" do
545 get :index, filters: [["repository", "=", "foo"],
546 ["script", "=", "hash"],
547 ["script_version", "not in git", "tag1"]]
548 assert_response :success
549 assert_not_nil assigns(:objects)
550 refute_includes(assigns(:objects).map { |job| job.uuid },
551 jobs(:previous_job_run).uuid)
554 test "find Job with Docker image range" do
555 get :index, filters: [["docker_image_locator", "in docker",
556 "arvados/apitestfixture"]]
557 assert_response :success
558 assert_not_nil assigns(:objects)
559 assert_includes(assigns(:objects).map { |job| job.uuid },
560 jobs(:previous_docker_job_run).uuid)
561 refute_includes(assigns(:objects).map { |job| job.uuid },
562 jobs(:previous_job_run).uuid)
565 test "find Job with Docker image using reader tokens" do
566 authorize_with :inactive
568 filters: [["docker_image_locator", "in docker",
569 "arvados/apitestfixture"]],
570 reader_tokens: [api_token(:active)],
572 assert_response :success
573 assert_not_nil assigns(:objects)
574 assert_includes(assigns(:objects).map { |job| job.uuid },
575 jobs(:previous_docker_job_run).uuid)
576 refute_includes(assigns(:objects).map { |job| job.uuid },
577 jobs(:previous_job_run).uuid)
580 test "'in docker' filter accepts arrays" do
581 get :index, filters: [["docker_image_locator", "in docker",
582 ["_nonesuchname_", "arvados/apitestfixture"]]]
583 assert_response :success
584 assert_not_nil assigns(:objects)
585 assert_includes(assigns(:objects).map { |job| job.uuid },
586 jobs(:previous_docker_job_run).uuid)
587 refute_includes(assigns(:objects).map { |job| job.uuid },
588 jobs(:previous_job_run).uuid)
591 test "'not in docker' filter accepts arrays" do
592 get :index, filters: [["docker_image_locator", "not in docker",
593 ["_nonesuchname_", "arvados/apitestfixture"]]]
594 assert_response :success
595 assert_not_nil assigns(:objects)
596 assert_includes(assigns(:objects).map { |job| job.uuid },
597 jobs(:previous_job_run).uuid)
598 refute_includes(assigns(:objects).map { |job| job.uuid },
599 jobs(:previous_docker_job_run).uuid)
602 def create_foo_hash_job_params(params)
603 if not params.has_key?(:find_or_create)
604 params[:find_or_create] = true
606 job_attrs = params.delete(:job) || {}
609 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
612 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
619 def check_new_job_created_from(params)
620 start_time = Time.now
621 post(:create, create_foo_hash_job_params(params))
622 assert_response :success
623 new_job = assigns(:object)
624 assert_not_nil new_job
625 assert_operator(start_time, :<=, new_job.created_at)
629 def check_errors_from(params)
630 post(:create, create_foo_hash_job_params(params))
631 assert_includes(405..499, @response.code.to_i)
632 errors = json_response.fetch("errors", [])
633 assert(errors.any?, "no errors assigned from #{params}")
634 refute(errors.any? { |msg| msg =~ /^#<[A-Za-z]+: / },
635 "errors include raw exception")
639 # 1de84a8 is on the b1 branch, after master's tip.
640 test "new job created from unsatisfiable minimum version filter" do
641 filters_hash = BASE_FILTERS.merge("script_version" => ["in git", "1de84a8"])
642 check_new_job_created_from(filters: filters_from_hash(filters_hash))
645 test "new job created from unsatisfiable minimum version parameter" do
646 check_new_job_created_from(minimum_script_version: "1de84a8")
649 test "new job created from unsatisfiable minimum version attribute" do
650 check_new_job_created_from(job: {minimum_script_version: "1de84a8"})
653 test "graceful error from nonexistent minimum version filter" do
654 filters_hash = BASE_FILTERS.merge("script_version" =>
655 ["in git", "__nosuchbranch__"])
656 errors = check_errors_from(filters: filters_from_hash(filters_hash))
657 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
658 "bad refspec not mentioned in error message")
661 test "graceful error from nonexistent minimum version parameter" do
662 errors = check_errors_from(minimum_script_version: "__nosuchbranch__")
663 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
664 "bad refspec not mentioned in error message")
667 test "graceful error from nonexistent minimum version attribute" do
668 errors = check_errors_from(job: {minimum_script_version: "__nosuchbranch__"})
669 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
670 "bad refspec not mentioned in error message")
673 test "can't reuse job with older Arvados SDK version" do
675 script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
676 runtime_constraints: {
677 "arvados_sdk_version" => "master",
678 "docker_image" => links(:docker_image_collection_tag).name,
681 check_new_job_created_from(job: params)
684 test "reuse job from arvados_sdk_version git filters" do
685 filters_hash = BASE_FILTERS.
686 merge("arvados_sdk_version" => ["in git", "commit2"])
687 filters_hash.delete("script_version")
688 params = create_foo_hash_job_params(filters:
689 filters_from_hash(filters_hash))
690 post(:create, params)
691 assert_response :success
692 assert_equal(jobs(:previous_job_run_with_arvados_sdk_version).uuid,
693 assigns(:object).uuid)
696 test "create new job because of arvados_sdk_version 'not in git' filters" do
697 filters_hash = BASE_FILTERS.reject { |k| k == "script_version" }
698 filters = filters_from_hash(filters_hash)
699 # Allow anything from the root commit, but before commit 2.
700 filters += [["arvados_sdk_version", "in git", "436637c8"],
701 ["arvados_sdk_version", "not in git", "00634b2b"]]
702 check_new_job_created_from(filters: filters)