12414: Enhance sweep process test to ensure no unwanted deletions happen
[arvados.git] / services / api / test / unit / container_request_test.rb
index 328273bfc25ae1cc2b494b1f43e1e32503691708..70ad11e0f47b32a78597b4f98dd42dc5a5750870 100644 (file)
@@ -1,8 +1,32 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 require 'test_helper'
+require 'helpers/container_test_helper'
 require 'helpers/docker_migration_helper'
 
 class ContainerRequestTest < ActiveSupport::TestCase
   include DockerMigrationHelper
+  include DbCurrentTime
+  include ContainerTestHelper
+
+  def with_container_auth(ctr)
+    auth_was = Thread.current[:api_client_authorization]
+    Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(ctr.auth_uuid)
+    begin
+      yield
+    ensure
+      Thread.current[:api_client_authorization] = auth_was
+    end
+  end
+
+  def lock_and_run(ctr)
+      act_as_system_user do
+        ctr.update_attributes!(state: Container::Locked)
+        ctr.update_attributes!(state: Container::Running)
+      end
+  end
 
   def create_minimal_req! attrs={}
     defaults = {
@@ -36,7 +60,7 @@ class ContainerRequestTest < ActiveSupport::TestCase
     cr = create_minimal_req!
 
     assert_nil cr.container_uuid
-    assert_nil cr.priority
+    assert_equal 0, cr.priority
 
     check_bogus_states cr
 
@@ -103,7 +127,8 @@ class ContainerRequestTest < ActiveSupport::TestCase
 
   test "Container request priority must be non-nil" do
     set_user_from_auth :active
-    cr = create_minimal_req!(priority: nil)
+    cr = create_minimal_req!
+    cr.priority = nil
     cr.state = "Committed"
     assert_raises(ActiveRecord::RecordInvalid) do
       cr.save!
@@ -123,6 +148,8 @@ class ContainerRequestTest < ActiveSupport::TestCase
 
     cr.reload
 
+    assert_equal({"vcpus" => 2, "ram" => 30}, cr.runtime_constraints)
+
     assert_not_nil cr.container_uuid
     c = Container.find_by_uuid cr.container_uuid
     assert_not_nil c
@@ -133,7 +160,7 @@ class ContainerRequestTest < ActiveSupport::TestCase
     assert_equal({"/out" => {"kind"=>"tmp", "capacity"=>1000000}}, c.mounts)
     assert_equal "/out", c.output_path
     assert_equal({"keep_cache_ram"=>268435456, "vcpus" => 2, "ram" => 30}, c.runtime_constraints)
-    assert_equal 1, c.priority
+    assert_operator 0, :<, c.priority
 
     assert_raises(ActiveRecord::RecordInvalid) do
       cr.priority = nil
@@ -149,50 +176,17 @@ class ContainerRequestTest < ActiveSupport::TestCase
     assert_equal 0, c.priority
   end
 
-
-  test "Container request max priority" do
-    set_user_from_auth :active
-    cr = create_minimal_req!(priority: 5, state: "Committed")
-
-    c = Container.find_by_uuid cr.container_uuid
-    assert_equal 5, c.priority
-
-    cr2 = create_minimal_req!
-    cr2.priority = 10
-    cr2.state = "Committed"
-    cr2.container_uuid = cr.container_uuid
-    act_as_system_user do
-      cr2.save!
-    end
-
-    # cr and cr2 have priority 5 and 10, and are being satisfied by
-    # the same container c, so c's priority should be
-    # max(priority)=10.
-    c.reload
-    assert_equal 10, c.priority
-
-    cr2.update_attributes!(priority: 0)
-
-    c.reload
-    assert_equal 5, c.priority
-
-    cr.update_attributes!(priority: 0)
-
-    c.reload
-    assert_equal 0, c.priority
-  end
-
-
   test "Independent container requests" do
     set_user_from_auth :active
     cr1 = create_minimal_req!(command: ["foo", "1"], priority: 5, state: "Committed")
     cr2 = create_minimal_req!(command: ["foo", "2"], priority: 10, state: "Committed")
 
     c1 = Container.find_by_uuid cr1.container_uuid
-    assert_equal 5, c1.priority
+    assert_operator 0, :<, c1.priority
 
     c2 = Container.find_by_uuid cr2.container_uuid
-    assert_equal 10, c2.priority
+    assert_operator c1.priority, :<, c2.priority
+    c2priority_was = c2.priority
 
     cr1.update_attributes!(priority: 0)
 
@@ -200,7 +194,7 @@ class ContainerRequestTest < ActiveSupport::TestCase
     assert_equal 0, c1.priority
 
     c2.reload
-    assert_equal 10, c2.priority
+    assert_equal c2priority_was, c2.priority
   end
 
   test "Request is finalized when its container is cancelled" do
@@ -262,14 +256,14 @@ class ContainerRequestTest < ActiveSupport::TestCase
     cr = create_minimal_req!(priority: 5, state: "Committed", container_count_max: 1)
 
     c = Container.find_by_uuid cr.container_uuid
-    assert_equal 5, c.priority
+    assert_operator 0, :<, c.priority
 
     cr2 = create_minimal_req!
     cr2.update_attributes!(priority: 10, state: "Committed", requesting_container_uuid: c.uuid, command: ["echo", "foo2"], container_count_max: 1)
     cr2.reload
 
     c2 = Container.find_by_uuid cr2.container_uuid
-    assert_equal 10, c2.priority
+    assert_operator 0, :<, c2.priority
 
     act_as_system_user do
       c.state = "Cancelled"
@@ -286,15 +280,102 @@ class ContainerRequestTest < ActiveSupport::TestCase
     assert_equal 0, c2.priority
   end
 
+  test "child container priority follows same ordering as corresponding top-level ancestors" do
+    findctr = lambda { |cr| Container.find_by_uuid(cr.container_uuid) }
+
+    set_user_from_auth :active
+
+    toplevel_crs = [
+      create_minimal_req!(priority: 5, state: "Committed", environment: {"workflow" => "0"}),
+      create_minimal_req!(priority: 5, state: "Committed", environment: {"workflow" => "1"}),
+      create_minimal_req!(priority: 5, state: "Committed", environment: {"workflow" => "2"}),
+    ]
+    parents = toplevel_crs.map(&findctr)
+
+    children = parents.map do |parent|
+      lock_and_run(parent)
+      with_container_auth(parent) do
+        create_minimal_req!(state: "Committed",
+                            priority: 1,
+                            environment: {"child" => parent.environment["workflow"]})
+      end
+    end.map(&findctr)
+
+    grandchildren = children.reverse.map do |child|
+      lock_and_run(child)
+      with_container_auth(child) do
+        create_minimal_req!(state: "Committed",
+                            priority: 1,
+                            environment: {"grandchild" => child.environment["child"]})
+      end
+    end.reverse.map(&findctr)
+
+    shared_grandchildren = children.map do |child|
+      with_container_auth(child) do
+        create_minimal_req!(state: "Committed",
+                            priority: 1,
+                            environment: {"grandchild" => "shared"})
+      end
+    end.map(&findctr)
+
+    assert_equal shared_grandchildren[0].uuid, shared_grandchildren[1].uuid
+    assert_equal shared_grandchildren[0].uuid, shared_grandchildren[2].uuid
+    shared_grandchild = shared_grandchildren[0]
+
+    set_user_from_auth :active
+
+    # parents should be prioritized by submit time.
+    assert_operator parents[0].priority, :>, parents[1].priority
+    assert_operator parents[1].priority, :>, parents[2].priority
+
+    # children should be prioritized in same order as their respective
+    # parents.
+    assert_operator children[0].priority, :>, children[1].priority
+    assert_operator children[1].priority, :>, children[2].priority
+
+    # grandchildren should also be prioritized in the same order,
+    # despite having been submitted in the opposite order.
+    assert_operator grandchildren[0].priority, :>, grandchildren[1].priority
+    assert_operator grandchildren[1].priority, :>, grandchildren[2].priority
+
+    # shared grandchild container should be prioritized above
+    # everything that isn't needed by parents[0], but not above
+    # earlier-submitted descendants of parents[0]
+    assert_operator shared_grandchild.priority, :>, grandchildren[1].priority
+    assert_operator shared_grandchild.priority, :>, children[1].priority
+    assert_operator shared_grandchild.priority, :>, parents[1].priority
+    assert_operator shared_grandchild.priority, :<=, grandchildren[0].priority
+    assert_operator shared_grandchild.priority, :<=, children[0].priority
+    assert_operator shared_grandchild.priority, :<=, parents[0].priority
+
+    # increasing priority of the most recent toplevel container should
+    # reprioritize all of its descendants (including the shared
+    # grandchild) above everything else.
+    toplevel_crs[2].update_attributes!(priority: 72)
+    (parents + children + grandchildren + [shared_grandchild]).map(&:reload)
+    assert_operator shared_grandchild.priority, :>, grandchildren[0].priority
+    assert_operator shared_grandchild.priority, :>, children[0].priority
+    assert_operator shared_grandchild.priority, :>, parents[0].priority
+    assert_operator shared_grandchild.priority, :>, grandchildren[1].priority
+    assert_operator shared_grandchild.priority, :>, children[1].priority
+    assert_operator shared_grandchild.priority, :>, parents[1].priority
+    # ...but the shared container should not have higher priority than
+    # the earlier-submitted descendants of the high-priority workflow.
+    assert_operator shared_grandchild.priority, :<=, grandchildren[2].priority
+    assert_operator shared_grandchild.priority, :<=, children[2].priority
+    assert_operator shared_grandchild.priority, :<=, parents[2].priority
+  end
+
   [
-    ['running_container_auth', 'zzzzz-dz642-runningcontainr'],
-    ['active_no_prefs', nil],
-  ].each do |token, expected|
+    ['running_container_auth', 'zzzzz-dz642-runningcontainr', 1],
+    ['active_no_prefs', nil, 0],
+  ].each do |token, expected, expected_priority|
     test "create as #{token} and expect requesting_container_uuid to be #{expected}" do
       set_user_from_auth token
       cr = ContainerRequest.create(container_image: "img", output_path: "/tmp", command: ["echo", "foo"])
       assert_not_nil cr.uuid, 'uuid should be set for newly created container_request'
       assert_equal expected, cr.requesting_container_uuid
