X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/a95f899d7ac84f29b3d019aa410d265bb40833e5..24bcfa0b87b87e4510fffe8a961a5d4a9fd34948:/services/api/test/unit/container_test.rb diff --git a/services/api/test/unit/container_test.rb b/services/api/test/unit/container_test.rb index 9cc098117f..8894ed9d4c 100644 --- a/services/api/test/unit/container_test.rb +++ b/services/api/test/unit/container_test.rb @@ -8,8 +8,18 @@ class ContainerTest < ActiveSupport::TestCase container_image: 'img', output_path: '/tmp', priority: 1, + runtime_constraints: {"vcpus" => 1, "ram" => 1}, } + REUSABLE_COMMON_ATTRS = {container_image: "test", + cwd: "test", + command: ["echo", "hello"], + output_path: "test", + runtime_constraints: {"vcpus" => 4, + "ram" => 12000000000}, + mounts: {"test" => {"kind" => "json"}}, + environment: {"var" => 'val'}} + def minimal_new attrs={} cr = ContainerRequest.new DEFAULT_ATTRS.merge(attrs) act_as_user users(:active) do @@ -66,7 +76,7 @@ class ContainerTest < ActiveSupport::TestCase mounts: {"BAR" => "FOO"}, output_path: "/tmp", priority: 1, - runtime_constraints: {}) + runtime_constraints: {"vcpus" => 1, "ram" => 1}) check_illegal_modify c check_bogus_states c @@ -77,6 +87,228 @@ class ContainerTest < ActiveSupport::TestCase end end + test "Container serialized hash attributes sorted before save" do + env = {"C" => 3, "B" => 2, "A" => 1} + m = {"F" => 3, "E" => 2, "D" => 1} + rc = {"vcpus" => 1, "ram" => 1} + c, _ = minimal_new(environment: env, mounts: m, runtime_constraints: rc) + assert_equal c.environment.to_json, Container.deep_sort_hash(env).to_json + assert_equal c.mounts.to_json, Container.deep_sort_hash(m).to_json + assert_equal c.runtime_constraints.to_json, Container.deep_sort_hash(rc).to_json + end + + test 'deep_sort_hash on array of hashes' do + a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]} + b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]} + assert_equal Container.deep_sort_hash(a).to_json, Container.deep_sort_hash(b).to_json + end + + test "find_reusable method should select higher priority queued container" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment:{"var" => "queued"}}) + c_low_priority, _ = minimal_new(common_attrs.merge({priority:1})) + c_high_priority, _ = minimal_new(common_attrs.merge({priority:2})) + assert_equal Container::Queued, c_low_priority.state + assert_equal Container::Queued, c_high_priority.state + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + assert_equal reused.uuid, c_high_priority.uuid + end + + test "find_reusable method should select latest completed container" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}}) + completed_attrs = { + state: Container::Complete, + exit_code: 0, + log: 'ea10d51bcf88862dbcc36eb292017dfd+45', + output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45' + } + + c_older, _ = minimal_new(common_attrs) + c_recent, _ = minimal_new(common_attrs) + + set_user_from_auth :dispatch1 + c_older.update_attributes!({state: Container::Locked}) + c_older.update_attributes!({state: Container::Running}) + c_older.update_attributes!(completed_attrs) + + c_recent.update_attributes!({state: Container::Locked}) + c_recent.update_attributes!({state: Container::Running}) + c_recent.update_attributes!(completed_attrs) + + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + assert_equal reused.uuid, c_older.uuid + end + + test "find_reusable method should not select completed container when inconsistent outputs exist" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}}) + completed_attrs = { + state: Container::Complete, + exit_code: 0, + log: 'ea10d51bcf88862dbcc36eb292017dfd+45', + } + + c_output1, _ = minimal_new(common_attrs) + c_output2, _ = minimal_new(common_attrs) + + set_user_from_auth :dispatch1 + c_output1.update_attributes!({state: Container::Locked}) + c_output1.update_attributes!({state: Container::Running}) + c_output1.update_attributes!(completed_attrs.merge({output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'})) + + c_output2.update_attributes!({state: Container::Locked}) + c_output2.update_attributes!({state: Container::Running}) + c_output2.update_attributes!(completed_attrs.merge({output: 'fa7aeb5140e2848d39b416daeef4ffc5+45'})) + + reused = Container.find_reusable(common_attrs) + assert_nil reused + end + + test "find_reusable method should select running container by start date" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running"}}) + c_slower, _ = minimal_new(common_attrs) + c_faster_started_first, _ = minimal_new(common_attrs) + c_faster_started_second, _ = minimal_new(common_attrs) + set_user_from_auth :dispatch1 + c_slower.update_attributes!({state: Container::Locked}) + c_slower.update_attributes!({state: Container::Running, + progress: 0.1}) + c_faster_started_first.update_attributes!({state: Container::Locked}) + c_faster_started_first.update_attributes!({state: Container::Running, + progress: 0.15}) + c_faster_started_second.update_attributes!({state: Container::Locked}) + c_faster_started_second.update_attributes!({state: Container::Running, + progress: 0.15}) + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + # Selected container is the one that started first + assert_equal reused.uuid, c_faster_started_first.uuid + end + + test "find_reusable method should select running container by progress" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running2"}}) + c_slower, _ = minimal_new(common_attrs) + c_faster_started_first, _ = minimal_new(common_attrs) + c_faster_started_second, _ = minimal_new(common_attrs) + set_user_from_auth :dispatch1 + c_slower.update_attributes!({state: Container::Locked}) + c_slower.update_attributes!({state: Container::Running, + progress: 0.1}) + c_faster_started_first.update_attributes!({state: Container::Locked}) + c_faster_started_first.update_attributes!({state: Container::Running, + progress: 0.15}) + c_faster_started_second.update_attributes!({state: Container::Locked}) + c_faster_started_second.update_attributes!({state: Container::Running, + progress: 0.2}) + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + # Selected container is the one with most progress done + assert_equal reused.uuid, c_faster_started_second.uuid + end + + test "find_reusable method should select locked container most likely to start sooner" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "locked"}}) + c_low_priority, _ = minimal_new(common_attrs) + c_high_priority_older, _ = minimal_new(common_attrs) + c_high_priority_newer, _ = minimal_new(common_attrs) + set_user_from_auth :dispatch1 + c_low_priority.update_attributes!({state: Container::Locked, + priority: 1}) + c_high_priority_older.update_attributes!({state: Container::Locked, + priority: 2}) + c_high_priority_newer.update_attributes!({state: Container::Locked, + priority: 2}) + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + assert_equal reused.uuid, c_high_priority_older.uuid + end + + test "find_reusable method should select running over failed container" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed_vs_running"}}) + c_failed, _ = minimal_new(common_attrs) + c_running, _ = minimal_new(common_attrs) + set_user_from_auth :dispatch1 + c_failed.update_attributes!({state: Container::Locked}) + c_failed.update_attributes!({state: Container::Running}) + c_failed.update_attributes!({state: Container::Complete, + exit_code: 42, + log: 'ea10d51bcf88862dbcc36eb292017dfd+45', + output: 'ea10d51bcf88862dbcc36eb292017dfd+45'}) + c_running.update_attributes!({state: Container::Locked}) + c_running.update_attributes!({state: Container::Running, + progress: 0.15}) + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + assert_equal reused.uuid, c_running.uuid + end + + test "find_reusable method should select complete over running container" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "completed_vs_running"}}) + c_completed, _ = minimal_new(common_attrs) + c_running, _ = minimal_new(common_attrs) + set_user_from_auth :dispatch1 + c_completed.update_attributes!({state: Container::Locked}) + c_completed.update_attributes!({state: Container::Running}) + c_completed.update_attributes!({state: Container::Complete, + exit_code: 0, + log: 'ea10d51bcf88862dbcc36eb292017dfd+45', + output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'}) + c_running.update_attributes!({state: Container::Locked}) + c_running.update_attributes!({state: Container::Running, + progress: 0.15}) + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + assert_equal c_completed.uuid, reused.uuid + end + + test "find_reusable method should select running over locked container" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}}) + c_locked, _ = minimal_new(common_attrs) + c_running, _ = minimal_new(common_attrs) + set_user_from_auth :dispatch1 + c_locked.update_attributes!({state: Container::Locked}) + c_running.update_attributes!({state: Container::Locked}) + c_running.update_attributes!({state: Container::Running, + progress: 0.15}) + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + assert_equal reused.uuid, c_running.uuid + end + + test "find_reusable method should select locked over queued container" do + set_user_from_auth :active + common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}}) + c_locked, _ = minimal_new(common_attrs) + c_queued, _ = minimal_new(common_attrs) + set_user_from_auth :dispatch1 + c_locked.update_attributes!({state: Container::Locked}) + reused = Container.find_reusable(common_attrs) + assert_not_nil reused + assert_equal reused.uuid, c_locked.uuid + end + + test "find_reusable method should not select failed container" do + set_user_from_auth :active + attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed"}}) + c, _ = minimal_new(attrs) + set_user_from_auth :dispatch1 + c.update_attributes!({state: Container::Locked}) + c.update_attributes!({state: Container::Running}) + c.update_attributes!({state: Container::Complete, + exit_code: 33}) + reused = Container.find_reusable(attrs) + assert_nil reused + end + test "Container running" do c, _ = minimal_new priority: 1 @@ -84,7 +316,7 @@ class ContainerTest < ActiveSupport::TestCase check_illegal_updates c, [{state: Container::Running}, {state: Container::Complete}] - c.update_attributes! state: Container::Locked + c.lock c.update_attributes! state: Container::Running check_illegal_modify c @@ -102,7 +334,7 @@ class ContainerTest < ActiveSupport::TestCase set_user_from_auth :dispatch1 assert_equal Container::Queued, c.state - refute c.update_attributes(state: Container::Locked), "no priority" + assert_raise(ActiveRecord::RecordInvalid) {c.lock} # "no priority" c.reload assert cr.update_attributes priority: 1 @@ -111,11 +343,14 @@ class ContainerTest < ActiveSupport::TestCase refute c.update_attributes(state: Container::Complete), "not locked" c.reload - assert c.update_attributes(state: Container::Locked), show_errors(c) + assert c.lock, show_errors(c) assert c.locked_by_uuid assert c.auth_uuid - assert c.update_attributes(state: Container::Queued), show_errors(c) + assert_raise(ArvadosModel::AlreadyLockedError) {c.lock} + c.reload + + assert c.unlock, show_errors(c) refute c.locked_by_uuid refute c.auth_uuid @@ -124,16 +359,16 @@ class ContainerTest < ActiveSupport::TestCase refute c.locked_by_uuid refute c.auth_uuid - assert c.update_attributes(state: Container::Locked), show_errors(c) + assert c.lock, show_errors(c) assert c.update_attributes(state: Container::Running), show_errors(c) assert c.locked_by_uuid assert c.auth_uuid auth_uuid_was = c.auth_uuid - refute c.update_attributes(state: Container::Locked), "already running" + assert_raise(ActiveRecord::RecordInvalid) {c.lock} # Running to Locked is not allowed c.reload - refute c.update_attributes(state: Container::Queued), "already running" + assert_raise(ActiveRecord::RecordInvalid) {c.unlock} # Running to Queued is not allowed c.reload assert c.update_attributes(state: Container::Complete), show_errors(c) @@ -154,7 +389,7 @@ class ContainerTest < ActiveSupport::TestCase test "Container locked cancel" do c, _ = minimal_new set_user_from_auth :dispatch1 - assert c.update_attributes(state: Container::Locked), show_errors(c) + assert c.lock, show_errors(c) assert c.update_attributes(state: Container::Cancelled), show_errors(c) check_no_change_from_cancelled c end @@ -162,8 +397,7 @@ class ContainerTest < ActiveSupport::TestCase test "Container running cancel" do c, _ = minimal_new set_user_from_auth :dispatch1 - c.update_attributes! state: Container::Queued - c.update_attributes! state: Container::Locked + c.lock c.update_attributes! state: Container::Running c.update_attributes! state: Container::Cancelled check_no_change_from_cancelled c @@ -185,7 +419,7 @@ class ContainerTest < ActiveSupport::TestCase test "Container only set exit code on complete" do c, _ = minimal_new set_user_from_auth :dispatch1 - c.update_attributes! state: Container::Locked + c.lock c.update_attributes! state: Container::Running check_illegal_updates c, [{exit_code: 1},