X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/1b4bfe1a002732b1e9246be8b2679fec0a262202..916d57c9fe68a9e12472e4d174d38d93086c6529:/services/api/app/models/container.rb diff --git a/services/api/app/models/container.rb b/services/api/app/models/container.rb index 558e8d18a0..c1c3eae94b 100644 --- a/services/api/app/models/container.rb +++ b/services/api/app/models/container.rb @@ -85,49 +85,64 @@ class Container < ArvadosModel 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). - where('state in (?)', [Queued, Locked, Running, Complete]). - reject {|c| c.state == Complete and - (c.exit_code != 0 or c.output.nil? or c.log.nil?)} - if candidates.empty? - nil - elsif candidates.count == 1 - candidates.first - else - # Multiple candidates found, search for the best one: - # The most recent completed container - winner = candidates.select {|c| c.state == Complete}. - sort_by {|c| c.finished_at}.last - return winner if not winner.nil? - # The running container that's most likely to finish sooner. - winner = candidates.select {|c| c.state == Running}. - sort {|a, b| [b.progress, a.started_at] <=> [a.progress, b.started_at]}.first - return winner if not winner.nil? - # The locked container that's most likely to start sooner. - winner = candidates.select {|c| c.state == Locked}. - sort {|a, b| [b.priority, a.created_at] <=> [a.priority, b.created_at]}.first - return winner if not winner.nil? - # The queued container that's most likely to start sooner. - winner = candidates.select {|c| c.state == Queued}. - sort {|a, b| [b.priority, a.created_at] <=> [a.priority, b.created_at]}.first - return winner if not winner.nil? + where('runtime_constraints = ?', self.deep_sort_hash(attrs[:runtime_constraints]).to_yaml) + + # Check for Completed candidates that only had consistent outputs. + completed = candidates.where(state: Complete).where(exit_code: 0) + if completed.select("output").group('output').limit(2).length == 1 + return completed.order('finished_at asc').limit(1).first 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 - protected + def lock + with_lock do + if self.state == Locked + raise AlreadyLockedError + end + self.state = Locked + self.save! + end + end - def self.deep_sort_hash(x) - if x.is_a? Hash - x.sort.collect do |k, v| - [k, deep_sort_hash(v)] - end.to_h - elsif x.is_a? Array - x.collect { |v| deep_sort_hash(v) } - else - x + 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 + end + user_uuids = users_list.map { |u| u.uuid } + uuid_list = user_uuids + users_list.flat_map { |u| u.groups_i_can(:read) } + uuid_list.uniq! + permitted = "(SELECT head_uuid FROM links WHERE link_class='permission' AND tail_uuid IN (:uuids))" + joins(:container_requests). + where("container_requests.uuid IN #{permitted} OR "+ + "container_requests.owner_uuid IN (:uuids)", + uuids: uuid_list) + end + + protected + def fill_field_defaults self.state ||= Queued self.environment ||= {}