+      assert_equal expected_priority, cr.priority
     end
   end
 
@@ -310,8 +391,7 @@ class ContainerRequestTest < ActiveSupport::TestCase
     lambda { |resolved| resolved["ram"] == 1234234234 }],
   ].each do |rc, okfunc|
     test "resolve runtime constraint range #{rc} to values" do
-      cr = ContainerRequest.new(runtime_constraints: rc)
-      resolved = cr.send :runtime_constraints_for_container
+      resolved = Container.resolve_runtime_constraints(rc)
       assert(okfunc.call(resolved),
              "container runtime_constraints was #{resolved.inspect}")
     end
@@ -343,10 +423,9 @@ class ContainerRequestTest < ActiveSupport::TestCase
   ].each do |mounts, okfunc|
     test "resolve mounts #{mounts.inspect} to values" do
       set_user_from_auth :active
-      cr = ContainerRequest.new(mounts: mounts)
-      resolved = cr.send :mounts_for_container
+      resolved = Container.resolve_mounts(mounts)
       assert(okfunc.call(resolved),
-             "mounts_for_container returned #{resolved.inspect}")
+             "Container.resolve_mounts returned #{resolved.inspect}")
     end
   end
 
@@ -359,9 +438,8 @@ class ContainerRequestTest < ActiveSupport::TestCase
         "path" => "/foo",
       },
     }
