6353132e908baa3d683ec0a9d320ff3a60d55804
[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   def container_completed!
66     # may implement retry logic here in the future.
67     self.state = ContainerRequest::Final
68     self.save!
69   end
70
71   protected
72
73   def fill_field_defaults
74     self.state ||= Uncommitted
75     self.environment ||= {}
76     self.runtime_constraints ||= {}
77     self.mounts ||= {}
78     self.cwd ||= "."
79   end
80
81   # Create a new container (or find an existing one) to satisfy this
82   # request.
83   def resolve
84     # TODO: resolve symbolic git and keep references to content
85     # addresses.
86     c = act_as_system_user do
87       Container.create!(command: self.command,
88                         container_image: self.container_image,
89                         cwd: self.cwd,
90                         environment: self.environment,
91                         mounts: self.mounts,
92                         output_path: self.output_path,
93                         runtime_constraints: self.runtime_constraints)
94     end
95     self.container_uuid = c.uuid
96   end
97
98   def set_container
99     if (container_uuid_changed? and
100         not current_user.andand.is_admin and
101         not container_uuid.nil?)
102       errors.add :container_uuid, "can only be updated to nil."
103       return false
104     end
105     if state_changed? and state == Committed and container_uuid.nil?
106       resolve
107     end
108   end
109
110   def validate_change
111     permitted = [:owner_uuid]
112
113     case self.state
114     when Uncommitted
115       # Permit updating most fields
116       permitted.push :command, :container_count_max,
117                      :container_image, :cwd, :description, :environment,
118                      :filters, :mounts, :name, :output_path, :priority,
119                      :properties, :requesting_container_uuid, :runtime_constraints,
120                      :state, :container_uuid
121
122     when Committed
123       if container_uuid.nil?
124         errors.add :container_uuid, "has not been resolved to a container."
125       end
126
127       if priority.nil?
128         errors.add :priority, "cannot be nil"
129       end
130
131       # Can update priority, container count.
132       permitted.push :priority, :container_count_max, :container_uuid
133
134       if self.state_changed?
135         # Allow create-and-commit in a single operation.
136         permitted.push :command, :container_image, :cwd, :description, :environment,
137                        :filters, :mounts, :name, :output_path, :properties,
138                        :requesting_container_uuid, :runtime_constraints,
139                        :state, :container_uuid
140       end
141
142     when Final
143       if not current_user.andand.is_admin
144         errors.add :state, "of container request can only be set to Final by system."
145       end
146
147       if self.state_changed?
148           permitted.push :state
149       else
150         errors.add :state, "does not allow updates"
151       end
152
153     else
154       errors.add :state, "invalid value"
155     end
156
157     check_update_whitelist permitted
158   end
159
160   def update_priority
161     if self.state_changed? or
162         self.priority_changed? or
163         self.container_uuid_changed?
164       act_as_system_user do
165         Container.
166           where('uuid in (?)',
167                 [self.container_uuid_was, self.container_uuid].compact).
168           map(&:update_priority!)
169       end
170     end
171   end
172
173 end