X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/d1baf718d0866c64252006bf61a6f0c5da353f7b..e5659d21c942c7c315fc33668717329d1487e6c6:/services/api/app/models/container_request.rb diff --git a/services/api/app/models/container_request.rb b/services/api/app/models/container_request.rb index 22b6bdeea8..acb751c894 100644 --- a/services/api/app/models/container_request.rb +++ b/services/api/app/models/container_request.rb @@ -1,7 +1,10 @@ +require 'whitelist_update' + class ContainerRequest < ArvadosModel include HasUuid include KindAndEtag include CommonApiTemplate + include WhitelistUpdate serialize :properties, Hash serialize :environment, Hash @@ -9,10 +12,12 @@ class ContainerRequest < ArvadosModel serialize :runtime_constraints, Hash serialize :command, Array - before_create :set_state_before_save - validate :validate_change_permitted - validate :validate_status + before_validation :fill_field_defaults, :if => :new_record? + before_validation :set_container + validates :command, :container_image, :output_path, :cwd, :presence => true validate :validate_state_change + validate :validate_change + after_save :update_priority api_accessible :user, extend: :common do |t| t.add :command @@ -42,80 +47,129 @@ class ContainerRequest < ArvadosModel (Final = 'Final'), ] - def set_state_before_save + State_transitions = { + nil => [Uncommitted, Committed], + Uncommitted => [Committed], + Committed => [Final] + } + + def state_transitions + State_transitions + end + + def skip_uuid_read_permission_check + # XXX temporary until permissions are sorted out. + %w(modified_by_client_uuid container_uuid requesting_container_uuid) + end + + def container_completed! + # may implement retry logic here in the future. + self.state = ContainerRequest::Final + self.save! + end + + protected + + def fill_field_defaults self.state ||= Uncommitted + self.environment ||= {} + self.runtime_constraints ||= {} + self.mounts ||= {} + self.cwd ||= "." + end + + # Turn a container request into a container. + def resolve + # In the future this will do things like resolve symbolic git and keep + # references to content addresses. + Container.create!({ :command => self.command, + :container_image => self.container_image, + :cwd => self.cwd, + :environment => self.environment, + :mounts => self.mounts, + :output_path => self.output_path, + :runtime_constraints => self.runtime_constraints }) end - def validate_change_permitted - if self.changed? - ok = case self.state - when nil - true - when Uncommitted - true - when Committed - # only allow state and priority to change. - not (self.command_changed? or - self.container_count_max_changed? or - self.container_image_changed? or - self.container_uuid_changed? or - self.cwd_changed? or - self.description_changed? or - self.environment_changed? or - self.expires_at_changed? or - self.filters_changed? or - self.mounts_changed? or - self.name_changed? or - self.output_path_changed? or - self.properties_changed? or - self.requesting_container_uuid_changed? or - self.runtime_constraints_changed?) - when Final - false - else - false - end - if not ok - errors.add :state, "Invalid update of container request in #{self.state} state" + def set_container + if self.container_uuid_changed? + if not current_user.andand.is_admin and not self.container_uuid.nil? + errors.add :container_uuid, "can only be updated to nil." + end + else + 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 = self.resolve.andand.uuid + end + end end end end - def validate_status - if self.state.in?(States) - true + def validate_change + permitted = [:owner_uuid] + + case self.state + when Uncommitted + # Permit updating most fields + 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 + + when Committed + if container_uuid.nil? + errors.add :container_uuid, "has not been resolved to a container." + end + + if priority.nil? + errors.add :priority, "cannot be nil" + end + + # Can update priority, container count. + permitted.push :priority, :container_count_max, :container_uuid + + if self.state_changed? + # Allow create-and-commit in a single operation. + permitted.push :command, :container_image, :cwd, :description, :environment, + :filters, :mounts, :name, :output_path, :properties, + :requesting_container_uuid, :runtime_constraints, + :state, :container_uuid + end + + when Final + if not current_user.andand.is_admin + errors.add :state, "of container request can only be set to Final by system." + end + + if self.state_changed? + permitted.push :state + else + errors.add :state, "does not allow updates" + end + else - errors.add :state, "#{state.inspect} must be one of: #{States.inspect}" - false + errors.add :state, "invalid value" end + + check_update_whitelist permitted end - def validate_state_change - ok = true - if self.state_changed? - ok = case self.state_was - when nil - # Must go to Uncommitted - self.state == Uncommitted - when Uncommitted - # Must go to Committed - self.state == Committed - when Committed - # Must to go Final - self.state == Final - when Final - # Once in a final state, don't permit any more state changes - false - else - # Any other state transition is also invalid - false - end - if not ok - errors.add :state, "invalid change from #{self.state_was} to #{self.state}" + def update_priority + if [Committed, Final].include? self.state and (self.state_changed? or + self.priority_changed? or + self.container_uuid_changed?) + [self.container_uuid_was, self.container_uuid].each do |cuuid| + unless cuuid.nil? + c = Container.find_by_uuid cuuid + act_as_system_user do + c.update_priority! + end + end end end - ok end - end