-    cr = ContainerRequest.new(mounts: m)
     assert_raises(ArvadosModel::UnresolvableContainerError) do
-      cr.send :mounts_for_container
+      Container.resolve_mounts(m)
     end
   end
 
@@ -375,9 +453,8 @@ class ContainerRequestTest < ActiveSupport::TestCase
         "path" => "/foo",
       },
     }
-    cr = ContainerRequest.new(mounts: m)
     assert_raises(ArgumentError) do
-      cr.send :mounts_for_container
+      Container.resolve_mounts(m)
     end
   end
 
@@ -385,20 +462,19 @@ class ContainerRequestTest < ActiveSupport::TestCase
    'arvados/apitestfixture',
    'd8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678',
   ].each do |tag|
-    test "container_image_for_container(#{tag.inspect})" do
+    test "Container.resolve_container_image(#{tag.inspect})" do
       set_user_from_auth :active
-      cr = ContainerRequest.new(container_image: tag)
-      resolved = cr.send :container_image_for_container
+      resolved = Container.resolve_container_image(tag)
       assert_equal resolved, collections(:docker_image).portable_data_hash
     end
   end
 
-  test "container_image_for_container(pdh)" do
+  test "Container.resolve_container_image(pdh)" do
     set_user_from_auth :active
-    [:docker_image, :docker_image_1_12].each do |coll|
+    [[:docker_image, 'v1'], [:docker_image_1_12, 'v2']].each do |coll, ver|
+      Rails.configuration.docker_image_formats = [ver]
       pdh = collections(coll).portable_data_hash
