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 test "do not reuse job because find_or_create=false" do
97 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
100 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
104 find_or_create: false
106 assert_response :success
107 assert_not_nil assigns(:object)
108 new_job = JSON.parse(@response.body)
109 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
110 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
113 test "do not reuse job because output is not readable by user" do
114 authorize_with :job_reader
118 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
121 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
127 assert_response :success
128 assert_not_nil assigns(:object)
129 new_job = JSON.parse(@response.body)
130 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
131 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
134 test "test_cannot_reuse_job_no_output" do
138 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
141 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
145 assert_response :success
146 assert_not_nil assigns(:object)
147 new_job = JSON.parse(@response.body)
148 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykppp', new_job['uuid']
151 test "test_reuse_job_range" do
155 minimum_script_version: "tag1",
156 script_version: "master",
159 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
163 assert_response :success
164 assert_not_nil assigns(:object)
165 new_job = JSON.parse(@response.body)
166 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
167 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
170 test "cannot_reuse_job_no_minimum_given_so_must_use_specified_commit" do
174 script_version: "master",
177 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
181 assert_response :success
182 assert_not_nil assigns(:object)
183 new_job = JSON.parse(@response.body)
184 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
185 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
188 test "test_cannot_reuse_job_different_input" do
192 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
195 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
199 assert_response :success
200 assert_not_nil assigns(:object)
201 new_job = JSON.parse(@response.body)
202 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
203 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
206 test "test_cannot_reuse_job_different_version" do
210 script_version: "master",
213 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
217 assert_response :success
218 assert_not_nil assigns(:object)
219 new_job = JSON.parse(@response.body)
220 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
221 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
224 test "test_can_reuse_job_submitted_nondeterministic" do
228 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
231 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
234 nondeterministic: true
236 assert_response :success
237 assert_not_nil assigns(:object)
238 new_job = JSON.parse(@response.body)
239 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
240 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
243 test "test_cannot_reuse_job_past_nondeterministic" do
247 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
250 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
254 assert_response :success
255 assert_not_nil assigns(:object)
256 new_job = JSON.parse(@response.body)
257 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykyyy', new_job['uuid']
258 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
261 test "test_cannot_reuse_job_no_permission" do
262 authorize_with :spectator
266 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
269 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
273 assert_response :success
274 assert_not_nil assigns(:object)
275 new_job = JSON.parse(@response.body)
276 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
277 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
280 test "test_cannot_reuse_job_excluded" do
284 minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
285 script_version: "master",
287 exclude_script_versions: ["tag1"],
289 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
293 assert_response :success
294 assert_not_nil assigns(:object)
295 new_job = JSON.parse(@response.body)
296 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
297 assert_not_equal('4fe459abe02d9b365932b8f5dc419439ab4e2577',
298 new_job['script_version'])
301 test "cannot reuse job with find_or_create but excluded version" do
305 script_version: "master",
308 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
312 find_or_create: true,
313 minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
314 exclude_script_versions: ["tag1"],
316 assert_response :success
317 assert_not_nil assigns(:object)
318 new_job = JSON.parse(@response.body)
319 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
320 assert_not_equal('4fe459abe02d9b365932b8f5dc419439ab4e2577',
321 new_job['script_version'])
325 'repository' => ['=', 'foo'],
326 'script' => ['=', 'hash'],
327 'script_version' => ['in git', 'master'],
328 'docker_image_locator' => ['=', nil],
329 'arvados_sdk_version' => ['=', nil],
332 def filters_from_hash(hash)
333 hash.each_pair.map { |name, filter| [name] + filter }
336 test "can reuse a Job based on filters" do
337 filters_hash = BASE_FILTERS.
338 merge('script_version' => ['in git', 'tag1'])
342 script_version: "master",
345 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
349 filters: filters_from_hash(filters_hash),
350 find_or_create: true,
352 assert_response :success
353 assert_not_nil assigns(:object)
354 new_job = JSON.parse(@response.body)
355 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
356 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
359 test "can not reuse a Job based on filters" do
360 filters = filters_from_hash(BASE_FILTERS
361 .reject { |k| k == 'script_version' })
362 filters += [["script_version", "in git",
363 "31ce37fe365b3dc204300a3e4c396ad333ed0556"],
364 ["script_version", "not in git", ["tag1"]]]
368 script_version: "master",
371 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
376 find_or_create: true,
378 assert_response :success
379 assert_not_nil assigns(:object)
380 new_job = JSON.parse(@response.body)
381 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
382 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
385 test "can not reuse a Job based on arbitrary filters" do
386 filters_hash = BASE_FILTERS.
387 merge("created_at" => ["<", "2010-01-01T00:00:00Z"])
391 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
394 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
398 filters: filters_from_hash(filters_hash),
399 find_or_create: true,
401 assert_response :success
402 assert_not_nil assigns(:object)
403 new_job = JSON.parse(@response.body)
404 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
405 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
408 test "can reuse a Job with a Docker image" do
412 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
415 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
418 runtime_constraints: {
419 docker_image: 'arvados/apitestfixture',
422 find_or_create: true,
424 assert_response :success
425 new_job = assigns(:object)
426 assert_not_nil new_job
427 target_job = jobs(:previous_docker_job_run)
428 [:uuid, :script_version, :docker_image_locator].each do |attr|
429 assert_equal(target_job.send(attr), new_job.send(attr))
433 test "can reuse a Job with a Docker image hash filter" do
434 filters_hash = BASE_FILTERS.
435 merge("script_version" =>
436 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
437 "docker_image_locator" =>
438 ["in docker", links(:docker_image_collection_hash).name])
442 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
445 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
449 filters: filters_from_hash(filters_hash),
450 find_or_create: true,
452 assert_response :success
453 new_job = assigns(:object)
454 assert_not_nil new_job
455 target_job = jobs(:previous_docker_job_run)
456 [:uuid, :script_version, :docker_image_locator].each do |attr|
457 assert_equal(target_job.send(attr), new_job.send(attr))
461 test "reuse Job with Docker image repo+tag" do
462 filters_hash = BASE_FILTERS.
463 merge("script_version" =>
464 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
465 "docker_image_locator" =>
466 ["in docker", links(:docker_image_collection_tag2).name])
470 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
473 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
477 filters: filters_from_hash(filters_hash),
478 find_or_create: true,
480 assert_response :success
481 new_job = assigns(:object)
482 assert_not_nil new_job
483 target_job = jobs(:previous_docker_job_run)
484 [:uuid, :script_version, :docker_image_locator].each do |attr|
485 assert_equal(target_job.send(attr), new_job.send(attr))
489 test "new job with unknown Docker image filter" do
490 filters_hash = BASE_FILTERS.
491 merge("docker_image_locator" => ["in docker", "_nonesuchname_"])
495 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
498 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
502 filters: filters_from_hash(filters_hash),
503 find_or_create: true,
505 assert_response :success
506 new_job = assigns(:object)
507 assert_not_nil new_job
508 assert_not_equal(jobs(:previous_docker_job_run).uuid, new_job.uuid)
511 ["repository", "script"].each do |skip_key|
512 test "missing #{skip_key} filter raises an error" do
513 filters = filters_from_hash(BASE_FILTERS.reject { |k| k == skip_key })
517 script_version: "master",
520 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
525 find_or_create: true,
527 assert_includes(405..599, @response.code.to_i,
528 "bad status code with missing #{skip_key} filter")
532 test "find Job with script version range" do
533 get :index, filters: [["repository", "=", "foo"],
534 ["script", "=", "hash"],
535 ["script_version", "in git", "tag1"]]
536 assert_response :success
537 assert_not_nil assigns(:objects)
538 assert_includes(assigns(:objects).map { |job| job.uuid },
539 jobs(:previous_job_run).uuid)
542 test "find Job with script version range exclusions" do
543 get :index, filters: [["repository", "=", "foo"],
544 ["script", "=", "hash"],
545 ["script_version", "not in git", "tag1"]]
546 assert_response :success
547 assert_not_nil assigns(:objects)
548 refute_includes(assigns(:objects).map { |job| job.uuid },
549 jobs(:previous_job_run).uuid)
552 test "find Job with Docker image range" do
553 get :index, filters: [["docker_image_locator", "in docker",
554 "arvados/apitestfixture"]]
555 assert_response :success
556 assert_not_nil assigns(:objects)
557 assert_includes(assigns(:objects).map { |job| job.uuid },
558 jobs(:previous_docker_job_run).uuid)
559 refute_includes(assigns(:objects).map { |job| job.uuid },
560 jobs(:previous_job_run).uuid)
563 test "find Job with Docker image using reader tokens" do
564 authorize_with :inactive
566 filters: [["docker_image_locator", "in docker",
567 "arvados/apitestfixture"]],
568 reader_tokens: [api_token(:active)],
570 assert_response :success
571 assert_not_nil assigns(:objects)
572 assert_includes(assigns(:objects).map { |job| job.uuid },
573 jobs(:previous_docker_job_run).uuid)
574 refute_includes(assigns(:objects).map { |job| job.uuid },
575 jobs(:previous_job_run).uuid)
578 test "'in docker' filter accepts arrays" do
579 get :index, filters: [["docker_image_locator", "in docker",
580 ["_nonesuchname_", "arvados/apitestfixture"]]]
581 assert_response :success
582 assert_not_nil assigns(:objects)
583 assert_includes(assigns(:objects).map { |job| job.uuid },
584 jobs(:previous_docker_job_run).uuid)
585 refute_includes(assigns(:objects).map { |job| job.uuid },
586 jobs(:previous_job_run).uuid)
589 test "'not in docker' filter accepts arrays" do
590 get :index, filters: [["docker_image_locator", "not in docker",
591 ["_nonesuchname_", "arvados/apitestfixture"]]]
592 assert_response :success
593 assert_not_nil assigns(:objects)
594 assert_includes(assigns(:objects).map { |job| job.uuid },
595 jobs(:previous_job_run).uuid)
596 refute_includes(assigns(:objects).map { |job| job.uuid },
597 jobs(:previous_docker_job_run).uuid)
600 def create_foo_hash_job_params(params)
601 if not params.has_key?(:find_or_create)
602 params[:find_or_create] = true
604 job_attrs = params.delete(:job) || {}
607 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
610 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
617 def check_new_job_created_from(params)
618 start_time = Time.now
619 post(:create, create_foo_hash_job_params(params))
620 assert_response :success
621 new_job = assigns(:object)
622 assert_not_nil new_job
623 assert_operator(start_time, :<=, new_job.created_at)
627 def check_errors_from(params)
628 post(:create, create_foo_hash_job_params(params))
629 assert_includes(405..499, @response.code.to_i)
630 errors = json_response.fetch("errors", [])
631 assert(errors.any?, "no errors assigned from #{params}")
632 refute(errors.any? { |msg| msg =~ /^#<[A-Za-z]+: / },
633 "errors include raw exception")
637 # 1de84a8 is on the b1 branch, after master's tip.
638 test "new job created from unsatisfiable minimum version filter" do
639 filters_hash = BASE_FILTERS.merge("script_version" => ["in git", "1de84a8"])
640 check_new_job_created_from(filters: filters_from_hash(filters_hash))
643 test "new job created from unsatisfiable minimum version parameter" do
644 check_new_job_created_from(minimum_script_version: "1de84a8")
647 test "new job created from unsatisfiable minimum version attribute" do
648 check_new_job_created_from(job: {minimum_script_version: "1de84a8"})
651 test "graceful error from nonexistent minimum version filter" do
652 filters_hash = BASE_FILTERS.merge("script_version" =>
653 ["in git", "__nosuchbranch__"])
654 errors = check_errors_from(filters: filters_from_hash(filters_hash))
655 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
656 "bad refspec not mentioned in error message")
659 test "graceful error from nonexistent minimum version parameter" do
660 errors = check_errors_from(minimum_script_version: "__nosuchbranch__")
661 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
662 "bad refspec not mentioned in error message")
665 test "graceful error from nonexistent minimum version attribute" do
666 errors = check_errors_from(job: {minimum_script_version: "__nosuchbranch__"})
667 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
668 "bad refspec not mentioned in error message")
671 test "can't reuse job with older Arvados SDK version" do
673 script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
674 runtime_constraints: {"arvados_sdk_version" => "master"},
676 check_new_job_created_from(job: params)
679 test "reuse job from arvados_sdk_version git filters" do
680 filters_hash = BASE_FILTERS.
681 merge("arvados_sdk_version" => ["in git", "commit2"])
682 filters_hash.delete("script_version")
683 params = create_foo_hash_job_params(filters:
684 filters_from_hash(filters_hash))
685 post(:create, params)
686 assert_response :success
687 assert_equal(jobs(:previous_job_run_with_arvados_sdk_version).uuid,
688 assigns(:object).uuid)
691 test "create new job because of arvados_sdk_version 'not in git' filters" do
692 filters_hash = BASE_FILTERS.reject { |k| k == "script_version" }
693 filters = filters_from_hash(filters_hash)
694 # Allow anything from the root commit, but before commit 2.
695 filters += [["arvados_sdk_version", "in git", "436637c8"],
696 ["arvados_sdk_version", "not in git", "00634b2b"]]
697 check_new_job_created_from(filters: filters)