9277: Add test that setting trashed, unreable collection is disallowed.
[arvados.git] / services / api / test / unit / job_test.rb
index f16c8b2ec497e0caaaf5d78bdf5bb063b58badad..8fe5e5db28c804881fd2ac24c44ede3da7ae4d53 100644 (file)
@@ -1,7 +1,9 @@
 require 'test_helper'
 require 'helpers/git_test_helper'
+require 'helpers/docker_migration_helper'
 
 class JobTest < ActiveSupport::TestCase
+  include DockerMigrationHelper
   include GitTestHelper
 
   BAD_COLLECTION = "#{'f' * 32}+0"
@@ -185,7 +187,7 @@ class JobTest < ActiveSupport::TestCase
       # Ensure valid_attrs doesn't produce errors -- otherwise we will
       # not know whether errors reported below are actually caused by
       # invalid_attrs.
-      dummy = Job.create! job_attrs
+      Job.create! job_attrs
 
       job = Job.create job_attrs(invalid_attrs)
       assert_raises(ActiveRecord::RecordInvalid, ArgumentError,
@@ -223,7 +225,7 @@ class JobTest < ActiveSupport::TestCase
 
       parameters.each do |parameter|
         expectations = parameter[2]
-        if parameter[1] == 'use_current_user_uuid'
+        if 'use_current_user_uuid' == parameter[1]
           parameter[1] = Thread.current[:user].uuid
         end
 
@@ -307,6 +309,24 @@ class JobTest < ActiveSupport::TestCase
     assert_equal "Failed", job.state
   end
 
+  test "admin user can cancel a running job despite lock" do
+    set_user_from_auth :active_trustedclient
+    job = Job.create! job_attrs
+    job.lock current_user.uuid
+    assert_equal Job::Running, job.state
+
+    set_user_from_auth :spectator
+    assert_raises do
+      job.update_attributes!(state: Job::Cancelled)
+    end
+
+    set_user_from_auth :admin
+    job.reload
+    assert_equal Job::Running, job.state
+    job.update_attributes!(state: Job::Cancelled)
+    assert_equal Job::Cancelled, job.state
+  end
+
   test "verify job queue position" do
     job1 = Job.create! job_attrs
     assert_equal 'Queued', job1.state, "Incorrect job state for newly created job1"
@@ -316,7 +336,6 @@ class JobTest < ActiveSupport::TestCase
 
     assert_not_nil job1.queue_position, "Expected non-nil queue position for job1"
     assert_not_nil job2.queue_position, "Expected non-nil queue position for job2"
-    assert_not_equal job1.queue_position, job2.queue_position
   end
 
   SDK_MASTER = "ca68b24e51992e790f29df5cc4bc54ce1da4a1c2"
@@ -394,6 +413,60 @@ class JobTest < ActiveSupport::TestCase
            "Job with SDK constraint valid after clearing Docker image")
   end
 
+  test "use migrated docker image if requesting old-format image by tag" do
+    Rails.configuration.docker_image_formats = ['v2']
+    add_docker19_migration_link
+    job = Job.create!(
+      job_attrs(
+        script: 'foo',
+        runtime_constraints: {
+          'docker_image' => links(:docker_image_collection_tag).name}))
+    assert(job.valid?)
+    assert_equal(job.docker_image_locator, collections(:docker_image_1_12).portable_data_hash)
+  end
+
+  test "use migrated docker image if requesting old-format image by pdh" do
+    Rails.configuration.docker_image_formats = ['v2']
+    add_docker19_migration_link
+    job = Job.create!(
+      job_attrs(
+        script: 'foo',
+        runtime_constraints: {
+          'docker_image' => collections(:docker_image).portable_data_hash}))
+    assert(job.valid?)
+    assert_equal(job.docker_image_locator, collections(:docker_image_1_12).portable_data_hash)
+  end
+
+  [[:docker_image, :docker_image, :docker_image_1_12],
+   [:docker_image_1_12, :docker_image, :docker_image_1_12],
+   [:docker_image, :docker_image_1_12, :docker_image_1_12],
+   [:docker_image_1_12, :docker_image_1_12, :docker_image_1_12],
+  ].each do |existing_image, request_image, expect_image|
+    test "if a #{existing_image} job exists, #{request_image} yields #{expect_image} after migration" do
+      Rails.configuration.docker_image_formats = ['v1']
+      oldjob = Job.create!(
+        job_attrs(
+          script: 'foobar1',
+          runtime_constraints: {
+            'docker_image' => collections(existing_image).portable_data_hash}))
+      oldjob.reload
+      assert_equal(oldjob.docker_image_locator,
+                   collections(existing_image).portable_data_hash)
+
+      Rails.configuration.docker_image_formats = ['v2']
+      add_docker19_migration_link
+
+      newjob = Job.create!(
+        job_attrs(
+          script: 'foobar1',
+          runtime_constraints: {
+            'docker_image' => collections(request_image).portable_data_hash}))
+      newjob.reload
+      assert_equal(newjob.docker_image_locator,
+                   collections(expect_image).portable_data_hash)
+    end
+  end
+
   test "can't create job with SDK version assigned directly" do
     check_creation_prohibited(arvados_sdk_version: SDK_MASTER)
   end
