6429: Complete/Cancelled containers finalize associated container requests;
[arvados.git] / services / api / app / models / container_request.rb
1 require 'whitelist_update'
2
3 class ContainerRequest < ArvadosModel
4   include HasUuid
5   include KindAndEtag
6   include CommonApiTemplate
7   include WhitelistUpdate
8
9   serialize :properties, Hash
10   serialize :environment, Hash
11   serialize :mounts, Hash
12   serialize :runtime_constraints, Hash
13   serialize :command, Array
14
15   before_validation :fill_field_defaults, :if => :new_record?
16   before_validation :set_container
17   validates :command, :container_image, :output_path, :cwd, :presence => true
18   validate :validate_state_change
19   validate :validate_change
20   after_save :update_priority
21
22   api_accessible :user, extend: :common do |t|
23     t.add :command
24     t.add :container_count_max
25     t.add :container_image
26     t.add :container_uuid
27     t.add :cwd
28     t.add :description
29     t.add :environment
30     t.add :expires_at
31     t.add :filters
32     t.add :mounts
33     t.add :name
34     t.add :output_path
35     t.add :priority
36     t.add :properties
37     t.add :requesting_container_uuid
38     t.add :runtime_constraints
39     t.add :state
40   end
41
42   # Supported states for a container request
43   States =
44     [
45      (Uncommitted = 'Uncommitted'),
46      (Committed = 'Committed'),
47      (Final = 'Final'),
48     ]
49
50   State_transitions = {
51     nil => [Uncommitted, Committed],
52     Uncommitted => [Committed],
53     Committed => [Final]
54   }
55
56   def state_transitions
57     State_transitions
58   end
59
60   def skip_uuid_read_permission_check
61     # XXX temporary until permissions are sorted out.
62     %w(modified_by_client_uuid container_uuid requesting_container_uuid)
63   end
64
65   protected
66
67   def fill_field_defaults
68     self.state ||= Uncommitted
69     self.environment ||= {}
70     self.runtime_constraints ||= {}
71     self.mounts ||= {}
72     self.cwd ||= "."
73     self.priority ||= 1
74   end
75
76   # Turn a container request into a container.
77   def resolve
78     # In the future this will do things like resolve symbolic git and keep
79     # references to content addresses.
80     Container.create!({ :command => self.command,
81                         :container_image => self.container_image,
82                         :cwd => self.cwd,
83                         :environment => self.environment,
84                         :mounts => self.mounts,
85                         :output_path => self.output_path,
86                         :runtime_constraints => self.runtime_constraints })
87   end
88
89   def set_container
90     if self.container_uuid_changed?
91       if not current_user.andand.is_admin and not self.container_uuid.nil?
92         errors.add :container_uuid, "can only be updated to nil."
93       end
94     else
95       if self.state_changed?
96         if self.state == Committed and (self.state_was == Uncommitted or self.state_was.nil?)
97           act_as_system_user do
98             self.container_uuid = self.resolve.andand.uuid
99           end
100         end
101       end
102     end
103   end
104
105   def validate_change
106     permitted = [:owner_uuid]
107
108     case self.state
109     when Uncommitted
110       # Permit updating most fields
111       permitted.push :command, :container_count_max,
112                      :container_image, :cwd, :description, :environment,
113                      :filters, :mounts, :name, :output_path, :priority,
114                      :properties, :requesting_container_uuid, :runtime_constraints,
115                      :state, :container_uuid
116
117     when Committed
118       if container_uuid.nil?
119         errors.add :container_uuid, "has not been resolved to a container."
120       end
121
122       # Can update priority, container count.
123       permitted.push :priority, :container_count_max, :container_uuid
124
125       if self.state_changed?
126         # Allow create-and-commit in a single operation.
127         permitted.push :command, :container_image, :cwd, :description, :environment,
128                        :filters, :mounts, :name, :output_path, :properties,
129                        :requesting_container_uuid, :runtime_constraints,
130                        :state, :container_uuid
131       end
132
133     when Final
134       if self.state_changed?
135           permitted.push :state
136       else
137         errors.add :state, "does not allow updates"
138       end
139
140     else
141       errors.add :state, "invalid value"
142     end
143
144     check_update_whitelist permitted
145   end
146
147   def update_priority
148     if [Committed, Final].include? self.state and (self.state_changed? or
149                                                    self.priority_changed? or
150                                                    self.container_uuid_changed?)
151       [self.container_uuid_was, self.container_uuid].each do |cuuid|
152         unless cuuid.nil?
153           c = Container.find_by_uuid cuuid
154           act_as_system_user do
155             c.update_priority!
156           end
157         end
158       end
159     end
160   end
161
162 end