6429: Committing container request creates container
authorPeter Amstutz <peter.amstutz@curoverse.com>
Mon, 7 Dec 2015 21:27:42 +0000 (16:27 -0500)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Mon, 7 Dec 2015 21:27:42 +0000 (16:27 -0500)
services/api/app/models/container.rb
services/api/app/models/container_request.rb
services/api/test/unit/container_request_test.rb

index 4d1908f69e5c822e6ea9a231cd2e60839f716762..f87004ab60935d1e7c59803c9a0cc411a4641a9e 100644 (file)
@@ -15,6 +15,8 @@ class Container < ArvadosModel
   before_validation :set_timestamps
   validates :command, :container_image, :output_path, :cwd, :presence => true
   validate :validate_change
+  after_save :request_finalize
+  after_save :process_tree_priority
 
   has_many :container_requests, :foreign_key => :container_uuid, :class_name => 'ContainerRequest', :primary_key => :uuid
 
@@ -44,6 +46,27 @@ class Container < ArvadosModel
      (Cancelled = 'Cancelled')
     ]
 
+  def self.resolve req
+    Container.create!({ :command => req.command,
+                        :container_image => req.container_image,
+                        :cwd => req.cwd,
+                        :environment => req.environment,
+                        :mounts => req.mounts,
+                        :output_path => req.output_path,
+                        :runtime_constraints => req.runtime_constraints })
+  end
+
+  def update_priority!
+    max = 0
+    ContainerRequest.where(container_uuid: uuid).each do |cr|
+      if cr.priority > max
+        max = cr.priority
+      end
+    end
+    self.priority = max
+    self.save!
+  end
+
   protected
 
   def fill_field_defaults
@@ -134,4 +157,28 @@ class Container < ArvadosModel
     check_update_whitelist permitted
   end
 
+  def request_finalize
+    if self.state_changed? and [Complete, Cancelled].include? self.state
+      act_as_system_user do
+        ContainerRequest.where(container_uuid: uuid).each do |cr|
+          cr.state = ContainerRequest.Final
+          cr.save!
+        end
+      end
+    end
+  end
+
+  def process_tree_priority
+    if self.priority_changed?
+      if self.priority == 0
+        act_as_system_user do
+          ContainerRequest.where(requesting_container_uuid: uuid).each do |cr|
+            cr.priority = self.priority
+            cr.save!
+          end
+        end
+      end
+    end
+  end
+
 end
index aaebcd3b908ae2775479857a81be90543f338df3..f9364683a159a0ceea75f1c6fe62e50787ba4c00 100644 (file)
@@ -13,8 +13,10 @@ class ContainerRequest < ArvadosModel
   serialize :command, Array
 
   before_validation :fill_field_defaults, :if => :new_record?
+  before_validation :set_container
   validates :command, :container_image, :output_path, :cwd, :presence => true
   validate :validate_change
+  after_save :update_priority
 
   api_accessible :user, extend: :common do |t|
     t.add :command
@@ -55,6 +57,20 @@ class ContainerRequest < ArvadosModel
     self.priority ||= 1
   end
 
+  def set_container
+    if self.state_changed?
+      if self.state == Committed and (self.state_was == Uncommitted or self.state_was.nil?)
+        act_as_system_user do
+          self.container_uuid = Container.resolve(self).andand.uuid
+          Link.create!(head_uuid: self.container_uuid,
+                       tail_uuid: self.owner_uuid,
+                       link_class: "permission",
+                       name: "can_read")
+        end
+      end
+    end
+  end
+
   def validate_change
     permitted = [:owner_uuid]
 
@@ -65,28 +81,29 @@ class ContainerRequest < ArvadosModel
                      :container_image, :cwd, :description, :environment,
                      :filters, :mounts, :name, :output_path, :priority,
                      :properties, :requesting_container_uuid, :runtime_constraints,
-                     :state
+                     :state, :container_uuid
 
-      if self.container_uuid_changed? and (current_user.andand.is_admin or self.container_uuid.nil?)
-        permitted.push :container_uuid
+    when Committed
+      if container_uuid.nil?
+        errors.add :container_uuid, "Has not been resolved to a container."
       end
 
