13773: Fix reuse query & add tests
[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" => "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                                                progress: 0.15})
346     c_faster_started_second.update_attributes!({state: Container::Locked})
347     c_faster_started_second.update_attributes!({state: Container::Running,
348                                                 runtime_status: {'error' => 'Something bad happened'},
349                                                 progress: 0.2})
350     reused = Container.find_reusable(common_attrs)
351     assert_not_nil reused
352     # Selected the non-failing container even if it's the one with less progress done
353     assert_equal reused.uuid, c_faster_started_first.uuid
354   end
355
356   test "find_reusable method should select locked container most likely to start sooner" do
357     set_user_from_auth :active
358     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "locked"}})
359     c_low_priority, _ = minimal_new(common_attrs.merge({use_existing: false}))
360     c_high_priority_older, _ = minimal_new(common_attrs.merge({use_existing: false}))
361     c_high_priority_newer, _ = minimal_new(common_attrs.merge({use_existing: false}))
362     # Confirm the 3 container UUIDs are different.
363     assert_equal 3, [c_low_priority.uuid, c_high_priority_older.uuid, c_high_priority_newer.uuid].uniq.length
364     set_user_from_auth :dispatch1
365     c_low_priority.update_attributes!({state: Container::Locked,
366                                        priority: 1})
367     c_high_priority_older.update_attributes!({state: Container::Locked,
368                                               priority: 2})
369     c_high_priority_newer.update_attributes!({state: Container::Locked,
370                                               priority: 2})
371     reused = Container.find_reusable(common_attrs)
372     assert_not_nil reused
373     assert_equal reused.uuid, c_high_priority_older.uuid
374   end
375
376   test "find_reusable method should select running over failed container" do
377     set_user_from_auth :active
378     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed_vs_running"}})
379     c_failed, _ = minimal_new(common_attrs.merge({use_existing: false}))
380     c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
381     assert_not_equal c_failed.uuid, c_running.uuid
382     set_user_from_auth :dispatch1
383     c_failed.update_attributes!({state: Container::Locked})
384     c_failed.update_attributes!({state: Container::Running})
385     c_failed.update_attributes!({state: Container::Complete,
386                                  exit_code: 42,
387                                  log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
388                                  output: 'ea10d51bcf88862dbcc36eb292017dfd+45'})
389     c_running.update_attributes!({state: Container::Locked})
390     c_running.update_attributes!({state: Container::Running,
391                                   progress: 0.15})
392     reused = Container.find_reusable(common_attrs)
393     assert_not_nil reused
394     assert_equal reused.uuid, c_running.uuid
395   end
396
397   test "find_reusable method should select complete over running container" do
398     set_user_from_auth :active
399     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "completed_vs_running"}})
400     c_completed, _ = minimal_new(common_attrs.merge({use_existing: false}))
401     c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
402     assert_not_equal c_completed.uuid, c_running.uuid
403     set_user_from_auth :dispatch1
404     c_completed.update_attributes!({state: Container::Locked})
405     c_completed.update_attributes!({state: Container::Running})
406     c_completed.update_attributes!({state: Container::Complete,
407                                     exit_code: 0,
408                                     log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
409                                     output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'})
410     c_running.update_attributes!({state: Container::Locked})
411     c_running.update_attributes!({state: Container::Running,
412                                   progress: 0.15})
413     reused = Container.find_reusable(common_attrs)
414     assert_not_nil reused
415     assert_equal c_completed.uuid, reused.uuid
416   end
417
418   test "find_reusable method should select running over locked container" do
419     set_user_from_auth :active
420     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
421     c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
422     c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
423     assert_not_equal c_running.uuid, c_locked.uuid
424     set_user_from_auth :dispatch1
425     c_locked.update_attributes!({state: Container::Locked})
426     c_running.update_attributes!({state: Container::Locked})
427     c_running.update_attributes!({state: Container::Running,
428                                   progress: 0.15})
429     reused = Container.find_reusable(common_attrs)
430     assert_not_nil reused
431     assert_equal reused.uuid, c_running.uuid
432   end
433
434   test "find_reusable method should select locked over queued container" do
435     set_user_from_auth :active
436     common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
437     c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
438     c_queued, _ = minimal_new(common_attrs.merge({use_existing: false}))
439     assert_not_equal c_queued.uuid, c_locked.uuid
440     set_user_from_auth :dispatch1
441     c_locked.update_attributes!({state: Container::Locked})
442     reused = Container.find_reusable(common_attrs)
443     assert_not_nil reused
444     assert_equal reused.uuid, c_locked.uuid
445   end
446
447   test "find_reusable method should not select failed container" do
448     set_user_from_auth :active
449     attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed"}})
450     c, _ = minimal_new(attrs)
451     set_user_from_auth :dispatch1
452     c.update_attributes!({state: Container::Locked})
453     c.update_attributes!({state: Container::Running})
454     c.update_attributes!({state: Container::Complete,
455                           exit_code: 33})
456     reused = Container.find_reusable(attrs)
457     assert_nil reused
458   end
459
460   test "find_reusable with logging disabled" do
461     set_user_from_auth :active
462     Rails.logger.expects(:info).never
463     Container.find_reusable(REUSABLE_COMMON_ATTRS)
464   end
465
466   test "find_reusable with logging enabled" do
467     set_user_from_auth :active
468     Rails.configuration.log_reuse_decisions = true
469     Rails.logger.expects(:info).at_least(3)
470     Container.find_reusable(REUSABLE_COMMON_ATTRS)
471   end
472
473   test "Container running" do
474     c, _ = minimal_new priority: 1
475
476     set_user_from_auth :dispatch1
477     check_illegal_updates c, [{state: Container::Running},
478                               {state: Container::Complete}]
479
480     c.lock
481     c.update_attributes! state: Container::Running
482
483     check_illegal_modify c
484     check_bogus_states c
485
486     check_illegal_updates c, [{state: Container::Queued}]
487     c.reload
488
489     c.update_attributes! priority: 3
490   end
491
492   test "Lock and unlock" do
493     c, cr = minimal_new priority: 0
494
495     set_user_from_auth :dispatch1
496     assert_equal Container::Queued, c.state
497
498     assert_raise(ArvadosModel::LockFailedError) do
499       # "no priority"
500       c.lock
501     end
502     c.reload
503     assert cr.update_attributes priority: 1
504
505     refute c.update_attributes(state: Container::Running), "not locked"
506     c.reload
507     refute c.update_attributes(state: Container::Complete), "not locked"
508     c.reload
509
510     assert c.lock, show_errors(c)
511     assert c.locked_by_uuid
512     assert c.auth_uuid
513
514     assert_raise(ArvadosModel::LockFailedError) {c.lock}
515     c.reload
516
517     assert c.unlock, show_errors(c)
518     refute c.locked_by_uuid
519     refute c.auth_uuid
520
521     refute c.update_attributes(state: Container::Running), "not locked"
522     c.reload
523     refute c.locked_by_uuid
524     refute c.auth_uuid
525
526     assert c.lock, show_errors(c)
527     assert c.update_attributes(state: Container::Running), show_errors(c)
528     assert c.locked_by_uuid
529     assert c.auth_uuid
530
531     auth_uuid_was = c.auth_uuid
532
533     assert_raise(ArvadosModel::LockFailedError) do
534       # Running to Locked is not allowed
535       c.lock
536     end
537     c.reload
538     assert_raise(ArvadosModel::InvalidStateTransitionError) do
539       # Running to Queued is not allowed
540       c.unlock
541     end
542     c.reload
543
544     assert c.update_attributes(state: Container::Complete), show_errors(c)
545     refute c.locked_by_uuid
546     refute c.auth_uuid
547
548     auth_exp = ApiClientAuthorization.find_by_uuid(auth_uuid_was).expires_at
549     assert_operator auth_exp, :<, db_current_time
550   end
551
552   test "Container queued cancel" do
553     c, cr = minimal_new({container_count_max: 1})
554     set_user_from_auth :dispatch1
555     assert c.update_attributes(state: Container::Cancelled), show_errors(c)
556     check_no_change_from_cancelled c
557     cr.reload
558     assert_equal ContainerRequest::Final, cr.state
559   end
560
561   test "Container queued count" do
562     assert_equal 1, Container.readable_by(users(:active)).where(state: "Queued").count
563   end
564
565   test "Container locked cancel" do
566     c, _ = minimal_new
567     set_user_from_auth :dispatch1
568     assert c.lock, show_errors(c)
569     assert c.update_attributes(state: Container::Cancelled), show_errors(c)
570     check_no_change_from_cancelled c
571   end
572
573   test "Container locked cancel with log" do
574     c, _ = minimal_new
575     set_user_from_auth :dispatch1
576     assert c.lock, show_errors(c)
577     assert c.update_attributes(
578              state: Container::Cancelled,
579              log: collections(:real_log_collection).portable_data_hash,
580            ), show_errors(c)
581     check_no_change_from_cancelled c
582   end
583
584   test "Container running cancel" do
585     c, _ = minimal_new
586     set_user_from_auth :dispatch1
587     c.lock
588     c.update_attributes! state: Container::Running
589     c.update_attributes! state: Container::Cancelled
590     check_no_change_from_cancelled c
591   end
592
593   test "Container create forbidden for non-admin" do
594     set_user_from_auth :active_trustedclient
595     c = Container.new DEFAULT_ATTRS
596     c.environment = {}
597     c.mounts = {"BAR" => "FOO"}
598     c.output_path = "/tmp"
599     c.priority = 1
600     c.runtime_constraints = {}
601     assert_raises(ArvadosModel::PermissionDeniedError) do
602       c.save!
603     end
604   end
605
606   test "Container only set exit code on complete" do
607     c, _ = minimal_new
608     set_user_from_auth :dispatch1
609     c.lock
610     c.update_attributes! state: Container::Running
611
612     check_illegal_updates c, [{exit_code: 1},
613                               {exit_code: 1, state: Container::Cancelled}]
614
615     assert c.update_attributes(exit_code: 1, state: Container::Complete)
616   end
617
618   test "locked_by_uuid can set output on running container" do
619     c, _ = minimal_new
620     set_user_from_auth :dispatch1
621     c.lock
622     c.update_attributes! state: Container::Running
623
624     assert_equal c.locked_by_uuid, Thread.current[:api_client_authorization].uuid
625
626     assert c.update_attributes output: collections(:collection_owned_by_active).portable_data_hash
627     assert c.update_attributes! state: Container::Complete
628   end
629
630   test "auth_uuid can set output on running container, but not change container state" do
631     c, _ = minimal_new
632     set_user_from_auth :dispatch1
633     c.lock
634     c.update_attributes! state: Container::Running
635
636     Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
637     Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
638     assert c.update_attributes output: collections(:collection_owned_by_active).portable_data_hash
639
640     assert_raises ArvadosModel::PermissionDeniedError do
641       # auth_uuid cannot set container state
642       c.update_attributes state: Container::Complete
643     end
644   end
645
646   test "not allowed to set output that is not readable by current user" do
647     c, _ = minimal_new
648     set_user_from_auth :dispatch1
649     c.lock
650     c.update_attributes! state: Container::Running
651
652     Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
653     Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
654
655     assert_raises ActiveRecord::RecordInvalid do
656       c.update_attributes! output: collections(:collection_not_readable_by_active).portable_data_hash
657     end
658   end
659
660   test "other token cannot set output on running container" do
661     c, _ = minimal_new
662     set_user_from_auth :dispatch1
663     c.lock
664     c.update_attributes! state: Container::Running
665
666     set_user_from_auth :running_to_be_deleted_container_auth
667     assert_raises ArvadosModel::PermissionDeniedError do
668       c.update_attributes! output: collections(:foo_file).portable_data_hash
669     end
670   end
671
672   test "can set trashed output on running container" do
673     c, _ = minimal_new
674     set_user_from_auth :dispatch1
675     c.lock
676     c.update_attributes! state: Container::Running
677
678     output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jk')
679
680     assert output.is_trashed
681     assert c.update_attributes output: output.portable_data_hash
682     assert c.update_attributes! state: Container::Complete
683   end
684
685   test "not allowed to set trashed output that is not readable by current user" do
686     c, _ = minimal_new
687     set_user_from_auth :dispatch1
688     c.lock
689     c.update_attributes! state: Container::Running
690
691     output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jr')
692
693     Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
694     Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
695
696     assert_raises ActiveRecord::RecordInvalid do
697       c.update_attributes! output: output.portable_data_hash
698     end
699   end
700
701   [
702     {state: Container::Complete, exit_code: 0, output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'},
703     {state: Container::Cancelled},
704   ].each do |final_attrs|
705     test "secret_mounts is null after container is #{final_attrs[:state]}" do
706       c, cr = minimal_new(secret_mounts: {'/secret' => {'kind' => 'text', 'content' => 'foo'}},
707                           container_count_max: 1)
708       set_user_from_auth :dispatch1
709       c.lock
710       c.update_attributes!(state: Container::Running)
711       c.reload
712       assert c.secret_mounts.has_key?('/secret')
713
714       c.update_attributes!(final_attrs)
715       c.reload
716       assert_equal({}, c.secret_mounts)
717       cr.reload
718       assert_equal({}, cr.secret_mounts)
719       assert_no_secrets_logged
720     end
721   end
722 end