X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/908261de15c98553a4200f01aff1f26ef57c8fb8..4e2763883588ac691da65ee316a52a052c002aa7:/services/api/app/models/container.rb diff --git a/services/api/app/models/container.rb b/services/api/app/models/container.rb index 787047df68..845374ee3f 100644 --- a/services/api/app/models/container.rb +++ b/services/api/app/models/container.rb @@ -16,9 +16,12 @@ class Container < ArvadosModel validates :command, :container_image, :output_path, :cwd, :priority, :presence => true validate :validate_state_change validate :validate_change + 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 @@ -27,6 +30,7 @@ class Container < ArvadosModel 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 @@ -36,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') @@ -49,7 +55,8 @@ class Container < ArvadosModel State_transitions = { nil => [Queued], - Queued => [Running, Cancelled], + Queued => [Locked, Cancelled], + Locked => [Queued, Running, Cancelled], Running => [Complete, Cancelled] } @@ -58,16 +65,13 @@ class Container < ArvadosModel end def update_priority! - if [Queued, Running].include? self.state + 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. - max = 0 - ContainerRequest.where(container_uuid: uuid).each do |cr| - if cr.state == ContainerRequest::Committed and cr.priority > max - max = cr.priority - end - end - self.priority = max + self.priority = ContainerRequest. + where(container_uuid: uuid, + state: ContainerRequest::Committed). + maximum('priority') self.save! end end @@ -102,52 +106,91 @@ 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, :exit_code - 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 validate_lock + if locked_by_uuid_was + if locked_by_uuid_was != Thread.current[:api_client_authorization].uuid + # Notably, prohibit changing state or locked_by_uuid: + check_update_whitelist [:priority] + end + end + + if [Locked, Running].include? self.state + need_lock = Thread.current[:api_client_authorization].uuid + else + need_lock = nil + end + 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.