-    when Committed
-      # Can only update priority, container count.
+      # Can update priority, container count.
       permitted.push :priority, :container_count_max
 
       if self.state_changed?
-        if self.state_was == Uncommitted
-          permitted.push :state
+        if self.state_was == Uncommitted or self.state_was.nil?
+          # Allow create-and-commit in a single operation.
+          permitted.push :command, :container_count_max,
+                         :container_image, :cwd, :description, :environment,
+                         :filters, :mounts, :name, :output_path, :priority,
+                         :properties, :requesting_container_uuid, :runtime_constraints,
+                         :state, :container_uuid
         else
           errors.add :state, "Can only go from Uncommitted to Committed"
         end
       end
 
-      if self.container_uuid_changed? and current_user.andand.is_admin
-        permitted.push :container_uuid
-      end
-
     when Final
       if self.state_changed?
         if self.state_was == Committed
@@ -105,4 +122,11 @@ class ContainerRequest < ArvadosModel
     check_update_whitelist permitted
   end
 
+  def update_priority
+    if self.state == Committed and self.priority_changed?
+      c = Container.find_by_uuid self.container_uuid
+      c.update_priority!
+    end
+  end
+
 end
index d962f300898e8ebaa42319e7d500acaf4766d382..1248475ffa12418413d085d3071b7bf64d2f868a 100644 (file)
@@ -1,7 +1,141 @@
 require 'test_helper'
 
 class ContainerRequestTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
+  def check_illegal_modify c
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.command = ["echo", "bar"]
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.container_image = "img2"
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.cwd = "/tmp2"
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.environment = {"FOO" => "BAR"}
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.mounts = {"FOO" => "BAR"}
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.output_path = "/tmp3"
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.runtime_constraints = {"FOO" => "BAR"}
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.name = "baz"
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.description = "baz"
+        c.save!
+      end
+
+  end
+
+  def check_bogus_states c
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.state = nil
+        c.save!
+      end
+
+      assert_raises(ActiveRecord::RecordInvalid) do
+        c.reload
+        c.state = "Flubber"
+        c.save!
+      end
+  end
+
+  test "Container request create" do
+    set_user_from_auth :active_trustedclient
+    c = ContainerRequest.new
+    c.command = ["echo", "foo"]
+    c.container_image = "img"
+    c.cwd = "/tmp"
+    c.environment = {}
+    c.mounts = {"BAR" => "FOO"}
+    c.output_path = "/tmp"
+    c.priority = 1
+    c.runtime_constraints = {}
+    c.name = "foo"
+    c.description = "bar"
+    c.save!
+
+    assert_nil c.container_uuid
+
+    check_bogus_states c
+
+    c.reload
+    c.command = ["echo", "foo3"]
+    c.container_image = "img3"
+    c.cwd = "/tmp3"
+    c.environment = {"BUP" => "BOP"}
+    c.mounts = {"BAR" => "BAZ"}
+    c.output_path = "/tmp4"
+    c.priority = 2
+    c.runtime_constraints = {"X" => "Y"}
+    c.name = "foo3"
+    c.description = "bar3"
+    c.save!
+
+    assert_nil c.container_uuid
+  end
+
+  test "Container request commit" do
+    set_user_from_auth :active_trustedclient
+    c = ContainerRequest.new
+    c.command = ["echo", "foo"]
+    c.container_image = "img"
+    c.cwd = "/tmp"
+    c.environment = {}
+    c.mounts = {"BAR" => "FOO"}
+    c.output_path = "/tmp"
+    c.priority = 1
+    c.runtime_constraints = {}
+    c.name = "foo"
+    c.description = "bar"
+    c.save!
+
+    c.reload
+    assert_nil c.container_uuid
+
+    c.reload
+    c.state = "Committed"
+    c.save!
+
+    c.reload
+
+    t = Container.find_by_uuid c.container_uuid
+    assert_equal c.command, t.command
+    assert_equal c.container_image, t.container_image
+    assert_equal c.cwd, t.cwd
+  end
+
+
 end