1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
17 "git.arvados.org/arvados.git/lib/config"
18 "git.arvados.org/arvados.git/sdk/go/arvados"
19 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
20 "git.arvados.org/arvados.git/sdk/go/arvadostest"
21 "git.arvados.org/arvados.git/sdk/go/ctxlog"
22 "git.arvados.org/arvados.git/sdk/go/keepclient"
26 var _ = Suite(&integrationSuite{})
28 type integrationSuite struct {
30 image arvados.Collection
31 input arvados.Collection
36 cr arvados.ContainerRequest
37 client *arvados.Client
38 ac *arvadosclient.ArvadosClient
39 kc *keepclient.KeepClient
41 logCollection arvados.Collection
42 outputCollection arvados.Collection
43 logFiles map[string]string // filename => contents
46 func (s *integrationSuite) SetUpSuite(c *C) {
47 _, err := exec.LookPath("docker")
49 c.Skip("looks like docker is not installed")
52 arvadostest.StartKeep(2, true)
54 out, err := exec.Command("docker", "load", "--input", arvadostest.BusyboxDockerImage(c)).CombinedOutput()
57 out, err = exec.Command("arv-keepdocker", "--no-resume", "busybox:uclibc").Output()
58 imageUUID := strings.TrimSpace(string(out))
59 c.Logf("image uuid %s", imageUUID)
60 if !c.Check(err, IsNil) {
61 if err, ok := err.(*exec.ExitError); ok {
62 c.Logf("%s", err.Stderr)
66 err = arvados.NewClientFromEnv().RequestAndDecode(&s.image, "GET", "arvados/v1/collections/"+imageUUID, nil, nil)
68 c.Logf("image pdh %s", s.image.PortableDataHash)
70 s.client = arvados.NewClientFromEnv()
71 s.ac, err = arvadosclient.New(s.client)
73 s.kc = keepclient.New(s.ac)
74 fs, err := s.input.FileSystem(s.client, s.kc)
76 f, err := fs.OpenFile("inputfile", os.O_CREATE|os.O_WRONLY, 0755)
78 _, err = f.Write([]byte("inputdata"))
82 s.input.ManifestText, err = fs.MarshalManifest(".")
84 err = s.client.RequestAndDecode(&s.input, "POST", "arvados/v1/collections", nil, map[string]interface{}{
85 "ensure_unique_name": true,
86 "collection": map[string]interface{}{
87 "manifest_text": s.input.ManifestText,
91 c.Logf("input pdh %s", s.input.PortableDataHash)
94 func (s *integrationSuite) TearDownSuite(c *C) {
95 os.Unsetenv("ARVADOS_KEEP_SERVICES")
100 err := s.client.RequestAndDecode(nil, "POST", "database/reset", nil, nil)
104 func (s *integrationSuite) SetUpTest(c *C) {
105 os.Unsetenv("ARVADOS_KEEP_SERVICES")
108 s.stdin = bytes.Buffer{}
109 s.stdout = bytes.Buffer{}
110 s.stderr = bytes.Buffer{}
111 s.logCollection = arvados.Collection{}
112 s.outputCollection = arvados.Collection{}
113 s.logFiles = map[string]string{}
114 s.cr = arvados.ContainerRequest{
117 OutputPath: "/mnt/out",
118 ContainerImage: s.image.PortableDataHash,
119 Mounts: map[string]arvados.Mount{
122 Content: []interface{}{
124 map[string]string{"foo": "bar"},
130 PortableDataHash: s.input.PortableDataHash,
137 RuntimeConstraints: arvados.RuntimeConstraints{
145 func (s *integrationSuite) setup(c *C) {
146 err := s.client.RequestAndDecode(&s.cr, "POST", "arvados/v1/container_requests", nil, map[string]interface{}{"container_request": map[string]interface{}{
147 "priority": s.cr.Priority,
149 "command": s.cr.Command,
150 "output_path": s.cr.OutputPath,
151 "output_glob": s.cr.OutputGlob,
152 "container_image": s.cr.ContainerImage,
153 "mounts": s.cr.Mounts,
154 "runtime_constraints": s.cr.RuntimeConstraints,
155 "use_existing": false,
158 c.Assert(s.cr.ContainerUUID, Not(Equals), "")
159 err = s.client.RequestAndDecode(nil, "POST", "arvados/v1/containers/"+s.cr.ContainerUUID+"/lock", nil, nil)
163 func (s *integrationSuite) TestRunTrivialContainerWithDocker(c *C) {
165 s.testRunTrivialContainer(c)
166 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*Using container runtime: docker Engine \d+\.\d+.*`)
169 func (s *integrationSuite) TestRunTrivialContainerWithSingularity(c *C) {
170 s.engine = "singularity"
171 s.testRunTrivialContainer(c)
172 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*Using container runtime: singularity.* version 3\.\d+.*`)
175 func (s *integrationSuite) TestRunTrivialContainerWithLocalKeepstore(c *C) {
176 for _, trial := range []struct {
180 matchStartupMessage Checker
182 {"none", Not(Matches), Not(Matches), Not(Matches)},
183 {"all", Matches, Matches, Matches},
184 {"errors", Not(Matches), Not(Matches), Matches},
186 c.Logf("=== testing with Containers.LocalKeepLogsToContainerLog: %q", trial.logConfig)
189 cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
191 cluster, err := cfg.GetCluster("")
193 for uuid, volume := range cluster.Volumes {
194 volume.AccessViaHosts = nil
195 volume.Replication = 2
196 cluster.Volumes[uuid] = volume
201 err = json.Unmarshal(volume.DriverParameters, &v)
203 err = os.Mkdir(v.Root, 0777)
204 if !os.IsExist(err) {
208 cluster.Containers.LocalKeepLogsToContainerLog = trial.logConfig
211 err = json.NewEncoder(&s.stdin).Encode(ConfigData{
219 s.testRunTrivialContainer(c)
221 log, logExists := s.logFiles["keepstore.txt"]
222 if trial.logConfig == "none" {
223 c.Check(logExists, Equals, false)
225 c.Check(log, Matches, `(?ms).*not running trash worker.*`)
226 c.Check(log, Matches, `(?ms).*not running trash emptier.*`)
227 c.Check(log, trial.matchGetReq, `(?ms).*"reqMethod":"GET".*`)
228 c.Check(log, trial.matchPutReq, `(?ms).*"reqMethod":"PUT".*,"reqPath":"0e3bcff26d51c895a60ea0d4585e134d".*`)
231 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*using local keepstore process .* at http://[\d\.]{7,}:\d+.*`)
232 c.Check(s.logFiles["crunch-run.txt"], Not(Matches), `(?ms).* at http://127\..*`)
233 c.Check(s.logFiles["crunch-run.txt"], Not(Matches), `(?ms).* at http://169\.254\..*`)
234 c.Check(s.logFiles["stderr.txt"], Matches, `(?ms).*ARVADOS_KEEP_SERVICES=http://[\d\.]{7,}:\d+\n.*`)
238 func (s *integrationSuite) TestRunTrivialContainerWithNoLocalKeepstore(c *C) {
239 // Check that (1) config is loaded from $ARVADOS_CONFIG when
240 // not provided on stdin and (2) if a local keepstore is not
241 // started, crunch-run.txt explains why not.
244 s.testRunTrivialContainer(c)
245 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*not starting a local keepstore process because KeepBuffers=0 in config\n.*`)
248 s.args = []string{"-config", c.MkDir() + "/config.yaml"}
250 buf, err := ioutil.ReadFile(os.Getenv("ARVADOS_CONFIG"))
252 err = ioutil.WriteFile(s.args[1], bytes.Replace(buf, []byte("LocalKeepBlobBuffersPerVCPU: 0"), []byte("LocalKeepBlobBuffersPerVCPU: 1"), -1), 0666)
254 s.testRunTrivialContainer(c)
255 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*not starting a local keepstore process because a volume \(zzzzz-nyw5e-00000000000000\d\) uses AccessViaHosts\n.*`)
257 // Check that config read errors are logged
259 s.args = []string{"-config", c.MkDir() + "/config-error.yaml"}
261 s.testRunTrivialContainer(c)
262 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*could not load config file \Q`+s.args[1]+`\E:.* no such file or directory\n.*`)
265 s.args = []string{"-config", c.MkDir() + "/config-unreadable.yaml"}
267 err = ioutil.WriteFile(s.args[1], []byte{}, 0)
269 s.testRunTrivialContainer(c)
270 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*could not load config file \Q`+s.args[1]+`\E:.* permission denied\n.*`)
274 s.testRunTrivialContainer(c)
275 c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*loaded config file \Q`+os.Getenv("ARVADOS_CONFIG")+`\E\n.*`)
278 func (s *integrationSuite) TestRunTrivialContainerWithOutputGlob(c *C) {
279 s.cr.OutputGlob = []string{"js?n"}
280 s.testRunTrivialContainer(c)
281 fs, err := s.outputCollection.FileSystem(s.client, s.kc)
283 _, err = fs.Stat("json")
285 _, err = fs.Stat("inputfile")
286 c.Check(err, Equals, os.ErrNotExist)
287 _, err = fs.Stat("emptydir")
288 c.Check(err, Equals, os.ErrNotExist)
291 func (s *integrationSuite) testRunTrivialContainer(c *C) {
292 if err := exec.Command("which", s.engine).Run(); err != nil {
293 c.Skip(fmt.Sprintf("%s: %s", s.engine, err))
295 s.cr.Command = []string{"sh", "-c", "env >&2 && cat /mnt/in/inputfile >/mnt/out/inputfile && cat /mnt/json >/mnt/out/json && ! touch /mnt/in/shouldbereadonly && mkdir /mnt/out/emptydir"}
299 "-runtime-engine=" + s.engine,
300 "-enable-memory-limit=false",
302 if s.stdin.Len() > 0 {
303 args = append(args, "-stdin-config=true")
305 args = append(args, s.args...)
306 args = append(args, s.cr.ContainerUUID)
307 code := command{}.RunCommand("crunch-run", args, &s.stdin, io.MultiWriter(&s.stdout, os.Stderr), io.MultiWriter(&s.stderr, os.Stderr))
308 c.Logf("\n===== stdout =====\n%s", s.stdout.String())
309 c.Logf("\n===== stderr =====\n%s", s.stderr.String())
310 c.Check(code, Equals, 0)
311 err := s.client.RequestAndDecode(&s.cr, "GET", "arvados/v1/container_requests/"+s.cr.UUID, nil, nil)
313 c.Logf("Finished container request: %#v", s.cr)
315 var log arvados.Collection
316 err = s.client.RequestAndDecode(&log, "GET", "arvados/v1/collections/"+s.cr.LogUUID, nil, nil)
318 fs, err := log.FileSystem(s.client, s.kc)
320 if d, err := fs.Open("/"); c.Check(err, IsNil) {
321 fis, err := d.Readdir(-1)
323 for _, fi := range fis {
327 f, err := fs.Open(fi.Name())
329 buf, err := ioutil.ReadAll(f)
331 c.Logf("\n===== %s =====\n%s", fi.Name(), buf)
332 s.logFiles[fi.Name()] = string(buf)
335 s.logCollection = log
337 var output arvados.Collection
338 err = s.client.RequestAndDecode(&output, "GET", "arvados/v1/collections/"+s.cr.OutputUUID, nil, nil)
340 s.outputCollection = output
342 if len(s.cr.OutputGlob) == 0 {
343 fs, err = output.FileSystem(s.client, s.kc)
345 if f, err := fs.Open("inputfile"); c.Check(err, IsNil) {
347 buf, err := ioutil.ReadAll(f)
349 c.Check(string(buf), Equals, "inputdata")
351 if f, err := fs.Open("json"); c.Check(err, IsNil) {
353 buf, err := ioutil.ReadAll(f)
355 c.Check(string(buf), Equals, `["foo",{"foo":"bar"},null]`)
357 if fi, err := fs.Stat("emptydir"); c.Check(err, IsNil) {
358 c.Check(fi.IsDir(), Equals, true)
360 if d, err := fs.Open("emptydir"); c.Check(err, IsNil) {
362 fis, err := d.Readdir(-1)
364 // crunch-run still saves a ".keep" file to preserve
365 // empty dirs even though that shouldn't be
366 // necessary. Ideally we would do:
367 // c.Check(fis, HasLen, 0)
368 for _, fi := range fis {
369 c.Check(fi.Name(), Equals, ".keep")