8123: Fix crunchstat formatting error and resulting crunchstat-summary crash.
[arvados.git] / services / api / app / models / container.rb
1 require 'whitelist_update'
2
3 class Container < ArvadosModel
4   include HasUuid
5   include KindAndEtag
6   include CommonApiTemplate
7   include WhitelistUpdate
8
9   serialize :environment, Hash
10   serialize :mounts, Hash
11   serialize :runtime_constraints, Hash
12   serialize :command, Array
13
14   before_validation :fill_field_defaults, :if => :new_record?
15   before_validation :set_timestamps
16   validates :command, :container_image, :output_path, :cwd, :priority, :presence => true
17   validate :validate_state_change
18   validate :validate_change
19   after_save :handle_completed
20
21   has_many :container_requests, :foreign_key => :container_uuid, :class_name => 'ContainerRequest', :primary_key => :uuid
22
23   api_accessible :user, extend: :common do |t|
24     t.add :command
25     t.add :container_image
26     t.add :cwd
27     t.add :environment
28     t.add :exit_code
29     t.add :finished_at
30     t.add :log
31     t.add :mounts
32     t.add :output
33     t.add :output_path
34     t.add :priority
35     t.add :progress
36     t.add :runtime_constraints
37     t.add :started_at
38     t.add :state
39   end
40
41   # Supported states for a container
42   States =
43     [
44      (Queued = 'Queued'),
45      (Running = 'Running'),
46      (Complete = 'Complete'),
47      (Cancelled = 'Cancelled')
48     ]
49
50   State_transitions = {
51     nil => [Queued],
52     Queued => [Running, Cancelled],
53     Running => [Complete, Cancelled]
54   }
55
56   def state_transitions
57     State_transitions
58   end
59
60   def update_priority!
61     if [Queued, Running].include? self.state
62       # Update the priority of this container to the maximum priority of any of
63       # its committed container requests and save the record.
64       max = 0
65       ContainerRequest.where(container_uuid: uuid).each do |cr|
66         if cr.state == ContainerRequest::Committed and cr.priority > max
67           max = cr.priority
68         end
69       end
70       self.priority = max
71       self.save!
72     end
73   end
74
75   protected
76
77   def fill_field_defaults
78     self.state ||= Queued
79     self.environment ||= {}
80     self.runtime_constraints ||= {}
81     self.mounts ||= {}
82     self.cwd ||= "."
83     self.priority ||= 1
84   end
85
86   def permission_to_create
87     current_user.andand.is_admin
88   end
89
90   def permission_to_update
91     current_user.andand.is_admin
92   end
93
94   def set_timestamps
95     if self.state_changed? and self.state == Running
96       self.started_at ||= db_current_time
97     end
98
99     if self.state_changed? and [Complete, Cancelled].include? self.state
100       self.finished_at ||= db_current_time
101     end
102   end
103
104   def validate_change
105     permitted = []
106
107     if self.new_record?
108       permitted.push :owner_uuid, :command, :container_image, :cwd, :environment,
109                      :mounts, :output_path, :priority, :runtime_constraints, :state
110     end
111
112     case self.state
113     when Queued
114       # permit priority change only.
115       permitted.push :priority
116
117     when Running
118       if self.state_changed?
119         # At point of state change, can set state and started_at
120         permitted.push :state, :started_at
121       else
122         # While running, can update priority and progress.
123         permitted.push :priority, :progress
124       end
125
126     when Complete
127       if self.state_changed?
128         permitted.push :state, :finished_at, :output, :log, :exit_code
129       else
130         errors.add :state, "cannot update record"
131       end
132
133     when Cancelled
134       if self.state_changed?
135         if self.state_was == Running
136           permitted.push :state, :finished_at, :output, :log
137         elsif self.state_was == Queued
138           permitted.push :state, :finished_at
139         end
140       else
141         errors.add :state, "cannot update record"
142       end
143
144     else
145       errors.add :state, "invalid state"
146     end
147
148     check_update_whitelist permitted
149   end
150
151   def handle_completed
152     # This container is finished so finalize any associated container requests
153     # that are associated with this container.
154     if self.state_changed? and [Complete, Cancelled].include? self.state
155       act_as_system_user do
156         # Notify container requests associated with this container
157         ContainerRequest.where(container_uuid: uuid,
158                                :state => ContainerRequest::Committed).each do |cr|
159           cr.container_completed!
160         end
161
162         # Try to cancel any outstanding container requests made by this container.
163         ContainerRequest.where(requesting_container_uuid: uuid,
164                                :state => ContainerRequest::Committed).each do |cr|
165           cr.priority = 0
166           cr.save
167         end
168       end
169     end
170   end
171
172 end