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, "cuda" => {"device_count":0, "driver_version": "", "hardware_capability": ""}},
20 REUSABLE_COMMON_ATTRS = {
21 container_image: "9ae44d5792468c58bcf85ce7353c7027+124",
23 command: ["echo", "hello"],
26 runtime_constraints: {
28 "keep_cache_disk" => 0,
29 "keep_cache_ram" => 0,
34 "test" => {"kind" => "json"},
40 runtime_user_uuid: "zzzzz-tpzed-xurymjxw79nv3jz",
41 runtime_auth_scopes: ["all"],
42 scheduling_parameters: {},
45 REUSABLE_ATTRS_SLIM = {
46 command: ["echo", "slim"],
47 container_image: "9ae44d5792468c58bcf85ce7353c7027+124",
53 runtime_auth_scopes: ["all"],
54 runtime_constraints: {
56 "keep_cache_disk" => 0,
57 "keep_cache_ram" => 0,
61 runtime_user_uuid: "zzzzz-tpzed-xurymjxw79nv3jz",
63 scheduling_parameters: {},
66 def request_only attrs
67 attrs.reject {|k| [:runtime_user_uuid, :runtime_auth_scopes].include? k}
70 def minimal_new attrs={}
71 cr = ContainerRequest.new request_only(DEFAULT_ATTRS.merge(attrs))
72 cr.state = ContainerRequest::Committed
74 c = Container.find_by_uuid cr.container_uuid
79 def check_illegal_updates c, bad_updates
80 bad_updates.each do |u|
81 refute c.update(u), u.inspect
82 refute c.valid?, u.inspect
87 def check_illegal_modify c
88 check_illegal_updates c, [{command: ["echo", "bar"]},
89 {container_image: "arvados/apitestfixture:june10"},
91 {environment: {"FOO" => "BAR"}},
92 {mounts: {"FOO" => "BAR"}},
93 {output_path: "/tmp3"},
94 {locked_by_uuid: "zzzzz-gj3su-027z32aux8dg2s1"},
95 {auth_uuid: "zzzzz-gj3su-017z32aux8dg2s1"},
96 {runtime_constraints: {"FOO" => "BAR"}}]
99 def check_bogus_states c
100 check_illegal_updates c, [{state: nil},
104 def check_no_change_from_cancelled c
105 check_illegal_modify c
107 check_illegal_updates c, [{ priority: 3 },
108 { state: Container::Queued },
109 { state: Container::Locked },
110 { state: Container::Running },
111 { state: Container::Complete }]
114 test "Container create" do
115 act_as_system_user do
116 c, _ = minimal_new(environment: {},
117 mounts: {"BAR" => {"kind" => "FOO"}},
120 runtime_constraints: {"vcpus" => 1, "ram" => 1})
122 check_illegal_modify c
131 test "Container valid priority" do
132 act_as_system_user do
133 c, _ = minimal_new(environment: {},
134 mounts: {"BAR" => {"kind" => "FOO"}},
137 runtime_constraints: {"vcpus" => 1, "ram" => 1})
139 assert_raises(ActiveRecord::RecordInvalid) do
159 c.priority = 1000 << 50
164 test "Container runtime_status data types" do
165 set_user_from_auth :active
168 mounts: {"BAR" => {"kind" => "FOO"}},
171 runtime_constraints: {"vcpus" => 1, "ram" => 1}
173 c, _ = minimal_new(attrs)
174 assert_equal c.runtime_status, {}
175 assert_equal Container::Queued, c.state
177 set_user_from_auth :dispatch1
178 c.update! state: Container::Locked
179 c.update! state: Container::Running
182 'error', 'errorDetail', 'warning', 'warningDetail', 'activity'
184 # String type is allowed
185 string_val = 'A string is accepted'
186 c.update! runtime_status: {k => string_val}
187 assert_equal string_val, c.runtime_status[k]
189 # Other types aren't allowed
191 42, false, [], {}, nil
192 ].each do |unallowed_val|
193 assert_raises ActiveRecord::RecordInvalid do
194 c.update! runtime_status: {k => unallowed_val}
200 test "Container runtime_status updates" do
201 set_user_from_auth :active
204 mounts: {"BAR" => {"kind" => "FOO"}},
207 runtime_constraints: {"vcpus" => 1, "ram" => 1}
209 c1, _ = minimal_new(attrs)
210 assert_equal c1.runtime_status, {}
212 assert_equal Container::Queued, c1.state
213 assert_raises ArvadosModel::PermissionDeniedError do
214 c1.update! runtime_status: {'error' => 'Oops!'}
217 set_user_from_auth :dispatch1
219 # Allow updates when state = Locked
220 c1.update! state: Container::Locked
221 c1.update! runtime_status: {'error' => 'Oops!'}
222 assert c1.runtime_status.key? 'error'
224 # Reset when transitioning from Locked to Queued
225 c1.update! state: Container::Queued
226 assert_equal c1.runtime_status, {}
228 # Allow updates when state = Running
229 c1.update! state: Container::Locked
230 c1.update! state: Container::Running
231 c1.update! runtime_status: {'error' => 'Oops!'}
232 assert c1.runtime_status.key? 'error'
234 # Don't allow updates on other states
235 c1.update! state: Container::Complete
236 assert_raises ActiveRecord::RecordInvalid do
237 c1.update! runtime_status: {'error' => 'Some other error'}
240 set_user_from_auth :active
241 c2, _ = minimal_new(attrs)
242 assert_equal c2.runtime_status, {}
243 set_user_from_auth :dispatch1
244 c2.update! state: Container::Locked
245 c2.update! state: Container::Running
246 c2.update! state: Container::Cancelled
247 assert_raises ActiveRecord::RecordInvalid do
248 c2.update! runtime_status: {'error' => 'Oops!'}
252 test "Container serialized hash attributes sorted before save" do
253 set_user_from_auth :active
254 env = {"C" => "3", "B" => "2", "A" => "1"}
255 m = {"F" => {"kind" => "3"}, "E" => {"kind" => "2"}, "D" => {"kind" => "1"}}
256 rc = {"vcpus" => 1, "ram" => 1, "keep_cache_ram" => 1, "keep_cache_disk" => 0, "API" => true, "cuda" => {"device_count":0, "driver_version": "", "hardware_capability": ""}}
257 c, _ = minimal_new(environment: env, mounts: m, runtime_constraints: rc)
259 assert_equal Container.deep_sort_hash(env).to_json, c.environment.to_json
260 assert_equal Container.deep_sort_hash(m).to_json, c.mounts.to_json
261 assert_equal Container.deep_sort_hash(rc).to_json, c.runtime_constraints.to_json
264 test 'deep_sort_hash on array of hashes' do
265 a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]}
266 b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]}
267 assert_equal Container.deep_sort_hash(a).to_json, Container.deep_sort_hash(b).to_json
270 test "find_reusable method should select higher priority queued container" do
271 Rails.configuration.Containers.LogReuseDecisions = true
272 set_user_from_auth :active
273 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment:{"var" => "queued"}})
274 c_low_priority, _ = minimal_new(common_attrs.merge({use_existing:false, priority:1}))
275 c_high_priority, _ = minimal_new(common_attrs.merge({use_existing:false, priority:2}))
276 assert_not_equal c_low_priority.uuid, c_high_priority.uuid
277 assert_equal Container::Queued, c_low_priority.state
278 assert_equal Container::Queued, c_high_priority.state
279 reused = Container.find_reusable(common_attrs)
280 assert_not_nil reused
281 assert_equal reused.uuid, c_high_priority.uuid
284 test "find_reusable method should select latest completed container" do
285 set_user_from_auth :active
286 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}})
288 state: Container::Complete,
290 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
291 output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
294 c_older, _ = minimal_new(common_attrs.merge({use_existing: false}))
295 c_recent, _ = minimal_new(common_attrs.merge({use_existing: false}))
296 assert_not_equal c_older.uuid, c_recent.uuid
298 set_user_from_auth :dispatch1
299 c_older.update!({state: Container::Locked})
300 c_older.update!({state: Container::Running})
301 c_older.update!(completed_attrs)
303 c_recent.update!({state: Container::Locked})
304 c_recent.update!({state: Container::Running})
305 c_recent.update!(completed_attrs)
307 reused = Container.find_reusable(common_attrs)
308 assert_not_nil reused
309 assert_equal reused.uuid, c_older.uuid
312 test "find_reusable method should select oldest completed container when inconsistent outputs exist" do
313 set_user_from_auth :active
314 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "complete"}, priority: 1})
316 state: Container::Complete,
318 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
321 cr = ContainerRequest.new request_only(common_attrs)
322 cr.use_existing = false
323 cr.state = ContainerRequest::Committed
325 c_output1 = Container.where(uuid: cr.container_uuid).first
327 cr = ContainerRequest.new request_only(common_attrs)
328 cr.use_existing = false
329 cr.state = ContainerRequest::Committed
331 c_output2 = Container.where(uuid: cr.container_uuid).first
333 assert_not_equal c_output1.uuid, c_output2.uuid
335 set_user_from_auth :dispatch1
337 out1 = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
338 log1 = collections(:log_collection).portable_data_hash
339 c_output1.update!({state: Container::Locked})
340 c_output1.update!({state: Container::Running})
341 c_output1.update!(completed_attrs.merge({log: log1, output: out1}))
343 out2 = 'fa7aeb5140e2848d39b416daeef4ffc5+45'
344 c_output2.update!({state: Container::Locked})
345 c_output2.update!({state: Container::Running})
346 c_output2.update!(completed_attrs.merge({log: log1, output: out2}))
348 set_user_from_auth :active
349 reused = Container.resolve(ContainerRequest.new(request_only(common_attrs)))
350 assert_equal c_output1.uuid, reused.uuid
353 test "find_reusable method should select running container by start date" do
354 set_user_from_auth :active
355 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running"}})
356 c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
357 c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
358 c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
359 # Confirm the 3 container UUIDs are different.
360 assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
361 set_user_from_auth :dispatch1
362 c_slower.update!({state: Container::Locked})
363 c_slower.update!({state: Container::Running,
365 c_faster_started_first.update!({state: Container::Locked})
366 c_faster_started_first.update!({state: Container::Running,
368 c_faster_started_second.update!({state: Container::Locked})
369 c_faster_started_second.update!({state: Container::Running,
371 reused = Container.find_reusable(common_attrs)
372 assert_not_nil reused
373 # Selected container is the one that started first
374 assert_equal reused.uuid, c_faster_started_first.uuid
377 test "find_reusable method should select running container by progress" do
378 set_user_from_auth :active
379 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running2"}})
380 c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
381 c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
382 c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
383 # Confirm the 3 container UUIDs are different.
384 assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
385 set_user_from_auth :dispatch1
386 c_slower.update!({state: Container::Locked})
387 c_slower.update!({state: Container::Running,
389 c_faster_started_first.update!({state: Container::Locked})
390 c_faster_started_first.update!({state: Container::Running,
392 c_faster_started_second.update!({state: Container::Locked})
393 c_faster_started_second.update!({state: Container::Running,
395 reused = Container.find_reusable(common_attrs)
396 assert_not_nil reused
397 # Selected container is the one with most progress done
398 assert_equal reused.uuid, c_faster_started_second.uuid
401 test "find_reusable method should select non-failing running container" do
402 set_user_from_auth :active
403 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running2"}})
404 c_slower, _ = minimal_new(common_attrs.merge({use_existing: false}))
405 c_faster_started_first, _ = minimal_new(common_attrs.merge({use_existing: false}))
406 c_faster_started_second, _ = minimal_new(common_attrs.merge({use_existing: false}))
407 # Confirm the 3 container UUIDs are different.
408 assert_equal 3, [c_slower.uuid, c_faster_started_first.uuid, c_faster_started_second.uuid].uniq.length
409 set_user_from_auth :dispatch1
410 c_slower.update!({state: Container::Locked})
411 c_slower.update!({state: Container::Running,
413 c_faster_started_first.update!({state: Container::Locked})
414 c_faster_started_first.update!({state: Container::Running,
415 runtime_status: {'warning' => 'This is not an error'},
417 c_faster_started_second.update!({state: Container::Locked})
418 assert_equal 0, Container.where("runtime_status->'error' is not null").count
419 c_faster_started_second.update!({state: Container::Running,
420 runtime_status: {'error' => 'Something bad happened'},
422 assert_equal 1, Container.where("runtime_status->'error' is not null").count
423 reused = Container.find_reusable(common_attrs)
424 assert_not_nil reused
425 # Selected the non-failing container even if it's the one with less progress done
426 assert_equal reused.uuid, c_faster_started_first.uuid
429 test "find_reusable method should select locked container most likely to start sooner" do
430 set_user_from_auth :active
431 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "locked"}})
432 c_low_priority, _ = minimal_new(common_attrs.merge({use_existing: false}))
433 c_high_priority_older, _ = minimal_new(common_attrs.merge({use_existing: false}))
434 c_high_priority_newer, _ = minimal_new(common_attrs.merge({use_existing: false}))
435 # Confirm the 3 container UUIDs are different.
436 assert_equal 3, [c_low_priority.uuid, c_high_priority_older.uuid, c_high_priority_newer.uuid].uniq.length
437 set_user_from_auth :dispatch1
438 c_low_priority.update!({state: Container::Locked,
440 c_high_priority_older.update!({state: Container::Locked,
442 c_high_priority_newer.update!({state: Container::Locked,
444 reused = Container.find_reusable(common_attrs)
445 assert_not_nil reused
446 assert_equal reused.uuid, c_high_priority_older.uuid
449 test "find_reusable method should select running over failed container" do
450 set_user_from_auth :active
451 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed_vs_running"}})
452 c_failed, _ = minimal_new(common_attrs.merge({use_existing: false}))
453 c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
454 assert_not_equal c_failed.uuid, c_running.uuid
455 set_user_from_auth :dispatch1
456 c_failed.update!({state: Container::Locked})
457 c_failed.update!({state: Container::Running})
458 c_failed.update!({state: Container::Complete,
460 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
461 output: 'ea10d51bcf88862dbcc36eb292017dfd+45'})
462 c_running.update!({state: Container::Locked})
463 c_running.update!({state: Container::Running,
465 reused = Container.find_reusable(common_attrs)
466 assert_not_nil reused
467 assert_equal reused.uuid, c_running.uuid
470 test "find_reusable method should select complete over running container" do
471 set_user_from_auth :active
472 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "completed_vs_running"}})
473 c_completed, _ = minimal_new(common_attrs.merge({use_existing: false}))
474 c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
475 assert_not_equal c_completed.uuid, c_running.uuid
476 set_user_from_auth :dispatch1
477 c_completed.update!({state: Container::Locked})
478 c_completed.update!({state: Container::Running})
479 c_completed.update!({state: Container::Complete,
481 log: 'ea10d51bcf88862dbcc36eb292017dfd+45',
482 output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'})
483 c_running.update!({state: Container::Locked})
484 c_running.update!({state: Container::Running,
486 reused = Container.find_reusable(common_attrs)
487 assert_not_nil reused
488 assert_equal c_completed.uuid, reused.uuid
491 test "find_reusable method should select running over locked container" do
492 set_user_from_auth :active
493 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
494 c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
495 c_running, _ = minimal_new(common_attrs.merge({use_existing: false}))
496 assert_not_equal c_running.uuid, c_locked.uuid
497 set_user_from_auth :dispatch1
498 c_locked.update!({state: Container::Locked})
499 c_running.update!({state: Container::Locked})
500 c_running.update!({state: Container::Running,
502 reused = Container.find_reusable(common_attrs)
503 assert_not_nil reused
504 assert_equal reused.uuid, c_running.uuid
507 test "find_reusable method should select locked over queued container" do
508 set_user_from_auth :active
509 common_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "running_vs_locked"}})
510 c_locked, _ = minimal_new(common_attrs.merge({use_existing: false}))
511 c_queued, _ = minimal_new(common_attrs.merge({use_existing: false}))
512 assert_not_equal c_queued.uuid, c_locked.uuid
513 set_user_from_auth :dispatch1
514 c_locked.update!({state: Container::Locked})
515 reused = Container.find_reusable(common_attrs)
516 assert_not_nil reused
517 assert_equal reused.uuid, c_locked.uuid
520 test "find_reusable method should not select failed container" do
521 set_user_from_auth :active
522 attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"var" => "failed"}})
523 c, _ = minimal_new(attrs)
524 set_user_from_auth :dispatch1
525 c.update!({state: Container::Locked})
526 c.update!({state: Container::Running})
527 c.update!({state: Container::Complete,
529 reused = Container.find_reusable(attrs)
533 [[false, false, true],
535 [true, false, false],
537 ].each do |c1_preemptible, c2_preemptible, should_reuse|
538 [[Container::Queued, 1],
539 [Container::Locked, 1],
540 [Container::Running, 0], # not cancelled yet, but obviously will be soon
541 ].each do |c1_state, c1_priority|
542 test "find_reusable for #{c2_preemptible ? '' : 'non-'}preemptible req should #{should_reuse ? '' : 'not'} reuse a #{c1_state} #{c1_preemptible ? '' : 'non-'}preemptible container with priority #{c1_priority}" do
543 configure_preemptible_instance_type
544 set_user_from_auth :active
545 c1_attrs = REUSABLE_COMMON_ATTRS.merge({environment: {"test" => name, "state" => c1_state}, scheduling_parameters: {"preemptible" => c1_preemptible}})
546 c1, _ = minimal_new(c1_attrs)
547 set_user_from_auth :dispatch1
548 c1.update!({state: Container::Locked}) if c1_state != Container::Queued
549 c1.update!({state: Container::Running, priority: c1_priority}) if c1_state == Container::Running
550 c2_attrs = c1_attrs.merge({scheduling_parameters: {"preemptible" => c2_preemptible}})
551 reused = Container.find_reusable(c2_attrs)
552 if should_reuse && c1_priority > 0
553 assert_not_nil reused
561 test "find_reusable with logging disabled" do
562 set_user_from_auth :active
563 Rails.logger.expects(:info).never
564 Container.find_reusable(REUSABLE_COMMON_ATTRS)
567 test "find_reusable with logging enabled" do
568 set_user_from_auth :active
569 Rails.configuration.Containers.LogReuseDecisions = true
570 Rails.logger.expects(:info).at_least(3)
571 Container.find_reusable(REUSABLE_COMMON_ATTRS)
574 def runtime_token_attr tok
575 auth = api_client_authorizations(tok)
576 {runtime_user_uuid: User.find_by_id(auth.user_id).uuid,
577 runtime_auth_scopes: auth.scopes,
578 runtime_token: auth.token}
581 test "find_reusable method with same runtime_token" do
582 set_user_from_auth :active
583 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
584 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:container_runtime_token).token}))
585 assert_equal Container::Queued, c1.state
586 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
587 assert_not_nil reused
588 assert_equal reused.uuid, c1.uuid
591 test "find_reusable method with different runtime_token, same user" do
592 set_user_from_auth :active
593 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
594 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:crt_user).token}))
595 assert_equal Container::Queued, c1.state
596 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
597 assert_not_nil reused
598 assert_equal reused.uuid, c1.uuid
601 test "find_reusable method with nil runtime_token, then runtime_token with same user" do
602 set_user_from_auth :crt_user
603 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
604 c1, _ = minimal_new(common_attrs)
605 assert_equal Container::Queued, c1.state
606 assert_equal users(:container_runtime_token_user).uuid, c1.runtime_user_uuid
607 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
608 assert_not_nil reused
609 assert_equal reused.uuid, c1.uuid
612 test "find_reusable method with different runtime_token, different user" do
613 set_user_from_auth :crt_user
614 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
615 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:active).token}))
616 assert_equal Container::Queued, c1.state
617 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
619 assert_not_nil reused
620 assert_equal c1.uuid, reused.uuid
623 test "find_reusable method with nil runtime_token, then runtime_token with different user" do
624 set_user_from_auth :active
625 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
626 c1, _ = minimal_new(common_attrs.merge({runtime_token: nil}))
627 assert_equal Container::Queued, c1.state
628 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
630 assert_not_nil reused
631 assert_equal c1.uuid, reused.uuid
634 test "find_reusable method with different runtime_token, different scope, same user" do
635 set_user_from_auth :active
636 common_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"}})
637 c1, _ = minimal_new(common_attrs.merge({runtime_token: api_client_authorizations(:runtime_token_limited_scope).token}))
638 assert_equal Container::Queued, c1.state
639 reused = Container.find_reusable(common_attrs.merge(runtime_token_attr(:container_runtime_token)))
641 assert_not_nil reused
642 assert_equal c1.uuid, reused.uuid
645 test "find_reusable method with cuda" do
646 set_user_from_auth :active
648 no_cuda_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"},
649 runtime_constraints: {"vcpus" => 1, "ram" => 1, "keep_cache_disk"=>0, "keep_cache_ram"=>268435456, "API" => false,
650 "cuda" => {"device_count":0, "driver_version": "", "hardware_capability": ""}},})
651 c1, _ = minimal_new(no_cuda_attrs)
652 assert_equal Container::Queued, c1.state
655 cuda_attrs = REUSABLE_COMMON_ATTRS.merge({use_existing:false, priority:1, environment:{"var" => "queued"},
656 runtime_constraints: {"vcpus" => 1, "ram" => 1, "keep_cache_disk"=>0, "keep_cache_ram"=>268435456, "API" => false,
657 "cuda" => {"device_count":1, "driver_version": "11.0", "hardware_capability": "9.0"}},})
658 c2, _ = minimal_new(cuda_attrs)
659 assert_equal Container::Queued, c2.state
661 # should find the no cuda one
662 reused = Container.find_reusable(no_cuda_attrs)
663 assert_not_nil reused
664 assert_equal reused.uuid, c1.uuid
666 # should find the cuda one
667 reused = Container.find_reusable(cuda_attrs)
668 assert_not_nil reused
669 assert_equal reused.uuid, c2.uuid
672 test "Container running" do
673 set_user_from_auth :active
674 c, _ = minimal_new priority: 1
676 set_user_from_auth :dispatch1
677 check_illegal_updates c, [{state: Container::Running},
678 {state: Container::Complete}]
681 c.update! state: Container::Running
683 check_illegal_modify c
686 check_illegal_updates c, [{state: Container::Queued}]
689 c.update! priority: 3
692 test "Lock and unlock" do
693 set_user_from_auth :active
694 c, cr = minimal_new priority: 0
696 set_user_from_auth :dispatch1
697 assert_equal Container::Queued, c.state
699 assert_raise(ArvadosModel::LockFailedError) do
704 assert cr.update priority: 1
706 refute c.update(state: Container::Running), "not locked"
708 refute c.update(state: Container::Complete), "not locked"
711 assert c.lock, show_errors(c)
712 assert c.locked_by_uuid
715 assert_raise(ArvadosModel::LockFailedError) {c.lock}
718 assert c.unlock, show_errors(c)
719 refute c.locked_by_uuid
722 refute c.update(state: Container::Running), "not locked"
724 refute c.locked_by_uuid
727 assert c.lock, show_errors(c)
728 assert c.update(state: Container::Running), show_errors(c)
729 assert c.locked_by_uuid
732 auth_uuid_was = c.auth_uuid
734 assert_raise(ArvadosModel::LockFailedError) do
735 # Running to Locked is not allowed
739 assert_raise(ArvadosModel::InvalidStateTransitionError) do
740 # Running to Queued is not allowed
745 assert c.update(state: Container::Complete), show_errors(c)
746 refute c.locked_by_uuid
749 auth_exp = ApiClientAuthorization.find_by_uuid(auth_uuid_was).expires_at
750 assert_operator auth_exp, :<, db_current_time
752 assert_nil ApiClientAuthorization.validate(token: ApiClientAuthorization.find_by_uuid(auth_uuid_was).token)
755 test "Exceed maximum lock-unlock cycles" do
756 Rails.configuration.Containers.MaxDispatchAttempts = 3
758 set_user_from_auth :active
761 set_user_from_auth :dispatch1
762 assert_equal Container::Queued, c.state
763 assert_equal 0, c.lock_count
767 assert_equal 1, c.lock_count
768 assert_equal Container::Locked, c.state
772 assert_equal 1, c.lock_count
773 assert_equal Container::Queued, c.state
777 assert_equal 2, c.lock_count
778 assert_equal Container::Locked, c.state
782 assert_equal 2, c.lock_count
783 assert_equal Container::Queued, c.state
787 assert_equal 3, c.lock_count
788 assert_equal Container::Locked, c.state
792 assert_equal 3, c.lock_count
793 assert_equal Container::Cancelled, c.state
795 assert_raise(ArvadosModel::LockFailedError) do
796 # Cancelled to Locked is not allowed
801 test "Container queued cancel" do
802 set_user_from_auth :active
803 c, cr = minimal_new({container_count_max: 1})
804 set_user_from_auth :dispatch1
805 assert c.update(state: Container::Cancelled), show_errors(c)
806 check_no_change_from_cancelled c
808 assert_equal ContainerRequest::Final, cr.state
811 test "Container queued count" do
812 assert_equal 1, Container.readable_by(users(:active)).where(state: "Queued").count
815 test "Containers with no matching request are readable by admin" do
816 uuids = Container.includes('container_requests').where(container_requests: {uuid: nil}).collect(&:uuid)
817 assert_not_empty uuids
818 assert_empty Container.readable_by(users(:active)).where(uuid: uuids)
819 assert_not_empty Container.readable_by(users(:admin)).where(uuid: uuids)
820 assert_equal uuids.count, Container.readable_by(users(:admin)).where(uuid: uuids).count
823 test "Container locked cancel" do
824 set_user_from_auth :active
826 set_user_from_auth :dispatch1
827 assert c.lock, show_errors(c)
828 assert c.update(state: Container::Cancelled), show_errors(c)
829 check_no_change_from_cancelled c
832 test "Container locked with non-expiring token" do
833 Rails.configuration.API.TokenMaxLifetime = 1.hour
834 set_user_from_auth :active
836 set_user_from_auth :dispatch1
837 assert c.lock, show_errors(c)
839 assert c.auth.expires_at.nil?
840 assert c.auth.user_id == User.find_by_uuid(users(:active).uuid).id
843 test "Container locked cancel with log" do
844 set_user_from_auth :active
846 set_user_from_auth :dispatch1
847 assert c.lock, show_errors(c)
849 state: Container::Cancelled,
850 log: collections(:log_collection).portable_data_hash,
852 check_no_change_from_cancelled c
855 test "Container running cancel" do
856 set_user_from_auth :active
858 set_user_from_auth :dispatch1
860 c.update! state: Container::Running
861 c.update! state: Container::Cancelled
862 check_no_change_from_cancelled c
865 test "Container create forbidden for non-admin" do
866 set_user_from_auth :active_trustedclient
867 c = Container.new DEFAULT_ATTRS
869 c.mounts = {"BAR" => "FOO"}
870 c.output_path = "/tmp"
872 c.runtime_constraints = {}
873 assert_raises(ArvadosModel::PermissionDeniedError) do
879 [Container::Queued, {state: Container::Locked}],
880 [Container::Queued, {state: Container::Running}],
881 [Container::Queued, {state: Container::Complete}],
882 [Container::Queued, {state: Container::Cancelled}],
883 [Container::Queued, {priority: 123456789}],
884 [Container::Queued, {runtime_status: {'error' => 'oops'}}],
885 [Container::Queued, {cwd: '/'}],
886 [Container::Locked, {state: Container::Running}],
887 [Container::Locked, {state: Container::Queued}],
888 [Container::Locked, {priority: 123456789}],
889 [Container::Locked, {runtime_status: {'error' => 'oops'}}],
890 [Container::Locked, {cwd: '/'}],
891 [Container::Running, {state: Container::Complete}],
892 [Container::Running, {state: Container::Cancelled}],
893 [Container::Running, {priority: 123456789}],
894 [Container::Running, {runtime_status: {'error' => 'oops'}}],
895 [Container::Running, {cwd: '/'}],
896 [Container::Running, {gateway_address: "172.16.0.1:12345"}],
897 [Container::Running, {interactive_session_started: true}],
898 [Container::Complete, {state: Container::Cancelled}],
899 [Container::Complete, {priority: 123456789}],
900 [Container::Complete, {runtime_status: {'error' => 'oops'}}],
901 [Container::Complete, {cwd: '/'}],
902 [Container::Cancelled, {cwd: '/'}],
903 ].each do |start_state, updates|
904 test "Container update #{updates.inspect} when #{start_state} forbidden for non-admin" do
905 set_user_from_auth :active
907 if start_state != Container::Queued
908 set_user_from_auth :dispatch1
910 if start_state != Container::Locked
911 c.update! state: Container::Running
912 if start_state != Container::Running
913 c.update! state: start_state
917 assert_equal c.state, start_state
918 set_user_from_auth :active
919 assert_raises(ArvadosModel::PermissionDeniedError) do
925 test "can only change exit code while running and at completion" do
926 set_user_from_auth :active
928 set_user_from_auth :dispatch1
930 check_illegal_updates c, [{exit_code: 1}]
931 c.update! state: Container::Running
932 assert c.update(exit_code: 1)
933 assert c.update(exit_code: 1, state: Container::Complete)
936 test "locked_by_uuid can update log when locked/running, and output when running" do
937 set_user_from_auth :active
938 logcoll = collections(:container_log_collection)
940 cr2 = ContainerRequest.new(DEFAULT_ATTRS)
941 cr2.state = ContainerRequest::Committed
942 act_as_user users(:active) do
945 assert_equal cr1.container_uuid, cr2.container_uuid
947 logpdh_time1 = logcoll.portable_data_hash
949 set_user_from_auth :dispatch1
951 assert_equal c.locked_by_uuid, Thread.current[:api_client_authorization].uuid
952 c.update!(log: logpdh_time1)
953 c.update!(state: Container::Running)
956 cr1log_uuid = cr1.log_uuid
957 cr2log_uuid = cr2.log_uuid
958 assert_not_nil cr1log_uuid
959 assert_not_nil cr2log_uuid
960 assert_not_equal logcoll.uuid, cr1log_uuid
961 assert_not_equal logcoll.uuid, cr2log_uuid
962 assert_not_equal cr1log_uuid, cr2log_uuid
964 logcoll.update!(manifest_text: logcoll.manifest_text + ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n")
965 logpdh_time2 = logcoll.portable_data_hash
967 assert c.update(output: collections(:collection_owned_by_active).portable_data_hash)
968 assert c.update(log: logpdh_time2)
969 assert c.update(state: Container::Complete, log: logcoll.portable_data_hash)
971 assert_equal collections(:collection_owned_by_active).portable_data_hash, c.output
972 assert_equal logpdh_time2, c.log
973 refute c.update(output: nil)
974 refute c.update(log: nil)
977 assert_equal cr1log_uuid, cr1.log_uuid
978 assert_equal cr2log_uuid, cr2.log_uuid
979 assert_equal 1, Collection.where(uuid: [cr1log_uuid, cr2log_uuid]).to_a.collect(&:portable_data_hash).uniq.length
980 assert_equal ". 8c12f5f5297b7337598170c6f531fcee+7882 acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 7882:3:foo.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt
981 ./log\\040for\\040container\\040#{cr1.container_uuid} 8c12f5f5297b7337598170c6f531fcee+7882 acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 7882:3:foo.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt
982 ", Collection.find_by_uuid(cr1log_uuid).manifest_text
985 ["auth_uuid", "runtime_token"].each do |tok|
986 test "#{tok} can set output, progress, runtime_status, state, exit_code on running container -- but not log" do
987 if tok == "runtime_token"
988 set_user_from_auth :spectator
989 c, _ = minimal_new(container_image: "9ae44d5792468c58bcf85ce7353c7027+124",
990 runtime_token: api_client_authorizations(:active).token)
992 set_user_from_auth :active
995 set_user_from_auth :dispatch1
997 c.update! state: Container::Running
999 if tok == "runtime_token"
1000 auth = ApiClientAuthorization.validate(token: c.runtime_token)
1001 Thread.current[:api_client_authorization] = auth
1002 Thread.current[:api_client] = auth.api_client
1003 Thread.current[:token] = auth.token
1004 Thread.current[:user] = auth.user
1006 auth = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
1007 Thread.current[:api_client_authorization] = auth
1008 Thread.current[:api_client] = auth.api_client
1009 Thread.current[:token] = auth.token
1010 Thread.current[:user] = auth.user
1013 assert c.update(gateway_address: "127.0.0.1:9")
1014 assert c.update(output: collections(:collection_owned_by_active).portable_data_hash)
1015 assert c.update(runtime_status: {'warning' => 'something happened'})
1016 assert c.update(progress: 0.5)
1017 assert c.update(exit_code: 0)
1018 refute c.update(log: collections(:log_collection).portable_data_hash)
1020 assert c.update(state: Container::Complete, exit_code: 0)
1024 test "not allowed to set output that is not readable by current user" do
1025 set_user_from_auth :active
1027 set_user_from_auth :dispatch1
1029 c.update! state: Container::Running
1031 Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
1032 Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
1034 assert_raises ActiveRecord::RecordInvalid do
1035 c.update! output: collections(:collection_not_readable_by_active).portable_data_hash
1039 test "other token cannot set output on running container" do
1040 set_user_from_auth :active
1042 set_user_from_auth :dispatch1
1044 c.update! state: Container::Running
1046 set_user_from_auth :running_to_be_deleted_container_auth
1047 assert_raises(ArvadosModel::PermissionDeniedError) do
1048 c.update(output: collections(:foo_file).portable_data_hash)
1052 test "can set trashed output on running container" do
1053 set_user_from_auth :active
1055 set_user_from_auth :dispatch1
1057 c.update! state: Container::Running
1059 output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jk')
1061 assert output.is_trashed
1062 assert c.update output: output.portable_data_hash
1063 assert c.update! state: Container::Complete
1066 test "not allowed to set trashed output that is not readable by current user" do
1067 set_user_from_auth :active
1069 set_user_from_auth :dispatch1
1071 c.update! state: Container::Running
1073 output = Collection.find_by_uuid('zzzzz-4zz18-mto52zx1s7sn3jr')
1075 Thread.current[:api_client_authorization] = ApiClientAuthorization.find_by_uuid(c.auth_uuid)
1076 Thread.current[:user] = User.find_by_id(Thread.current[:api_client_authorization].user_id)
1078 assert_raises ActiveRecord::RecordInvalid do
1079 c.update! output: output.portable_data_hash
1083 test "user cannot delete" do
1084 set_user_from_auth :active
1086 assert_raises ArvadosModel::PermissionDeniedError do
1089 assert Container.find_by_uuid(c.uuid)
1093 {state: Container::Complete, exit_code: 0, output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'},
1094 {state: Container::Cancelled},
1095 ].each do |final_attrs|
1096 test "secret_mounts and runtime_token are null after container is #{final_attrs[:state]}" do
1097 set_user_from_auth :active
1098 c, cr = minimal_new(secret_mounts: {'/secret' => {'kind' => 'text', 'content' => 'foo'}},
1099 container_count_max: 1, runtime_token: api_client_authorizations(:active).token)
1100 set_user_from_auth :dispatch1
1102 c.update!(state: Container::Running)
1104 assert c.secret_mounts.has_key?('/secret')
1105 assert_equal api_client_authorizations(:active).token, c.runtime_token
1107 c.update!(final_attrs)
1109 assert_equal({}, c.secret_mounts)
1110 assert_nil c.runtime_token
1112 assert_equal({}, cr.secret_mounts)
1113 assert_nil cr.runtime_token
1114 assert_no_secrets_logged
1118 def configure_preemptible_instance_type
1119 Rails.configuration.InstanceTypes = ConfigLoader.to_OrderedOptions({
1121 "Preemptible" => true,
1123 "ProviderType" => "a1.small",
1125 "RAM" => 1000000000,
1130 def vary_parameters(**kwargs)
1131 # kwargs is a hash that maps parameters to an array of values.
1132 # This function enumerates every possible hash where each key has one of
1133 # the values from its array.
1134 # The output keys are strings since that's what container hash attributes
1136 # A nil value yields a hash without that key.
1137 [[:_, nil]].product(
1138 *kwargs.map { |(key, values)| [key.to_s].product(values) },
1139 ).map { |param_pairs| Hash[param_pairs].compact }
1142 def retry_with_scheduling_parameters(param_hashes)
1143 set_user_from_auth :admin
1146 param_hashes.each do |scheduling_parameters|
1147 container, request = minimal_new(scheduling_parameters: scheduling_parameters)
1148 containers[container.uuid] = container
1151 refute(containers.empty?, "buggy test: no scheduling parameters enumerated")
1152 assert_equal(1, containers.length)
1153 _, container1 = containers.shift
1155 container1.update!(state: Container::Cancelled)
1157 request1 = requests.shift
1159 assert_not_equal(container1.uuid, request1.container_uuid)
1160 requests.each do |request|
1162 assert_equal(request1.container_uuid, request.container_uuid)
1164 container2 = Container.find_by_uuid(request1.container_uuid)
1165 assert_not_nil(container2)
1169 preemptible_values = [true, false, nil]
1170 preemptible_values.permutation(1).chain(
1171 preemptible_values.product(preemptible_values),
1172 preemptible_values.product(preemptible_values, preemptible_values),
1173 ).each do |preemptible_a|
1174 # If the first req has preemptible=true but a subsequent req
1175 # doesn't, we want to avoid reusing the first container, so this
1176 # test isn't appropriate.
1177 next if preemptible_a[0] &&
1178 ((preemptible_a.length > 1 && !preemptible_a[1]) ||
1179 (preemptible_a.length > 2 && !preemptible_a[2]))
1180 test "retry requests scheduled with preemptible=#{preemptible_a}" do
1181 configure_preemptible_instance_type
1182 param_hashes = vary_parameters(preemptible: preemptible_a)
1183 container = retry_with_scheduling_parameters(param_hashes)
1184 assert_equal(preemptible_a.all?,
1185 container.scheduling_parameters["preemptible"] || false)
1189 partition_values = [nil, [], ["alpha"], ["alpha", "bravo"], ["bravo", "charlie"]]
1190 partition_values.permutation(1).chain(
1191 partition_values.permutation(2),
1192 ).each do |partitions_a|
1193 test "retry requests scheduled with partitions=#{partitions_a}" do
1194 param_hashes = vary_parameters(partitions: partitions_a)
1195 container = retry_with_scheduling_parameters(param_hashes)
1196 expected = if partitions_a.any? { |value| value.nil? or value.empty? }
1199 partitions_a.flatten.uniq
1201 actual = container.scheduling_parameters["partitions"] || []
1202 assert_equal(expected.sort, actual.sort)
1206 runtime_values = [nil, 0, 1, 2, 3]
1207 runtime_values.permutation(1).chain(
1208 runtime_values.permutation(2),
1209 runtime_values.permutation(3),
1210 ).each do |max_run_time_a|
1211 test "retry requests scheduled with max_run_time=#{max_run_time_a}" do
1212 param_hashes = vary_parameters(max_run_time: max_run_time_a)
1213 container = retry_with_scheduling_parameters(param_hashes)
1214 expected = if max_run_time_a.any? { |value| value.nil? or value == 0 }
1219 actual = container.scheduling_parameters["max_run_time"] || 0
1220 assert_equal(expected, actual)
1224 test "retry requests with multi-varied scheduling parameters" do
1225 configure_preemptible_instance_type
1227 "partitions": ["alpha", "bravo"],
1228 "preemptible": false,
1231 "partitions": ["alpha", "charlie"],
1234 "partitions": ["bravo", "charlie"],
1235 "preemptible": true,
1238 container = retry_with_scheduling_parameters(param_hashes)
1239 actual = container.scheduling_parameters
1240 assert_equal(["alpha", "bravo", "charlie"], actual["partitions"]&.sort)
1241 assert_equal(false, actual["preemptible"] || false)
1242 assert_equal(30, actual["max_run_time"])
1245 test "retry requests with unset scheduling parameters" do
1246 configure_preemptible_instance_type
1247 param_hashes = vary_parameters(
1248 preemptible: [nil, true],
1249 partitions: [nil, ["alpha"]],
1250 max_run_time: [nil, 5],
1252 container = retry_with_scheduling_parameters(param_hashes)
1253 actual = container.scheduling_parameters
1254 assert_equal([], actual["partitions"] || [])
1255 assert_equal(false, actual["preemptible"] || false)
1256 assert_equal(0, actual["max_run_time"] || 0)
1259 test "retry requests with default scheduling parameters" do
1260 configure_preemptible_instance_type
1261 param_hashes = vary_parameters(
1262 preemptible: [false, true],
1263 partitions: [[], ["bravo"]],
1264 max_run_time: [0, 1],
1266 container = retry_with_scheduling_parameters(param_hashes)
1267 actual = container.scheduling_parameters
1268 assert_equal([], actual["partitions"] || [])
1269 assert_equal(false, actual["preemptible"] || false)
1270 assert_equal(0, actual["max_run_time"] || 0)
1273 def run_container(request_params, final_attrs)
1274 final_attrs[:state] ||= Container::Complete
1275 if final_attrs[:state] == Container::Complete
1276 final_attrs[:exit_code] ||= 0
1277 final_attrs[:log] ||= collections(:log_collection).portable_data_hash
1278 final_attrs[:output] ||= collections(:multilevel_collection_1).portable_data_hash
1280 container, request = minimal_new(request_params)
1282 container.update!(state: Container::Running)
1283 container.update!(final_attrs)
1284 return container, request
1287 def check_reuse_with_variations(default_keep_cache_ram, vary_attr, start_value, variations)
1288 container_params = REUSABLE_ATTRS_SLIM.merge(vary_attr => start_value)
1289 orig_default = Rails.configuration.Containers.DefaultKeepCacheRAM
1291 Rails.configuration.Containers.DefaultKeepCacheRAM = default_keep_cache_ram
1292 set_user_from_auth :admin
1293 expected, _ = run_container(container_params, {})
1294 variations.each do |variation|
1295 full_variation = REUSABLE_ATTRS_SLIM[vary_attr].merge(variation)
1296 parameters = REUSABLE_ATTRS_SLIM.merge(vary_attr => full_variation)
1297 actual = Container.find_reusable(parameters)
1298 assert_equal(expected.uuid, actual&.uuid,
1299 "request with #{vary_attr}=#{variation} did not reuse container")
1302 Rails.configuration.Containers.DefaultKeepCacheRAM = orig_default
1306 # Test that we can reuse a container with a known keep_cache_ram constraint,
1307 # no matter what keep_cache_* constraints the new request uses.
1308 [0, 2 << 30, 4 << 30].product(
1311 ).each do |(default_keep_cache_ram, multiplier, keep_disk_constraint)|
1312 test "reuse request with DefaultKeepCacheRAM=#{default_keep_cache_ram}, keep_cache_ram*=#{multiplier}, keep_cache_disk=#{keep_disk_constraint}" do
1313 runtime_constraints = REUSABLE_ATTRS_SLIM[:runtime_constraints].merge(
1314 "keep_cache_ram" => default_keep_cache_ram * multiplier,
1316 if not keep_disk_constraint
1317 # Simulate a container that predates keep_cache_disk by deleting
1318 # the constraint entirely.
1319 runtime_constraints.delete("keep_cache_disk")
1321 # Important values are:
1323 # * 2GiB, the minimum default keep_cache_disk
1324 # * 8GiB, the default keep_cache_disk based on container ram
1325 # * 32GiB, the maximum default keep_cache_disk
1326 # Check these values and values in between.
1327 vary_values = [0, 1, 2, 6, 8, 10, 32, 33].map { |v| v << 30 }.to_a
1328 variations = vary_parameters(keep_cache_ram: vary_values)
1329 .chain(vary_parameters(keep_cache_disk: vary_values))
1330 check_reuse_with_variations(
1331 default_keep_cache_ram,
1332 :runtime_constraints,
1333 runtime_constraints,
1339 # Test that we can reuse a container with a known keep_cache_disk constraint,
1340 # no matter what keep_cache_* constraints the new request uses.
1341 # keep_cache_disk values are the important values discussed in the test above.
1342 [0, 2 << 30, 4 << 30]
1343 .product([0, 2 << 30, 8 << 30, 32 << 30])
1344 .each do |(default_keep_cache_ram, keep_cache_disk)|
1345 test "reuse request with DefaultKeepCacheRAM=#{default_keep_cache_ram} and keep_cache_disk=#{keep_cache_disk}" do
1346 runtime_constraints = REUSABLE_ATTRS_SLIM[:runtime_constraints].merge(
1347 "keep_cache_disk" => keep_cache_disk,
1349 vary_values = [0, 1, 2, 6, 8, 10, 32, 33].map { |v| v << 30 }.to_a
1350 variations = vary_parameters(keep_cache_ram: vary_values)
1351 .chain(vary_parameters(keep_cache_disk: vary_values))
1352 check_reuse_with_variations(
1353 default_keep_cache_ram,
1354 :runtime_constraints,
1355 runtime_constraints,
1361 # Test that a container request can reuse a container with an exactly
1362 # matching keep_cache_* constraint, no matter what the defaults.
1363 [0, 2 << 30, 4 << 30].product(
1364 ["keep_cache_disk", "keep_cache_ram"],
1365 [135790, 13 << 30, 135 << 30],
1366 ).each do |(default_keep_cache_ram, constraint_key, constraint_value)|
1367 test "reuse request with #{constraint_key}=#{constraint_value} and DefaultKeepCacheRAM=#{default_keep_cache_ram}" do
1368 runtime_constraints = REUSABLE_ATTRS_SLIM[:runtime_constraints].merge(
1369 constraint_key => constraint_value,
1371 check_reuse_with_variations(
1372 default_keep_cache_ram,
1373 :runtime_constraints,
1374 runtime_constraints,
1375 [runtime_constraints],