13773: Show 'Warning' label on jobs that have this runtime status and aren't failing.
[arvados.git] / services / api / test / unit / container_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6 require 'helpers/container_test_helper'
7
8 class ContainerTest < ActiveSupport::TestCase
9   include DbCurrentTime
10   include ContainerTestHelper
11
12   DEFAULT_ATTRS = {
13     command: ['echo', 'foo'],
14     container_image: 'fa3c1a9cb6783f85f2ecda037e07b8c3+167',
15     output_path: '/tmp',
16     priority: 1,
17     runtime_constraints: {"vcpus" => 1, "ram" => 1},
18   }
19
20   REUSABLE_COMMON_ATTRS = {
21     container_image: "9ae44d5792468c58bcf85ce7353c7027+124",
22     cwd: "test",
23     command: ["echo", "hello"],
24     output_path: "test",
25     runtime_constraints: {
26       "ram" => 12000000000,
27       "vcpus" => 4,
28     },
29     mounts: {
30       "test" => {"kind" => "json"},
31     },
32     environment: {
33       "var" => "val",
34     },
35     secret_mounts: {},
36   }
37
38   def minimal_new attrs={}
39     cr = ContainerRequest.new DEFAULT_ATTRS.merge(attrs)
40     cr.state = ContainerRequest::Committed
41     act_as_user users(:active) do
42       cr.save!
43     end
44     c = Container.find_by_uuid cr.container_uuid
45     assert_not_nil c
46     return c, cr
47   end
48
49   def check_illegal_updates c, bad_updates
50     bad_updates.each do |u|
51       refute c.update_attributes(u), u.inspect
52       refute c.valid?, u.inspect
53       c.reload
54     end
55   end
56
57   def check_illegal_modify c
58     check_illegal_updates c, [{command: ["echo", "bar"]},
59                               {container_image: "arvados/apitestfixture:june10"},
60                               {cwd: "/tmp2"},
61                               {environment: {"FOO" => "BAR"}},
62                               {mounts: {"FOO" => "BAR"}},
63                               {output_path: "/tmp3"},
64                               {locked_by_uuid: "zzzzz-gj3su-027z32aux8dg2s1"},
65                               {auth_uuid: "zzzzz-gj3su-017z32aux8dg2s1"},
66                               {runtime_constraints: {"FOO" => "BAR"}}]
67   end
68
69   def check_bogus_states c
70     check_illegal_updates c, [{state: nil},
71                               {state: "Flubber"}]
72   end
73
74   def check_no_change_from_cancelled c
75     check_illegal_modify c
76     check_bogus_states c
77     check_illegal_updates c, [{ priority: 3 },
78                               { state: Container::Queued },
79                               { state: Container::Locked },
80                               { state: Container::Running },
81                               { state: Container::Complete }]
82   end
83
84   test "Container create" do
85     act_as_system_user do
86       c, _ = minimal_new(environment: {},
87                       mounts: {"BAR" => {"kind" => "FOO"}},
88                       output_path: "/tmp",
89                       priority: 1,
90                       runtime_constraints: {"vcpus" => 1, "ram" => 1})
91
92       check_illegal_modify c
93       check_bogus_states c
94
95       c.reload
96       c.priority = 2
97       c.save!
98     end
99   end
100
101   test "Container valid priority" do
102     act_as_system_user do
103       c, _ = minimal_new(environment: {},
104                       mounts: {"BAR" => {"kind" => "FOO"}},
105                       output_path: "/tmp",
106                       priority: 1,
107                       runtime_constraints: {"vcpus" => 1, "ram" => 1})
108
109       assert_raises(ActiveRecord::RecordInvalid) do
110         c.priority = -1
111         c.save!
112       end
113
114       c.priority = 0
115       c.save!
116
117       c.priority = 1
118       c.save!
119
120       c.priority = 500
121       c.save!
122
123       c.priority = 999
124       c.save!
125
126       c.priority = 1000
127       c.save!
128
129       c.priority = 1000 << 50
130       c.save!
131     end
132   end
133
134   test "Container runtime_status updates" do
135     set_user_from_auth :active
136     attrs = {
137       environment: {},
138       mounts: {"BAR" => {"kind" => "FOO"}},
139       output_path: "/tmp",
140       priority: 1,
141       runtime_constraints: {"vcpus" => 1, "ram" => 1}
142     }
143     c1, _ = minimal_new(attrs)
144     assert_equal c1.runtime_status, {}
145
146     assert_equal Container::Queued, c1.state
147     assert_raises ActiveRecord::RecordInvalid do
148       c1.update_attributes! runtime_status: {'error' => 'Oops!'}
149     end
150
151     set_user_from_auth :dispatch1
152
153     # Allow updates when state = Locked
154     c1.update_attributes! state: Container::Locked
155     c1.update_attributes! runtime_status: {'error' => 'Oops!'}
156     assert c1.runtime_status.key? 'error'
157
158     # Reset when transitioning from Locked to Queued
159     c1.update_attributes! state: Container::Queued
160     assert_equal c1.runtime_status, {}
161
162     # Allow updates when state = Running
163     c1.update_attributes! state: Container::Locked
164     c1.update_attributes! state: Container::Running
165     c1.update_attributes! runtime_status: {'error' => 'Oops!'}
166     assert c1.runtime_status.key? 'error'
167
168     # Don't allow updates on other states
169     c1.update_attributes! state: Container::Complete
170     assert_raises ActiveRecord::RecordInvalid do
171       c1.update_attributes! runtime_status: {'error' => 'Some other error'}
172     end
173
174     set_user_from_auth :active
175     c2, _ = minimal_new(attrs)
176     assert_equal c2.runtime_status, {}
177     set_user_from_auth :dispatch1
178     c2.update_attributes! state: Container::Locked
179     c2.update_attributes! state: Container::Running
180     c2.update_attributes! state: Container::Cancelled
181     assert_raises ActiveRecord::RecordInvalid do
182       c2.update_attributes! runtime_status: {'error' => 'Oops!'}
183     end
184   end
185
186   test "Container serialized hash attributes sorted before save" do
187     env = {"C" => "3", "B" => "2", "A" => "1"}
188     m = {"F" => {"kind" => "3"}, "E" => {"kind" => "2"}, "D" => {"kind" => "1"}}
189     rc = {"vcpus" => 1, "ram" => 1, "keep_cache_ram" => 1}
190     c, _ = minimal_new(environment: env, mounts: m, runtime_constraints: rc)
191     assert_equal c.environment.to_json, Container.deep_sort_hash(env).to_json
192     assert_equal c.mounts.to_json, Container.deep_sort_hash(m).to_json
193     assert_equal c.runtime_constraints.to_json, Container.deep_sort_hash(rc).to_json
194   end
195
196   test 'deep_sort_hash on array of hashes' do
197     a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]}
198     b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]}
199     assert_equal Container.deep_sort_hash(a).to_json, Container.deep_sort_hash(b).to_json
200   end
201
202   test "find_reusable method should select higher priority queued container" do
203     set_user_from_auth :active
204     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment:{"var" => "queued"}})
205     c_low_priority, _ = minimal_new(common_attrs.merge({use_existing:false, priority:1}))
206     c_high_priority, _ = minimal_new(common_attrs.merge({use_existing:false, priority:2}))
207     assert_not_equal c_low_priority.uuid, c_high_priority.uuid
208     assert_equal Container::Queued, c_low_priority.state
209     assert_equal Container::Queued, c_high_priority.state
210     reused = Container.find_reusable(common_attrs)
211     assert_not_nil reused
212     assert_equal reused.uuid, c_high_priority.uuid
213   end
214
215   test "find_reusable method should select latest completed container" do
216     set_user_from_auth :active
217     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}})
218     completed_attrs = {
219       state: Container::Complete,
220       exit_code: 0,
221       log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
222       output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
223     }
224
225     c_older, _ = minimal_new(common_attrs.merge({use_existing: false}))
226     c_recent, _ = minimal_new(common_attrs.merge({use_existing: false}))
227     assert_not_equal c_older.uuid, c_recent.uuid
228
229     set_user_from_auth :dispatch1
230     c_older.update_attributes!({state: Container::Locked})
231     c_older.update_attributes!({state: Container::Running})
232     c_older.update_attributes!(completed_attrs)
233
234     c_recent.update_attributes!({state: Container::Locked})
235     c_recent.update_attributes!({state: Container::Running})
236     c_recent.update_attributes!(completed_attrs)
237
238     reused = Container.find_reusable(common_attrs)
239     assert_not_nil reused
240     assert_equal reused.uuid, c_older.uuid
241   end
242
243   test "find_reusable method should select oldest completed container when inconsistent outputs exist" do
244     set_user_from_auth :active
245     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}, priority: 1})
246     completed_attrs = {
247       state: Container::Complete,
248       exit_code: 0,
249       log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
250     }
251
252     cr = ContainerRequest.new common_attrs
253     cr.use_existing = false
254     cr.state = ContainerRequest::Committed
255     cr.save!
256     c_output1 = Container.where(uuid: cr.container_uuid).first
257
258     cr = ContainerRequest.new common_attrs
259     cr.use_existing = false
260     cr.state = ContainerRequest::Committed
261     cr.save!
262     c_output2 = Container.where(uuid: cr.container_uuid).first
263
264     assert_not_equal c_output1.uuid, c_output2.uuid
265
266     set_user_from_auth :dispatch1
267
268     out1 = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
269     log1 = collections(:real_log_collection).portable_data_hash
270     c_output1.update_attributes!({state: Container::Locked})
271     c_output1.update_attributes!({state: Container::Running})
272     c_output1.update_attributes!(completed_attrs.merge({log: log1, output: out1}))
273
274     out2 = 'fa7aeb5140e2848d39b416daeef4ffc5+45'
275     c_output2.update_attributes!({state: Container::Locked})
276     c_output2.update_attributes!({state: Container::Running})
277     c_output2.update_attributes!(completed_attrs.merge({log: log1, output: out2}))
278
279     reused = Container.resolve(ContainerRequest.new(common_attrs))
280     assert_equal c_output1.uuid, reused.uuid
281   end
282
283   test "find_reusable method should select running container by start date" do
284     set_user_from_auth :active
285     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running"}})
286     c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
287     c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
288     c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
289     # Confirm the 3 container UUIDs are different.
290     assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
291     set_user_from_auth :dispatch1
292     c_slower.update_attributes!({state: Container::Locked})
293     c_slower.update_attributes!({state: Container::Running,
294                                  progress: 0.1})
295     c_faster_started_first.update_attributes!({state: Container::Locked})
296     c_faster_started_first.update_attributes!({state: Container::Running,
297                                                progress: 0.15})
298     c_faster_started_second.update_attributes!({state: Container::Locked})
299     c_faster_started_second.update_attributes!({state: Container::Running,
300                                                 progress: 0.15})
301     reused = Container.find_reusable(common_attrs)
302     assert_not_nil reused
303     # Selected container is the one that started first
304     assert_equal reused.uuid, c_faster_started_first.uuid
305   end
306
307   test "find_reusable method should select running container by progress" do
308     set_user_from_auth :active
309     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running2"}})
310     c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
311     c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
312     c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
313     # Confirm the 3 container UUIDs are different.
314     assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
315     set_user_from_auth :dispatch1
316     c_slower.update_attributes!({state: Container::Locked})
317     c_slower.update_attributes!({state: Container::Running,
318                                  progress: 0.1})
319     c_faster_started_first.update_attributes!({state: Container::Locked})
320     c_faster_started_first.update_attributes!({state: Container::Running,
321                                                progress: 0.15})
322     c_faster_started_second.update_attributes!({state: Container::Locked})
323     c_faster_started_second.update_attributes!({state: Container::Running,
324                                                 progress: 0.2})
325     reused = Container.find_reusable(common_attrs)
326     assert_not_nil reused
327     # Selected container is the one with most progress done
328     assert_equal reused.uuid, c_faster_started_second.uuid
329   end
330
331   test "find_reusable method should select non-failing running container" do
332     set_user_from_auth :active
333     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running2"}})
334     c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
335     c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
336     c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
337     # Confirm the 3 container UUIDs are different.
338     assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
339     set_user_from_auth :dispatch1
340     c_slower.update_attributes!({state: Container::Locked})
341     c_slower.update_attributes!({state: Container::Running,
342                                  progress: 0.1})
343     c_faster_started_first.update_attributes!({state: Container::Locked})
344     c_faster_started_first.update_attributes!({state: Container::Running,
345                                                runtime_status: {'warning' => 'This is not an error'},
346                                                progress: 0.15})
347     c_faster_started_second.update_attributes!({state: Container::Locked})
348     c_faster_started_second.update_attributes!({state: Container::Running,
349                                                 runtime_status: {'error' => 'Something bad happened'},
350                                                 progress: 0.2})
351     reused = Container.find_reusable(common_attrs)
352     assert_not_nil reused
353     # Selected the non-failing container even if it's the one with less progress done
354     assert_equal reused.uuid, c_faster_started_first.uuid
355   end
356
357   test "find_reusable method should select locked container most likely to start sooner" do
358     set_user_from_auth :active
359     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "locked"}})
360     c_low_priority, _ = minimal_new(common_attrs.merge({use_existing: false}))
361     c_high_priority_older, _ = minimal_new(common_attrs.merge({use_existing: false}))
362     c_high_priority_newer, _ = minimal_new(common_attrs.merge({use_existing: false}))
363     # Confirm the 3 container UUIDs are different.
364     assert_equal 3, [c_low_priority.uuid, c_high_priority_older.uuid, c_high_priority_newer.uuid].uniq.length
365     set_user_from_auth :dispatch1
366     c_low_priority.update_attributes!({state: Container::Locked,
367                                        priority: 1})
368     c_high_priority_older.update_attributes!({state: Container::Locked,
369                                               priority: 2})
370     c_high_priority_newer.update_attributes!({state: Container::Locked,
371                                               priority: 2})
372     reused = Container.find_reusable(common_attrs)
373     assert_not_nil reused
374     assert_equal reused.uuid, c_high_priority_older.uuid
375   end
376
377   test "find_reusable method should select running over failed container" do
378     set_user_from_auth :active
379     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed_vs_running"}})
380     c_failed, _ = minimal_new(common_attrs.merge({use_existing: false}))
381     c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
382     assert_not_equal c_failed.uuid, c_running.uuid
383     set_user_from_auth :dispatch1
384     c_failed.update_attributes!({state: Container::Locked})
385     c_failed.update_attributes!({state: Container::Running})
386     c_failed.update_attributes!({state: Container::Complete,
387                                  exit_code: 42,
388                                  log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
389                                  output: 'ea10d51bcf88862dbcc36eb292017dfd+45'})
390     c_running.update_attributes!({state: Container::Locked})
391     c_running.update_attributes!({state: Container::Running,
392                                   progress: 0.15})
393     reused = Container.find_reusable(common_attrs)
394     assert_not_nil reused
395     assert_equal reused.uuid, c_running.uuid
396   end
397
398   test "find_reusable method should select complete over running container" do
399     set_user_from_auth :active
400     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "completed_vs_running"}})
401     c_completed, _ = minimal_new(common_attrs.merge({use_existing: false}))
402     c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
403     assert_not_equal c_completed.uuid, c_running.uuid
404     set_user_from_auth :dispatch1
405     c_completed.update_attributes!({state: Container::Locked})
406     c_completed.update_attributes!({state: Container::Running})
407     c_completed.update_attributes!({state: Container::Complete,
408                                     exit_code: 0,
409                                     log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
410                                     output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'})
411     c_running.update_attributes!({state: Container::Locked})
412     c_running.update_attributes!({state: Container::Running,
413                                   progress: 0.15})
414     reused = Container.find_reusable(common_attrs)
415     assert_not_nil reused
416     assert_equal c_completed.uuid, reused.uuid
417   end
418
419   test "find_reusable method should select running over locked container" do
420     set_user_from_auth :active
421     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
422     c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
423     c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
424     assert_not_equal c_running.uuid, c_locked.uuid
425     set_user_from_auth :dispatch1
426     c_locked.update_attributes!({state: Container::Locked})
427     c_running.update_attributes!({state: Container::Locked})
428     c_running.update_attributes!({state: Container::Running,
429                                   progress: 0.15})
430     reused = Container.find_reusable(common_attrs)
431     assert_not_nil reused
432     assert_equal reused.uuid, c_running.uuid
433   end
434
435   test "find_reusable method should select locked over queued container" do
436     set_user_from_auth :active
437     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
438     c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
439     c_queued, _ = minimal_new(common_attrs.merge({use_existing: false}))
440     assert_not_equal c_queued.uuid, c_locked.uuid
441     set_user_from_auth :dispatch1
442     c_locked.update_attributes!({state: Container::Locked})
443     reused = Container.find_reusable(common_attrs)
444     assert_not_nil reused
445     assert_equal reused.uuid, c_locked.uuid
446   end
447
448   test "find_reusable method should not select failed container" do
449     set_user_from_auth :active
450     attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed"}})
451     c, _ = minimal_new(attrs)
452     set_user_from_auth :dispatch1
453     c.update_attributes!({state: Container::Locked})
454     c.update_attributes!({state: Container::Running})
455     c.update_attributes!({state: Container::Complete,
456                           exit_code: 33})
457     reused = Container.find_reusable(attrs)
458     assert_nil reused
459   end
460
461   test "find_reusable with logging disabled" do
462     set_user_from_auth :active
463     Rails.logger.expects(:info).never
464     Container.find_reusable(REUSABLE_COMMON_ATTRS)
465   end
466
467   test "find_reusable with logging enabled" do
468     set_user_from_auth :active
469     Rails.configuration.log_reuse_decisions = true
470     Rails.logger.expects(:info).at_least(3)
471     Container.find_reusable(REUSABLE_COMMON_ATTRS)
472   end
473
474   test "Container running" do
475     c, _ = minimal_new priority: 1
476
477     set_user_from_auth :dispatch1
478     check_illegal_updates c, [{state: Container::Running},
479                               {state: Container::Complete}]
480
481     c.lock
482     c.update_attributes! state: Container::Running
483
484     check_illegal_modify c
485     check_bogus_states c
486
487     check_illegal_updates c, [{state: Container::Queued}]
488     c.reload
489
490     c.update_attributes! priority: 3
491   end
492
493   test "Lock and unlock" do
494     c, cr = minimal_new priority: 0
495
496     set_user_from_auth :dispatch1
497     assert_equal Container::Queued, c.state
498
499     assert_raise(ArvadosModel::LockFailedError) do
500       # "no priority"
501       c.lock
502     end
503     c.reload
504     assert cr.update_attributes priority: 1
505
506     refute c.update_attributes(state: Container::Running), "not locked"
507     c.reload
508     refute c.update_attributes(state: Container::Complete), "not locked"
509     c.reload
510
511     assert c.lock, show_errors(c)
512     assert c.locked_by_uuid
513     assert c.auth_uuid
514
515     assert_raise(ArvadosModel::LockFailedError) {c.lock}
516     c.reload
517
518     assert c.unlock, show_errors(c)
519     refute c.locked_by_uuid
520     refute c.auth_uuid
521
522     refute c.update_attributes(state: Container::Running), "not locked"
523     c.reload
524     refute c.locked_by_uuid
525     refute c.auth_uuid
526
527     assert c.lock, show_errors(c)
528     assert c.update_attributes(state: Container::Running), show_errors(c)
529     assert c.locked_by_uuid
530     assert c.auth_uuid
531
532     auth_uuid_was = c.auth_uuid
533
534     assert_raise(ArvadosModel::LockFailedError) do
535       # Running to Locked is not allowed
536       c.lock
537     end
538     c.reload
539     assert_raise(ArvadosModel::InvalidStateTransitionError) do
540       # Running to Queued is not allowed
541       c.unlock
542     end
543     c.reload
544
545     assert c.update_attributes(state: Container::Complete), show_errors(c)
546     refute c.locked_by_uuid
547     refute c.auth_uuid
548
549     auth_exp = ApiClientAuthorization.find_by_uuid(auth_uuid_was).expires_at
550     assert_operator auth_exp, :<, db_current_time
551   end
552
553   test "Container queued cancel" do
554     c, cr = minimal_new({container_count_max: 1})
555     set_user_from_auth :dispatch1
556     assert c.update_attributes(state: Container::Cancelled), show_errors(c)
557     check_no_change_from_cancelled c
558     cr.reload
559     assert_equal ContainerRequest::Final, cr.state
560   end
561
562   test "Container queued count" do
563     assert_equal 1, Container.readable_by(users(:active)).where(state: "Queued").count
564   end
565
566   test "Container locked cancel" do
567     c, _ = minimal_new
568     set_user_from_auth :dispatch1
569     assert c.lock, show_errors(c)
570     assert c.update_attributes(state: Container::Cancelled), show_errors(c)
571     check_no_change_from_cancelled c
572   end
573
574   test "Container locked cancel with log" do
575     c, _ = minimal_new
576     set_user_from_auth :dispatch1
577     assert c.lock, show_errors(c)
578     assert c.update_attributes(
579              state: Container::Cancelled,
580              log: collections(:real_log_collection).portable_data_hash,
581            ), show_errors(c)
582     check_no_change_from_cancelled c
583   end
584
585   test "Container running cancel" do
586     c, _ = minimal_new
587     set_user_from_auth :dispatch1
588     c.lock
589     c.update_attributes! state: Container::Running
590     c.update_attributes! state: Container::Cancelled
591     check_no_change_from_cancelled c
592   end
593
594   test "Container create forbidden for non-admin" do
595     set_user_from_auth :active_trustedclient
596     c = Container.new DEFAULT_ATTRS
597     c.environment = {}
598     c.mounts = {"BAR" => "FOO"}
599     c.output_path = "/tmp"
600     c.priority = 1
601     c.runtime_constraints = {}
602     assert_raises(ArvadosModel::PermissionDeniedError) do
603       c.save!
604     end
605   end
606
607   test "Container only set exit code on complete" do
608     c, _ = minimal_new
609     set_user_from_auth :dispatch1
610     c.lock
611     c.update_attributes! state: Container::Running
612
613     check_illegal_updates c, [{exit_code: 1},
614                               {exit_code: 1, state: Container::Cancelled}]
615
616     assert c.update_attributes(exit_code: 1, state: Container::Complete)
617   end
618
619   test "locked_by_uuid can set output on running container" do
620     c, _ = minimal_new
621     set_user_from_auth :dispatch1
622     c.lock
623     c.update_attributes! state: Container::Running
624
625     assert_equal c.locked_by_uuid, Thread.current[:api_client_authorization].uuid
626
627     assert c.update_attributes output: collections(:collection_owned_by_active).portable_data_hash
628     assert c.update_attributes! state: Container::Complete
629   end
630
631   test "auth_uuid can set output on running container, but not change container state" do
632     c, _ = minimal_new
633     set_user_from_auth :dispatch1
634     c.lock
635     c.update_attributes! state: Container::Running
636
637     Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
638     Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
639     assert c.update_attributes output: collections(:collection_owned_by_active).portable_data_hash
640
641     assert_raises ArvadosModel::PermissionDeniedError do
642       # auth_uuid cannot set container state
643       c.update_attributes state: Container::Complete
644     end
645   end
646
647   test "not allowed to set output that is not readable by current user" do
648     c, _ = minimal_new
649     set_user_from_auth :dispatch1
650     c.lock
651     c.update_attributes! state: Container::Running
652
653     Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
654     Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
655
656     assert_raises ActiveRecord::RecordInvalid do
657       c.update_attributes! output: collections(:collection_not_readable_by_active).portable_data_hash
658     end
659   end
660
661   test "other token cannot set output on running container" do
662     c, _ = minimal_new
663     set_user_from_auth :dispatch1
664     c.lock
665     c.update_attributes! state: Container::Running
666
667     set_user_from_auth :running_to_be_deleted_container_auth
668     assert_raises ArvadosModel::PermissionDeniedError do
669       c.update_attributes! output: collections(:foo_file).portable_data_hash
670     end
671   end
672
673   test "can set trashed output on running container" do
674     c, _ = minimal_new
675     set_user_from_auth :dispatch1
676     c.lock
677     c.update_attributes! state: Container::Running
678
679     output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jk')
680
681     assert output.is_trashed
682     assert c.update_attributes output: output.portable_data_hash
683     assert c.update_attributes! state: Container::Complete
684   end
685
686   test "not allowed to set trashed output that is not readable by current user" do
687     c, _ = minimal_new
688     set_user_from_auth :dispatch1
689     c.lock
690     c.update_attributes! state: Container::Running
691
692     output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jr')
693
694     Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
695     Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
696
697     assert_raises ActiveRecord::RecordInvalid do
698       c.update_attributes! output: output.portable_data_hash
699     end
700   end
701
702   [
703     {state: Container::Complete, exit_code: 0, output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'},
704     {state: Container::Cancelled},
705   ].each do |final_attrs|
706     test "secret_mounts is null after container is #{final_attrs[:state]}" do
707       c, cr = minimal_new(secret_mounts: {'/secret' => {'kind' => 'text', 'content' => 'foo'}},
708                           container_count_max: 1)
709       set_user_from_auth :dispatch1
710       c.lock
711       c.update_attributes!(state: Container::Running)
712       c.reload
713       assert c.secret_mounts.has_key?('/secret')
714
715       c.update_attributes!(final_attrs)
716       c.reload
717       assert_equal({}, c.secret_mounts)
718       cr.reload
719       assert_equal({}, cr.secret_mounts)
720       assert_no_secrets_logged
721     end
722   end
723 end