9623: Added method to find a reusable container, used by ContainerRequest#resolve...
authorLucas Di Pentima <lucas@curoverse.com>
Wed, 7 Sep 2016 00:59:29 +0000 (21:59 -0300)
committerLucas Di Pentima <lucas@curoverse.com>
Wed, 7 Sep 2016 00:59:29 +0000 (21:59 -0300)
services/api/app/models/container.rb
services/api/app/models/container_request.rb
services/api/test/fixtures/containers.yml
services/api/test/unit/container_test.rb

index f8e27e7bbc5ff1a3fafff32755aedd3b49830e1d..435f5f4df88402db52f95f735b33bee085a4e57c 100644 (file)
@@ -77,6 +77,42 @@ class Container < ArvadosModel
     end
   end
 
+  def self.find_reusable(attrs)
+    candidates = Container.
+      where('command = ?', attrs[:command].to_yaml).
+      where('cwd = ?', attrs[:cwd]).
+      where('environment = ?', self.deep_sort_hash(attrs[:environment]).to_yaml).
+      where('output_path = ?', attrs[:output_path]).
+      where('container_image = ?', attrs[:container_image]).
+      where('mounts = ?', self.deep_sort_hash(attrs[:mounts]).to_yaml).
+      where('runtime_constraints = ?', self.deep_sort_hash(attrs[:runtime_constraints]).to_yaml).
+      where('state in (?)', ['Queued', 'Locked', 'Running', 'Complete']).
+      reject {|c| c.state == 'Complete' and c.exit_code != 0}
+
+    if candidates.empty?
+      nil
+    elsif candidates.count == 1
+      candidates.first
+    else
+      # Multiple candidates found, search for the best one:
+      # The most recent completed container
+      winner = candidates.select {|c| c.state == 'Complete'}.sort_by {|c| c.finished_at}.last
+      winner if not winner.nil?
+      # The running container that's most likely to finish sooner.
+      winner = candidates.select {|c| c.state == 'Running'}.
+        sort {|a, b| [b.progress, a.started_at] <=> [a.progress, b.started_at]}.first
+      winner if not winner.nil?
+      # The locked container that's most likely to start sooner.
+      winner = candidates.select {|c| c.state == 'Locked'}.
+        sort {|a, b| [b.priority, a.created_at] <=> [a.priority, b.created_at]}.first
+      winner if not winner.nil?
+      # The queued container that's most likely to start sooner.
+      winner = candidates.select {|c| c.state == 'Queued'}.
+        sort {|a, b| [b.priority, a.created_at] <=> [a.priority, b.created_at]}.first
+      winner if not winner.nil?
+    end
+  end
+
   protected
 
   def self.deep_sort_hash(x)
index a56c34184d49f7d2374f8cada9f8523f1beb8ac9..47165a8598e1bfea7b5e0ef5cdb3783fc9f7b935 100644 (file)
@@ -87,13 +87,19 @@ class ContainerRequest < ArvadosModel
     c_runtime_constraints = runtime_constraints_for_container
     c_container_image = container_image_for_container
     c = act_as_system_user do
-      Container.create!(command: self.command,
-                        cwd: self.cwd,
-                        environment: self.environment,
-                        output_path: self.output_path,
-                        container_image: c_container_image,
-                        mounts: c_mounts,
-                        runtime_constraints: c_runtime_constraints)
+      c_attrs = {command: self.command,
+                 cwd: self.cwd,
+                 environment: self.environment,
+                 output_path: self.output_path,
+                 container_image: c_container_image,
+                 mounts: c_mounts,
+                 runtime_constraints: c_runtime_constraints}
+      reusable = Container.find_reusable(c_attrs)
+      if not reusable.nil?
+        reusable
+      else
+        Container.create!(c_attrs)
+      end
     end
     self.container_uuid = c.uuid
   end
index 049cd3c6db0307d7ce029f641f8dc93fb8ce917e..906a8a27e6b713065990154c03044408d29112ef 100644 (file)
@@ -81,6 +81,11 @@ completed:
   output: zzzzz-4zz18-znfnqtbbv4spc3w
   output_path: test
   command: ["echo", "hello"]
+  mounts:
+    test:
+      kind: json
+  environment:
+    var: test
   runtime_constraints:
     ram: 12000000000
     vcpus: 4
index 7b2a64471bf5f63737cd838a7c1f620a31591d04..edc9db66e064be5e7a2a6861ed96c9899185271d 100644 (file)
@@ -94,6 +94,19 @@ class ContainerTest < ActiveSupport::TestCase
     assert_equal Container.deep_sort_hash(a).to_json, Container.deep_sort_hash(b).to_json
   end
 
+  test "Container find reusable method" do
+    set_user_from_auth :active
+    c = Container.find_reusable(container_image: "test",
+                                cwd: "test",
+                                command: ["echo", "hello"],
+                                output_path: "test",
+                                runtime_constraints: {"vcpus" => 4, "ram" => 12000000000},
+                                mounts: {"test" => {"kind" => "json"}},
+                                environment: {"var" => "test"})
+    assert_not_nil c
+    assert_equal c.uuid, containers(:completed).uuid
+  end
+
   test "Container running" do
     c, _ = minimal_new priority: 1