Merge branch '17004-properties-on-output' refs #17004
[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 require 'arvados/collection'
9
10 class ContainerRequestTest < ActiveSupport::TestCase
11   include DockerMigrationHelper
12   include DbCurrentTime
13   include ContainerTestHelper
14
15   def with_container_auth(ctr)
16     auth_was = Thread.current[:api_client_authorization]
17     client_was = Thread.current[:api_client]
18     token_was = Thread.current[:token]
19     user_was = Thread.current[:user]
20     auth = ApiClientAuthorization.find_by_uuid(ctr.auth_uuid)
21     Thread.current[:api_client_authorization] = auth
22     Thread.current[:api_client] = auth.api_client
23     Thread.current[:token] = auth.token
24     Thread.current[:user] = auth.user
25     begin
26       yield
27     ensure
28       Thread.current[:api_client_authorization] = auth_was
29       Thread.current[:api_client] = client_was
30       Thread.current[:token] = token_was
31       Thread.current[:user] = user_was
32     end
33   end
34
35   def lock_and_run(ctr)
36       act_as_system_user do
37         ctr.update_attributes!(state: Container::Locked)
38         ctr.update_attributes!(state: Container::Running)
39       end
40   end
41
42   def create_minimal_req! attrs={}
43     defaults = {
44       command: ["echo", "foo"],
45       container_image: links(:docker_image_collection_tag).name,
46       cwd: "/tmp",
47       environment: {},
48       mounts: {"/out" => {"kind" => "tmp", "capacity" => 1000000}},
49       output_path: "/out",
50       runtime_constraints: {"vcpus" => 1, "ram" => 2},
51       name: "foo",
52       description: "bar",
53     }
54     cr = ContainerRequest.create!(defaults.merge(attrs))
55     cr.reload
56     return cr
57   end
58
59   def check_bogus_states cr
60     [nil, "Flubber"].each do |state|
61       assert_raises(ActiveRecord::RecordInvalid) do
62         cr.state = state
63         cr.save!
64       end
65       cr.reload
66     end
67   end
68
69   def configure_preemptible_instance_type
70     Rails.configuration.InstanceTypes = ConfigLoader.to_OrderedOptions({
71       "a1.small.pre" => {
72         "Preemptible" => true,
73         "Price" => 0.1,
74         "ProviderType" => "a1.small",
75         "VCPUs" => 1,
76         "RAM" => 1000000000,
77       },
78     })
79   end
80
81   test "Container request create" do
82     set_user_from_auth :active
83     cr = create_minimal_req!
84
85     assert_nil cr.container_uuid
86     assert_equal 0, cr.priority
87
88     check_bogus_states cr
89
90     # Ensure we can modify all attributes
91     cr.command = ["echo", "foo3"]
92     cr.container_image = "img3"
93     cr.cwd = "/tmp3"
94     cr.environment = {"BUP" => "BOP"}
95     cr.mounts = {"BAR" => {"kind" => "BAZ"}}
96     cr.output_path = "/tmp4"
97     cr.priority = 2
98     cr.runtime_constraints = {"vcpus" => 4}
99     cr.name = "foo3"
100     cr.description = "bar3"
101     cr.save!
102
103     assert_nil cr.container_uuid
104   end
105
106   [
107     {"runtime_constraints" => {"vcpus" => 1}},
108     {"runtime_constraints" => {"vcpus" => 1, "ram" => nil}},
109     {"runtime_constraints" => {"vcpus" => 0, "ram" => 123}},
110     {"runtime_constraints" => {"vcpus" => "1", "ram" => "123"}},
111     {"mounts" => {"FOO" => "BAR"}},
112     {"mounts" => {"FOO" => {}}},
113     {"mounts" => {"FOO" => {"kind" => "tmp", "capacity" => 42.222}}},
114     {"command" => ["echo", 55]},
115     {"environment" => {"FOO" => 55}}
116   ].each do |value|
117     test "Create with invalid #{value}" do
118       set_user_from_auth :active
119       assert_raises(ActiveRecord::RecordInvalid) do
120         cr = create_minimal_req!({state: "Committed",
121                priority: 1}.merge(value))
122         cr.save!
123       end
124     end
125
126     test "Update with invalid #{value}" do
127       set_user_from_auth :active
128       cr = create_minimal_req!(state: "Uncommitted", priority: 1)
129       cr.save!
130       assert_raises(ActiveRecord::RecordInvalid) do
131         cr = ContainerRequest.find_by_uuid cr.uuid
132         cr.update_attributes!({state: "Committed",
133                                priority: 1}.merge(value))
134       end
135     end
136   end
137
138   test "Update from fixture" do
139     set_user_from_auth :active
140     cr = ContainerRequest.find_by_uuid(container_requests(:running).uuid)
141     cr.update_attributes!(description: "New description")
142     assert_equal "New description", cr.description
143   end
144
145   test "Update with valid runtime constraints" do
146       set_user_from_auth :active
147       cr = create_minimal_req!(state: "Uncommitted", priority: 1)
148       cr.save!
149       cr = ContainerRequest.find_by_uuid cr.uuid
150       cr.update_attributes!(state: "Committed",
151                             runtime_constraints: {"vcpus" => 1, "ram" => 23})
152       assert_not_nil cr.container_uuid
153   end
154
155   test "Container request priority must be non-nil" do
156     set_user_from_auth :active
157     cr = create_minimal_req!
158     cr.priority = nil
159     cr.state = "Committed"
160     assert_raises(ActiveRecord::RecordInvalid) do
161       cr.save!
162     end
163   end
164
165   test "Container request commit" do
166     set_user_from_auth :active
167     cr = create_minimal_req!(runtime_constraints: {"vcpus" => 2, "ram" => 30})
168
169     assert_nil cr.container_uuid
170
171     cr.reload
172     cr.state = "Committed"
173     cr.priority = 1
174     cr.save!
175
176     cr.reload
177
178     assert ({"vcpus" => 2, "ram" => 30}.to_a - cr.runtime_constraints.to_a).empty?
179
180     assert_not_nil cr.container_uuid
181     c = Container.find_by_uuid cr.container_uuid
182     assert_not_nil c
183     assert_equal ["echo", "foo"], c.command
184     assert_equal collections(:docker_image).portable_data_hash, c.container_image
185     assert_equal "/tmp", c.cwd
186     assert_equal({}, c.environment)
187     assert_equal({"/out" => {"kind"=>"tmp", "capacity"=>1000000}}, c.mounts)
188     assert_equal "/out", c.output_path
189     assert ({"keep_cache_ram"=>268435456, "vcpus" => 2, "ram" => 30}.to_a - c.runtime_constraints.to_a).empty?
190     assert_operator 0, :<, c.priority
191
192     assert_raises(ActiveRecord::RecordInvalid) do
193       cr.priority = nil
194       cr.save!
195     end
196
197     cr.priority = 0
198     cr.save!
199
200     cr.reload
201     c.reload
202     assert_equal 0, cr.priority
203     assert_equal 0, c.priority
204   end
205
206   test "Independent container requests" do
207     set_user_from_auth :active
208     cr1 = create_minimal_req!(command: ["foo", "1"], priority: 5, state: "Committed")
209     cr2 = create_minimal_req!(command: ["foo", "2"], priority: 10, state: "Committed")
210
211     c1 = Container.find_by_uuid cr1.container_uuid
212     assert_operator 0, :<, c1.priority
213
214     c2 = Container.find_by_uuid cr2.container_uuid
215     assert_operator c1.priority, :<, c2.priority
216     c2priority_was = c2.priority
217
218     cr1.update_attributes!(priority: 0)
219
220     c1.reload
221     assert_equal 0, c1.priority
222
223     c2.reload
224     assert_equal c2priority_was, c2.priority
225   end
226
227   test "Request is finalized when its container is cancelled" do
228     set_user_from_auth :active
229     cr = create_minimal_req!(priority: 1, state: "Committed", container_count_max: 1)
230     assert_equal users(:active).uuid, cr.modified_by_user_uuid
231
232     act_as_system_user do
233       Container.find_by_uuid(cr.container_uuid).
234         update_attributes!(state: Container::Cancelled)
235     end
236
237     cr.reload
238     assert_equal "Final", cr.state
239     assert_equal users(:active).uuid, cr.modified_by_user_uuid
240   end
241
242   test "Request is finalized when its container is completed" do
243     set_user_from_auth :active
244     project = groups(:private)
245     cr = create_minimal_req!(owner_uuid: project.uuid,
246                              priority: 1,
247                              state: "Committed")
248     assert_equal users(:active).uuid, cr.modified_by_user_uuid
249
250     c = act_as_system_user do
251       c = Container.find_by_uuid(cr.container_uuid)
252       c.update_attributes!(state: Container::Locked)
253       c.update_attributes!(state: Container::Running)
254       c
255     end
256
257     cr.reload
258     assert_equal "Committed", cr.state
259
260     output_pdh = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
261     log_pdh = 'fa7aeb5140e2848d39b416daeef4ffc5+45'
262     act_as_system_user do
263       c.update_attributes!(state: Container::Complete,
264                            output: output_pdh,
265                            log: log_pdh)
266     end
267
268     cr.reload
269     assert_equal "Final", cr.state
270     assert_equal users(:active).uuid, cr.modified_by_user_uuid
271
272     assert_not_nil cr.output_uuid
273     assert_not_nil cr.log_uuid
274     output = Collection.find_by_uuid cr.output_uuid
275     assert_equal output_pdh, output.portable_data_hash
276     assert_equal output.owner_uuid, project.uuid, "Container output should be copied to #{project.uuid}"
277     assert_not_nil output.modified_at
278
279     log = Collection.find_by_uuid cr.log_uuid
280     assert_equal log.manifest_text, ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar
281 ./log\\040for\\040container\\040#{cr.container_uuid} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
282
283     assert_equal log.owner_uuid, project.uuid, "Container log should be copied to #{project.uuid}"
284   end
285
286   # This tests bug report #16144
287   test "Request is finalized when its container is completed even when log & output don't exist" do
288     set_user_from_auth :active
289     project = groups(:private)
290     cr = create_minimal_req!(owner_uuid: project.uuid,
291                              priority: 1,
292                              state: "Committed")
293     assert_equal users(:active).uuid, cr.modified_by_user_uuid
294
295     output_pdh = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
296     log_pdh = 'fa7aeb5140e2848d39b416daeef4ffc5+45'
297
298     c = act_as_system_user do
299       c = Container.find_by_uuid(cr.container_uuid)
300       c.update_attributes!(state: Container::Locked)
301       c.update_attributes!(state: Container::Running,
302                            output: output_pdh,
303                            log: log_pdh)
304       c
305     end
306
307     cr.reload
308     assert_equal "Committed", cr.state
309
310     act_as_system_user do
311       Collection.where(portable_data_hash: output_pdh).delete_all
312       Collection.where(portable_data_hash: log_pdh).delete_all
313       c.update_attributes!(state: Container::Complete)
314     end
315
316     cr.reload
317     assert_equal "Final", cr.state
318   end
319
320   # This tests bug report #16144
321   test "Can destroy CR even if its container doesn't exist" do
322     set_user_from_auth :active
323     project = groups(:private)
324     cr = create_minimal_req!(owner_uuid: project.uuid,
325                              priority: 1,
326                              state: "Committed")
327     assert_equal users(:active).uuid, cr.modified_by_user_uuid
328
329     c = act_as_system_user do
330       c = Container.find_by_uuid(cr.container_uuid)
331       c.update_attributes!(state: Container::Locked)
332       c.update_attributes!(state: Container::Running)
333       c
334     end
335
336     cr.reload
337     assert_equal "Committed", cr.state
338
339     cr_uuid = cr.uuid
340     act_as_system_user do
341       Container.find_by_uuid(cr.container_uuid).destroy
342       cr.destroy
343     end
344     assert_nil ContainerRequest.find_by_uuid(cr_uuid)
345   end
346
347   test "Container makes container request, then is cancelled" do
348     set_user_from_auth :active
349     cr = create_minimal_req!(priority: 5, state: "Committed", container_count_max: 1)
350
351     c = Container.find_by_uuid cr.container_uuid
352     assert_operator 0, :<, c.priority
353     lock_and_run(c)
354
355     cr2 = with_container_auth(c) do
356       create_minimal_req!(priority: 10, state: "Committed", container_count_max: 1, command: ["echo", "foo2"])
357     end
358     assert_equal c.uuid, cr2.requesting_container_uuid
359     assert_equal users(:active).uuid, cr2.modified_by_user_uuid
360
361     c2 = Container.find_by_uuid cr2.container_uuid
362     assert_operator 0, :<, c2.priority
363
364     act_as_system_user do
365       c.state = "Cancelled"
366       c.save!
367     end
368
369     cr.reload
370     assert_equal "Final", cr.state
371
372     cr2.reload
373     assert_equal 0, cr2.priority
374     assert_equal users(:active).uuid, cr2.modified_by_user_uuid
375
376     c2.reload
377     assert_equal 0, c2.priority
378   end
379
380   test "child container priority follows same ordering as corresponding top-level ancestors" do
381     findctr = lambda { |cr| Container.find_by_uuid(cr.container_uuid) }
382
383     set_user_from_auth :active
384
385     toplevel_crs = [
386       create_minimal_req!(priority: 5, state: "Committed", environment: {"workflow" => "0"}),
387       create_minimal_req!(priority: 5, state: "Committed", environment: {"workflow" => "1"}),
388       create_minimal_req!(priority: 5, state: "Committed", environment: {"workflow" => "2"}),
389     ]
390     parents = toplevel_crs.map(&findctr)
391
392     children = parents.map do |parent|
393       lock_and_run(parent)
394       with_container_auth(parent) do
395         create_minimal_req!(state: "Committed",
396                             priority: 1,
397                             environment: {"child" => parent.environment["workflow"]})
398       end
399     end.map(&findctr)
400
401     grandchildren = children.reverse.map do |child|
402       lock_and_run(child)
403       with_container_auth(child) do
404         create_minimal_req!(state: "Committed",
405                             priority: 1,
406                             environment: {"grandchild" => child.environment["child"]})
407       end
408     end.reverse.map(&findctr)
409
410     shared_grandchildren = children.map do |child|
411       with_container_auth(child) do
412         create_minimal_req!(state: "Committed",
413                             priority: 1,
414                             environment: {"grandchild" => "shared"})
415       end
416     end.map(&findctr)
417
418     assert_equal shared_grandchildren[0].uuid, shared_grandchildren[1].uuid
419     assert_equal shared_grandchildren[0].uuid, shared_grandchildren[2].uuid
420     shared_grandchild = shared_grandchildren[0]
421
422     set_user_from_auth :active
423
424     # parents should be prioritized by submit time.
425     assert_operator parents[0].priority, :>, parents[1].priority
426     assert_operator parents[1].priority, :>, parents[2].priority
427
428     # children should be prioritized in same order as their respective
429     # parents.
430     assert_operator children[0].priority, :>, children[1].priority
431     assert_operator children[1].priority, :>, children[2].priority
432
433     # grandchildren should also be prioritized in the same order,
434     # despite having been submitted in the opposite order.
435     assert_operator grandchildren[0].priority, :>, grandchildren[1].priority
436     assert_operator grandchildren[1].priority, :>, grandchildren[2].priority
437
438     # shared grandchild container should be prioritized above
439     # everything that isn't needed by parents[0], but not above
440     # earlier-submitted descendants of parents[0]
441     assert_operator shared_grandchild.priority, :>, grandchildren[1].priority
442     assert_operator shared_grandchild.priority, :>, children[1].priority
443     assert_operator shared_grandchild.priority, :>, parents[1].priority
444     assert_operator shared_grandchild.priority, :<=, grandchildren[0].priority
445     assert_operator shared_grandchild.priority, :<=, children[0].priority
446     assert_operator shared_grandchild.priority, :<=, parents[0].priority
447
448     # increasing priority of the most recent toplevel container should
449     # reprioritize all of its descendants (including the shared
450     # grandchild) above everything else.
451     toplevel_crs[2].update_attributes!(priority: 72)
452     (parents + children + grandchildren + [shared_grandchild]).map(&:reload)
453     assert_operator shared_grandchild.priority, :>, grandchildren[0].priority
454     assert_operator shared_grandchild.priority, :>, children[0].priority
455     assert_operator shared_grandchild.priority, :>, parents[0].priority
456     assert_operator shared_grandchild.priority, :>, grandchildren[1].priority
457     assert_operator shared_grandchild.priority, :>, children[1].priority
458     assert_operator shared_grandchild.priority, :>, parents[1].priority
459     # ...but the shared container should not have higher priority than
460     # the earlier-submitted descendants of the high-priority workflow.
461     assert_operator shared_grandchild.priority, :<=, grandchildren[2].priority
462     assert_operator shared_grandchild.priority, :<=, children[2].priority
463     assert_operator shared_grandchild.priority, :<=, parents[2].priority
464   end
465
466   [
467     ['running_container_auth', 'zzzzz-dz642-runningcontainr', 501],
468     ['active_no_prefs', nil, 0]
469   ].each do |token, expected, expected_priority|
470     test "create as #{token} and expect requesting_container_uuid to be #{expected}" do
471       set_user_from_auth token
472       cr = create_minimal_req!
473       assert_not_nil cr.uuid, 'uuid should be set for newly created container_request'
474       assert_equal expected, cr.requesting_container_uuid
475       assert_equal expected_priority, cr.priority
476     end
477   end
478
479   [
480     ['running_container_auth', 'zzzzz-dz642-runningcontainr', 501],
481   ].each do |token, expected, expected_priority|
482     test "create as #{token} with requesting_container_uuid set and expect output to be intermediate" do
483       set_user_from_auth token
484       cr = create_minimal_req!
485       assert_not_nil cr.uuid, 'uuid should be set for newly created container_request'
486       assert_equal expected, cr.requesting_container_uuid
487       assert_equal expected_priority, cr.priority
488
489       cr.state = ContainerRequest::Committed
490       cr.save!
491
492       run_container(cr)
493       cr.reload
494       output = Collection.find_by_uuid(cr.output_uuid)
495       props = {"type": "intermediate", "container_request": cr.uuid}
496       assert_equal props.symbolize_keys, output.properties.symbolize_keys
497     end
498   end
499
500   test "create as container_runtime_token and expect requesting_container_uuid to be zzzzz-dz642-20isqbkl8xwnsao" do
501     set_user_from_auth :container_runtime_token
502     Thread.current[:token] = "#{Thread.current[:token]}/zzzzz-dz642-20isqbkl8xwnsao"
503     cr = ContainerRequest.create(container_image: "img", output_path: "/tmp", command: ["echo", "foo"])
504     assert_not_nil cr.uuid, 'uuid should be set for newly created container_request'
505     assert_equal 'zzzzz-dz642-20isqbkl8xwnsao', cr.requesting_container_uuid
506     assert_equal 1, cr.priority
507   end
508
509   [[{"vcpus" => [2, nil]},
510     lambda { |resolved| resolved["vcpus"] == 2 }],
511    [{"vcpus" => [3, 7]},
512     lambda { |resolved| resolved["vcpus"] == 3 }],
513    [{"vcpus" => 4},
514     lambda { |resolved| resolved["vcpus"] == 4 }],
515    [{"ram" => [1000000000, 2000000000]},
516     lambda { |resolved| resolved["ram"] == 1000000000 }],
517    [{"ram" => [1234234234]},
518     lambda { |resolved| resolved["ram"] == 1234234234 }],
519   ].each do |rc, okfunc|
520     test "resolve runtime constraint range #{rc} to values" do
521       resolved = Container.resolve_runtime_constraints(rc)
522       assert(okfunc.call(resolved),
523              "container runtime_constraints was #{resolved.inspect}")
524     end
525   end
526
527   [[{"/out" => {
528         "kind" => "collection",
529         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
530         "path" => "/foo"}},
531     lambda do |resolved|
532       resolved["/out"] == {
533         "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
534         "kind" => "collection",
535         "path" => "/foo",
536       }
537     end],
538    [{"/out" => {
539         "kind" => "collection",
540         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
541         "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
542         "path" => "/foo"}},
543     lambda do |resolved|
544       resolved["/out"] == {
545         "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
546         "kind" => "collection",
547         "path" => "/foo",
548       }
549     end],
550    [{"/out" => {
551       "kind" => "collection",
552       "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
553       "path" => "/foo"}},
554     lambda do |resolved|
555       resolved["/out"] == {
556         "portable_data_hash" => "1f4b0bc7583c2a7f9102c395f4ffc5e3+45",
557         "kind" => "collection",
558         "path" => "/foo",
559       }
560     end],
561     # Empty collection
562     [{"/out" => {
563       "kind" => "collection",
564       "path" => "/foo"}},
565     lambda do |resolved|
566       resolved["/out"] == {
567         "kind" => "collection",
568         "path" => "/foo",
569       }
570     end],
571   ].each do |mounts, okfunc|
572     test "resolve mounts #{mounts.inspect} to values" do
573       set_user_from_auth :active
574       resolved = Container.resolve_mounts(mounts)
575       assert(okfunc.call(resolved),
576              "Container.resolve_mounts returned #{resolved.inspect}")
577     end
578   end
579
580   test 'mount unreadable collection' do
581     set_user_from_auth :spectator
582     m = {
583       "/foo" => {
584         "kind" => "collection",
585         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
586         "path" => "/foo",
587       },
588     }
589     assert_raises(ArvadosModel::UnresolvableContainerError) do
590       Container.resolve_mounts(m)
591     end
592   end
593
594   test 'mount collection with mismatched UUID and PDH' do
595     set_user_from_auth :active
596     m = {
597       "/foo" => {
598         "kind" => "collection",
599         "uuid" => "zzzzz-4zz18-znfnqtbbv4spc3w",
600         "portable_data_hash" => "fa7aeb5140e2848d39b416daeef4ffc5+45",
601         "path" => "/foo",
602       },
603     }
604     resolved_mounts = Container.resolve_mounts(m)
605     assert_equal m['portable_data_hash'], resolved_mounts['portable_data_hash']
606   end
607
608   ['arvados/apitestfixture:latest',
609    'arvados/apitestfixture',
610    'd8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678',
611   ].each do |tag|
612     test "Container.resolve_container_image(#{tag.inspect})" do
613       set_user_from_auth :active
614       resolved = Container.resolve_container_image(tag)
615       assert_equal resolved, collections(:docker_image).portable_data_hash
616     end
617   end
618
619   test "Container.resolve_container_image(pdh)" do
620     set_user_from_auth :active
621     [[:docker_image, 'v1'], [:docker_image_1_12, 'v2']].each do |coll, ver|
622       Rails.configuration.Containers.SupportedDockerImageFormats = ConfigLoader.to_OrderedOptions({ver=>{}})
623       pdh = collections(coll).portable_data_hash
624       resolved = Container.resolve_container_image(pdh)
625       assert_equal resolved, pdh
626     end
627   end
628
629   ['acbd18db4cc2f85cedef654fccc4a4d8+3',
630    'ENOEXIST',
631    'arvados/apitestfixture:ENOEXIST',
632   ].each do |img|
633     test "container_image_for_container(#{img.inspect}) => 422" do
634       set_user_from_auth :active
635       assert_raises(ArvadosModel::UnresolvableContainerError) do
636         Container.resolve_container_image(img)
637       end
638     end
639   end
640
641   test "allow unrecognized container when there are remote_hosts" do
642     set_user_from_auth :active
643     Rails.configuration.RemoteClusters = Rails.configuration.RemoteClusters.merge({foooo: ActiveSupport::InheritableOptions.new({Host: "bar.com"})})
644     Container.resolve_container_image('acbd18db4cc2f85cedef654fccc4a4d8+3')
645   end
646
647   test "migrated docker image" do
648     Rails.configuration.Containers.SupportedDockerImageFormats = ConfigLoader.to_OrderedOptions({'v2'=>{}})
649     add_docker19_migration_link
650
651     # Test that it returns only v2 images even though request is for v1 image.
652
653     set_user_from_auth :active
654     cr = create_minimal_req!(command: ["true", "1"],
655                              container_image: collections(:docker_image).portable_data_hash)
656     assert_equal(Container.resolve_container_image(cr.container_image),
657                  collections(:docker_image_1_12).portable_data_hash)
658
659     cr = create_minimal_req!(command: ["true", "2"],
660                              container_image: links(:docker_image_collection_tag).name)
661     assert_equal(Container.resolve_container_image(cr.container_image),
662                  collections(:docker_image_1_12).portable_data_hash)
663   end
664
665   test "use unmigrated docker image" do
666     Rails.configuration.Containers.SupportedDockerImageFormats = ConfigLoader.to_OrderedOptions({'v1'=>{}})
667     add_docker19_migration_link
668
669     # Test that it returns only supported v1 images even though there is a
670     # migration link.
671
672     set_user_from_auth :active
673     cr = create_minimal_req!(command: ["true", "1"],
674                              container_image: collections(:docker_image).portable_data_hash)
675     assert_equal(Container.resolve_container_image(cr.container_image),
676                  collections(:docker_image).portable_data_hash)
677
678     cr = create_minimal_req!(command: ["true", "2"],
679                              container_image: links(:docker_image_collection_tag).name)
680     assert_equal(Container.resolve_container_image(cr.container_image),
681                  collections(:docker_image).portable_data_hash)
682   end
683
684   test "incompatible docker image v1" do
685     Rails.configuration.Containers.SupportedDockerImageFormats = ConfigLoader.to_OrderedOptions({'v1'=>{}})
686     add_docker19_migration_link
687
688     # Don't return unsupported v2 image even if we ask for it directly.
689     set_user_from_auth :active
690     cr = create_minimal_req!(command: ["true", "1"],
691                              container_image: collections(:docker_image_1_12).portable_data_hash)
692     assert_raises(ArvadosModel::UnresolvableContainerError) do
693       Container.resolve_container_image(cr.container_image)
694     end
695   end
696
697   test "incompatible docker image v2" do
698     Rails.configuration.Containers.SupportedDockerImageFormats = ConfigLoader.to_OrderedOptions({'v2'=>{}})
699     # No migration link, don't return unsupported v1 image,
700
701     set_user_from_auth :active
702     cr = create_minimal_req!(command: ["true", "1"],
703                              container_image: collections(:docker_image).portable_data_hash)
704     assert_raises(ArvadosModel::UnresolvableContainerError) do
705       Container.resolve_container_image(cr.container_image)
706     end
707     cr = create_minimal_req!(command: ["true", "2"],
708                              container_image: links(:docker_image_collection_tag).name)
709     assert_raises(ArvadosModel::UnresolvableContainerError) do
710       Container.resolve_container_image(cr.container_image)
711     end
712   end
713
714   test "requestor can retrieve container owned by dispatch" do
715     assert_not_empty Container.readable_by(users(:admin)).where(uuid: containers(:running).uuid)
716     assert_not_empty Container.readable_by(users(:active)).where(uuid: containers(:running).uuid)
717     assert_empty Container.readable_by(users(:spectator)).where(uuid: containers(:running).uuid)
718   end
719
720   [
721     [{"var" => "value1"}, {"var" => "value1"}, nil],
722     [{"var" => "value1"}, {"var" => "value1"}, true],
723     [{"var" => "value1"}, {"var" => "value1"}, false],
724     [{"var" => "value1"}, {"var" => "value2"}, nil],
725   ].each do |env1, env2, use_existing|
726     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
727       common_attrs = {cwd: "test",
728                       priority: 1,
729                       command: ["echo", "hello"],
730                       output_path: "test",
731                       runtime_constraints: {"vcpus" => 4,
732                                             "ram" => 12000000000},
733                       mounts: {"test" => {"kind" => "json"}}}
734       set_user_from_auth :active
735       cr1 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Committed,
736                                                     environment: env1}))
737       run_container(cr1)
738       cr1.reload
739       if use_existing.nil?
740         # Testing with use_existing default value
741         cr2 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Uncommitted,
742                                                       environment: env2}))
743       else
744
745         cr2 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Uncommitted,
746                                                       environment: env2,
747                                                       use_existing: use_existing}))
748       end
749       assert_not_nil cr1.container_uuid
750       assert_nil cr2.container_uuid
751
752       # Update cr2 to commited state and check for container equality on different cases:
753       # * When env1 and env2 are equal and use_existing is true, the same container
754       #   should be assigned.
755       # * When use_existing is false, a different container should be assigned.
756       # * When env1 and env2 are different, a different container should be assigned.
757       cr2.update_attributes!({state: ContainerRequest::Committed})
758       assert_equal (cr2.use_existing == true and (env1 == env2)),
759                    (cr1.container_uuid == cr2.container_uuid)
760     end
761   end
762
763   test "requesting_container_uuid at create is not allowed" do
764     set_user_from_auth :active
765     assert_raises(ActiveRecord::RecordInvalid) do
766       create_minimal_req!(state: "Uncommitted", priority: 1, requesting_container_uuid: 'youcantdothat')
767     end
768   end
769
770   test "Retry on container cancelled" do
771     set_user_from_auth :active
772     cr = create_minimal_req!(priority: 1, state: "Committed", container_count_max: 2)
773     cr2 = create_minimal_req!(priority: 1, state: "Committed", container_count_max: 2, command: ["echo", "baz"])
774     prev_container_uuid = cr.container_uuid
775
776     c = act_as_system_user do
777       c = Container.find_by_uuid(cr.container_uuid)
778       c.update_attributes!(state: Container::Locked)
779       c.update_attributes!(state: Container::Running)
780       c
781     end
782
783     cr.reload
784     cr2.reload
785     assert_equal "Committed", cr.state
786     assert_equal prev_container_uuid, cr.container_uuid
787     assert_not_equal cr2.container_uuid, cr.container_uuid
788     prev_container_uuid = cr.container_uuid
789
790     act_as_system_user do
791       c.update_attributes!(state: Container::Cancelled)
792     end
793
794     cr.reload
795     cr2.reload
796     assert_equal "Committed", cr.state
797     assert_not_equal prev_container_uuid, cr.container_uuid
798     assert_not_equal cr2.container_uuid, cr.container_uuid
799     prev_container_uuid = cr.container_uuid
800
801     c = act_as_system_user do
802       c = Container.find_by_uuid(cr.container_uuid)
803       c.update_attributes!(state: Container::Cancelled)
804       c
805     end
806
807     cr.reload
808     cr2.reload
809     assert_equal "Final", cr.state
810     assert_equal prev_container_uuid, cr.container_uuid
811     assert_not_equal cr2.container_uuid, cr.container_uuid
812   end
813
814   test "Retry on container cancelled with runtime_token" do
815     set_user_from_auth :spectator
816     spec = api_client_authorizations(:active)
817     cr = create_minimal_req!(priority: 1, state: "Committed",
818                              runtime_token: spec.token,
819                              container_count_max: 2)
820     prev_container_uuid = cr.container_uuid
821
822     c = act_as_system_user do
823       c = Container.find_by_uuid(cr.container_uuid)
824       assert_equal spec.token, c.runtime_token
825       c.update_attributes!(state: Container::Locked)
826       c.update_attributes!(state: Container::Running)
827       c
828     end
829
830     cr.reload
831     assert_equal "Committed", cr.state
832     assert_equal prev_container_uuid, cr.container_uuid
833     prev_container_uuid = cr.container_uuid
834
835     act_as_system_user do
836       c.update_attributes!(state: Container::Cancelled)
837     end
838
839     cr.reload
840     assert_equal "Committed", cr.state
841     assert_not_equal prev_container_uuid, cr.container_uuid
842     prev_container_uuid = cr.container_uuid
843
844     c = act_as_system_user do
845       c = Container.find_by_uuid(cr.container_uuid)
846       assert_equal spec.token, c.runtime_token
847       c.update_attributes!(state: Container::Cancelled)
848       c
849     end
850
851     cr.reload
852     assert_equal "Final", cr.state
853     assert_equal prev_container_uuid, cr.container_uuid
854   end
855
856
857   test "Retry saves logs from previous attempts" do
858     set_user_from_auth :active
859     cr = create_minimal_req!(priority: 1, state: "Committed", container_count_max: 3)
860
861     c = act_as_system_user do
862       c = Container.find_by_uuid(cr.container_uuid)
863       c.update_attributes!(state: Container::Locked)
864       c.update_attributes!(state: Container::Running)
865       c
866     end
867
868     container_uuids = []
869
870     [0, 1, 2].each do
871       cr.reload
872       assert_equal "Committed", cr.state
873       container_uuids << cr.container_uuid
874
875       c = act_as_system_user do
876         logc = Collection.new(manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n")
877         logc.save!
878         c = Container.find_by_uuid(cr.container_uuid)
879         c.update_attributes!(state: Container::Cancelled, log: logc.portable_data_hash)
880         c
881       end
882     end
883
884     container_uuids.sort!
885
886     cr.reload
887     assert_equal "Final", cr.state
888     assert_equal 3, cr.container_count
889     assert_equal ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar
890 ./log\\040for\\040container\\040#{container_uuids[0]} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar
891 ./log\\040for\\040container\\040#{container_uuids[1]} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar
892 ./log\\040for\\040container\\040#{container_uuids[2]} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar
893 " , Collection.find_by_uuid(cr.log_uuid).manifest_text
894
895   end
896
897   test "Output collection name setting using output_name with name collision resolution" do
898     set_user_from_auth :active
899     output_name = 'unimaginative name'
900     Collection.create!(name: output_name)
901
902     cr = create_minimal_req!(priority: 1,
903                              state: ContainerRequest::Committed,
904                              output_name: output_name)
905     run_container(cr)
906     cr.reload
907     assert_equal ContainerRequest::Final, cr.state
908     output_coll = Collection.find_by_uuid(cr.output_uuid)
909     # Make sure the resulting output collection name include the original name
910     # plus the date
911     assert_not_equal output_name, output_coll.name,
912                      "more than one collection with the same owner and name"
913     assert output_coll.name.include?(output_name),
914            "New name should include original name"
915     assert_match /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/, output_coll.name,
916                  "New name should include ISO8601 date"
917   end
918
919   [[0, :check_output_ttl_0],
920    [1, :check_output_ttl_1s],
921    [365*86400, :check_output_ttl_1y],
922   ].each do |ttl, checker|
923     test "output_ttl=#{ttl}" do
924       act_as_user users(:active) do
925         cr = create_minimal_req!(priority: 1,
926                                  state: ContainerRequest::Committed,
927                                  output_name: 'foo',
928                                  output_ttl: ttl)
929         run_container(cr)
930         cr.reload
931         output = Collection.find_by_uuid(cr.output_uuid)
932         send(checker, db_current_time, output.trash_at, output.delete_at)
933       end
934     end
935   end
936
937   def check_output_ttl_0(now, trash, delete)
938     assert_nil(trash)
939     assert_nil(delete)
940   end
941
942   def check_output_ttl_1s(now, trash, delete)
943     assert_not_nil(trash)
944     assert_not_nil(delete)
945     assert_in_delta(trash, now + 1.second, 10)
946     assert_in_delta(delete, now + Rails.configuration.Collections.BlobSigningTTL, 10)
947   end
948
949   def check_output_ttl_1y(now, trash, delete)
950     year = (86400*365).second
951     assert_not_nil(trash)
952     assert_not_nil(delete)
953     assert_in_delta(trash, now + year, 10)
954     assert_in_delta(delete, now + year, 10)
955   end
956
957   def run_container(cr)
958     act_as_system_user do
959       logc = Collection.new(owner_uuid: system_user_uuid,
960                             manifest_text: ". ef772b2f28e2c8ca84de45466ed19ee9+7815 0:0:arv-mount.txt\n")
961       logc.save!
962
963       c = Container.find_by_uuid(cr.container_uuid)
964       c.update_attributes!(state: Container::Locked)
965       c.update_attributes!(state: Container::Running)
966       c.update_attributes!(state: Container::Complete,
967                            exit_code: 0,
968                            output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45',
969                            log: logc.portable_data_hash)
970       logc.destroy
971       c
972     end
973   end
974
975   test "Finalize committed request when reusing a finished container" do
976     set_user_from_auth :active
977     cr = create_minimal_req!(priority: 1, state: ContainerRequest::Committed)
978     cr.reload
979     assert_equal ContainerRequest::Committed, cr.state
980     run_container(cr)
981     cr.reload
982     assert_equal ContainerRequest::Final, cr.state
983
984     cr2 = create_minimal_req!(priority: 1, state: ContainerRequest::Committed)
985     assert_equal cr.container_uuid, cr2.container_uuid
986     assert_equal ContainerRequest::Final, cr2.state
987
988     cr3 = create_minimal_req!(priority: 1, state: ContainerRequest::Uncommitted)
989     assert_equal ContainerRequest::Uncommitted, cr3.state
990     cr3.update_attributes!(state: ContainerRequest::Committed)
991     assert_equal cr.container_uuid, cr3.container_uuid
992     assert_equal ContainerRequest::Final, cr3.state
993   end
994
995   [
996     # client requests preemptible, but types are not configured
997     [false, false, false, true, ActiveRecord::RecordInvalid],
998     [true, false, false, true, ActiveRecord::RecordInvalid],
999     # client requests preemptible, types are configured
1000     [false, true, false, true, true],
1001     [true, true, false, true, true],
1002     # client requests non-preemptible for top-level container
1003     [false, false, false, false, false],
1004     [true, false, false, false, false],
1005     [false, true, false, false, false],
1006     [true, true, false, false, false],
1007     # client requests non-preemptible for child container, preemptible
1008     # is enabled anyway if AlwaysUsePreemptibleInstances and instance types
1009     # are configured.
1010     [false, false, true, false, false],
1011     [true, false, true, false, false],
1012     [false, true, true, false, false],
1013     [true, true, true, false, true],
1014   ].each do |use_preemptible, have_preemptible, is_child, ask, expect|
1015     test "with AlwaysUsePreemptibleInstances=#{use_preemptible} and preemptible types #{have_preemptible ? '' : 'not '}configured, create #{is_child ? 'child' : 'top-level'} container request with preemptible=#{ask} and expect #{expect}" do
1016       Rails.configuration.Containers.AlwaysUsePreemptibleInstances = use_preemptible
1017       if have_preemptible
1018         configure_preemptible_instance_type
1019       end
1020       common_attrs = {
1021         cwd: "test",
1022         priority: 1,
1023         command: ["echo", "hello"],
1024         output_path: "test",
1025         scheduling_parameters: {"preemptible" => ask},
1026         mounts: {"test" => {"kind" => "json"}},
1027       }
1028       set_user_from_auth :active
1029
1030       if is_child
1031         cr = with_container_auth(containers(:running)) do
1032           create_minimal_req!(common_attrs)
1033         end
1034       else
1035         cr = create_minimal_req!(common_attrs)
1036       end
1037
1038       cr.reload
1039       cr.state = ContainerRequest::Committed
1040
1041       if expect == true || expect == false
1042         cr.save!
1043         assert_equal expect, cr.scheduling_parameters["preemptible"]
1044       else
1045         assert_raises(expect) do
1046           cr.save!
1047         end
1048       end
1049     end
1050   end
1051
1052   test "config update does not flip preemptible flag on already-committed container requests" do
1053     parent = containers(:running_container_with_logs)
1054     attrs_p = {
1055       scheduling_parameters: {"preemptible" => true},
1056       "state" => "Committed",
1057       "priority" => 1,
1058     }
1059     attrs_nonp = {
1060       scheduling_parameters: {"preemptible" => false},
1061       "state" => "Committed",
1062       "priority" => 1,
1063     }
1064     expect = {false => [], true => []}
1065
1066     with_container_auth(parent) do
1067       configure_preemptible_instance_type
1068       Rails.configuration.Containers.AlwaysUsePreemptibleInstances = false
1069
1070       expect[true].push create_minimal_req!(attrs_p)
1071       expect[false].push create_minimal_req!(attrs_nonp)
1072
1073       Rails.configuration.Containers.AlwaysUsePreemptibleInstances = true
1074
1075       expect[true].push create_minimal_req!(attrs_p)
1076       expect[true].push create_minimal_req!(attrs_nonp)
1077       commit_later = create_minimal_req!()
1078
1079       Rails.configuration.InstanceTypes = ConfigLoader.to_OrderedOptions({})
1080
1081       expect[false].push create_minimal_req!(attrs_nonp)
1082
1083       # Even though preemptible is not allowed, we should be able to
1084       # commit a CR that was created earlier when preemptible was the
1085       # default.
1086       commit_later.update_attributes!(priority: 1, state: "Committed")
1087       expect[false].push commit_later
1088     end
1089
1090     set_user_from_auth :active
1091     [false, true].each do |pflag|
1092       expect[pflag].each do |cr|
1093         cr.reload
1094         assert_equal pflag, cr.scheduling_parameters['preemptible']
1095       end
1096     end
1097
1098     act_as_system_user do
1099       # Cancelling the parent used to fail while updating the child
1100       # containers' priority, because the child containers' unchanged
1101       # preemptible fields caused validation to fail.
1102       parent.update_attributes!(state: 'Cancelled')
1103
1104       [false, true].each do |pflag|
1105         expect[pflag].each do |cr|
1106           cr.reload
1107           assert_equal 0, cr.priority, "unexpected non-zero priority #{cr.priority} for #{cr.uuid}"
1108         end
1109       end
1110     end
1111   end
1112
1113   [
1114     [{"partitions" => ["fastcpu","vfastcpu", 100]}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
1115     [{"partitions" => ["fastcpu","vfastcpu", 100]}, ContainerRequest::Uncommitted],
1116     [{"partitions" => "fastcpu"}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
1117     [{"partitions" => "fastcpu"}, ContainerRequest::Uncommitted],
1118     [{"partitions" => ["fastcpu","vfastcpu"]}, ContainerRequest::Committed],
1119     [{"max_run_time" => "one day"}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
1120     [{"max_run_time" => "one day"}, ContainerRequest::Uncommitted],
1121     [{"max_run_time" => -1}, ContainerRequest::Committed, ActiveRecord::RecordInvalid],
1122     [{"max_run_time" => -1}, ContainerRequest::Uncommitted],
1123     [{"max_run_time" => 86400}, ContainerRequest::Committed],
1124   ].each do |sp, state, expected|
1125     test "create container request with scheduling_parameters #{sp} in state #{state} and verify #{expected}" do
1126       common_attrs = {cwd: "test",
1127                       priority: 1,
1128                       command: ["echo", "hello"],
1129                       output_path: "test",
1130                       scheduling_parameters: sp,
1131                       mounts: {"test" => {"kind" => "json"}}}
1132       set_user_from_auth :active
1133
1134       if expected == ActiveRecord::RecordInvalid
1135         assert_raises(ActiveRecord::RecordInvalid) do
1136           create_minimal_req!(common_attrs.merge({state: state}))
1137         end
1138       else
1139         cr = create_minimal_req!(common_attrs.merge({state: state}))
1140         assert (sp.to_a - cr.scheduling_parameters.to_a).empty?
1141
1142         if state == ContainerRequest::Committed
1143           c = Container.find_by_uuid(cr.container_uuid)
1144           assert (sp.to_a - c.scheduling_parameters.to_a).empty?
1145         end
1146       end
1147     end
1148   end
1149
1150   test "AlwaysUsePreemptibleInstances makes child containers preemptible" do
1151     Rails.configuration.Containers.AlwaysUsePreemptibleInstances = true
1152     common_attrs = {cwd: "test",
1153                     priority: 1,
1154                     command: ["echo", "hello"],
1155                     output_path: "test",
1156                     state: ContainerRequest::Committed,
1157                     mounts: {"test" => {"kind" => "json"}}}
1158     set_user_from_auth :active
1159     configure_preemptible_instance_type
1160
1161     cr = with_container_auth(Container.find_by_uuid 'zzzzz-dz642-runningcontainr') do
1162       create_minimal_req!(common_attrs)
1163     end
1164     assert_equal 'zzzzz-dz642-runningcontainr', cr.requesting_container_uuid
1165     assert_equal true, cr.scheduling_parameters["preemptible"]
1166
1167     c = Container.find_by_uuid(cr.container_uuid)
1168     assert_equal true, c.scheduling_parameters["preemptible"]
1169   end
1170
1171   [['Committed', true, {name: "foobar", priority: 123}],
1172    ['Committed', false, {container_count: 2}],
1173    ['Committed', false, {container_count: 0}],
1174    ['Committed', false, {container_count: nil}],
1175    ['Committed', true, {priority: 0, mounts: {"/out" => {"kind" => "tmp", "capacity" => 1000000}}}],
1176    ['Committed', true, {priority: 0, mounts: {"/out" => {"capacity" => 1000000, "kind" => "tmp"}}}],
1177    # Addition of default values for mounts / runtime_constraints /
1178    # scheduling_parameters, as happens in a round-trip through
1179    # controller, does not have any real effect and should be
1180    # accepted/ignored rather than causing an error when the CR state
1181    # dictates those attributes are not allowed to change.
1182    ['Committed', true, {priority: 0, mounts: {"/out" => {"capacity" => 0, "kind" => "tmp"}}}, {mounts: {"/out" => {"kind" => "tmp"}}}],
1183    ['Committed', true, {priority: 0, mounts: {"/out" => {"capacity" => 1000000, "kind" => "tmp", "exclude_from_output": false}}}],
1184    ['Committed', true, {priority: 0, mounts: {"/out" => {"capacity" => 1000000, "kind" => "tmp", "repository_name": ""}}}],
1185    ['Committed', true, {priority: 0, mounts: {"/out" => {"capacity" => 1000000, "kind" => "tmp", "content": nil}}}],
1186    ['Committed', false, {priority: 0, mounts: {"/out" => {"capacity" => 1000000, "kind" => "tmp", "content": {}}}}],
1187    ['Committed', false, {priority: 0, mounts: {"/out" => {"capacity" => 1000000, "kind" => "tmp", "repository_name": "foo"}}}],
1188    ['Committed', false, {priority: 0, mounts: {"/out" => {"kind" => "tmp", "capacity" => 1234567}}}],
1189    ['Committed', false, {priority: 0, mounts: {}}],
1190    ['Committed', true, {priority: 0, runtime_constraints: {"vcpus" => 1, "ram" => 2}}],
1191    ['Committed', true, {priority: 0, runtime_constraints: {"vcpus" => 1, "ram" => 2, "keep_cache_ram" => 0}}],
1192    ['Committed', true, {priority: 0, runtime_constraints: {"vcpus" => 1, "ram" => 2, "API" => false}}],
1193    ['Committed', false, {priority: 0, runtime_constraints: {"vcpus" => 1, "ram" => 2, "keep_cache_ram" => 1}}],
1194    ['Committed', false, {priority: 0, runtime_constraints: {"vcpus" => 1, "ram" => 2, "API" => true}}],
1195    ['Committed', true, {priority: 0, scheduling_parameters: {"preemptible" => false}}],
1196    ['Committed', true, {priority: 0, scheduling_parameters: {"partitions" => []}}],
1197    ['Committed', true, {priority: 0, scheduling_parameters: {"max_run_time" => 0}}],
1198    ['Committed', false, {priority: 0, scheduling_parameters: {"preemptible" => true}}],
1199    ['Committed', false, {priority: 0, scheduling_parameters: {"partitions" => ["foo"]}}],
1200    ['Committed', false, {priority: 0, scheduling_parameters: {"max_run_time" => 1}}],
1201    ['Final', false, {state: ContainerRequest::Committed, name: "foobar"}],
1202    ['Final', false, {name: "foobar", priority: 123}],
1203    ['Final', false, {name: "foobar", output_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
1204    ['Final', false, {name: "foobar", log_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
1205    ['Final', false, {log_uuid: "zzzzz-4zz18-znfnqtbbv4spc3w"}],
1206    ['Final', false, {priority: 123}],
1207    ['Final', false, {mounts: {}}],
1208    ['Final', false, {container_count: 2}],
1209    ['Final', true, {name: "foobar"}],
1210    ['Final', true, {name: "foobar", description: "baz"}],
1211   ].each do |state, permitted, updates, create_attrs|
1212     test "state=#{state} can#{'not' if !permitted} update #{updates.inspect}" do
1213       act_as_user users(:active) do
1214         attrs = {
1215           priority: 1,
1216           state: "Committed",
1217           container_count_max: 1
1218         }
1219         if !create_attrs.nil?
1220           attrs.merge!(create_attrs)
1221         end
1222         cr = create_minimal_req!(attrs)
1223         case state
1224         when 'Committed'
1225           # already done
1226         when 'Final'
1227           act_as_system_user do
1228             Container.find_by_uuid(cr.container_uuid).
1229               update_attributes!(state: Container::Cancelled)
1230           end
1231           cr.reload
1232         else
1233           raise 'broken test case'
1234         end
1235         assert_equal state, cr.state
1236         if permitted
1237           assert cr.update_attributes!(updates)
1238         else
1239           assert_raises(ActiveRecord::RecordInvalid) do
1240             cr.update_attributes!(updates)
1241           end
1242         end
1243       end
1244     end
1245   end
1246
1247   test "delete container_request and check its container's priority" do
1248     act_as_user users(:active) do
1249       cr = ContainerRequest.find_by_uuid container_requests(:running_to_be_deleted).uuid
1250
1251       # initially the cr's container has priority > 0
1252       c = Container.find_by_uuid(cr.container_uuid)
1253       assert_equal 1, c.priority
1254
1255       cr.destroy
1256
1257       # the cr's container now has priority of 0
1258       c = Container.find_by_uuid(cr.container_uuid)
1259       assert_equal 0, c.priority
1260     end
1261   end
1262
1263   test "delete container_request in final state and expect no error due to before_destroy callback" do
1264     act_as_user users(:active) do
1265       cr = ContainerRequest.find_by_uuid container_requests(:completed).uuid
1266       assert_nothing_raised {cr.destroy}
1267     end
1268   end
1269
1270   test "Container request valid priority" do
1271     set_user_from_auth :active
1272     cr = create_minimal_req!
1273
1274     assert_raises(ActiveRecord::RecordInvalid) do
1275       cr.priority = -1
1276       cr.save!
1277     end
1278
1279     cr.priority = 0
1280     cr.save!
1281
1282     cr.priority = 1
1283     cr.save!
1284
1285     cr.priority = 500
1286     cr.save!
1287
1288     cr.priority = 999
1289     cr.save!
1290
1291     cr.priority = 1000
1292     cr.save!
1293
1294     assert_raises(ActiveRecord::RecordInvalid) do
1295       cr.priority = 1001
1296       cr.save!
1297     end
1298   end
1299
1300   # Note: some of these tests might look redundant because they test
1301   # that out-of-order spellings of hashes are still considered equal
1302   # regardless of whether the existing (container) or new (container
1303   # request) hash needs to be re-ordered.
1304   secrets = {"/foo" => {"kind" => "text", "content" => "xyzzy"}}
1305   same_secrets = {"/foo" => {"content" => "xyzzy", "kind" => "text"}}
1306   different_secrets = {"/foo" => {"kind" => "text", "content" => "something completely different"}}
1307   [
1308     [true, nil, nil],
1309     [true, nil, {}],
1310     [true, {}, nil],
1311     [true, {}, {}],
1312     [true, secrets, same_secrets],
1313     [true, same_secrets, secrets],
1314     [false, nil, secrets],
1315     [false, {}, secrets],
1316     [false, secrets, {}],
1317     [false, secrets, nil],
1318     [false, secrets, different_secrets],
1319   ].each do |expect_reuse, sm1, sm2|
1320     test "container reuse secret_mounts #{sm1.inspect}, #{sm2.inspect}" do
1321       set_user_from_auth :active
1322       cr1 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm1)
1323       cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm2)
1324       assert_not_nil cr1.container_uuid
1325       assert_not_nil cr2.container_uuid
1326       if expect_reuse
1327         assert_equal cr1.container_uuid, cr2.container_uuid
1328       else
1329         assert_not_equal cr1.container_uuid, cr2.container_uuid
1330       end
1331     end
1332   end
1333
1334   test "scrub secret_mounts but reuse container for request with identical secret_mounts" do
1335     set_user_from_auth :active
1336     sm = {'/secret/foo' => {'kind' => 'text', 'content' => secret_string}}
1337     cr1 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm.dup)
1338     run_container(cr1)
1339     cr1.reload
1340
1341     # secret_mounts scrubbed from db
1342     c = Container.where(uuid: cr1.container_uuid).first
1343     assert_equal({}, c.secret_mounts)
1344     assert_equal({}, cr1.secret_mounts)
1345
1346     # can reuse container if secret_mounts match
1347     cr2 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: sm.dup)
1348     assert_equal cr1.container_uuid, cr2.container_uuid
1349
1350     # don't reuse container if secret_mounts don't match
1351     cr3 = create_minimal_req!(state: "Committed", priority: 1, secret_mounts: {})
1352     assert_not_equal cr1.container_uuid, cr3.container_uuid
1353
1354     assert_no_secrets_logged
1355   end
1356
1357   test "conflicting key in mounts and secret_mounts" do
1358     sm = {'/secret/foo' => {'kind' => 'text', 'content' => secret_string}}
1359     set_user_from_auth :active
1360     cr = create_minimal_req!
1361     assert_equal false, cr.update_attributes(state: "Committed",
1362                                              priority: 1,
1363                                              mounts: cr.mounts.merge(sm),
1364                                              secret_mounts: sm)
1365     assert_equal [:secret_mounts], cr.errors.messages.keys
1366   end
1367
1368   test "using runtime_token" do
1369     set_user_from_auth :spectator
1370     spec = api_client_authorizations(:active)
1371     cr = create_minimal_req!(state: "Committed", runtime_token: spec.token, priority: 1)
1372     cr.save!
1373     c = Container.find_by_uuid cr.container_uuid
1374     lock_and_run c
1375     assert_nil c.auth_uuid
1376     assert_equal c.runtime_token, spec.token
1377
1378     assert_not_nil ApiClientAuthorization.find_by_uuid(spec.uuid)
1379
1380     act_as_system_user do
1381       c.update_attributes!(state: Container::Complete,
1382                            exit_code: 0,
1383                            output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45',
1384                            log: 'fa7aeb5140e2848d39b416daeef4ffc5+45')
1385     end
1386
1387     cr.reload
1388     c.reload
1389     assert_nil cr.runtime_token
1390     assert_nil c.runtime_token
1391   end
1392
1393   test "invalid runtime_token" do
1394     set_user_from_auth :active
1395     spec = api_client_authorizations(:spectator)
1396     assert_raises(ArgumentError) do
1397       cr = create_minimal_req!(state: "Committed", runtime_token: "#{spec.token}xx")
1398       cr.save!
1399     end
1400   end
1401
1402   test "default output_storage_classes" do
1403     saved = Rails.configuration.DefaultStorageClasses
1404     Rails.configuration.DefaultStorageClasses = ["foo"]
1405     begin
1406       act_as_user users(:active) do
1407         cr = create_minimal_req!(priority: 1,
1408                                  state: ContainerRequest::Committed,
1409                                  output_name: 'foo')
1410         run_container(cr)
1411         cr.reload
1412         output = Collection.find_by_uuid(cr.output_uuid)
1413         assert_equal ["foo"], output.storage_classes_desired
1414       end
1415     ensure
1416       Rails.configuration.DefaultStorageClasses = saved
1417     end
1418   end
1419
1420   test "setting output_storage_classes" do
1421     act_as_user users(:active) do
1422       cr = create_minimal_req!(priority: 1,
1423                                state: ContainerRequest::Committed,
1424                                output_name: 'foo',
1425                                output_storage_classes: ["foo_storage_class", "bar_storage_class"])
1426       run_container(cr)
1427       cr.reload
1428       output = Collection.find_by_uuid(cr.output_uuid)
1429       assert_equal ["foo_storage_class", "bar_storage_class"], output.storage_classes_desired
1430       log = Collection.find_by_uuid(cr.log_uuid)
1431       assert_equal ["foo_storage_class", "bar_storage_class"], log.storage_classes_desired
1432     end
1433   end
1434
1435   test "reusing container with different container_request.output_storage_classes" do
1436     common_attrs = {cwd: "test",
1437                     priority: 1,
1438                     command: ["echo", "hello"],
1439                     output_path: "test",
1440                     runtime_constraints: {"vcpus" => 4,
1441                                           "ram" => 12000000000},
1442                     mounts: {"test" => {"kind" => "json"}},
1443                     environment: {"var" => "value1"},
1444                     output_storage_classes: ["foo_storage_class"]}
1445     set_user_from_auth :active
1446     cr1 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Committed}))
1447     cont1 = run_container(cr1)
1448     cr1.reload
1449
1450     output1 = Collection.find_by_uuid(cr1.output_uuid)
1451
1452     # Testing with use_existing default value
1453     cr2 = create_minimal_req!(common_attrs.merge({state: ContainerRequest::Uncommitted,
1454                                                   output_storage_classes: ["bar_storage_class"]}))
1455
1456     assert_not_nil cr1.container_uuid
1457     assert_nil cr2.container_uuid
1458
1459     # Update cr2 to commited state, check for reuse, then run it
1460     cr2.update_attributes!({state: ContainerRequest::Committed})
1461     assert_equal cr1.container_uuid, cr2.container_uuid
1462
1463     cr2.reload
1464     output2 = Collection.find_by_uuid(cr2.output_uuid)
1465
1466     # the original CR output has the original storage class,
1467     # but the second CR output has the new storage class.
1468     assert_equal ["foo_storage_class"], cont1.output_storage_classes
1469     assert_equal ["foo_storage_class"], output1.storage_classes_desired
1470     assert_equal ["bar_storage_class"], output2.storage_classes_desired
1471   end
1472
1473   [
1474     [{},               {},           {"type": "output"}],
1475     [{"a1": "b1"},     {},           {"type": "output", "a1": "b1"}],
1476     [{},               {"a1": "b1"}, {"type": "output", "a1": "b1"}],
1477     [{"a1": "b1"},     {"a1": "c1"}, {"type": "output", "a1": "b1"}],
1478     [{"a1": "b1"},     {"a2": "c2"}, {"type": "output", "a1": "b1", "a2": "c2"}],
1479     [{"type": "blah"}, {},           {"type": "blah"}],
1480   ].each do |cr_prop, container_prop, expect_prop|
1481     test "setting output_properties #{cr_prop} #{container_prop} on current container" do
1482       act_as_user users(:active) do
1483         cr = create_minimal_req!(priority: 1,
1484                                  state: ContainerRequest::Committed,
1485                                  output_name: 'foo',
1486                                  output_properties: cr_prop)
1487
1488         act_as_system_user do
1489           logc = Collection.new(owner_uuid: system_user_uuid,
1490                                 manifest_text: ". ef772b2f28e2c8ca84de45466ed19ee9+7815 0:0:arv-mount.txt\n")
1491           logc.save!
1492
1493           c = Container.find_by_uuid(cr.container_uuid)
1494           c.update_attributes!(state: Container::Locked)
1495           c.update_attributes!(state: Container::Running)
1496
1497           c.update_attributes!(output_properties: container_prop)
1498
1499           c.update_attributes!(state: Container::Complete,
1500                                exit_code: 0,
1501                                output: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45',
1502                                log: logc.portable_data_hash)
1503           logc.destroy
1504         end
1505
1506         cr.reload
1507         expect_prop["container_request"] = cr.uuid
1508         output = Collection.find_by_uuid(cr.output_uuid)
1509         assert_equal expect_prop.symbolize_keys, output.properties.symbolize_keys
1510       end
1511     end
1512   end
1513
1514 end