-      cr = ContainerRequest.new(container_image: pdh)
-      resolved = cr.send :container_image_for_container
+      resolved = Container.resolve_container_image(pdh)
       assert_equal resolved, pdh
     end
   end
@@ -409,9 +485,8 @@ class ContainerRequestTest < ActiveSupport::TestCase
   ].each do |img|
     test "container_image_for_container(#{img.inspect}) => 422" do
       set_user_from_auth :active
-      cr = ContainerRequest.new(container_image: img)
       assert_raises(ArvadosModel::UnresolvableContainerError) do
-        cr.send :container_image_for_container
+        Container.resolve_container_image(img)
       end
     end
   end
@@ -420,18 +495,69 @@ class ContainerRequestTest < ActiveSupport::TestCase
     Rails.configuration.docker_image_formats = ['v2']
     add_docker19_migration_link
 
+    # Test that it returns only v2 images even though request is for v1 image.
+
     set_user_from_auth :active
     cr = create_minimal_req!(command: ["true", "1"],
                              container_image: collections(:docker_image).portable_data_hash)
-    assert_equal(cr.send(:container_image_for_container),
+    assert_equal(Container.resolve_container_image(cr.container_image),
                  collections(:docker_image_1_12).portable_data_hash)
 
     cr = create_minimal_req!(command: ["true", "2"],
                              container_image: links(:docker_image_collection_tag).name)
-    assert_equal(cr.send(:container_image_for_container),
+    assert_equal(Container.resolve_container_image(cr.container_image),
                  collections(:docker_image_1_12).portable_data_hash)
   end
 
+  test "use unmigrated docker image" do
+    Rails.configuration.docker_image_formats = ['v1']
+    add_docker19_migration_link
+
+    # Test that it returns only supported v1 images even though there is a
+    # migration link.
+
+    set_user_from_auth :active
+    cr = create_minimal_req!(command: ["true", "1"],
+                             container_image: collections(:docker_image).portable_data_hash)
+    assert_equal(Container.resolve_container_image(cr.container_image),
+                 collections(:docker_image).portable_data_hash)
+
+    cr = create_minimal_req!(command: ["true", "2"],
+                             container_image: links(:docker_image_collection_tag).name)
+    assert_equal(Container.resolve_container_image(cr.container_image),
+                 collections(:docker_image).portable_data_hash)
+  end
+
+  test "incompatible docker image v1" do
+    Rails.configuration.docker_image_formats = ['v1']
+    add_docker19_migration_link
+
+    # Don't return unsupported v2 image even if we ask for it directly.
+    set_user_from_auth :active
+    cr = create_minimal_req!(command: ["true", "1"],
+                             container_image: collections(:docker_image_1_12).portable_data_hash)
+    assert_raises(ArvadosModel::UnresolvableContainerError) do
+      Container.resolve_container_image(cr.container_image)
+    end
+  end
+
+  test "incompatible docker image v2" do
+    Rails.configuration.docker_image_formats = ['v2']
+    # No migration link, don't return unsupported v1 image,
+
+    set_user_from_auth :active
+    cr = create_minimal_req!(command: ["true", "1"],
+                             container_image: collections(:docker_image).portable_data_hash)
+    assert_raises(ArvadosModel::UnresolvableContainerError) do
+      Container.resolve_container_image(cr.container_image)
+    end
+    cr = create_minimal_req!(command: ["true", "2"],
+                             container_image: links(:docker_image_collection_tag).name)
+    assert_raises(ArvadosModel::UnresolvableContainerError) do
+      Container.resolve_container_image(cr.container_image)
+    end
+  end
+
   test "requestor can retrieve container owned by dispatch" do
     assert_not_empty Container.readable_by(users(:admin)).where(uuid: containers(:running).uuid)
     assert_not_empty Container.readable_by(users(:active)).where(uuid: containers(:running).uuid)
@@ -450,8 +576,7 @@ class ContainerRequestTest < ActiveSupport::TestCase
                       command: ["echo", "hello"],
                       output_path: "test",
                       runtime_constraints: {"vcpus" => 4,
-                                            "ram" => 12000000000,
-                                            "keep_cache_ram" => 268435456},
+                                            "ram" => 12000000000},
                       mounts: {"test" => {"kind" => "json"}}}
       set_user_from_auth :active
       cr1 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Committed,
