X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/374a802e502d044973fd21ca68d2f6ab707bd770..6c0bf267d795a3ca49c3258c9490714c9e18d333:/services/api/app/models/container.rb diff --git a/services/api/app/models/container.rb b/services/api/app/models/container.rb index 1e645e4256..a60ea427b7 100644 --- a/services/api/app/models/container.rb +++ b/services/api/app/models/container.rb @@ -5,6 +5,7 @@ class Container < ArvadosModel include KindAndEtag include CommonApiTemplate include WhitelistUpdate + extend CurrentApiClient serialize :environment, Hash serialize :mounts, Hash @@ -18,6 +19,7 @@ class Container < ArvadosModel validate :validate_change validate :validate_lock after_validation :assign_auth + before_save :sort_serialized_attrs after_save :handle_completed has_many :container_requests, :foreign_key => :container_uuid, :class_name => 'ContainerRequest', :primary_key => :uuid @@ -76,6 +78,80 @@ class Container < ArvadosModel end end + def self.find_reusable(attrs) + candidates = Container. + where('command = ?', attrs[:command].to_yaml). + where('cwd = ?', attrs[:cwd]). + where('environment = ?', self.deep_sort_hash(attrs[:environment]).to_yaml). + where('output_path = ?', attrs[:output_path]). + where('container_image = ?', attrs[:container_image]). + where('mounts = ?', self.deep_sort_hash(attrs[:mounts]).to_yaml). + where('runtime_constraints = ?', self.deep_sort_hash(attrs[:runtime_constraints]).to_yaml) + + # Check for Completed candidates that had consistent outputs. + completed = candidates.where(state: Complete).where(exit_code: 0) + outputs = completed.select('output').group('output').limit(2) + if outputs.count.count != 1 + Rails.logger.debug("Found #{outputs.count.length} different outputs") + elsif Collection. + readable_by(current_user). + where(portable_data_hash: outputs.first.output). + count < 1 + Rails.logger.info("Found reusable container(s) " + + "but output #{outputs.first} is not readable " + + "by user #{current_user.uuid}") + else + # Return the oldest eligible container whose log is still + # present and readable by current_user. + readable_pdh = Collection. + readable_by(current_user). + select('portable_data_hash') + completed = completed. + where("log in (#{readable_pdh.to_sql})"). + order('finished_at asc'). + limit(1) + if completed.first + return completed.first + else + Rails.logger.info("Found reusable container(s) but none with a log " + + "readable by user #{current_user.uuid}") + end + end + + # Check for Running candidates and return the most likely to finish sooner. + running = candidates.where(state: Running). + order('progress desc, started_at asc').limit(1).first + return running if not running.nil? + + # Check for Locked or Queued ones and return the most likely to start first. + locked_or_queued = candidates.where("state IN (?)", [Locked, Queued]). + order('state asc, priority desc, created_at asc').limit(1).first + return locked_or_queued if not locked_or_queued.nil? + + # No suitable candidate found. + nil + end + + def lock + with_lock do + if self.state == Locked + raise AlreadyLockedError + end + self.state = Locked + self.save! + end + end + + def unlock + with_lock do + if self.state == Queued + raise InvalidStateTransitionError + end + self.state = Queued + self.save! + end + end + def self.readable_by(*users_list) if users_list.select { |u| u.is_admin }.any? return self @@ -214,6 +290,18 @@ class Container < ArvadosModel api_client_id: 0) end + def sort_serialized_attrs + if self.environment_changed? + self.environment = self.class.deep_sort_hash(self.environment) + end + if self.mounts_changed? + self.mounts = self.class.deep_sort_hash(self.mounts) + end + if self.runtime_constraints_changed? + self.runtime_constraints = self.class.deep_sort_hash(self.runtime_constraints) + end + end + def handle_completed # This container is finished so finalize any associated container requests # that are associated with this container. @@ -221,13 +309,13 @@ class Container < ArvadosModel act_as_system_user do # Notify container requests associated with this container ContainerRequest.where(container_uuid: uuid, - :state => ContainerRequest::Committed).each do |cr| + state: ContainerRequest::Committed).each do |cr| cr.container_completed! end # Try to cancel any outstanding container requests made by this container. ContainerRequest.where(requesting_container_uuid: uuid, - :state => ContainerRequest::Committed).each do |cr| + state: ContainerRequest::Committed).each do |cr| cr.priority = 0 cr.save end