Merge branch '15599-keepstore-iam-role'
[arvados.git] / services / api / app / models / container_request.rb
index 921d4bee60f7f5f679b0531d61f259f15b4ff96c..5a7818147308a6f3fa41966430ffce837ab2cee7 100644 (file)
@@ -19,13 +19,16 @@ class ContainerRequest < ArvadosModel
                primary_key: :uuid,
              }
 
-  serialize :properties, Hash
+  # Posgresql JSONB columns should NOT be declared as serialized, Rails 5
+  # already know how to properly treat them.
+  attribute :properties, :jsonbHash, default: {}
+  attribute :secret_mounts, :jsonbHash, default: {}
+
   serialize :environment, Hash
   serialize :mounts, Hash
   serialize :runtime_constraints, Hash
   serialize :command, Array
   serialize :scheduling_parameters, Hash
-  serialize :secret_mounts, Hash
 
   before_validation :fill_field_defaults, :if => :new_record?
   before_validation :validate_runtime_constraints
@@ -116,20 +119,61 @@ class ContainerRequest < ArvadosModel
   end
 
   def finalize_if_needed
-    if state == Committed && Container.find_by_uuid(container_uuid).final?
-      reload
-      act_as_system_user do
-        leave_modified_by_user_alone do
-          finalize!
+    return if state != Committed
+    while true
+      # get container lock first, then lock current container request
+      # (same order as Container#handle_completed). Locking always
+      # reloads the Container and ContainerRequest records.
+      c = Container.find_by_uuid(container_uuid)
+      c.lock!
+      self.lock!
+
+      if container_uuid != c.uuid
+        # After locking, we've noticed a race, the container_uuid is
+        # different than the container record we just loaded.  This
+        # can happen if Container#handle_completed scheduled a new
+        # container for retry and set container_uuid while we were
+        # waiting on the container lock.  Restart the loop and get the
+        # new container.
+        redo
+      end
+
+      if state == Committed && c.final?
+        # The current container is
+        act_as_system_user do
+          leave_modified_by_user_alone do
+            finalize!
+          end
         end
       end
+      return true
     end
   end
 
   # Finalize the container request after the container has
   # finished/cancelled.
   def finalize!
-    update_collections(container: Container.find_by_uuid(container_uuid))
+    container = Container.find_by_uuid(container_uuid)
+    update_collections(container: container)
+
+    if container.state == Container::Complete
+      log_col = Collection.where(portable_data_hash: container.log).first
+      if log_col
+        # Need to save collection
+        completed_coll = Collection.new(
+          owner_uuid: self.owner_uuid,
+          name: "Container log for container #{container_uuid}",
+          properties: {
+            'type' => 'log',
+            'container_request' => self.uuid,
+            'container_uuid' => container_uuid,
+          },
+          portable_data_hash: log_col.portable_data_hash,
+          manifest_text: log_col.manifest_text)
+        completed_coll.save_with_unique_name!
+      end
+    end
+
     update_attributes!(state: Final)
   end
 
@@ -163,6 +207,7 @@ class ContainerRequest < ArvadosModel
       end
 
       if out_type == "log"
+        # Copy the log into a merged collection
         src = Arv::Collection.new(manifest)
         dst = Arv::Collection.new(coll.manifest_text)
         dst.cp_r("./", ".", src)
@@ -191,8 +236,9 @@ class ContainerRequest < ArvadosModel
     self.environment ||= {}
     self.runtime_constraints ||= {}
     self.mounts ||= {}
+    self.secret_mounts ||= {}
     self.cwd ||= "."
-    self.container_count_max ||= Rails.configuration.container_count_max
+    self.container_count_max ||= Rails.configuration.Containers.MaxRetryAttempts
     self.scheduling_parameters ||= {}
     self.output_ttl ||= 0
     self.priority ||= 0
@@ -206,7 +252,18 @@ class ContainerRequest < ArvadosModel
       return false
     end
     if state_changed? and state == Committed and container_uuid.nil?
-      self.container_uuid = Container.resolve(self).uuid
+      while true
+        c = Container.resolve(self)
+        c.lock!
+        if c.state == Container::Cancelled
+          # Lost a race, we have a lock on the container but the
+          # container was cancelled in a different request, restart
+          # the loop and resolve request to a new container.
+          redo
+        end
+        self.container_uuid = c.uuid
+        break
+      end
     end
     if self.container_uuid != self.container_uuid_was
       if self.container_count_changed?
@@ -248,7 +305,7 @@ class ContainerRequest < ArvadosModel
     if self.state == Committed
       # If preemptible instances (eg: AWS Spot Instances) are allowed,
       # ask them on child containers by default.
-      if Rails.configuration.preemptible_instances and !c.nil? and
+      if Rails.configuration.Containers.UsePreemptibleInstances and !c.nil? and
         self.scheduling_parameters['preemptible'].nil?
           self.scheduling_parameters['preemptible'] = true
       end
@@ -318,7 +375,7 @@ class ContainerRequest < ArvadosModel
             scheduling_parameters['partitions'].size)
             errors.add :scheduling_parameters, "partitions must be an array of strings"
       end
-      if !Rails.configuration.preemptible_instances and scheduling_parameters['preemptible']
+      if !Rails.configuration.Containers.UsePreemptibleInstances and scheduling_parameters['preemptible']
         errors.add :scheduling_parameters, "preemptible instances are not allowed"
       end
       if scheduling_parameters.include? 'max_run_time' and