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