X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/d4939c581f629a19220bc5b469fed98462b2b7eb..5bcb3cee7d38123480cccc84a22a451ea17f2ae9:/services/api/app/models/container.rb diff --git a/services/api/app/models/container.rb b/services/api/app/models/container.rb index 79c9868189..4c77008378 100644 --- a/services/api/app/models/container.rb +++ b/services/api/app/models/container.rb @@ -13,19 +13,24 @@ class Container < ArvadosModel before_validation :fill_field_defaults, :if => :new_record? before_validation :set_timestamps - validates :command, :container_image, :output_path, :cwd, :presence => true + validates :command, :container_image, :output_path, :cwd, :priority, :presence => true validate :validate_state_change validate :validate_change - after_save :request_finalize + validate :validate_lock + after_validation :assign_auth + after_save :handle_completed has_many :container_requests, :foreign_key => :container_uuid, :class_name => 'ContainerRequest', :primary_key => :uuid + belongs_to :auth, :class_name => 'ApiClientAuthorization', :foreign_key => :auth_uuid, :primary_key => :uuid api_accessible :user, extend: :common do |t| t.add :command t.add :container_image t.add :cwd t.add :environment + t.add :exit_code t.add :finished_at + t.add :locked_by_uuid t.add :log t.add :mounts t.add :output @@ -35,12 +40,14 @@ class Container < ArvadosModel t.add :runtime_constraints t.add :started_at t.add :state + t.add :auth_uuid end # Supported states for a container States = [ (Queued = 'Queued'), + (Locked = 'Locked'), (Running = 'Running'), (Complete = 'Complete'), (Cancelled = 'Cancelled') @@ -48,7 +55,8 @@ class Container < ArvadosModel State_transitions = { nil => [Queued], - Queued => [Running, Cancelled], + Queued => [Locked, Cancelled], + Locked => [Queued, Running, Cancelled], Running => [Complete, Cancelled] } @@ -57,16 +65,15 @@ class Container < ArvadosModel end def update_priority! - # Update the priority of this container to the maximum priority of any of - # its committed container requests and save the record. - max = 0 - ContainerRequest.where(container_uuid: uuid).each do |cr| - if cr.state == ContainerRequest::Committed and cr.priority > max - max = cr.priority - end + if [Queued, Locked, Running].include? self.state + # Update the priority of this container to the maximum priority of any of + # its committed container requests and save the record. + self.priority = ContainerRequest. + where(container_uuid: uuid, + state: ContainerRequest::Committed). + maximum('priority') + self.save! end - self.priority = max - self.save! end protected @@ -99,68 +106,115 @@ class Container < ArvadosModel end def validate_change - permitted = [] + permitted = [:state] if self.new_record? - permitted.push :owner_uuid, :command, :container_image, :cwd, :environment, - :mounts, :output_path, :priority, :runtime_constraints, :state + permitted.push(:owner_uuid, :command, :container_image, :cwd, + :environment, :mounts, :output_path, :priority, + :runtime_constraints) end case self.state - when Queued - # permit priority change only. + when Queued, Locked permitted.push :priority when Running + permitted.push :priority, :progress if self.state_changed? - # At point of state change, can set state and started_at - permitted.push :state, :started_at - else - # While running, can update priority and progress. - permitted.push :priority, :progress + permitted.push :started_at end when Complete - if self.state_changed? - permitted.push :state, :finished_at, :output, :log - else - errors.add :state, "cannot update record" + if self.state_was == Running + permitted.push :finished_at, :output, :log, :exit_code 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 - end - else - errors.add :state, "cannot update record" + case self.state_was + when Running + permitted.push :finished_at, :output, :log + when Queued, Locked + permitted.push :finished_at end else - errors.add :state, "invalid state" + # The state_transitions check will add an error message for this + return false end check_update_whitelist permitted end - def request_finalize + def validate_lock + # If the Container is already locked by someone other than the + # current api_client_auth, disallow all changes -- except + # priority, which needs to change to reflect max(priority) of + # relevant ContainerRequests. + if locked_by_uuid_was + if locked_by_uuid_was != Thread.current[:api_client_authorization].uuid + check_update_whitelist [:priority] + end + end + + if [Locked, Running].include? self.state + # If the Container was already locked, locked_by_uuid must not + # changes. Otherwise, the current auth gets the lock. + need_lock = locked_by_uuid_was || Thread.current[:api_client_authorization].uuid + else + need_lock = nil + end + + # The caller can provide a new value for locked_by_uuid, but only + # if it's exactly what we expect. This allows a caller to perform + # an update like {"state":"Unlocked","locked_by_uuid":null}. + if self.locked_by_uuid_changed? + if self.locked_by_uuid != need_lock + return errors.add :locked_by_uuid, "can only change to #{need_lock}" + end + end + self.locked_by_uuid = need_lock + end + + def assign_auth + if self.auth_uuid_changed? + return errors.add :auth_uuid, 'is readonly' + end + if not [Locked, Running].include? self.state + # don't need one + self.auth.andand.update_attributes(expires_at: db_current_time) + self.auth = nil + return + elsif self.auth + # already have one + return + end + cr = ContainerRequest. + where('container_uuid=? and priority>0', self.uuid). + order('priority desc'). + first + if !cr + return errors.add :auth_uuid, "cannot be assigned because priority <= 0" + end + self.auth = ApiClientAuthorization. + create!(user_id: User.find_by_uuid(cr.modified_by_user_uuid).id, + api_client_id: 0) + end + + def handle_completed # This container is finished so finalize any associated container requests # that are associated with this container. if self.state_changed? and [Complete, Cancelled].include? self.state act_as_system_user do - # Try to close container requests associated with this container + # Notify container requests associated with this container ContainerRequest.where(container_uuid: uuid, :state => ContainerRequest::Committed).each do |cr| - cr.state = ContainerRequest::Final - cr.save + cr.container_completed! end - # Try to close any outstanding container requests made by this container. + # Try to cancel any outstanding container requests made by this container. ContainerRequest.where(requesting_container_uuid: uuid, :state => ContainerRequest::Committed).each do |cr| - cr.state = ContainerRequest::Final + cr.priority = 0 cr.save end end