1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
6 require 'helpers/container_test_helper'
8 class ContainerTest < ActiveSupport::TestCase
10 include ContainerTestHelper
13 command: ['echo', 'foo'],
14 container_image: 'fa3c1a9cb6783f85f2ecda037e07b8c3+167',
17 runtime_constraints: {"vcpus" => 1, "ram" => 1},
20 REUSABLE_COMMON_ATTRS = {
21 container_image: "9ae44d5792468c58bcf85ce7353c7027+124",
23 command: ["echo", "hello"],
25 runtime_constraints: {
30 "test" => {"kind" => "json"},
36 runtime_user_uuid: "zzzzz-tpzed-xurymjxw79nv3jz",
37 runtime_auth_scopes: ["all"]
40 def request_only attrs
41 attrs.reject {|k| [:runtime_user_uuid, :runtime_auth_scopes].include? k}
44 def minimal_new attrs={}
45 cr = ContainerRequest.new request_only(DEFAULT_ATTRS.merge(attrs))
46 cr.state = ContainerRequest::Committed
48 c = Container.find_by_uuid cr.container_uuid
53 def check_illegal_updates c, bad_updates
54 bad_updates.each do |u|
55 refute c.update_attributes(u), u.inspect
56 refute c.valid?, u.inspect
61 def check_illegal_modify c
62 check_illegal_updates c, [{command: ["echo", "bar"]},
63 {container_image: "arvados/apitestfixture:june10"},
65 {environment: {"FOO" => "BAR"}},
66 {mounts: {"FOO" => "BAR"}},
67 {output_path: "/tmp3"},
68 {locked_by_uuid: "zzzzz-gj3su-027z32aux8dg2s1"},
69 {auth_uuid: "zzzzz-gj3su-017z32aux8dg2s1"},
70 {runtime_constraints: {"FOO" => "BAR"}}]
73 def check_bogus_states c
74 check_illegal_updates c, [{state: nil},
78 def check_no_change_from_cancelled c
79 check_illegal_modify c
81 check_illegal_updates c, [{ priority: 3 },
82 { state: Container::Queued },
83 { state: Container::Locked },
84 { state: Container::Running },
85 { state: Container::Complete }]
88 test "Container create" do
90 c, _ = minimal_new(environment: {},
91 mounts: {"BAR" => {"kind" => "FOO"}},
94 runtime_constraints: {"vcpus" => 1, "ram" => 1})
96 check_illegal_modify c
105 test "Container valid priority" do
106 act_as_system_user do
107 c, _ = minimal_new(environment: {},
108 mounts: {"BAR" => {"kind" => "FOO"}},
111 runtime_constraints: {"vcpus" => 1, "ram" => 1})
113 assert_raises(ActiveRecord::RecordInvalid) do
133 c.priority = 1000 << 50
138 test "Container runtime_status data types" do
139 set_user_from_auth :active
142 mounts: {"BAR" => {"kind" => "FOO"}},
145 runtime_constraints: {"vcpus" => 1, "ram" => 1}
147 c, _ = minimal_new(attrs)
148 assert_equal c.runtime_status, {}
149 assert_equal Container::Queued, c.state
151 set_user_from_auth :dispatch1
152 c.update_attributes! state: Container::Locked
153 c.update_attributes! state: Container::Running
156 'error', 'errorDetail', 'warning', 'warningDetail', 'activity'
158 # String type is allowed
159 string_val = 'A string is accepted'
160 c.update_attributes! runtime_status: {k => string_val}
161 assert_equal string_val, c.runtime_status[k]
163 # Other types aren't allowed
165 42, false, [], {}, nil
166 ].each do |unallowed_val|
167 assert_raises ActiveRecord::RecordInvalid do
168 c.update_attributes! runtime_status: {k => unallowed_val}
174 test "Container runtime_status updates" do
175 set_user_from_auth :active
178 mounts: {"BAR" => {"kind" => "FOO"}},
181 runtime_constraints: {"vcpus" => 1, "ram" => 1}
183 c1, _ = minimal_new(attrs)
184 assert_equal c1.runtime_status, {}
186 assert_equal Container::Queued, c1.state
187 assert_raises ActiveRecord::RecordInvalid do
188 c1.update_attributes! runtime_status: {'error' => 'Oops!'}
191 set_user_from_auth :dispatch1
193 # Allow updates when state = Locked
194 c1.update_attributes! state: Container::Locked
195 c1.update_attributes! runtime_status: {'error' => 'Oops!'}
196 assert c1.runtime_status.key? 'error'
198 # Reset when transitioning from Locked to Queued
199 c1.update_attributes! state: Container::Queued
200 assert_equal c1.runtime_status, {}
202 # Allow updates when state = Running
203 c1.update_attributes! state: Container::Locked
204 c1.update_attributes! state: Container::Running
205 c1.update_attributes! runtime_status: {'error' => 'Oops!'}
206 assert c1.runtime_status.key? 'error'
208 # Don't allow updates on other states
209 c1.update_attributes! state: Container::Complete
210 assert_raises ActiveRecord::RecordInvalid do
211 c1.update_attributes! runtime_status: {'error' => 'Some other error'}
214 set_user_from_auth :active
215 c2, _ = minimal_new(attrs)
216 assert_equal c2.runtime_status, {}
217 set_user_from_auth :dispatch1
218 c2.update_attributes! state: Container::Locked
219 c2.update_attributes! state: Container::Running
220 c2.update_attributes! state: Container::Cancelled
221 assert_raises ActiveRecord::RecordInvalid do
222 c2.update_attributes! runtime_status: {'error' => 'Oops!'}
226 test "Container serialized hash attributes sorted before save" do
227 set_user_from_auth :active
228 env = {"C" => "3", "B" => "2", "A" => "1"}
229 m = {"F" => {"kind" => "3"}, "E" => {"kind" => "2"}, "D" => {"kind" => "1"}}
230 rc = {"vcpus" => 1, "ram" => 1, "keep_cache_ram" => 1}
231 c, _ = minimal_new(environment: env, mounts: m, runtime_constraints: rc)
232 assert_equal c.environment.to_json, Container.deep_sort_hash(env).to_json
233 assert_equal c.mounts.to_json, Container.deep_sort_hash(m).to_json
234 assert_equal c.runtime_constraints.to_json, Container.deep_sort_hash(rc).to_json
237 test 'deep_sort_hash on array of hashes' do
238 a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]}
239 b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]}
240 assert_equal Container.deep_sort_hash(a).to_json, Container.deep_sort_hash(b).to_json
243 test "find_reusable method should select higher priority queued container" do
244 Rails.configuration.log_reuse_decisions = true
245 set_user_from_auth :active
246 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment:{"var" => "queued"}})
247 c_low_priority, _ = minimal_new(common_attrs.merge({use_existing:false, priority:1}))
248 c_high_priority, _ = minimal_new(common_attrs.merge({use_existing:false, priority:2}))
249 assert_not_equal c_low_priority.uuid, c_high_priority.uuid
250 assert_equal Container::Queued, c_low_priority.state
251 assert_equal Container::Queued, c_high_priority.state
252 reused = Container.find_reusable(common_attrs)
253 assert_not_nil reused
254 assert_equal reused.uuid, c_high_priority.uuid
257 test "find_reusable method should select latest completed container" do
258 set_user_from_auth :active
259 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}})
261 state: Container::Complete,
263 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
264 output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
267 c_older, _ = minimal_new(common_attrs.merge({use_existing: false}))
268 c_recent, _ = minimal_new(common_attrs.merge({use_existing: false}))
269 assert_not_equal c_older.uuid, c_recent.uuid
271 set_user_from_auth :dispatch1
272 c_older.update_attributes!({state: Container::Locked})
273 c_older.update_attributes!({state: Container::Running})
274 c_older.update_attributes!(completed_attrs)
276 c_recent.update_attributes!({state: Container::Locked})
277 c_recent.update_attributes!({state: Container::Running})
278 c_recent.update_attributes!(completed_attrs)
280 reused = Container.find_reusable(common_attrs)
281 assert_not_nil reused
282 assert_equal reused.uuid, c_older.uuid
285 test "find_reusable method should select oldest completed container when inconsistent outputs exist" do
286 set_user_from_auth :active
287 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}, priority: 1})
289 state: Container::Complete,
291 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
294 cr = ContainerRequest.new request_only(common_attrs)
295 cr.use_existing = false
296 cr.state = ContainerRequest::Committed
298 c_output1 = Container.where(uuid: cr.container_uuid).first
300 cr = ContainerRequest.new request_only(common_attrs)
301 cr.use_existing = false
302 cr.state = ContainerRequest::Committed
304 c_output2 = Container.where(uuid: cr.container_uuid).first
306 assert_not_equal c_output1.uuid, c_output2.uuid
308 set_user_from_auth :dispatch1
310 out1 = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
311 log1 = collections(:real_log_collection).portable_data_hash
312 c_output1.update_attributes!({state: Container::Locked})
313 c_output1.update_attributes!({state: Container::Running})
314 c_output1.update_attributes!(completed_attrs.merge({log: log1, output: out1}))
316 out2 = 'fa7aeb5140e2848d39b416daeef4ffc5+45'
317 c_output2.update_attributes!({state: Container::Locked})
318 c_output2.update_attributes!({state: Container::Running})
319 c_output2.update_attributes!(completed_attrs.merge({log: log1, output: out2}))
321 set_user_from_auth :active
322 reused = Container.resolve(ContainerRequest.new(request_only(common_attrs)))
323 assert_equal c_output1.uuid, reused.uuid
326 test "find_reusable method should select running container by start date" do
327 set_user_from_auth :active
328 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running"}})
329 c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
330 c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
331 c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
332 # Confirm the 3 container UUIDs are different.
333 assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
334 set_user_from_auth :dispatch1
335 c_slower.update_attributes!({state: Container::Locked})
336 c_slower.update_attributes!({state: Container::Running,
338 c_faster_started_first.update_attributes!({state: Container::Locked})
339 c_faster_started_first.update_attributes!({state: Container::Running,
341 c_faster_started_second.update_attributes!({state: Container::Locked})
342 c_faster_started_second.update_attributes!({state: Container::Running,
344 reused = Container.find_reusable(common_attrs)
345 assert_not_nil reused
346 # Selected container is the one that started first
347 assert_equal reused.uuid, c_faster_started_first.uuid
350 test "find_reusable method should select running container by progress" do
351 set_user_from_auth :active
352 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running2"}})
353 c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
354 c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
355 c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
356 # Confirm the 3 container UUIDs are different.
357 assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
358 set_user_from_auth :dispatch1
359 c_slower.update_attributes!({state: Container::Locked})
360 c_slower.update_attributes!({state: Container::Running,
362 c_faster_started_first.update_attributes!({state: Container::Locked})
363 c_faster_started_first.update_attributes!({state: Container::Running,
365 c_faster_started_second.update_attributes!({state: Container::Locked})
366 c_faster_started_second.update_attributes!({state: Container::Running,
368 reused = Container.find_reusable(common_attrs)
369 assert_not_nil reused
370 # Selected container is the one with most progress done
371 assert_equal reused.uuid, c_faster_started_second.uuid
374 test "find_reusable method should select non-failing running container" do
375 set_user_from_auth :active
376 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running2"}})
377 c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
378 c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
379 c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
380 # Confirm the 3 container UUIDs are different.
381 assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
382 set_user_from_auth :dispatch1
383 c_slower.update_attributes!({state: Container::Locked})
384 c_slower.update_attributes!({state: Container::Running,
386 c_faster_started_first.update_attributes!({state: Container::Locked})
387 c_faster_started_first.update_attributes!({state: Container::Running,
388 runtime_status: {'warning' => 'This is not an error'},
390 c_faster_started_second.update_attributes!({state: Container::Locked})
391 c_faster_started_second.update_attributes!({state: Container::Running,
392 runtime_status: {'error' => 'Something bad happened'},
394 reused = Container.find_reusable(common_attrs)
395 assert_not_nil reused
396 # Selected the non-failing container even if it's the one with less progress done
397 assert_equal reused.uuid, c_faster_started_first.uuid
400 test "find_reusable method should select locked container most likely to start sooner" do
401 set_user_from_auth :active
402 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "locked"}})
403 c_low_priority, _ = minimal_new(common_attrs.merge({use_existing: false}))
404 c_high_priority_older, _ = minimal_new(common_attrs.merge({use_existing: false}))
405 c_high_priority_newer, _ = minimal_new(common_attrs.merge({use_existing: false}))
406 # Confirm the 3 container UUIDs are different.
407 assert_equal 3, [c_low_priority.uuid, c_high_priority_older.uuid, c_high_priority_newer.uuid].uniq.length
408 set_user_from_auth :dispatch1
409 c_low_priority.update_attributes!({state: Container::Locked,
411 c_high_priority_older.update_attributes!({state: Container::Locked,
413 c_high_priority_newer.update_attributes!({state: Container::Locked,
415 reused = Container.find_reusable(common_attrs)
416 assert_not_nil reused
417 assert_equal reused.uuid, c_high_priority_older.uuid
420 test "find_reusable method should select running over failed container" do
421 set_user_from_auth :active
422 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed_vs_running"}})
423 c_failed, _ = minimal_new(common_attrs.merge({use_existing: false}))
424 c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
425 assert_not_equal c_failed.uuid, c_running.uuid
426 set_user_from_auth :dispatch1
427 c_failed.update_attributes!({state: Container::Locked})
428 c_failed.update_attributes!({state: Container::Running})
429 c_failed.update_attributes!({state: Container::Complete,
431 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
432 output: 'ea10d51bcf88862dbcc36eb292017dfd+45'})
433 c_running.update_attributes!({state: Container::Locked})
434 c_running.update_attributes!({state: Container::Running,
436 reused = Container.find_reusable(common_attrs)
437 assert_not_nil reused
438 assert_equal reused.uuid, c_running.uuid
441 test "find_reusable method should select complete over running container" do
442 set_user_from_auth :active
443 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "completed_vs_running"}})
444 c_completed, _ = minimal_new(common_attrs.merge({use_existing: false}))
445 c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
446 assert_not_equal c_completed.uuid, c_running.uuid
447 set_user_from_auth :dispatch1
448 c_completed.update_attributes!({state: Container::Locked})
449 c_completed.update_attributes!({state: Container::Running})
450 c_completed.update_attributes!({state: Container::Complete,
452 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
453 output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'})
454 c_running.update_attributes!({state: Container::Locked})
455 c_running.update_attributes!({state: Container::Running,
457 reused = Container.find_reusable(common_attrs)
458 assert_not_nil reused
459 assert_equal c_completed.uuid, reused.uuid
462 test "find_reusable method should select running over locked container" do
463 set_user_from_auth :active
464 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
465 c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
466 c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
467 assert_not_equal c_running.uuid, c_locked.uuid
468 set_user_from_auth :dispatch1
469 c_locked.update_attributes!({state: Container::Locked})
470 c_running.update_attributes!({state: Container::Locked})
471 c_running.update_attributes!({state: Container::Running,
473 reused = Container.find_reusable(common_attrs)
474 assert_not_nil reused
475 assert_equal reused.uuid, c_running.uuid
478 test "find_reusable method should select locked over queued container" do
479 set_user_from_auth :active
480 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
481 c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
482 c_queued, _ = minimal_new(common_attrs.merge({use_existing: false}))
483 assert_not_equal c_queued.uuid, c_locked.uuid
484 set_user_from_auth :dispatch1
485 c_locked.update_attributes!({state: Container::Locked})
486 reused = Container.find_reusable(common_attrs)
487 assert_not_nil reused
488 assert_equal reused.uuid, c_locked.uuid
491 test "find_reusable method should not select failed container" do
492 set_user_from_auth :active
493 attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed"}})
494 c, _ = minimal_new(attrs)
495 set_user_from_auth :dispatch1
496 c.update_attributes!({state: Container::Locked})
497 c.update_attributes!({state: Container::Running})
498 c.update_attributes!({state: Container::Complete,
500 reused = Container.find_reusable(attrs)
504 test "find_reusable with logging disabled" do
505 set_user_from_auth :active
506 Rails.logger.expects(:info).never
507 Container.find_reusable(REUSABLE_COMMON_ATTRS)
510 test "find_reusable with logging enabled" do
511 set_user_from_auth :active
512 Rails.configuration.log_reuse_decisions = true
513 Rails.logger.expects(:info).at_least(3)
514 Container.find_reusable(REUSABLE_COMMON_ATTRS)
517 def runtime_token_attr tok
518 auth = api_client_authorizations(tok)
519 {runtime_user_uuid: User.find_by_id(auth.user_id).uuid,
520 runtime_auth_scopes: auth.scopes,
521 runtime_token: auth.token}
524 test "find_reusable method with same runtime_token" do
525 set_user_from_auth :active
526 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
527 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:container_runtime_token).token}))
528 assert_equal Container::Queued, c1.state
529 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
530 assert_not_nil reused
531 assert_equal reused.uuid, c1.uuid
534 test "find_reusable method with different runtime_token, same user" do
535 set_user_from_auth :active
536 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
537 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:crt_user).token}))
538 assert_equal Container::Queued, c1.state
539 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
540 assert_not_nil reused
541 assert_equal reused.uuid, c1.uuid
544 test "find_reusable method with nil runtime_token, then runtime_token with same user" do
545 set_user_from_auth :crt_user
546 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
547 c1, _ = minimal_new(common_attrs)
548 assert_equal Container::Queued, c1.state
549 assert_equal users(:container_runtime_token_user).uuid, c1.runtime_user_uuid
550 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
551 assert_not_nil reused
552 assert_equal reused.uuid, c1.uuid
555 test "find_reusable method with different runtime_token, different user" do
556 set_user_from_auth :crt_user
557 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
558 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:active).token}))
559 assert_equal Container::Queued, c1.state
560 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
564 test "find_reusable method with nil runtime_token, then runtime_token with different user" do
565 set_user_from_auth :active
566 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
567 c1, _ = minimal_new(common_attrs.merge({runtime_token: nil}))
568 assert_equal Container::Queued, c1.state
569 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
573 test "find_reusable method with different runtime_token, different scope, same user" do
574 set_user_from_auth :active
575 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
576 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:runtime_token_limited_scope).token}))
577 assert_equal Container::Queued, c1.state
578 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
582 test "Container running" do
583 set_user_from_auth :active
584 c, _ = minimal_new priority: 1
586 set_user_from_auth :dispatch1
587 check_illegal_updates c, [{state: Container::Running},
588 {state: Container::Complete}]
591 c.update_attributes! state: Container::Running
593 check_illegal_modify c
596 check_illegal_updates c, [{state: Container::Queued}]
599 c.update_attributes! priority: 3
602 test "Lock and unlock" do
603 set_user_from_auth :active
604 c, cr = minimal_new priority: 0
606 set_user_from_auth :dispatch1
607 assert_equal Container::Queued, c.state
609 assert_raise(ArvadosModel::LockFailedError) do
614 assert cr.update_attributes priority: 1
616 refute c.update_attributes(state: Container::Running), "not locked"
618 refute c.update_attributes(state: Container::Complete), "not locked"
621 assert c.lock, show_errors(c)
622 assert c.locked_by_uuid
625 assert_raise(ArvadosModel::LockFailedError) {c.lock}
628 assert c.unlock, show_errors(c)
629 refute c.locked_by_uuid
632 refute c.update_attributes(state: Container::Running), "not locked"
634 refute c.locked_by_uuid
637 assert c.lock, show_errors(c)
638 assert c.update_attributes(state: Container::Running), show_errors(c)
639 assert c.locked_by_uuid
642 auth_uuid_was = c.auth_uuid
644 assert_raise(ArvadosModel::LockFailedError) do
645 # Running to Locked is not allowed
649 assert_raise(ArvadosModel::InvalidStateTransitionError) do
650 # Running to Queued is not allowed
655 assert c.update_attributes(state: Container::Complete), show_errors(c)
656 refute c.locked_by_uuid
659 auth_exp = ApiClientAuthorization.find_by_uuid(auth_uuid_was).expires_at
660 assert_operator auth_exp, :<, db_current_time
663 test "Container queued cancel" do
664 set_user_from_auth :active
665 c, cr = minimal_new({container_count_max: 1})
666 set_user_from_auth :dispatch1
667 assert c.update_attributes(state: Container::Cancelled), show_errors(c)
668 check_no_change_from_cancelled c
670 assert_equal ContainerRequest::Final, cr.state
673 test "Container queued count" do
674 assert_equal 1, Container.readable_by(users(:active)).where(state: "Queued").count
677 test "Container locked cancel" do
678 set_user_from_auth :active
680 set_user_from_auth :dispatch1
681 assert c.lock, show_errors(c)
682 assert c.update_attributes(state: Container::Cancelled), show_errors(c)
683 check_no_change_from_cancelled c
686 test "Container locked cancel with log" do
687 set_user_from_auth :active
689 set_user_from_auth :dispatch1
690 assert c.lock, show_errors(c)
691 assert c.update_attributes(
692 state: Container::Cancelled,
693 log: collections(:real_log_collection).portable_data_hash,
695 check_no_change_from_cancelled c
698 test "Container running cancel" do
699 set_user_from_auth :active
701 set_user_from_auth :dispatch1
703 c.update_attributes! state: Container::Running
704 c.update_attributes! state: Container::Cancelled
705 check_no_change_from_cancelled c
708 test "Container create forbidden for non-admin" do
709 set_user_from_auth :active_trustedclient
710 c = Container.new DEFAULT_ATTRS
712 c.mounts = {"BAR" => "FOO"}
713 c.output_path = "/tmp"
715 c.runtime_constraints = {}
716 assert_raises(ArvadosModel::PermissionDeniedError) do
721 test "Container only set exit code on complete" do
722 set_user_from_auth :active
724 set_user_from_auth :dispatch1
726 c.update_attributes! state: Container::Running
728 check_illegal_updates c, [{exit_code: 1},
729 {exit_code: 1, state: Container::Cancelled}]
731 assert c.update_attributes(exit_code: 1, state: Container::Complete)
734 test "locked_by_uuid can update log when locked/running, and output when running" do
735 set_user_from_auth :active
736 logcoll = collections(:real_log_collection)
738 cr2 = ContainerRequest.new(DEFAULT_ATTRS)
739 cr2.state = ContainerRequest::Committed
740 act_as_user users(:active) do
743 assert_equal cr1.container_uuid, cr2.container_uuid
745 logpdh_time1 = logcoll.portable_data_hash
747 set_user_from_auth :dispatch1
749 assert_equal c.locked_by_uuid, Thread.current[:api_client_authorization].uuid
750 c.update_attributes!(log: logpdh_time1)
751 c.update_attributes!(state: Container::Running)
754 cr1log_uuid = cr1.log_uuid
755 cr2log_uuid = cr2.log_uuid
756 assert_not_nil cr1log_uuid
757 assert_not_nil cr2log_uuid
758 assert_not_equal logcoll.uuid, cr1log_uuid
759 assert_not_equal logcoll.uuid, cr2log_uuid
760 assert_not_equal cr1log_uuid, cr2log_uuid
762 logcoll.update_attributes!(manifest_text: logcoll.manifest_text + ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n")
763 logpdh_time2 = logcoll.portable_data_hash
765 assert c.update_attributes(output: collections(:collection_owned_by_active).portable_data_hash)
766 assert c.update_attributes(log: logpdh_time2)
767 assert c.update_attributes(state: Container::Complete, log: logcoll.portable_data_hash)
769 assert_equal collections(:collection_owned_by_active).portable_data_hash, c.output
770 assert_equal logpdh_time2, c.log
771 refute c.update_attributes(output: nil)
772 refute c.update_attributes(log: nil)
775 assert_equal cr1log_uuid, cr1.log_uuid
776 assert_equal cr2log_uuid, cr2.log_uuid
777 assert_equal [logpdh_time2], Collection.where(uuid: [cr1log_uuid, cr2log_uuid]).to_a.collect(&:portable_data_hash).uniq
780 test "auth_uuid can set output, progress, runtime_status, state on running container -- but not log" do
781 set_user_from_auth :active
783 set_user_from_auth :dispatch1
785 c.update_attributes! state: Container::Running
787 auth = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
788 Thread.current[:api_client_authorization] = auth
789 Thread.current[:api_client] = auth.api_client
790 Thread.current[:token] = auth.token
791 Thread.current[:user] = auth.user
793 assert c.update_attributes(output: collections(:collection_owned_by_active).portable_data_hash)
794 assert c.update_attributes(runtime_status: {'warning' => 'something happened'})
795 assert c.update_attributes(progress: 0.5)
796 refute c.update_attributes(log: collections(:real_log_collection).portable_data_hash)
798 assert c.update_attributes(state: Container::Complete, exit_code: 0)
801 test "not allowed to set output that is not readable by current user" do
802 set_user_from_auth :active
804 set_user_from_auth :dispatch1
806 c.update_attributes! state: Container::Running
808 Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
809 Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
811 assert_raises ActiveRecord::RecordInvalid do
812 c.update_attributes! output: collections(:collection_not_readable_by_active).portable_data_hash
816 test "other token cannot set output on running container" do
817 set_user_from_auth :active
819 set_user_from_auth :dispatch1
821 c.update_attributes! state: Container::Running
823 set_user_from_auth :running_to_be_deleted_container_auth
824 refute c.update_attributes(output: collections(:foo_file).portable_data_hash)
827 test "can set trashed output on running container" do
828 set_user_from_auth :active
830 set_user_from_auth :dispatch1
832 c.update_attributes! state: Container::Running
834 output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jk')
836 assert output.is_trashed
837 assert c.update_attributes output: output.portable_data_hash
838 assert c.update_attributes! state: Container::Complete
841 test "not allowed to set trashed output that is not readable by current user" do
842 set_user_from_auth :active
844 set_user_from_auth :dispatch1
846 c.update_attributes! state: Container::Running
848 output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jr')
850 Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
851 Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
853 assert_raises ActiveRecord::RecordInvalid do
854 c.update_attributes! output: output.portable_data_hash
859 {state: Container::Complete, exit_code: 0, output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'},
860 {state: Container::Cancelled},
861 ].each do |final_attrs|
862 test "secret_mounts and runtime_token are null after container is #{final_attrs[:state]}" do
863 set_user_from_auth :active
864 c, cr = minimal_new(secret_mounts: {'/secret' => {'kind' => 'text', 'content' => 'foo'}},
865 container_count_max: 1, runtime_token: api_client_authorizations(:active).token)
866 set_user_from_auth :dispatch1
868 c.update_attributes!(state: Container::Running)
870 assert c.secret_mounts.has_key?('/secret')
871 assert_equal api_client_authorizations(:active).token, c.runtime_token
873 c.update_attributes!(final_attrs)
875 assert_equal({}, c.secret_mounts)
876 assert_nil c.runtime_token
878 assert_equal({}, cr.secret_mounts)
879 assert_nil cr.runtime_token
880 assert_no_secrets_logged