@@ -412,7 +485,7 @@ class JobTest < ActiveSupport::TestCase
     }
     assert_raises(ActiveRecord::RecordInvalid,
                   "created job with a collection uuid in script_parameters") do
-      job = Job.create!(job_attrs(bad_params))
+      Job.create!(job_attrs(bad_params))
     end
   end
 
@@ -441,4 +514,95 @@ class JobTest < ActiveSupport::TestCase
     assert_equal('077ba2ad3ea24a929091a9e6ce545c93199b8e57',
                  internal_tag(j.uuid))
   end
+
+  test 'script_parameters_digest is independent of key order' do
+    j1 = Job.new(job_attrs(script_parameters: {'a' => 'a', 'ddee' => {'d' => 'd', 'e' => 'e'}}))
+    j2 = Job.new(job_attrs(script_parameters: {'ddee' => {'e' => 'e', 'd' => 'd'}, 'a' => 'a'}))
+    assert j1.valid?
+    assert j2.valid?
+    assert_equal(j1.script_parameters_digest, j2.script_parameters_digest)
+  end
+
+  test 'job fixtures have correct script_parameters_digest' do
+    Job.all.each do |j|
+      d = j.script_parameters_digest
+      assert_equal(j.update_script_parameters_digest, d,
+                   "wrong script_parameters_digest for #{j.uuid}")
+    end
+  end
+
+  test 'deep_sort_hash on array of hashes' do
+    a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]}
+    b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]}
+    assert_equal Job.deep_sort_hash(a).to_json, Job.deep_sort_hash(b).to_json
+  end
+
+  test 'find_reusable' do
+    foobar = jobs(:foobar)
+    example_attrs = {
+      script_version: foobar.script_version,
+      script: foobar.script,
+      script_parameters: foobar.script_parameters,
+      repository: foobar.repository,
+    }
+
+    # Two matching jobs exist with identical outputs. The older one
+    # should be reused.
+    j = Job.find_reusable(example_attrs, {}, [], [users(:active)])
+    assert j
+    assert_equal foobar.uuid, j.uuid
+
+    # Two matching jobs exist with different outputs. Neither should
+    # be reused.
+    Job.where(uuid: jobs(:job_with_latest_version).uuid).
+      update_all(output: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+1')
+    assert_nil Job.find_reusable(example_attrs, {}, [], [users(:active)])
+  end
+
+  [
+    true,
+    false,
+  ].each do |cascade|
+    test "cancel job with cascade #{cascade}" do
+      job = Job.find_by_uuid jobs(:running_job_with_components_at_level_1).uuid
+      job.cancel cascade: cascade
+      assert_equal Job::Cancelled, job.state
+
+      descendents = ['zzzzz-8i9sb-jobcomponentsl2',
+                     'zzzzz-d1hrv-picomponentsl02',
+                     'zzzzz-8i9sb-job1atlevel3noc',
+                     'zzzzz-8i9sb-job2atlevel3noc']
+
+      jobs = Job.where(uuid: descendents)
+      jobs.each do |j|
+        assert_equal ('Cancelled' == j.state), cascade
+      end
+
+      pipelines = PipelineInstance.where(uuid: descendents)
+      pipelines.each do |pi|
+        assert_equal ('Paused' == pi.state), cascade
+      end
+    end
+  end
+
+  test 'cancelling a completed job raises error' do
+    job = Job.find_by_uuid jobs(:job_with_latest_version).uuid
+    assert job
+    assert_equal 'Complete', job.state
+
+    assert_raises(ArvadosModel::InvalidStateTransitionError) do
+      job.cancel
+    end
+  end
+
+  test 'cancelling a job with circular relationship with another does not result in an infinite loop' do
+    job = Job.find_by_uuid jobs(:running_job_2_with_circular_component_relationship).uuid
+
+    job.cancel cascade: true
+
+    assert_equal Job::Cancelled, job.state
+
+    child = Job.find_by_uuid job.components.collect{|_, uuid| uuid}[0]
+    assert_equal Job::Cancelled, child.state
+  end
 end