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_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
300 test "cannot reuse job with find_or_create but excluded version" do
304 script_version: "master",
307 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
311 find_or_create: true,
312 minimum_script_version: "31ce37fe365b3dc204300a3e4c396ad333ed0556",
313 exclude_script_versions: ["tag1"],
315 assert_response :success
316 assert_not_nil assigns(:object)
317 new_job = JSON.parse(@response.body)
318 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
319 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
323 'repository' => ['=', 'foo'],
324 'script' => ['=', 'hash'],
325 'script_version' => ['in git', 'master'],
326 'docker_image_locator' => ['=', nil],
329 def filters_from_hash(hash)
330 hash.each_pair.map { |name, filter| [name] + filter }
333 test "can reuse a Job based on filters" do
334 filter_h = BASE_FILTERS.
335 merge('script_version' => ['in git', 'tag1'])
339 script_version: "master",
342 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
346 filters: filters_from_hash(filter_h),
347 find_or_create: true,
349 assert_response :success
350 assert_not_nil assigns(:object)
351 new_job = JSON.parse(@response.body)
352 assert_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
353 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
356 test "can not reuse a Job based on filters" do
357 filter_a = filters_from_hash(BASE_FILTERS.reject { |k| k == 'script_version' })
358 filter_a += [["script_version", "in git",
359 "31ce37fe365b3dc204300a3e4c396ad333ed0556"],
360 ["script_version", "not in git", ["tag1"]]]
364 script_version: "master",
367 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
372 find_or_create: true,
374 assert_response :success
375 assert_not_nil assigns(:object)
376 new_job = JSON.parse(@response.body)
377 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
378 assert_equal '077ba2ad3ea24a929091a9e6ce545c93199b8e57', new_job['script_version']
381 test "can not reuse a Job based on arbitrary filters" do
382 filter_h = BASE_FILTERS.
383 merge("created_at" => ["<", "2010-01-01T00:00:00Z"])
387 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
390 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
394 filters: filters_from_hash(filter_h),
395 find_or_create: true,
397 assert_response :success
398 assert_not_nil assigns(:object)
399 new_job = JSON.parse(@response.body)
400 assert_not_equal 'zzzzz-8i9sb-cjs4pklxxjykqqq', new_job['uuid']
401 assert_equal '4fe459abe02d9b365932b8f5dc419439ab4e2577', new_job['script_version']
404 test "can reuse a Job with a Docker image" do
408 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
411 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
414 runtime_constraints: {
415 docker_image: 'arvados/apitestfixture',
418 find_or_create: true,
420 assert_response :success
421 new_job = assigns(:object)
422 assert_not_nil new_job
423 target_job = jobs(:previous_docker_job_run)
424 [:uuid, :script_version, :docker_image_locator].each do |attr|
425 assert_equal(target_job.send(attr), new_job.send(attr))
429 test "can reuse a Job with a Docker image hash filter" do
430 filter_h = BASE_FILTERS.
431 merge("script_version" =>
432 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
433 "docker_image_locator" =>
434 ["in docker", links(:docker_image_collection_hash).name])
438 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
441 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
445 filters: filters_from_hash(filter_h),
446 find_or_create: true,
448 assert_response :success
449 new_job = assigns(:object)
450 assert_not_nil new_job
451 target_job = jobs(:previous_docker_job_run)
452 [:uuid, :script_version, :docker_image_locator].each do |attr|
453 assert_equal(target_job.send(attr), new_job.send(attr))
457 test "reuse Job with Docker image repo+tag" do
458 filter_h = BASE_FILTERS.
459 merge("script_version" =>
460 ["=", "4fe459abe02d9b365932b8f5dc419439ab4e2577"],
461 "docker_image_locator" =>
462 ["in docker", links(:docker_image_collection_tag2).name])
466 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
469 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
473 filters: filters_from_hash(filter_h),
474 find_or_create: true,
476 assert_response :success
477 new_job = assigns(:object)
478 assert_not_nil new_job
479 target_job = jobs(:previous_docker_job_run)
480 [:uuid, :script_version, :docker_image_locator].each do |attr|
481 assert_equal(target_job.send(attr), new_job.send(attr))
485 test "new job with unknown Docker image filter" do
486 filter_h = BASE_FILTERS.
487 merge("docker_image_locator" => ["in docker", "_nonesuchname_"])
491 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
494 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
498 filters: filters_from_hash(filter_h),
499 find_or_create: true,
501 assert_response :success
502 new_job = assigns(:object)
503 assert_not_nil new_job
504 assert_not_equal(jobs(:previous_docker_job_run).uuid, new_job.uuid)
507 ["repository", "script"].each do |skip_key|
508 test "missing #{skip_key} filter raises an error" do
509 filter_a = filters_from_hash(BASE_FILTERS.reject { |k| k == skip_key })
513 script_version: "master",
516 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
521 find_or_create: true,
523 assert_includes(405..599, @response.code.to_i,
524 "bad status code with missing #{skip_key} filter")
528 test "find Job with script version range" do
529 get :index, filters: [["repository", "=", "foo"],
530 ["script", "=", "hash"],
531 ["script_version", "in git", "tag1"]]
532 assert_response :success
533 assert_not_nil assigns(:objects)
534 assert_includes(assigns(:objects).map { |job| job.uuid },
535 jobs(:previous_job_run).uuid)
538 test "find Job with script version range exclusions" do
539 get :index, filters: [["repository", "=", "foo"],
540 ["script", "=", "hash"],
541 ["script_version", "not in git", "tag1"]]
542 assert_response :success
543 assert_not_nil assigns(:objects)
544 refute_includes(assigns(:objects).map { |job| job.uuid },
545 jobs(:previous_job_run).uuid)
548 test "find Job with Docker image range" do
549 get :index, filters: [["docker_image_locator", "in docker",
550 "arvados/apitestfixture"]]
551 assert_response :success
552 assert_not_nil assigns(:objects)
553 assert_includes(assigns(:objects).map { |job| job.uuid },
554 jobs(:previous_docker_job_run).uuid)
555 refute_includes(assigns(:objects).map { |job| job.uuid },
556 jobs(:previous_job_run).uuid)
559 test "find Job with Docker image using reader tokens" do
560 authorize_with :inactive
562 filters: [["docker_image_locator", "in docker",
563 "arvados/apitestfixture"]],
564 reader_tokens: [api_token(:active)],
566 assert_response :success
567 assert_not_nil assigns(:objects)
568 assert_includes(assigns(:objects).map { |job| job.uuid },
569 jobs(:previous_docker_job_run).uuid)
570 refute_includes(assigns(:objects).map { |job| job.uuid },
571 jobs(:previous_job_run).uuid)
574 test "'in docker' filter accepts arrays" do
575 get :index, filters: [["docker_image_locator", "in docker",
576 ["_nonesuchname_", "arvados/apitestfixture"]]]
577 assert_response :success
578 assert_not_nil assigns(:objects)
579 assert_includes(assigns(:objects).map { |job| job.uuid },
580 jobs(:previous_docker_job_run).uuid)
581 refute_includes(assigns(:objects).map { |job| job.uuid },
582 jobs(:previous_job_run).uuid)
585 test "'not in docker' filter accepts arrays" do
586 get :index, filters: [["docker_image_locator", "not in docker",
587 ["_nonesuchname_", "arvados/apitestfixture"]]]
588 assert_response :success
589 assert_not_nil assigns(:objects)
590 assert_includes(assigns(:objects).map { |job| job.uuid },
591 jobs(:previous_job_run).uuid)
592 refute_includes(assigns(:objects).map { |job| job.uuid },
593 jobs(:previous_docker_job_run).uuid)
596 def create_foo_hash_job_params(params)
597 if not params.has_key?(:find_or_create)
598 params[:find_or_create] = true
600 job_attrs = params.delete(:job) || {}
603 script_version: "4fe459abe02d9b365932b8f5dc419439ab4e2577",
606 input: 'fa7aeb5140e2848d39b416daeef4ffc5+45',
613 def check_new_job_created_from(params)
614 start_time = Time.now
615 post(:create, create_foo_hash_job_params(params))
616 assert_response :success
617 new_job = assigns(:object)
618 assert_not_nil new_job
619 assert_operator(start_time, :<=, new_job.created_at)
623 def check_errors_from(params)
624 post(:create, create_foo_hash_job_params(params))
625 assert_includes(405..499, @response.code.to_i)
626 errors = json_response.fetch("errors", [])
627 assert(errors.any?, "no errors assigned from #{params}")
628 refute(errors.any? { |msg| msg =~ /^#<[A-Za-z]+: / },
629 "errors include raw exception")
633 # 1de84a8 is on the b1 branch, after master's tip.
634 test "new job created from unsatisfiable minimum version filter" do
635 filter_h = BASE_FILTERS.merge("script_version" => ["in git", "1de84a8"])
636 check_new_job_created_from(filters: filters_from_hash(filter_h))
639 test "new job created from unsatisfiable minimum version parameter" do
640 check_new_job_created_from(minimum_script_version: "1de84a8")
643 test "new job created from unsatisfiable minimum version attribute" do
644 check_new_job_created_from(job: {minimum_script_version: "1de84a8"})
647 test "graceful error from nonexistent minimum version filter" do
648 filter_h = BASE_FILTERS.merge("script_version" =>
649 ["in git", "__nosuchbranch__"])
650 errors = check_errors_from(filters: filters_from_hash(filter_h))
651 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
652 "bad refspec not mentioned in error message")
655 test "graceful error from nonexistent minimum version parameter" do
656 errors = check_errors_from(minimum_script_version: "__nosuchbranch__")
657 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
658 "bad refspec not mentioned in error message")
661 test "graceful error from nonexistent minimum version attribute" do
662 errors = check_errors_from(job: {minimum_script_version: "__nosuchbranch__"})
663 assert(errors.any? { |msg| msg.include? "__nosuchbranch__" },
664 "bad refspec not mentioned in error message")