@@ -533,38 +658,65 @@ class ContainerRequestTest < ActiveSupport::TestCase
 
   test "Output collection name setting using output_name with name collision resolution" do
     set_user_from_auth :active
-    output_name = collections(:foo_file).name
+    output_name = 'unimaginative name'
+    Collection.create!(name: output_name)
 
     cr = create_minimal_req!(priority: 1,
                              state: ContainerRequest::Committed,
                              output_name: output_name)
-    act_as_system_user do
-      c = Container.find_by_uuid(cr.container_uuid)
-      c.update_attributes!(state: Container::Locked)
-      c.update_attributes!(state: Container::Running)
-      c.update_attributes!(state: Container::Complete,
-                           exit_code: 0,
-                           output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45',
-                           log: 'fa7aeb5140e2848d39b416daeef4ffc5+45')
-    end
-    cr.save
+    run_container(cr)
+    cr.reload
     assert_equal ContainerRequest::Final, cr.state
     output_coll = Collection.find_by_uuid(cr.output_uuid)
     # Make sure the resulting output collection name include the original name
     # plus the date
     assert_not_equal output_name, output_coll.name,
-                     "It shouldn't exist more than one collection with the same owner and name '${output_name}'"
+                     "more than one collection with the same owner and name"
     assert output_coll.name.include?(output_name),
            "New name should include original name"
-    assert_match /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/, output_coll.name,
+    assert_match /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/, output_coll.name,
                  "New name should include ISO8601 date"
   end
 
-  test "Finalize committed request when reusing a finished container" do
-    set_user_from_auth :active
-    cr = create_minimal_req!(priority: 1, state: ContainerRequest::Committed)
-    cr.reload
-    assert_equal ContainerRequest::Committed, cr.state
+  [[0, :check_output_ttl_0],
+   [1, :check_output_ttl_1s],
+   [365*86400, :check_output_ttl_1y],
+  ].each do |ttl, checker|
+    test "output_ttl=#{ttl}" do
+      act_as_user users(:active) do
+        cr = create_minimal_req!(priority: 1,
+                                 state: ContainerRequest::Committed,
+                                 output_name: 'foo',
+                                 output_ttl: ttl)
+        run_container(cr)
+        cr.reload
+        output = Collection.find_by_uuid(cr.output_uuid)
+        send(checker, db_current_time, output.trash_at, output.delete_at)
+      end
+    end
+  end
+
+  def check_output_ttl_0(now, trash, delete)
+    assert_nil(trash)
+    assert_nil(delete)
+  end
+
+  def check_output_ttl_1s(now, trash, delete)
+    assert_not_nil(trash)
+    assert_not_nil(delete)
+    assert_in_delta(trash, now + 1.second, 10)
+    assert_in_delta(delete, now + Rails.configuration.blob_signature_ttl.second, 10)
+  end
+
+  def check_output_ttl_1y(now, trash, delete)
+    year = (86400*365).second
+    assert_not_nil(trash)
+    assert_not_nil(delete)
+    assert_in_delta(trash, now + year, 10)
+    assert_in_delta(delete, now + year, 10)
+  end
+
+  def run_container(cr)
     act_as_system_user do
       c = Container.find_by_uuid(cr.container_uuid)
       c.update_attributes!(state: Container::Locked)
