Merge branch '17296-singularity'
[arvados.git] / lib / crunchrun / integration_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package crunchrun
6
7 import (
8         "bytes"
9         "fmt"
10         "io"
11         "io/ioutil"
12         "os"
13         "os/exec"
14         "strings"
15
16         "git.arvados.org/arvados.git/sdk/go/arvados"
17         "git.arvados.org/arvados.git/sdk/go/arvadosclient"
18         "git.arvados.org/arvados.git/sdk/go/arvadostest"
19         "git.arvados.org/arvados.git/sdk/go/keepclient"
20         . "gopkg.in/check.v1"
21 )
22
23 var _ = Suite(&integrationSuite{})
24
25 type integrationSuite struct {
26         engine string
27         image  arvados.Collection
28         input  arvados.Collection
29         stdin  bytes.Buffer
30         stdout bytes.Buffer
31         stderr bytes.Buffer
32         cr     arvados.ContainerRequest
33         client *arvados.Client
34         ac     *arvadosclient.ArvadosClient
35         kc     *keepclient.KeepClient
36 }
37
38 func (s *integrationSuite) SetUpSuite(c *C) {
39         _, err := exec.LookPath("docker")
40         if err != nil {
41                 c.Skip("looks like docker is not installed")
42         }
43
44         arvadostest.StartKeep(2, true)
45
46         out, err := exec.Command("docker", "load", "--input", busyboxDockerImage(c)).CombinedOutput()
47         c.Log(string(out))
48         c.Assert(err, IsNil)
49         out, err = exec.Command("arv-keepdocker", "--no-resume", "busybox:uclibc").Output()
50         imageUUID := strings.TrimSpace(string(out))
51         c.Logf("image uuid %s", imageUUID)
52         c.Assert(err, IsNil)
53         err = arvados.NewClientFromEnv().RequestAndDecode(&s.image, "GET", "arvados/v1/collections/"+imageUUID, nil, nil)
54         c.Assert(err, IsNil)
55         c.Logf("image pdh %s", s.image.PortableDataHash)
56
57         s.client = arvados.NewClientFromEnv()
58         s.ac, err = arvadosclient.New(s.client)
59         c.Assert(err, IsNil)
60         s.kc = keepclient.New(s.ac)
61         fs, err := s.input.FileSystem(s.client, s.kc)
62         c.Assert(err, IsNil)
63         f, err := fs.OpenFile("inputfile", os.O_CREATE|os.O_WRONLY, 0755)
64         c.Assert(err, IsNil)
65         _, err = f.Write([]byte("inputdata"))
66         c.Assert(err, IsNil)
67         err = f.Close()
68         c.Assert(err, IsNil)
69         s.input.ManifestText, err = fs.MarshalManifest(".")
70         c.Assert(err, IsNil)
71         err = s.client.RequestAndDecode(&s.input, "POST", "arvados/v1/collections", nil, map[string]interface{}{
72                 "ensure_unique_name": true,
73                 "collection": map[string]interface{}{
74                         "manifest_text": s.input.ManifestText,
75                 },
76         })
77         c.Assert(err, IsNil)
78         c.Logf("input pdh %s", s.input.PortableDataHash)
79 }
80
81 func (s *integrationSuite) TearDownSuite(c *C) {
82         if s.client == nil {
83                 // didn't set up
84                 return
85         }
86         err := s.client.RequestAndDecode(nil, "POST", "database/reset", nil, nil)
87         c.Check(err, IsNil)
88 }
89
90 func (s *integrationSuite) SetUpTest(c *C) {
91         s.engine = "docker"
92         s.stdin = bytes.Buffer{}
93         s.stdout = bytes.Buffer{}
94         s.stderr = bytes.Buffer{}
95         s.cr = arvados.ContainerRequest{
96                 Priority:       1,
97                 State:          "Committed",
98                 OutputPath:     "/mnt/out",
99                 ContainerImage: s.image.PortableDataHash,
100                 Mounts: map[string]arvados.Mount{
101                         "/mnt/json": {
102                                 Kind: "json",
103                                 Content: []interface{}{
104                                         "foo",
105                                         map[string]string{"foo": "bar"},
106                                         nil,
107                                 },
108                         },
109                         "/mnt/in": {
110                                 Kind:             "collection",
111                                 PortableDataHash: s.input.PortableDataHash,
112                         },
113                         "/mnt/out": {
114                                 Kind:     "tmp",
115                                 Capacity: 1000,
116                         },
117                 },
118                 RuntimeConstraints: arvados.RuntimeConstraints{
119                         RAM:   128000000,
120                         VCPUs: 1,
121                         API:   true,
122                 },
123         }
124 }
125
126 func (s *integrationSuite) setup(c *C) {
127         err := s.client.RequestAndDecode(&s.cr, "POST", "arvados/v1/container_requests", nil, map[string]interface{}{"container_request": map[string]interface{}{
128                 "priority":            s.cr.Priority,
129                 "state":               s.cr.State,
130                 "command":             s.cr.Command,
131                 "output_path":         s.cr.OutputPath,
132                 "container_image":     s.cr.ContainerImage,
133                 "mounts":              s.cr.Mounts,
134                 "runtime_constraints": s.cr.RuntimeConstraints,
135                 "use_existing":        false,
136         }})
137         c.Assert(err, IsNil)
138         c.Assert(s.cr.ContainerUUID, Not(Equals), "")
139         err = s.client.RequestAndDecode(nil, "POST", "arvados/v1/containers/"+s.cr.ContainerUUID+"/lock", nil, nil)
140         c.Assert(err, IsNil)
141 }
142
143 func (s *integrationSuite) TestRunTrivialContainerWithDocker(c *C) {
144         s.engine = "docker"
145         s.testRunTrivialContainer(c)
146 }
147
148 func (s *integrationSuite) TestRunTrivialContainerWithSingularity(c *C) {
149         s.engine = "singularity"
150         s.testRunTrivialContainer(c)
151 }
152
153 func (s *integrationSuite) testRunTrivialContainer(c *C) {
154         if err := exec.Command("which", s.engine).Run(); err != nil {
155                 c.Skip(fmt.Sprintf("%s: %s", s.engine, err))
156         }
157         s.cr.Command = []string{"sh", "-c", "cat /mnt/in/inputfile >/mnt/out/inputfile && cat /mnt/json >/mnt/out/json && ! touch /mnt/in/shouldbereadonly && mkdir /mnt/out/emptydir"}
158         s.setup(c)
159         code := command{}.RunCommand("crunch-run", []string{
160                 "-runtime-engine=" + s.engine,
161                 "-enable-memory-limit=false",
162                 s.cr.ContainerUUID,
163         }, &s.stdin, io.MultiWriter(&s.stdout, os.Stderr), io.MultiWriter(&s.stderr, os.Stderr))
164         c.Check(code, Equals, 0)
165         err := s.client.RequestAndDecode(&s.cr, "GET", "arvados/v1/container_requests/"+s.cr.UUID, nil, nil)
166         c.Assert(err, IsNil)
167         c.Logf("Finished container request: %#v", s.cr)
168
169         var log arvados.Collection
170         err = s.client.RequestAndDecode(&log, "GET", "arvados/v1/collections/"+s.cr.LogUUID, nil, nil)
171         c.Assert(err, IsNil)
172         fs, err := log.FileSystem(s.client, s.kc)
173         c.Assert(err, IsNil)
174         if d, err := fs.Open("/"); c.Check(err, IsNil) {
175                 fis, err := d.Readdir(-1)
176                 c.Assert(err, IsNil)
177                 for _, fi := range fis {
178                         if fi.IsDir() {
179                                 continue
180                         }
181                         f, err := fs.Open(fi.Name())
182                         c.Assert(err, IsNil)
183                         buf, err := ioutil.ReadAll(f)
184                         c.Assert(err, IsNil)
185                         c.Logf("\n===== %s =====\n%s", fi.Name(), buf)
186                 }
187         }
188
189         var output arvados.Collection
190         err = s.client.RequestAndDecode(&output, "GET", "arvados/v1/collections/"+s.cr.OutputUUID, nil, nil)
191         c.Assert(err, IsNil)
192         fs, err = output.FileSystem(s.client, s.kc)
193         c.Assert(err, IsNil)
194         if f, err := fs.Open("inputfile"); c.Check(err, IsNil) {
195                 defer f.Close()
196                 buf, err := ioutil.ReadAll(f)
197                 c.Check(err, IsNil)
198                 c.Check(string(buf), Equals, "inputdata")
199         }
200         if f, err := fs.Open("json"); c.Check(err, IsNil) {
201                 defer f.Close()
202                 buf, err := ioutil.ReadAll(f)
203                 c.Check(err, IsNil)
204                 c.Check(string(buf), Equals, `["foo",{"foo":"bar"},null]`)
205         }
206         if fi, err := fs.Stat("emptydir"); c.Check(err, IsNil) {
207                 c.Check(fi.IsDir(), Equals, true)
208         }
209         if d, err := fs.Open("emptydir"); c.Check(err, IsNil) {
210                 defer d.Close()
211                 fis, err := d.Readdir(-1)
212                 c.Assert(err, IsNil)
213                 // crunch-run still saves a ".keep" file to preserve
214                 // empty dirs even though that shouldn't be
215                 // necessary. Ideally we would do:
216                 // c.Check(fis, HasLen, 0)
217                 for _, fi := range fis {
218                         c.Check(fi.Name(), Equals, ".keep")
219                 }
220         }
221 }