6429: Committing container request creates container
[arvados.git] / services / api / app / models / container.rb
index f4d37de08df9fc9694d89a2effdc4bb58b7b96f9..f87004ab60935d1e7c59803c9a0cc411a4641a9e 100644 (file)
@@ -1,4 +1,24 @@
+require 'whitelist_update'
+
 class Container < ArvadosModel
+  include HasUuid
+  include KindAndEtag
+  include CommonApiTemplate
+  include WhitelistUpdate
+
+  serialize :environment, Hash
+  serialize :mounts, Hash
+  serialize :runtime_constraints, Hash
+  serialize :command, Array
+
+  before_validation :fill_field_defaults, :if => :new_record?
+  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
 
   api_accessible :user, extend: :common do |t|
     t.add :command
@@ -15,14 +35,150 @@ class Container < ArvadosModel
     t.add :runtime_constraints
     t.add :started_at
     t.add :state
-    t.add :uuid
   end
 
-  serialize :properties, Hash
-  serialize :environment, Hash
-  serialize :mounts, Hash
-  serialize :runtime_constraints, Hash
-  serialize :command, Array
+  # Supported states for a container
+  States =
+    [
+     (Queued = 'Queued'),
+     (Running = 'Running'),
+     (Complete = 'Complete'),
+     (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
+    self.state ||= Queued
+    self.environment ||= {}
+    self.runtime_constraints ||= {}
+    self.mounts ||= {}
+    self.cwd ||= "."
+    self.priority ||= 1
+  end
+
+  def permission_to_create
+    current_user.andand.is_admin
+  end
+
+  def permission_to_update
+    current_user.andand.is_admin
+  end
+
+  def set_timestamps
+    if self.state_changed? and self.state == Running
+      self.started_at ||= db_current_time
+    end
+
+    if self.state_changed? and [Complete, Cancelled].include? self.state
+      self.finished_at ||= db_current_time
+    end
+  end
+
+  def validate_change
+    permitted = []
+
+    if self.new_record?
+      permitted.push :owner_uuid, :command, :container_image, :cwd, :environment,
+                     :mounts, :output_path, :priority, :runtime_constraints, :state
+    end
+
+    case self.state
+    when Queued
+      # permit priority change only.
+      permitted.push :priority
+
+      if self.state_changed? and not self.state_was.nil?
+        errors.add :state, "Can only go to Queued from nil"
+      end
+
+    when Running
+      if self.state_changed?
+        # At point of state change, can only set state and started_at
+        if self.state_was == Queued
+          permitted.push :state, :started_at
+        else
+          errors.add :state, "Can only go from Queued to Running"
+        end
+      else
+        # While running, can update priority and progress.
+        permitted.push :priority, :progress
+      end
+
+    when Complete
+      if self.state_changed?
+        if self.state_was == Running
+          permitted.push :state, :finished_at, :output, :log
+        else
+          errors.add :state, "Cannot go from #{self.state_was} to #{self.state}"
+        end
+      else
+        errors.add :state, "Cannot update record in Complete state"
+      end
+
+    when Cancelled
+      if self.state_changed?
+        if self.state_was == Running
+          permitted.push :state, :finished_at, :output, :log
+        elsif self.state_was == Queued
+          permitted.push :state, :finished_at
+        else
+          errors.add :state, "Cannot go from #{self.state_was} to #{self.state}"
+        end
+      else
+        errors.add :state, "Cannot update record in Cancelled state"
+      end
+
+    else
+      errors.add :state, "Invalid state #{self.state}"
+    end
+
+    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
 
-  has_many :container_requests, :foreign_key => :container_uuid, :class_name => 'ContainerRequest', :primary_key => :uuid
 end