@@ -573,7 +725,16 @@ class ContainerRequestTest < ActiveSupport::TestCase
                            exit_code: 0,
                            output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45',
                            log: 'fa7aeb5140e2848d39b416daeef4ffc5+45')
+      c
     end
+  end
+
+  test "Finalize committed request when reusing a finished container" do
+    set_user_from_auth :active
+    cr = create_minimal_req!(priority: 1, state: ContainerRequest::Committed)
+    cr.reload
+    assert_equal ContainerRequest::Committed, cr.state
+    run_container(cr)
     cr.reload
     assert_equal ContainerRequest::Final, cr.state
 
@@ -588,34 +749,6 @@ class ContainerRequestTest < ActiveSupport::TestCase
     assert_equal ContainerRequest::Final, cr3.state
   end
 
-  [
-    [{"vcpus" => 1, "ram" => 123, "keep_cache_ram" => 100}, ContainerRequest::Committed, 100],
-    [{"vcpus" => 1, "ram" => 123}, ContainerRequest::Uncommitted],
-    [{"vcpus" => 1, "ram" => 123}, ContainerRequest::Committed],
-    [{"vcpus" => 1, "ram" => 123, "keep_cache_ram" => -1}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
-    [{"vcpus" => 1, "ram" => 123, "keep_cache_ram" => '123'}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
-  ].each do |rc, state, expected|
-    test "create container request with #{rc} in state #{state} and verify keep_cache_ram #{expected}" do
-      common_attrs = {cwd: "test",
-                      priority: 1,
-                      command: ["echo", "hello"],
-                      output_path: "test",
-                      runtime_constraints: rc,
-                      mounts: {"test" => {"kind" => "json"}}}
-      set_user_from_auth :active
-
-      if expected == ActiveRecord::RecordInvalid
-        assert_raises(ActiveRecord::RecordInvalid) do
-          create_minimal_req!(common_attrs.merge({state: state}))
-        end
-      else
-        cr = create_minimal_req!(common_attrs.merge({state: state}))
-        expected = Rails.configuration.container_default_keep_cache_ram if state == ContainerRequest::Committed and expected.nil?
-        assert_equal expected, cr.runtime_constraints['keep_cache_ram']
-      end
-    end
-  end
-
   [
     [{"partitions" => ["fastcpu","vfastcpu", 100]}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
     [{"partitions" => ["fastcpu","vfastcpu", 100]}, ContainerRequest::Uncommitted],
@@ -647,4 +780,169 @@ class ContainerRequestTest < ActiveSupport::TestCase
       end
     end
   end
+
+  [['Committed', true, {name: "foobar", priority: 123}],
+   ['Committed', false, {container_count: 2}],
+   ['Committed', false, {container_count: 0}],
+   ['Committed', false, {container_count: nil}],
+   ['Final', false, {state: ContainerRequest::Committed, name: "foobar"}],
+   ['Final', false, {name: "foobar", priority: 123}],
+   ['Final', false, {name: "foobar", output_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
+   ['Final', false, {name: "foobar", log_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
+   ['Final', false, {log_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
+   ['Final', false, {priority: 123}],
+   ['Final', false, {mounts: {}}],
+   ['Final', false, {container_count: 2}],
+   ['Final', true, {name: "foobar"}],
+   ['Final', true, {name: "foobar", description: "baz"}],
+  ].each do |state, permitted, updates|
+    test "state=#{state} can#{'not' if !permitted} update #{updates.inspect}" do
+      act_as_user users(:active) do
+        cr = create_minimal_req!(priority: 1,
+                                 state: "Committed",
+                                 container_count_max: 1)
+        case state
+        when 'Committed'
+          # already done
+        when 'Final'
+          act_as_system_user do
+            Container.find_by_uuid(cr.container_uuid).
+              update_attributes!(state: Container::Cancelled)
+          end
+          cr.reload
+        else
+          raise 'broken test case'
+        end
+        assert_equal state, cr.state
+        if permitted
+          assert cr.update_attributes!(updates)
+        else
+          assert_raises(ActiveRecord::RecordInvalid) do
+            cr.update_attributes!(updates)
+          end
+        end
+      end
+    end
+  end
+
+  test "delete container_request and check its container's priority" do
+    act_as_user users(:active) do
+      cr = ContainerRequest.find_by_uuid container_requests(:running_to_be_deleted).uuid
+
+      # initially the cr's container has priority > 0
+      c = Container.find_by_uuid(cr.container_uuid)
+      assert_equal 1, c.priority
+
+      cr.destroy
+
+      # the cr's container now has priority of 0
+      c = Container.find_by_uuid(cr.container_uuid)
+      assert_equal 0, c.priority
+    end
+  end
+
+  test "delete container_request in final state and expect no error due to before_destroy callback" do
+    act_as_user users(:active) do
+      cr = ContainerRequest.find_by_uuid container_requests(:completed).uuid
+      assert_nothing_raised {cr.destroy}
+    end
+  end
+
+  test "Container request valid priority" do
+    set_user_from_auth :active
+    cr = create_minimal_req!
+
+    assert_raises(ActiveRecord::RecordInvalid) do
+      cr.priority = -1
+      cr.save!
+    end
+
+    cr.priority = 0
+    cr.save!
+
+    cr.priority = 1
+    cr.save!
+
+    cr.priority = 500
+    cr.save!
+
+    cr.priority = 999
+    cr.save!
+
+    cr.priority = 1000
+    cr.save!
+
+    assert_raises(ActiveRecord::RecordInvalid) do
+      cr.priority = 1001
+      cr.save!
+    end
+  end
+
+  # Note: some of these tests might look redundant because they test
+  # that out-of-order spellings of hashes are still considered equal
+  # regardless of whether the existing (container) or new (container
+  # request) hash needs to be re-ordered.
+  secrets = {"/foo" => {"kind" => "text", "content" => "xyzzy"}}
+  same_secrets = {"/foo" => {"content" => "xyzzy", "kind" => "text"}}
+  different_secrets = {"/foo" => {"kind" => "text", "content" => "something completely different"}}
+  [
+    [true, nil, nil],
+    [true, nil, {}],
+    [true, {}, nil],
+    [true, {}, {}],
+    [true, secrets, same_secrets],
+    [true, same_secrets, secrets],
+    [false, nil, secrets],
+    [false, {}, secrets],
+    [false, secrets, {}],
+    [false, secrets, nil],
+    [false, secrets, different_secrets],
+  ].each do |expect_reuse, sm1, sm2|
+    test "container reuse secret_mounts #{sm1.inspect}, #{sm2.inspect}" do
+      set_user_from_auth :active
+      cr1 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm1)
+      cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm2)
+      assert_not_nil cr1.container_uuid
+      assert_not_nil cr2.container_uuid
+      if expect_reuse
+        assert_equal cr1.container_uuid, cr2.container_uuid
+      else
+        assert_not_equal cr1.container_uuid, cr2.container_uuid
+      end
+    end
+  end
+
+  test "scrub secret_mounts but reuse container for request with identical secret_mounts" do
+    set_user_from_auth :active
+    sm = {'/secret/foo' => {'kind' => 'text', 'content' => secret_string}}
+    cr1 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm.dup)
+    run_container(cr1)
+    cr1.reload
+
+    # secret_mounts scrubbed from db
+    c = Container.where(uuid: cr1.container_uuid).first
+    assert_equal({}, c.secret_mounts)
+    assert_equal({}, cr1.secret_mounts)
+
+    # can reuse container if secret_mounts match
+    cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm.dup)
+    assert_equal cr1.container_uuid, cr2.container_uuid
+
+    # don't reuse container if secret_mounts don't match
+    cr3 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: {})
+    assert_not_equal cr1.container_uuid, cr3.container_uuid
+
+    assert_no_secrets_logged
+  end
+
+  test "conflicting key in mounts and secret_mounts" do
+    sm = {'/secret/foo' => {'kind' => 'text', 'content' => secret_string}}
+    set_user_from_auth :active
+    cr = create_minimal_req!
+    assert_equal false, cr.update_attributes(state: "Committed",
+                                             priority: 1,
+                                             mounts: cr.mounts.merge(sm),
+                                             secret_mounts: sm)
+    assert_equal [:secret_mounts], cr.errors.messages.keys
+  end
 end