79c6e47fed235239d1635cb0296c1f696c2c9995
[arvados.git] / services / api / app / models / job.rb
1 class Job < ArvadosModel
2   include AssignUuid
3   include KindAndEtag
4   include CommonApiTemplate
5   serialize :script_parameters, Hash
6   serialize :resource_limits, Hash
7   serialize :tasks_summary, Hash
8   before_create :ensure_unique_submit_id
9   before_create :ensure_script_version_is_commit
10
11   has_many :commit_ancestors, :foreign_key => :descendant, :primary_key => :script_version
12
13   class SubmitIdReused < StandardError
14   end
15
16   api_accessible :superuser, :extend => :common do |t|
17     t.add :submit_id
18     t.add :priority
19     t.add :script
20     t.add :script_parameters
21     t.add :script_version
22     t.add :cancelled_at
23     t.add :cancelled_by_client
24     t.add :cancelled_by_user
25     t.add :started_at
26     t.add :finished_at
27     t.add :output
28     t.add :success
29     t.add :running
30     t.add :is_locked_by
31     t.add :log
32     t.add :resource_limits
33     t.add :tasks_summary
34     t.add :dependencies
35   end
36
37   def assert_finished
38     update_attributes(finished_at: finished_at || Time.now,
39                       success: success.nil? ? false : success,
40                       running: false)
41   end
42
43   def self.queue
44     self.where('started_at is ? and is_locked_by is ? and cancelled_at is ?',
45                nil, nil, nil).
46       order('priority desc, created_at')
47   end
48
49   protected
50
51   def ensure_script_version_is_commit
52     sha1 = Commit.find_by_commit_ish(self.script_version) rescue nil
53     if sha1
54       self.script_version = sha1
55     else
56       raise ArgumentError.new("Specified script_version does not resolve to a commit")
57     end
58   end
59
60   def ensure_unique_submit_id
61     if !submit_id.nil?
62       if Job.where('submit_id=?',self.submit_id).first
63         raise SubmitIdReused.new
64       end
65     end
66     true
67   end
68
69   def dependencies
70     deps = {}
71     self.script_parameters.values.each do |v|
72       next unless v.is_a? String
73       v.match(/^(([0-9a-f]{32})\b(\+[^,]+)?,?)*$/) do |locator|
74         bare_locator = locator[0].gsub(/\+[^,]+/,'')
75         deps[bare_locator] = true
76       end
77     end
78     deps.keys
79   end
80
81   def permission_to_update
82     if is_locked_by_was and !(current_user and
83                               current_user.uuid == is_locked_by_was)
84       if script_changed? or
85           script_parameters_changed? or
86           script_version_changed? or
87           cancelled_by_client_changed? or
88           cancelled_by_user_changed? or
89           cancelled_at_changed? or
90           started_at_changed? or
91           finished_at_changed? or
92           running_changed? or
93           success_changed? or
94           output_changed? or
95           log_changed? or
96           tasks_summary_changed?
97         logger.warn "User #{current_user.uuid if current_user} tried to change protected job attributes on locked #{self.class.to_s} #{uuid_was}"
98         return false
99       end
100     end
101     if !is_locked_by_changed?
102       super
103     else
104       if !current_user
105         logger.warn "Anonymous user tried to change lock on #{self.class.to_s} #{uuid_was}"
106         false
107       elsif is_locked_by_was and is_locked_by_was != current_user.uuid
108         logger.warn "User #{current_user.uuid} tried to steal lock on #{self.class.to_s} #{uuid_was} from #{is_locked_by_was}"
109         false
110       elsif !is_locked_by.nil? and is_locked_by != current_user.uuid
111         logger.warn "User #{current_user.uuid} tried to lock #{self.class.to_s} #{uuid_was} with uuid #{is_locked_by}"
112         false
113       else
114         super
115       end
116     end
117   end
118 end