13143: Prohibit identical path in mounts + secret_mounts.
[arvados.git] / services / api / test / unit / container_request_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6 require 'helpers/container_test_helper'
7 require 'helpers/docker_migration_helper'
8
9 class ContainerRequestTest < ActiveSupport::TestCase
10   include DockerMigrationHelper
11   include DbCurrentTime
12   include ContainerTestHelper
13
14   def create_minimal_req! attrs={}
15     defaults = {
16       command: ["echo", "foo"],
17       container_image: links(:docker_image_collection_tag).name,
18       cwd: "/tmp",
19       environment: {},
20       mounts: {"/out" => {"kind" => "tmp", "capacity" => 1000000}},
21       output_path: "/out",
22       runtime_constraints: {"vcpus" => 1, "ram" => 2},
23       name: "foo",
24       description: "bar",
25     }
26     cr = ContainerRequest.create!(defaults.merge(attrs))
27     cr.reload
28     return cr
29   end
30
31   def check_bogus_states cr
32     [nil, "Flubber"].each do |state|
33       assert_raises(ActiveRecord::RecordInvalid) do
34         cr.state = state
35         cr.save!
36       end
37       cr.reload
38     end
39   end
40
41   test "Container request create" do
42     set_user_from_auth :active
43     cr = create_minimal_req!
44
45     assert_nil cr.container_uuid
46     assert_equal 0, cr.priority
47
48     check_bogus_states cr
49
50     # Ensure we can modify all attributes
51     cr.command = ["echo", "foo3"]
52     cr.container_image = "img3"
53     cr.cwd = "/tmp3"
54     cr.environment = {"BUP" => "BOP"}
55     cr.mounts = {"BAR" => "BAZ"}
56     cr.output_path = "/tmp4"
57     cr.priority = 2
58     cr.runtime_constraints = {"vcpus" => 4}
59     cr.name = "foo3"
60     cr.description = "bar3"
61     cr.save!
62
63     assert_nil cr.container_uuid
64   end
65
66   [
67     {"vcpus" => 1},
68     {"vcpus" => 1, "ram" => nil},
69     {"vcpus" => 0, "ram" => 123},
70     {"vcpus" => "1", "ram" => "123"}
71   ].each do |invalid_constraints|
72     test "Create with #{invalid_constraints}" do
73       set_user_from_auth :active
74       assert_raises(ActiveRecord::RecordInvalid) do
75         cr = create_minimal_req!(state: "Committed",
76                                  priority: 1,
77                                  runtime_constraints: invalid_constraints)
78         cr.save!
79       end
80     end
81
82     test "Update with #{invalid_constraints}" do
83       set_user_from_auth :active
84       cr = create_minimal_req!(state: "Uncommitted", priority: 1)
85       cr.save!
86       assert_raises(ActiveRecord::RecordInvalid) do
87         cr = ContainerRequest.find_by_uuid cr.uuid
88         cr.update_attributes!(state: "Committed",
89                               runtime_constraints: invalid_constraints)
90       end
91     end
92   end
93
94   test "Update from fixture" do
95     set_user_from_auth :active
96     cr = ContainerRequest.find_by_uuid(container_requests(:running).uuid)
97     cr.update_attributes!(description: "New description")
98     assert_equal "New description", cr.description
99   end
100
101   test "Update with valid runtime constraints" do
102       set_user_from_auth :active
103       cr = create_minimal_req!(state: "Uncommitted", priority: 1)
104       cr.save!
105       cr = ContainerRequest.find_by_uuid cr.uuid
106       cr.update_attributes!(state: "Committed",
107                             runtime_constraints: {"vcpus" => 1, "ram" => 23})
108       assert_not_nil cr.container_uuid
109   end
110
111   test "Container request priority must be non-nil" do
112     set_user_from_auth :active
113     cr = create_minimal_req!
114     cr.priority = nil
115     cr.state = "Committed"
116     assert_raises(ActiveRecord::RecordInvalid) do
117       cr.save!
118     end
119   end
120
121   test "Container request commit" do
122     set_user_from_auth :active
123     cr = create_minimal_req!(runtime_constraints: {"vcpus" => 2, "ram" => 30})
124
125     assert_nil cr.container_uuid
126
127     cr.reload
128     cr.state = "Committed"
129     cr.priority = 1
130     cr.save!
131
132     cr.reload
133
134     assert_equal({"vcpus" => 2, "ram" => 30}, cr.runtime_constraints)
135
136     assert_not_nil cr.container_uuid
137     c = Container.find_by_uuid cr.container_uuid
138     assert_not_nil c
139     assert_equal ["echo", "foo"], c.command
140     assert_equal collections(:docker_image).portable_data_hash, c.container_image
141     assert_equal "/tmp", c.cwd
142     assert_equal({}, c.environment)
143     assert_equal({"/out" => {"kind"=>"tmp", "capacity"=>1000000}}, c.mounts)
144     assert_equal "/out", c.output_path
145     assert_equal({"keep_cache_ram"=>268435456, "vcpus" => 2, "ram" => 30}, c.runtime_constraints)
146     assert_equal 1, c.priority
147
148     assert_raises(ActiveRecord::RecordInvalid) do
149       cr.priority = nil
150       cr.save!
151     end
152
153     cr.priority = 0
154     cr.save!
155
156     cr.reload
157     c.reload
158     assert_equal 0, cr.priority
159     assert_equal 0, c.priority
160   end
161
162
163   test "Container request max priority" do
164     set_user_from_auth :active
165     cr = create_minimal_req!(priority: 5, state: "Committed")
166
167     c = Container.find_by_uuid cr.container_uuid
168     assert_equal 5, c.priority
169
170     cr2 = create_minimal_req!
171     cr2.priority = 10
172     cr2.state = "Committed"
173     cr2.container_uuid = cr.container_uuid
174     act_as_system_user do
175       cr2.save!
176     end
177
178     # cr and cr2 have priority 5 and 10, and are being satisfied by
179     # the same container c, so c's priority should be
180     # max(priority)=10.
181     c.reload
182     assert_equal 10, c.priority
183
184     cr2.update_attributes!(priority: 0)
185
186     c.reload
187     assert_equal 5, c.priority
188
189     cr.update_attributes!(priority: 0)
190
191     c.reload
192     assert_equal 0, c.priority
193   end
194
195
196   test "Independent container requests" do
197     set_user_from_auth :active
198     cr1 = create_minimal_req!(command: ["foo", "1"], priority: 5, state: "Committed")
199     cr2 = create_minimal_req!(command: ["foo", "2"], priority: 10, state: "Committed")
200
201     c1 = Container.find_by_uuid cr1.container_uuid
202     assert_equal 5, c1.priority
203
204     c2 = Container.find_by_uuid cr2.container_uuid
205     assert_equal 10, c2.priority
206
207     cr1.update_attributes!(priority: 0)
208
209     c1.reload
210     assert_equal 0, c1.priority
211
212     c2.reload
213     assert_equal 10, c2.priority
214   end
215
216   test "Request is finalized when its container is cancelled" do
217     set_user_from_auth :active
218     cr = create_minimal_req!(priority: 1, state: "Committed", container_count_max: 1)
219
220     act_as_system_user do
221       Container.find_by_uuid(cr.container_uuid).
222         update_attributes!(state: Container::Cancelled)
223     end
224
225     cr.reload
226     assert_equal "Final", cr.state
227   end
228
229   test "Request is finalized when its container is completed" do
230     set_user_from_auth :active
231     project = groups(:private)
232     cr = create_minimal_req!(owner_uuid: project.uuid,
233                              priority: 1,
234                              state: "Committed")
235
236     c = act_as_system_user do
237       c = Container.find_by_uuid(cr.container_uuid)
238       c.update_attributes!(state: Container::Locked)
239       c.update_attributes!(state: Container::Running)
240       c
241     end
242
243     cr.reload
244     assert_equal "Committed", cr.state
245
246     output_pdh = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
247     log_pdh = 'fa7aeb5140e2848d39b416daeef4ffc5+45'
248     act_as_system_user do
249       c.update_attributes!(state: Container::Complete,
250                            output: output_pdh,
251                            log: log_pdh)
252     end
253
254     cr.reload
255     assert_equal "Final", cr.state
256     ['output', 'log'].each do |out_type|
257       pdh = Container.find_by_uuid(cr.container_uuid).send(out_type)
258       assert_equal(1, Collection.where(portable_data_hash: pdh,
259                                        owner_uuid: project.uuid).count,
260                    "Container #{out_type} should be copied to #{project.uuid}")
261     end
262     assert_not_nil cr.output_uuid
263     assert_not_nil cr.log_uuid
264     output = Collection.find_by_uuid cr.output_uuid
265     assert_equal output_pdh, output.portable_data_hash
266     log = Collection.find_by_uuid cr.log_uuid
267     assert_equal log_pdh, log.portable_data_hash
268   end
269
270   test "Container makes container request, then is cancelled" do
271     set_user_from_auth :active
272     cr = create_minimal_req!(priority: 5, state: "Committed", container_count_max: 1)
273
274     c = Container.find_by_uuid cr.container_uuid
275     assert_equal 5, c.priority
276
277     cr2 = create_minimal_req!
278     cr2.update_attributes!(priority: 10, state: "Committed", requesting_container_uuid: c.uuid, command: ["echo", "foo2"], container_count_max: 1)
279     cr2.reload
280
281     c2 = Container.find_by_uuid cr2.container_uuid
282     assert_equal 10, c2.priority
283
284     act_as_system_user do
285       c.state = "Cancelled"
286       c.save!
287     end
288
289     cr.reload
290     assert_equal "Final", cr.state
291
292     cr2.reload
293     assert_equal 0, cr2.priority
294
295     c2.reload
296     assert_equal 0, c2.priority
297   end
298
299
300   test "Container makes container request, then changes priority" do
301     set_user_from_auth :active
302     cr = create_minimal_req!(priority: 5, state: "Committed", container_count_max: 1)
303
304     c = Container.find_by_uuid cr.container_uuid
305     assert_equal 5, c.priority
306
307     cr2 = create_minimal_req!
308     cr2.update_attributes!(priority: 5, state: "Committed", requesting_container_uuid: c.uuid, command: ["echo", "foo2"], container_count_max: 1)
309     cr2.reload
310
311     c2 = Container.find_by_uuid cr2.container_uuid
312     assert_equal 5, c2.priority
313
314     act_as_system_user do
315       c.priority = 10
316       c.save!
317     end
318
319     cr.reload
320
321     cr2.reload
322     assert_equal 10, cr2.priority
323
324     c2.reload
325     assert_equal 10, c2.priority
326   end
327
328   [
329     ['running_container_auth', 'zzzzz-dz642-runningcontainr', 12],
330     ['active_no_prefs', nil, 0],
331   ].each do |token, expected, expected_priority|
332     test "create as #{token} and expect requesting_container_uuid to be #{expected}" do
333       set_user_from_auth token
334       cr = ContainerRequest.create(container_image: "img", output_path: "/tmp", command: ["echo", "foo"])
335       assert_not_nil cr.uuid, 'uuid should be set for newly created container_request'
336       assert_equal expected, cr.requesting_container_uuid
337       assert_equal expected_priority, cr.priority
338     end
339   end
340
341   [[{"vcpus" => [2, nil]},
342     lambda { |resolved| resolved["vcpus"] == 2 }],
343    [{"vcpus" => [3, 7]},
344     lambda { |resolved| resolved["vcpus"] == 3 }],
345    [{"vcpus" => 4},
346     lambda { |resolved| resolved["vcpus"] == 4 }],
347    [{"ram" => [1000000000, 2000000000]},
348     lambda { |resolved| resolved["ram"] == 1000000000 }],
349    [{"ram" => [1234234234]},
350     lambda { |resolved| resolved["ram"] == 1234234234 }],
351   ].each do |rc, okfunc|
352     test "resolve runtime constraint range #{rc} to values" do
353       resolved = Container.resolve_runtime_constraints(rc)
354       assert(okfunc.call(resolved),
355              "container runtime_constraints was #{resolved.inspect}")
356     end
357   end
358
359   [[{"/out" => {
360         "kind" => "collection",
361         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
362         "path" => "/foo"}},
363     lambda do |resolved|
364       resolved["/out"] == {
365         "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
366         "kind" => "collection",
367         "path" => "/foo",
368       }
369     end],
370    [{"/out" => {
371         "kind" => "collection",
372         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
373         "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
374         "path" => "/foo"}},
375     lambda do |resolved|
376       resolved["/out"] == {
377         "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
378         "kind" => "collection",
379         "path" => "/foo",
380       }
381     end],
382   ].each do |mounts, okfunc|
383     test "resolve mounts #{mounts.inspect} to values" do
384       set_user_from_auth :active
385       resolved = Container.resolve_mounts(mounts)
386       assert(okfunc.call(resolved),
387              "Container.resolve_mounts returned #{resolved.inspect}")
388     end
389   end
390
391   test 'mount unreadable collection' do
392     set_user_from_auth :spectator
393     m = {
394       "/foo" => {
395         "kind" => "collection",
396         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
397         "path" => "/foo",
398       },
399     }
400     assert_raises(ArvadosModel::UnresolvableContainerError) do
401       Container.resolve_mounts(m)
402     end
403   end
404
405   test 'mount collection with mismatched UUID and PDH' do
406     set_user_from_auth :active
407     m = {
408       "/foo" => {
409         "kind" => "collection",
410         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
411         "portable_data_hash" => "fa7aeb5140e2848d39b416daeef4ffc5+45",
412         "path" => "/foo",
413       },
414     }
415     assert_raises(ArgumentError) do
416       Container.resolve_mounts(m)
417     end
418   end
419
420   ['arvados/apitestfixture:latest',
421    'arvados/apitestfixture',
422    'd8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678',
423   ].each do |tag|
424     test "Container.resolve_container_image(#{tag.inspect})" do
425       set_user_from_auth :active
426       resolved = Container.resolve_container_image(tag)
427       assert_equal resolved, collections(:docker_image).portable_data_hash
428     end
429   end
430
431   test "Container.resolve_container_image(pdh)" do
432     set_user_from_auth :active
433     [[:docker_image, 'v1'], [:docker_image_1_12, 'v2']].each do |coll, ver|
434       Rails.configuration.docker_image_formats = [ver]
435       pdh = collections(coll).portable_data_hash
436       resolved = Container.resolve_container_image(pdh)
437       assert_equal resolved, pdh
438     end
439   end
440
441   ['acbd18db4cc2f85cedef654fccc4a4d8+3',
442    'ENOEXIST',
443    'arvados/apitestfixture:ENOEXIST',
444   ].each do |img|
445     test "container_image_for_container(#{img.inspect}) => 422" do
446       set_user_from_auth :active
447       assert_raises(ArvadosModel::UnresolvableContainerError) do
448         Container.resolve_container_image(img)
449       end
450     end
451   end
452
453   test "migrated docker image" do
454     Rails.configuration.docker_image_formats = ['v2']
455     add_docker19_migration_link
456
457     # Test that it returns only v2 images even though request is for v1 image.
458
459     set_user_from_auth :active
460     cr = create_minimal_req!(command: ["true", "1"],
461                              container_image: collections(:docker_image).portable_data_hash)
462     assert_equal(Container.resolve_container_image(cr.container_image),
463                  collections(:docker_image_1_12).portable_data_hash)
464
465     cr = create_minimal_req!(command: ["true", "2"],
466                              container_image: links(:docker_image_collection_tag).name)
467     assert_equal(Container.resolve_container_image(cr.container_image),
468                  collections(:docker_image_1_12).portable_data_hash)
469   end
470
471   test "use unmigrated docker image" do
472     Rails.configuration.docker_image_formats = ['v1']
473     add_docker19_migration_link
474
475     # Test that it returns only supported v1 images even though there is a
476     # migration link.
477
478     set_user_from_auth :active
479     cr = create_minimal_req!(command: ["true", "1"],
480                              container_image: collections(:docker_image).portable_data_hash)
481     assert_equal(Container.resolve_container_image(cr.container_image),
482                  collections(:docker_image).portable_data_hash)
483
484     cr = create_minimal_req!(command: ["true", "2"],
485                              container_image: links(:docker_image_collection_tag).name)
486     assert_equal(Container.resolve_container_image(cr.container_image),
487                  collections(:docker_image).portable_data_hash)
488   end
489
490   test "incompatible docker image v1" do
491     Rails.configuration.docker_image_formats = ['v1']
492     add_docker19_migration_link
493
494     # Don't return unsupported v2 image even if we ask for it directly.
495     set_user_from_auth :active
496     cr = create_minimal_req!(command: ["true", "1"],
497                              container_image: collections(:docker_image_1_12).portable_data_hash)
498     assert_raises(ArvadosModel::UnresolvableContainerError) do
499       Container.resolve_container_image(cr.container_image)
500     end
501   end
502
503   test "incompatible docker image v2" do
504     Rails.configuration.docker_image_formats = ['v2']
505     # No migration link, don't return unsupported v1 image,
506
507     set_user_from_auth :active
508     cr = create_minimal_req!(command: ["true", "1"],
509                              container_image: collections(:docker_image).portable_data_hash)
510     assert_raises(ArvadosModel::UnresolvableContainerError) do
511       Container.resolve_container_image(cr.container_image)
512     end
513     cr = create_minimal_req!(command: ["true", "2"],
514                              container_image: links(:docker_image_collection_tag).name)
515     assert_raises(ArvadosModel::UnresolvableContainerError) do
516       Container.resolve_container_image(cr.container_image)
517     end
518   end
519
520   test "requestor can retrieve container owned by dispatch" do
521     assert_not_empty Container.readable_by(users(:admin)).where(uuid: containers(:running).uuid)
522     assert_not_empty Container.readable_by(users(:active)).where(uuid: containers(:running).uuid)
523     assert_empty Container.readable_by(users(:spectator)).where(uuid: containers(:running).uuid)
524   end
525
526   [
527     [{"var" => "value1"}, {"var" => "value1"}, nil],
528     [{"var" => "value1"}, {"var" => "value1"}, true],
529     [{"var" => "value1"}, {"var" => "value1"}, false],
530     [{"var" => "value1"}, {"var" => "value2"}, nil],
531   ].each do |env1, env2, use_existing|
532     test "Container request #{((env1 == env2) and (use_existing.nil? or use_existing == true)) ? 'does' : 'does not'} reuse container when committed#{use_existing.nil? ? '' : use_existing ? ' and use_existing == true' : ' and use_existing == false'}" do
533       common_attrs = {cwd: "test",
534                       priority: 1,
535                       command: ["echo", "hello"],
536                       output_path: "test",
537                       runtime_constraints: {"vcpus" => 4,
538                                             "ram" => 12000000000},
539                       mounts: {"test" => {"kind" => "json"}}}
540       set_user_from_auth :active
541       cr1 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Committed,
542                                                     environment: env1}))
543       if use_existing.nil?
544         # Testing with use_existing default value
545         cr2 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Uncommitted,
546                                                       environment: env2}))
547       else
548
549         cr2 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Uncommitted,
550                                                       environment: env2,
551                                                       use_existing: use_existing}))
552       end
553       assert_not_nil cr1.container_uuid
554       assert_nil cr2.container_uuid
555
556       # Update cr2 to commited state and check for container equality on different cases:
557       # * When env1 and env2 are equal and use_existing is true, the same container
558       #   should be assigned.
559       # * When use_existing is false, a different container should be assigned.
560       # * When env1 and env2 are different, a different container should be assigned.
561       cr2.update_attributes!({state: ContainerRequest::Committed})
562       assert_equal (cr2.use_existing == true and (env1 == env2)),
563                    (cr1.container_uuid == cr2.container_uuid)
564     end
565   end
566
567   test "requesting_container_uuid at create is not allowed" do
568     set_user_from_auth :active
569     assert_raises(ActiveRecord::RecordNotSaved) do
570       create_minimal_req!(state: "Uncommitted", priority: 1, requesting_container_uuid: 'youcantdothat')
571     end
572   end
573
574   test "Retry on container cancelled" do
575     set_user_from_auth :active
576     cr = create_minimal_req!(priority: 1, state: "Committed", container_count_max: 2)
577     cr2 = create_minimal_req!(priority: 1, state: "Committed", container_count_max: 2, command: ["echo", "baz"])
578     prev_container_uuid = cr.container_uuid
579
580     c = act_as_system_user do
581       c = Container.find_by_uuid(cr.container_uuid)
582       c.update_attributes!(state: Container::Locked)
583       c.update_attributes!(state: Container::Running)
584       c
585     end
586
587     cr.reload
588     cr2.reload
589     assert_equal "Committed", cr.state
590     assert_equal prev_container_uuid, cr.container_uuid
591     assert_not_equal cr2.container_uuid, cr.container_uuid
592     prev_container_uuid = cr.container_uuid
593
594     act_as_system_user do
595       c.update_attributes!(state: Container::Cancelled)
596     end
597
598     cr.reload
599     cr2.reload
600     assert_equal "Committed", cr.state
601     assert_not_equal prev_container_uuid, cr.container_uuid
602     assert_not_equal cr2.container_uuid, cr.container_uuid
603     prev_container_uuid = cr.container_uuid
604
605     c = act_as_system_user do
606       c = Container.find_by_uuid(cr.container_uuid)
607       c.update_attributes!(state: Container::Cancelled)
608       c
609     end
610
611     cr.reload
612     cr2.reload
613     assert_equal "Final", cr.state
614     assert_equal prev_container_uuid, cr.container_uuid
615     assert_not_equal cr2.container_uuid, cr.container_uuid
616   end
617
618   test "Output collection name setting using output_name with name collision resolution" do
619     set_user_from_auth :active
620     output_name = 'unimaginative name'
621     Collection.create!(name: output_name)
622
623     cr = create_minimal_req!(priority: 1,
624                              state: ContainerRequest::Committed,
625                              output_name: output_name)
626     run_container(cr)
627     cr.reload
628     assert_equal ContainerRequest::Final, cr.state
629     output_coll = Collection.find_by_uuid(cr.output_uuid)
630     # Make sure the resulting output collection name include the original name
631     # plus the date
632     assert_not_equal output_name, output_coll.name,
633                      "more than one collection with the same owner and name"
634     assert output_coll.name.include?(output_name),
635            "New name should include original name"
636     assert_match /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/, output_coll.name,
637                  "New name should include ISO8601 date"
638   end
639
640   [[0, :check_output_ttl_0],
641    [1, :check_output_ttl_1s],
642    [365*86400, :check_output_ttl_1y],
643   ].each do |ttl, checker|
644     test "output_ttl=#{ttl}" do
645       act_as_user users(:active) do
646         cr = create_minimal_req!(priority: 1,
647                                  state: ContainerRequest::Committed,
648                                  output_name: 'foo',
649                                  output_ttl: ttl)
650         run_container(cr)
651         cr.reload
652         output = Collection.find_by_uuid(cr.output_uuid)
653         send(checker, db_current_time, output.trash_at, output.delete_at)
654       end
655     end
656   end
657
658   def check_output_ttl_0(now, trash, delete)
659     assert_nil(trash)
660     assert_nil(delete)
661   end
662
663   def check_output_ttl_1s(now, trash, delete)
664     assert_not_nil(trash)
665     assert_not_nil(delete)
666     assert_in_delta(trash, now + 1.second, 10)
667     assert_in_delta(delete, now + Rails.configuration.blob_signature_ttl.second, 10)
668   end
669
670   def check_output_ttl_1y(now, trash, delete)
671     year = (86400*365).second
672     assert_not_nil(trash)
673     assert_not_nil(delete)
674     assert_in_delta(trash, now + year, 10)
675     assert_in_delta(delete, now + year, 10)
676   end
677
678   def run_container(cr)
679     act_as_system_user do
680       c = Container.find_by_uuid(cr.container_uuid)
681       c.update_attributes!(state: Container::Locked)
682       c.update_attributes!(state: Container::Running)
683       c.update_attributes!(state: Container::Complete,
684                            exit_code: 0,
685                            output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45',
686                            log: 'fa7aeb5140e2848d39b416daeef4ffc5+45')
687       c
688     end
689   end
690
691   test "Finalize committed request when reusing a finished container" do
692     set_user_from_auth :active
693     cr = create_minimal_req!(priority: 1, state: ContainerRequest::Committed)
694     cr.reload
695     assert_equal ContainerRequest::Committed, cr.state
696     run_container(cr)
697     cr.reload
698     assert_equal ContainerRequest::Final, cr.state
699
700     cr2 = create_minimal_req!(priority: 1, state: ContainerRequest::Committed)
701     assert_equal cr.container_uuid, cr2.container_uuid
702     assert_equal ContainerRequest::Final, cr2.state
703
704     cr3 = create_minimal_req!(priority: 1, state: ContainerRequest::Uncommitted)
705     assert_equal ContainerRequest::Uncommitted, cr3.state
706     cr3.update_attributes!(state: ContainerRequest::Committed)
707     assert_equal cr.container_uuid, cr3.container_uuid
708     assert_equal ContainerRequest::Final, cr3.state
709   end
710
711   [
712     [{"partitions" => ["fastcpu","vfastcpu", 100]}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
713     [{"partitions" => ["fastcpu","vfastcpu", 100]}, ContainerRequest::Uncommitted],
714     [{"partitions" => "fastcpu"}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
715     [{"partitions" => "fastcpu"}, ContainerRequest::Uncommitted],
716     [{"partitions" => ["fastcpu","vfastcpu"]}, ContainerRequest::Committed],
717   ].each do |sp, state, expected|
718     test "create container request with scheduling_parameters #{sp} in state #{state} and verify #{expected}" do
719       common_attrs = {cwd: "test",
720                       priority: 1,
721                       command: ["echo", "hello"],
722                       output_path: "test",
723                       scheduling_parameters: sp,
724                       mounts: {"test" => {"kind" => "json"}}}
725       set_user_from_auth :active
726
727       if expected == ActiveRecord::RecordInvalid
728         assert_raises(ActiveRecord::RecordInvalid) do
729           create_minimal_req!(common_attrs.merge({state: state}))
730         end
731       else
732         cr = create_minimal_req!(common_attrs.merge({state: state}))
733         assert_equal sp, cr.scheduling_parameters
734
735         if state == ContainerRequest::Committed
736           c = Container.find_by_uuid(cr.container_uuid)
737           assert_equal sp, c.scheduling_parameters
738         end
739       end
740     end
741   end
742
743   [['Committed', true, {name: "foobar", priority: 123}],
744    ['Committed', false, {container_count: 2}],
745    ['Committed', false, {container_count: 0}],
746    ['Committed', false, {container_count: nil}],
747    ['Final', false, {state: ContainerRequest::Committed, name: "foobar"}],
748    ['Final', false, {name: "foobar", priority: 123}],
749    ['Final', false, {name: "foobar", output_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
750    ['Final', false, {name: "foobar", log_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
751    ['Final', false, {log_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
752    ['Final', false, {priority: 123}],
753    ['Final', false, {mounts: {}}],
754    ['Final', false, {container_count: 2}],
755    ['Final', true, {name: "foobar"}],
756    ['Final', true, {name: "foobar", description: "baz"}],
757   ].each do |state, permitted, updates|
758     test "state=#{state} can#{'not' if !permitted} update #{updates.inspect}" do
759       act_as_user users(:active) do
760         cr = create_minimal_req!(priority: 1,
761                                  state: "Committed",
762                                  container_count_max: 1)
763         case state
764         when 'Committed'
765           # already done
766         when 'Final'
767           act_as_system_user do
768             Container.find_by_uuid(cr.container_uuid).
769               update_attributes!(state: Container::Cancelled)
770           end
771           cr.reload
772         else
773           raise 'broken test case'
774         end
775         assert_equal state, cr.state
776         if permitted
777           assert cr.update_attributes!(updates)
778         else
779           assert_raises(ActiveRecord::RecordInvalid) do
780             cr.update_attributes!(updates)
781           end
782         end
783       end
784     end
785   end
786
787   test "delete container_request and check its container's priority" do
788     act_as_user users(:active) do
789       cr = ContainerRequest.find_by_uuid container_requests(:running_to_be_deleted).uuid
790
791       # initially the cr's container has priority > 0
792       c = Container.find_by_uuid(cr.container_uuid)
793       assert_equal 1, c.priority
794
795       # destroy the cr
796       assert_nothing_raised {cr.destroy}
797
798       # the cr's container now has priority of 0
799       c = Container.find_by_uuid(cr.container_uuid)
800       assert_equal 0, c.priority
801     end
802   end
803
804   test "delete container_request in final state and expect no error due to before_destroy callback" do
805     act_as_user users(:active) do
806       cr = ContainerRequest.find_by_uuid container_requests(:completed).uuid
807       assert_nothing_raised {cr.destroy}
808     end
809   end
810
811   test "Container request valid priority" do
812     set_user_from_auth :active
813     cr = create_minimal_req!
814
815     assert_raises(ActiveRecord::RecordInvalid) do
816       cr.priority = -1
817       cr.save!
818     end
819
820     cr.priority = 0
821     cr.save!
822
823     cr.priority = 1
824     cr.save!
825
826     cr.priority = 500
827     cr.save!
828
829     cr.priority = 999
830     cr.save!
831
832     cr.priority = 1000
833     cr.save!
834
835     assert_raises(ActiveRecord::RecordInvalid) do
836       cr.priority = 1001
837       cr.save!
838     end
839   end
840
841   test "reuse container with same secret_mounts" do
842     set_user_from_auth :active
843     cr1 = create_minimal_req!(state: "Committed", priority: 1)
844     cr1.save!
845     cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: {})
846     cr2.save!
847     assert_not_nil cr1.container_uuid
848     assert_equal cr1.container_uuid, cr2.container_uuid
849   end
850
851   secrets = {"/foo" => {"kind" => "binary"}}
852   [
853     [true, nil, nil],
854     [true, nil, {}],
855     [true, {}, {}],
856     [true, secrets, secrets],
857     [false, nil, secrets],
858     [false, {}, secrets],
859   ].each do |expect_reuse, sm1, sm2|
860     test "container reuse secret_mounts #{sm1.inspect}, #{sm2.inspect}" do
861       set_user_from_auth :active
862       cr1 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm1)
863       cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm2)
864       assert_not_nil cr1.container_uuid
865       assert_not_nil cr2.container_uuid
866       if expect_reuse
867         assert_equal cr1.container_uuid, cr2.container_uuid
868       else
869         assert_not_equal cr1.container_uuid, cr2.container_uuid
870       end
871     end
872   end
873
874   test "scrub secret_mounts but reuse container for request with identical secret_mounts" do
875     set_user_from_auth :active
876     sm = {'/secret/foo' => {'kind' => 'text', 'content' => secret_string}}
877     cr1 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm.dup)
878     run_container(cr1)
879     cr1.reload
880
881     # secret_mounts scrubbed from db
882     c = Container.where(uuid: cr1.container_uuid).first
883     assert_equal({}, c.secret_mounts)
884     assert_equal({}, cr1.secret_mounts)
885
886     # can reuse container if secret_mounts match
887     cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm.dup)
888     assert_equal cr1.container_uuid, cr2.container_uuid
889
890     # don't reuse container if secret_mounts don't match
891     cr3 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: {})
892     assert_not_equal cr1.container_uuid, cr3.container_uuid
893
894     assert_no_secrets_logged
895   end
896
897   test "not reuse container with different secret_mounts" do
898     secrets = {"/foo" => {"kind" => "binary"}}
899     set_user_from_auth :active
900     cr1 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: secrets.dup)
901     cr1.save!
902     cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: secrets.dup)
903     cr2.save!
904     assert_not_nil cr1.container_uuid
905     assert_equal cr1.container_uuid, cr2.container_uuid
906   end
907
908   test "conflicting key in mounts and secret_mounts" do
909     sm = {'/secret/foo' => {'kind' => 'text', 'content' => secret_string}}
910     set_user_from_auth :active
911     cr = create_minimal_req!
912     assert_equal false, cr.update_attributes(state: "Committed",
913                                              priority: 1,
914                                              mounts: cr.mounts.merge(sm),
915                                              secret_mounts: sm)
916     assert_equal [:secret_mounts], cr.errors.messages.keys
917   end
918 end