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)))
562 assert_equal c1.uuid, reused.uuid
565 test "find_reusable method with nil runtime_token, then runtime_token with different user" do
566 set_user_from_auth :active
567 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
568 c1, _ = minimal_new(common_attrs.merge({runtime_token: nil}))
569 assert_equal Container::Queued, c1.state
570 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
572 assert_equal c1.uuid, reused.uuid
575 test "find_reusable method with different runtime_token, different scope, same user" do
576 set_user_from_auth :active
577 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
578 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:runtime_token_limited_scope).token}))
579 assert_equal Container::Queued, c1.state
580 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
582 assert_equal c1.uuid, reused.uuid
585 test "Container running" do
586 set_user_from_auth :active
587 c, _ = minimal_new priority: 1
589 set_user_from_auth :dispatch1
590 check_illegal_updates c, [{state: Container::Running},
591 {state: Container::Complete}]
594 c.update_attributes! state: Container::Running
596 check_illegal_modify c
599 check_illegal_updates c, [{state: Container::Queued}]
602 c.update_attributes! priority: 3
605 test "Lock and unlock" do
606 set_user_from_auth :active
607 c, cr = minimal_new priority: 0
609 set_user_from_auth :dispatch1
610 assert_equal Container::Queued, c.state
612 assert_raise(ArvadosModel::LockFailedError) do
617 assert cr.update_attributes priority: 1
619 refute c.update_attributes(state: Container::Running), "not locked"
621 refute c.update_attributes(state: Container::Complete), "not locked"
624 assert c.lock, show_errors(c)
625 assert c.locked_by_uuid
628 assert_raise(ArvadosModel::LockFailedError) {c.lock}
631 assert c.unlock, show_errors(c)
632 refute c.locked_by_uuid
635 refute c.update_attributes(state: Container::Running), "not locked"
637 refute c.locked_by_uuid
640 assert c.lock, show_errors(c)
641 assert c.update_attributes(state: Container::Running), show_errors(c)
642 assert c.locked_by_uuid
645 auth_uuid_was = c.auth_uuid
647 assert_raise(ArvadosModel::LockFailedError) do
648 # Running to Locked is not allowed
652 assert_raise(ArvadosModel::InvalidStateTransitionError) do
653 # Running to Queued is not allowed
658 assert c.update_attributes(state: Container::Complete), show_errors(c)
659 refute c.locked_by_uuid
662 auth_exp = ApiClientAuthorization.find_by_uuid(auth_uuid_was).expires_at
663 assert_operator auth_exp, :<, db_current_time
666 test "Exceed maximum lock-unlock cycles" do
667 Rails.configuration.max_container_dispatch_attempts = 3
669 set_user_from_auth :active
672 set_user_from_auth :dispatch1
673 assert_equal Container::Queued, c.state
674 assert_equal 0, c.lock_count
678 assert_equal 1, c.lock_count
679 assert_equal Container::Locked, c.state
683 assert_equal 1, c.lock_count
684 assert_equal Container::Queued, c.state
688 assert_equal 2, c.lock_count
689 assert_equal Container::Locked, c.state
693 assert_equal 2, c.lock_count
694 assert_equal Container::Queued, c.state
698 assert_equal 3, c.lock_count
699 assert_equal Container::Locked, c.state
703 assert_equal 3, c.lock_count
704 assert_equal Container::Cancelled, c.state
706 assert_raise(ArvadosModel::LockFailedError) do
707 # Cancelled to Locked is not allowed
712 test "Container queued cancel" do
713 set_user_from_auth :active
714 c, cr = minimal_new({container_count_max: 1})
715 set_user_from_auth :dispatch1
716 assert c.update_attributes(state: Container::Cancelled), show_errors(c)
717 check_no_change_from_cancelled c
719 assert_equal ContainerRequest::Final, cr.state
722 test "Container queued count" do
723 assert_equal 1, Container.readable_by(users(:active)).where(state: "Queued").count
726 test "Containers with no matching request are readable by admin" do
727 uuids = Container.includes('container_requests').where(container_requests: {uuid: nil}).collect(&:uuid)
728 assert_not_empty uuids
729 assert_empty Container.readable_by(users(:active)).where(uuid: uuids)
730 assert_not_empty Container.readable_by(users(:admin)).where(uuid: uuids)
731 assert_equal uuids.count, Container.readable_by(users(:admin)).where(uuid: uuids).count
734 test "Container locked cancel" do
735 set_user_from_auth :active
737 set_user_from_auth :dispatch1
738 assert c.lock, show_errors(c)
739 assert c.update_attributes(state: Container::Cancelled), show_errors(c)
740 check_no_change_from_cancelled c
743 test "Container locked cancel with log" do
744 set_user_from_auth :active
746 set_user_from_auth :dispatch1
747 assert c.lock, show_errors(c)
748 assert c.update_attributes(
749 state: Container::Cancelled,
750 log: collections(:real_log_collection).portable_data_hash,
752 check_no_change_from_cancelled c
755 test "Container running cancel" do
756 set_user_from_auth :active
758 set_user_from_auth :dispatch1
760 c.update_attributes! state: Container::Running
761 c.update_attributes! state: Container::Cancelled
762 check_no_change_from_cancelled c
765 test "Container create forbidden for non-admin" do
766 set_user_from_auth :active_trustedclient
767 c = Container.new DEFAULT_ATTRS
769 c.mounts = {"BAR" => "FOO"}
770 c.output_path = "/tmp"
772 c.runtime_constraints = {}
773 assert_raises(ArvadosModel::PermissionDeniedError) do
778 test "Container only set exit code on complete" do
779 set_user_from_auth :active
781 set_user_from_auth :dispatch1
783 c.update_attributes! state: Container::Running
785 check_illegal_updates c, [{exit_code: 1},
786 {exit_code: 1, state: Container::Cancelled}]
788 assert c.update_attributes(exit_code: 1, state: Container::Complete)
791 test "locked_by_uuid can update log when locked/running, and output when running" do
792 set_user_from_auth :active
793 logcoll = collections(:real_log_collection)
795 cr2 = ContainerRequest.new(DEFAULT_ATTRS)
796 cr2.state = ContainerRequest::Committed
797 act_as_user users(:active) do
800 assert_equal cr1.container_uuid, cr2.container_uuid
802 logpdh_time1 = logcoll.portable_data_hash
804 set_user_from_auth :dispatch1
806 assert_equal c.locked_by_uuid, Thread.current[:api_client_authorization].uuid
807 c.update_attributes!(log: logpdh_time1)
808 c.update_attributes!(state: Container::Running)
811 cr1log_uuid = cr1.log_uuid
812 cr2log_uuid = cr2.log_uuid
813 assert_not_nil cr1log_uuid
814 assert_not_nil cr2log_uuid
815 assert_not_equal logcoll.uuid, cr1log_uuid
816 assert_not_equal logcoll.uuid, cr2log_uuid
817 assert_not_equal cr1log_uuid, cr2log_uuid
819 logcoll.update_attributes!(manifest_text: logcoll.manifest_text + ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n")
820 logpdh_time2 = logcoll.portable_data_hash
822 assert c.update_attributes(output: collections(:collection_owned_by_active).portable_data_hash)
823 assert c.update_attributes(log: logpdh_time2)
824 assert c.update_attributes(state: Container::Complete, log: logcoll.portable_data_hash)
826 assert_equal collections(:collection_owned_by_active).portable_data_hash, c.output
827 assert_equal logpdh_time2, c.log
828 refute c.update_attributes(output: nil)
829 refute c.update_attributes(log: nil)
832 assert_equal cr1log_uuid, cr1.log_uuid
833 assert_equal cr2log_uuid, cr2.log_uuid
834 assert_equal [logpdh_time2], Collection.where(uuid: [cr1log_uuid, cr2log_uuid]).to_a.collect(&:portable_data_hash).uniq
837 ["auth_uuid", "runtime_token"].each do |tok|
838 test "#{tok} can set output, progress, runtime_status, state on running container -- but not log" do
839 if tok == "runtime_token"
840 set_user_from_auth :spectator
841 c, _ = minimal_new(container_image: "9ae44d5792468c58bcf85ce7353c7027+124",
842 runtime_token: api_client_authorizations(:active).token)
844 set_user_from_auth :active
847 set_user_from_auth :dispatch1
849 c.update_attributes! state: Container::Running
851 if tok == "runtime_token"
852 auth = ApiClientAuthorization.validate(token: c.runtime_token)
853 Thread.current[:api_client_authorization] = auth
854 Thread.current[:api_client] = auth.api_client
855 Thread.current[:token] = auth.token
856 Thread.current[:user] = auth.user
858 auth = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
859 Thread.current[:api_client_authorization] = auth
860 Thread.current[:api_client] = auth.api_client
861 Thread.current[:token] = auth.token
862 Thread.current[:user] = auth.user
865 assert c.update_attributes(output: collections(:collection_owned_by_active).portable_data_hash)
866 assert c.update_attributes(runtime_status: {'warning' => 'something happened'})
867 assert c.update_attributes(progress: 0.5)
868 refute c.update_attributes(log: collections(:real_log_collection).portable_data_hash)
870 assert c.update_attributes(state: Container::Complete, exit_code: 0)
874 test "not allowed to set output that is not readable by current user" do
875 set_user_from_auth :active
877 set_user_from_auth :dispatch1
879 c.update_attributes! state: Container::Running
881 Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
882 Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
884 assert_raises ActiveRecord::RecordInvalid do
885 c.update_attributes! output: collections(:collection_not_readable_by_active).portable_data_hash
889 test "other token cannot set output on running container" do
890 set_user_from_auth :active
892 set_user_from_auth :dispatch1
894 c.update_attributes! state: Container::Running
896 set_user_from_auth :running_to_be_deleted_container_auth
897 refute c.update_attributes(output: collections(:foo_file).portable_data_hash)
900 test "can set trashed output on running container" do
901 set_user_from_auth :active
903 set_user_from_auth :dispatch1
905 c.update_attributes! state: Container::Running
907 output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jk')
909 assert output.is_trashed
910 assert c.update_attributes output: output.portable_data_hash
911 assert c.update_attributes! state: Container::Complete
914 test "not allowed to set trashed output that is not readable by current user" do
915 set_user_from_auth :active
917 set_user_from_auth :dispatch1
919 c.update_attributes! state: Container::Running
921 output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jr')
923 Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
924 Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
926 assert_raises ActiveRecord::RecordInvalid do
927 c.update_attributes! output: output.portable_data_hash
932 {state: Container::Complete, exit_code: 0, output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'},
933 {state: Container::Cancelled},
934 ].each do |final_attrs|
935 test "secret_mounts and runtime_token are null after container is #{final_attrs[:state]}" do
936 set_user_from_auth :active
937 c, cr = minimal_new(secret_mounts: {'/secret' => {'kind' => 'text', 'content' => 'foo'}},
938 container_count_max: 1, runtime_token: api_client_authorizations(:active).token)
939 set_user_from_auth :dispatch1
941 c.update_attributes!(state: Container::Running)
943 assert c.secret_mounts.has_key?('/secret')
944 assert_equal api_client_authorizations(:active).token, c.runtime_token
946 c.update_attributes!(final_attrs)
948 assert_equal({}, c.secret_mounts)
949 assert_nil c.runtime_token
951 assert_equal({}, cr.secret_mounts)
952 assert_nil cr.runtime_token
953 assert_no_secrets_logged