Merge branch '8508-datamanager-test-badpaths' of https://github.com/wtsi-hgi/arvados...
[arvados.git] / services / crunch-run / crunchrun_test.go
1 package main
2
3 import (
4         "bytes"
5         "crypto/md5"
6         "encoding/json"
7         "errors"
8         "fmt"
9         "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
10         "git.curoverse.com/arvados.git/sdk/go/keepclient"
11         "git.curoverse.com/arvados.git/sdk/go/manifest"
12         "github.com/curoverse/dockerclient"
13         . "gopkg.in/check.v1"
14         "io"
15         "io/ioutil"
16         "log"
17         "os"
18         "os/exec"
19         "sort"
20         "strings"
21         "syscall"
22         "testing"
23         "time"
24 )
25
26 // Gocheck boilerplate
27 func TestCrunchExec(t *testing.T) {
28         TestingT(t)
29 }
30
31 type TestSuite struct{}
32
33 // Gocheck boilerplate
34 var _ = Suite(&TestSuite{})
35
36 type ArvTestClient struct {
37         Total   int64
38         Calls   int
39         Content arvadosclient.Dict
40         ContainerRecord
41         Logs          map[string]*bytes.Buffer
42         WasSetRunning bool
43 }
44
45 type KeepTestClient struct {
46         Called  bool
47         Content []byte
48 }
49
50 var hwManifest = ". 82ab40c24fc8df01798e57ba66795bb1+841216+Aa124ac75e5168396c73c0a18eda641a4f41791c0@569fa8c3 0:841216:9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7.tar\n"
51 var hwPDH = "a45557269dcb65a6b78f9ac061c0850b+120"
52 var hwImageId = "9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7"
53
54 var otherManifest = ". 68a84f561b1d1708c6baff5e019a9ab3+46+Ae5d0af96944a3690becb1decdf60cc1c937f556d@5693216f 0:46:md5sum.txt\n"
55 var otherPDH = "a3e8f74c6f101eae01fa08bfb4e49b3a+54"
56
57 type TestDockerClient struct {
58         imageLoaded string
59         logReader   io.ReadCloser
60         logWriter   io.WriteCloser
61         fn          func(t *TestDockerClient)
62         finish      chan dockerclient.WaitResult
63         stop        chan bool
64         cwd         string
65         env         []string
66 }
67
68 func NewTestDockerClient() *TestDockerClient {
69         t := &TestDockerClient{}
70         t.logReader, t.logWriter = io.Pipe()
71         t.finish = make(chan dockerclient.WaitResult)
72         t.stop = make(chan bool)
73         t.cwd = "/"
74         return t
75 }
76
77 func (t *TestDockerClient) StopContainer(id string, timeout int) error {
78         t.stop <- true
79         return nil
80 }
81
82 func (t *TestDockerClient) InspectImage(id string) (*dockerclient.ImageInfo, error) {
83         if t.imageLoaded == id {
84                 return &dockerclient.ImageInfo{}, nil
85         } else {
86                 return nil, errors.New("")
87         }
88 }
89
90 func (t *TestDockerClient) LoadImage(reader io.Reader) error {
91         _, err := io.Copy(ioutil.Discard, reader)
92         if err != nil {
93                 return err
94         } else {
95                 t.imageLoaded = hwImageId
96                 return nil
97         }
98 }
99
100 func (t *TestDockerClient) CreateContainer(config *dockerclient.ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (string, error) {
101         if config.WorkingDir != "" {
102                 t.cwd = config.WorkingDir
103         }
104         t.env = config.Env
105         return "abcde", nil
106 }
107
108 func (t *TestDockerClient) StartContainer(id string, config *dockerclient.HostConfig) error {
109         if id == "abcde" {
110                 go t.fn(t)
111                 return nil
112         } else {
113                 return errors.New("Invalid container id")
114         }
115 }
116
117 func (t *TestDockerClient) AttachContainer(id string, options *dockerclient.AttachOptions) (io.ReadCloser, error) {
118         return t.logReader, nil
119 }
120
121 func (t *TestDockerClient) Wait(id string) <-chan dockerclient.WaitResult {
122         return t.finish
123 }
124
125 func (*TestDockerClient) RemoveImage(name string, force bool) ([]*dockerclient.ImageDelete, error) {
126         return nil, nil
127 }
128
129 func (this *ArvTestClient) Create(resourceType string,
130         parameters arvadosclient.Dict,
131         output interface{}) error {
132
133         this.Calls += 1
134         this.Content = parameters
135
136         if resourceType == "logs" {
137                 et := parameters["log"].(arvadosclient.Dict)["event_type"].(string)
138                 if this.Logs == nil {
139                         this.Logs = make(map[string]*bytes.Buffer)
140                 }
141                 if this.Logs[et] == nil {
142                         this.Logs[et] = &bytes.Buffer{}
143                 }
144                 this.Logs[et].Write([]byte(parameters["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]))
145         }
146
147         if resourceType == "collections" && output != nil {
148                 mt := parameters["collection"].(arvadosclient.Dict)["manifest_text"].(string)
149                 outmap := output.(*CollectionRecord)
150                 outmap.PortableDataHash = fmt.Sprintf("%x+%d", md5.Sum([]byte(mt)), len(mt))
151         }
152
153         return nil
154 }
155
156 func (this *ArvTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
157         if resourceType == "collections" {
158                 if uuid == hwPDH {
159                         output.(*CollectionRecord).ManifestText = hwManifest
160                 } else if uuid == otherPDH {
161                         output.(*CollectionRecord).ManifestText = otherManifest
162                 }
163         }
164         if resourceType == "containers" {
165                 (*output.(*ContainerRecord)) = this.ContainerRecord
166         }
167         return nil
168 }
169
170 func (this *ArvTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
171
172         this.Content = parameters
173         if resourceType == "containers" {
174                 if parameters["container"].(arvadosclient.Dict)["state"] == "Running" {
175                         this.WasSetRunning = true
176                 }
177
178         }
179         return nil
180 }
181
182 func (this *KeepTestClient) PutHB(hash string, buf []byte) (string, int, error) {
183         this.Content = buf
184         return fmt.Sprintf("%s+%d", hash, len(buf)), len(buf), nil
185 }
186
187 type FileWrapper struct {
188         io.ReadCloser
189         len uint64
190 }
191
192 func (this FileWrapper) Len() uint64 {
193         return this.len
194 }
195
196 func (this *KeepTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
197         if filename == hwImageId+".tar" {
198                 rdr := ioutil.NopCloser(&bytes.Buffer{})
199                 this.Called = true
200                 return FileWrapper{rdr, 1321984}, nil
201         }
202         return nil, nil
203 }
204
205 func (s *TestSuite) TestLoadImage(c *C) {
206         kc := &KeepTestClient{}
207         docker := NewTestDockerClient()
208         cr := NewContainerRunner(&ArvTestClient{}, kc, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
209
210         _, err := cr.Docker.RemoveImage(hwImageId, true)
211
212         _, err = cr.Docker.InspectImage(hwImageId)
213         c.Check(err, NotNil)
214
215         cr.ContainerRecord.ContainerImage = hwPDH
216
217         // (1) Test loading image from keep
218         c.Check(kc.Called, Equals, false)
219         c.Check(cr.ContainerConfig.Image, Equals, "")
220
221         err = cr.LoadImage()
222
223         c.Check(err, IsNil)
224         defer func() {
225                 cr.Docker.RemoveImage(hwImageId, true)
226         }()
227
228         c.Check(kc.Called, Equals, true)
229         c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
230
231         _, err = cr.Docker.InspectImage(hwImageId)
232         c.Check(err, IsNil)
233
234         // (2) Test using image that's already loaded
235         kc.Called = false
236         cr.ContainerConfig.Image = ""
237
238         err = cr.LoadImage()
239         c.Check(err, IsNil)
240         c.Check(kc.Called, Equals, false)
241         c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
242
243 }
244
245 type ArvErrorTestClient struct{}
246 type KeepErrorTestClient struct{}
247 type KeepReadErrorTestClient struct{}
248
249 func (this ArvErrorTestClient) Create(resourceType string,
250         parameters arvadosclient.Dict,
251         output interface{}) error {
252         return nil
253 }
254
255 func (this ArvErrorTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
256         return errors.New("ArvError")
257 }
258
259 func (this ArvErrorTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
260         return nil
261 }
262
263 func (this KeepErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
264         return "", 0, errors.New("KeepError")
265 }
266
267 func (this KeepErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
268         return nil, errors.New("KeepError")
269 }
270
271 func (this KeepReadErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
272         return "", 0, nil
273 }
274
275 type ErrorReader struct{}
276
277 func (this ErrorReader) Read(p []byte) (n int, err error) {
278         return 0, errors.New("ErrorReader")
279 }
280
281 func (this ErrorReader) Close() error {
282         return nil
283 }
284
285 func (this ErrorReader) Len() uint64 {
286         return 0
287 }
288
289 func (this KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
290         return ErrorReader{}, nil
291 }
292
293 func (s *TestSuite) TestLoadImageArvError(c *C) {
294         // (1) Arvados error
295         cr := NewContainerRunner(ArvErrorTestClient{}, &KeepTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
296         cr.ContainerRecord.ContainerImage = hwPDH
297
298         err := cr.LoadImage()
299         c.Check(err.Error(), Equals, "While getting container image collection: ArvError")
300 }
301
302 func (s *TestSuite) TestLoadImageKeepError(c *C) {
303         // (2) Keep error
304         docker := NewTestDockerClient()
305         cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
306         cr.ContainerRecord.ContainerImage = hwPDH
307
308         err := cr.LoadImage()
309         c.Check(err.Error(), Equals, "While creating ManifestFileReader for container image: KeepError")
310 }
311
312 func (s *TestSuite) TestLoadImageCollectionError(c *C) {
313         // (3) Collection doesn't contain image
314         cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
315         cr.ContainerRecord.ContainerImage = otherPDH
316
317         err := cr.LoadImage()
318         c.Check(err.Error(), Equals, "First file in the container image collection does not end in .tar")
319 }
320
321 func (s *TestSuite) TestLoadImageKeepReadError(c *C) {
322         // (4) Collection doesn't contain image
323         docker := NewTestDockerClient()
324         cr := NewContainerRunner(&ArvTestClient{}, KeepReadErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
325         cr.ContainerRecord.ContainerImage = hwPDH
326
327         err := cr.LoadImage()
328         c.Check(err, NotNil)
329 }
330
331 type ClosableBuffer struct {
332         bytes.Buffer
333 }
334
335 type TestLogs struct {
336         Stdout ClosableBuffer
337         Stderr ClosableBuffer
338 }
339
340 func (this *ClosableBuffer) Close() error {
341         return nil
342 }
343
344 func (this *TestLogs) NewTestLoggingWriter(logstr string) io.WriteCloser {
345         if logstr == "stdout" {
346                 return &this.Stdout
347         }
348         if logstr == "stderr" {
349                 return &this.Stderr
350         }
351         return nil
352 }
353
354 func dockerLog(fd byte, msg string) []byte {
355         by := []byte(msg)
356         header := make([]byte, 8+len(by))
357         header[0] = fd
358         header[7] = byte(len(by))
359         copy(header[8:], by)
360         return header
361 }
362
363 func (s *TestSuite) TestRunContainer(c *C) {
364         docker := NewTestDockerClient()
365         docker.fn = func(t *TestDockerClient) {
366                 t.logWriter.Write(dockerLog(1, "Hello world\n"))
367                 t.logWriter.Close()
368                 t.finish <- dockerclient.WaitResult{}
369         }
370         cr := NewContainerRunner(&ArvTestClient{}, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
371
372         var logs TestLogs
373         cr.NewLogWriter = logs.NewTestLoggingWriter
374         cr.ContainerRecord.ContainerImage = hwPDH
375         cr.ContainerRecord.Command = []string{"./hw"}
376         err := cr.LoadImage()
377         c.Check(err, IsNil)
378
379         err = cr.StartContainer()
380         c.Check(err, IsNil)
381
382         err = cr.WaitFinish()
383         c.Check(err, IsNil)
384
385         c.Check(strings.HasSuffix(logs.Stdout.String(), "Hello world\n"), Equals, true)
386         c.Check(logs.Stderr.String(), Equals, "")
387 }
388
389 func (s *TestSuite) TestCommitLogs(c *C) {
390         api := &ArvTestClient{}
391         kc := &KeepTestClient{}
392         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
393         cr.CrunchLog.Timestamper = (&TestTimestamper{}).Timestamp
394
395         cr.CrunchLog.Print("Hello world!")
396         cr.CrunchLog.Print("Goodbye")
397         cr.finalState = "Complete"
398
399         err := cr.CommitLogs()
400         c.Check(err, IsNil)
401
402         c.Check(api.Content["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
403         c.Check(api.Content["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
404         c.Check(*cr.LogsPDH, Equals, "63da7bdacf08c40f604daad80c261e9a+60")
405 }
406
407 func (s *TestSuite) TestUpdateContainerRecordRunning(c *C) {
408         api := &ArvTestClient{}
409         kc := &KeepTestClient{}
410         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
411
412         err := cr.UpdateContainerRecordRunning()
413         c.Check(err, IsNil)
414
415         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Running")
416 }
417
418 func (s *TestSuite) TestUpdateContainerRecordComplete(c *C) {
419         api := &ArvTestClient{}
420         kc := &KeepTestClient{}
421         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
422
423         cr.LogsPDH = new(string)
424         *cr.LogsPDH = "d3a229d2fe3690c2c3e75a71a153c6a3+60"
425
426         cr.ExitCode = new(int)
427         *cr.ExitCode = 42
428         cr.finalState = "Complete"
429
430         err := cr.UpdateContainerRecordComplete()
431         c.Check(err, IsNil)
432
433         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
434         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
435         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
436 }
437
438 func (s *TestSuite) TestUpdateContainerRecordCancelled(c *C) {
439         api := &ArvTestClient{}
440         kc := &KeepTestClient{}
441         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
442         cr.Cancelled = true
443         cr.finalState = "Cancelled"
444
445         err := cr.UpdateContainerRecordComplete()
446         c.Check(err, IsNil)
447
448         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], IsNil)
449         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], IsNil)
450         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
451 }
452
453 // Used by the TestFullRun*() test below to DRY up boilerplate setup to do full
454 // dress rehersal of the Run() function, starting from a JSON container record.
455 func FullRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner) {
456         rec := ContainerRecord{}
457         err := json.NewDecoder(strings.NewReader(record)).Decode(&rec)
458         c.Check(err, IsNil)
459
460         docker := NewTestDockerClient()
461         docker.fn = fn
462         docker.RemoveImage(hwImageId, true)
463
464         api = &ArvTestClient{ContainerRecord: rec}
465         cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
466         am := &ArvMountCmdLine{}
467         cr.RunArvMount = am.ArvMountTest
468
469         err = cr.Run()
470         c.Check(err, IsNil)
471         c.Check(api.WasSetRunning, Equals, true)
472
473         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
474
475         if err != nil {
476                 for k, v := range api.Logs {
477                         c.Log(k)
478                         c.Log(v.String())
479                 }
480         }
481
482         return
483 }
484
485 func (s *TestSuite) TestFullRunHello(c *C) {
486         api, _ := FullRunHelper(c, `{
487     "command": ["echo", "hello world"],
488     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
489     "cwd": ".",
490     "environment": {},
491     "mounts": {"/tmp": {"kind": "tmp"} },
492     "output_path": "/tmp",
493     "priority": 1,
494     "runtime_constraints": {}
495 }`, func(t *TestDockerClient) {
496                 t.logWriter.Write(dockerLog(1, "hello world\n"))
497                 t.logWriter.Close()
498                 t.finish <- dockerclient.WaitResult{}
499         })
500
501         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
502         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
503
504         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello world\n"), Equals, true)
505
506 }
507
508 func (s *TestSuite) TestFullRunStderr(c *C) {
509         api, _ := FullRunHelper(c, `{
510     "command": ["/bin/sh", "-c", "echo hello ; echo world 1>&2 ; exit 1"],
511     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
512     "cwd": ".",
513     "environment": {},
514     "mounts": {"/tmp": {"kind": "tmp"} },
515     "output_path": "/tmp",
516     "priority": 1,
517     "runtime_constraints": {}
518 }`, func(t *TestDockerClient) {
519                 t.logWriter.Write(dockerLog(1, "hello\n"))
520                 t.logWriter.Write(dockerLog(2, "world\n"))
521                 t.logWriter.Close()
522                 t.finish <- dockerclient.WaitResult{ExitCode: 1}
523         })
524
525         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
526         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
527         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
528
529         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello\n"), Equals, true)
530         c.Check(strings.HasSuffix(api.Logs["stderr"].String(), "world\n"), Equals, true)
531 }
532
533 func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
534         api, _ := FullRunHelper(c, `{
535     "command": ["pwd"],
536     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
537     "cwd": ".",
538     "environment": {},
539     "mounts": {"/tmp": {"kind": "tmp"} },
540     "output_path": "/tmp",
541     "priority": 1,
542     "runtime_constraints": {}
543 }`, func(t *TestDockerClient) {
544                 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
545                 t.logWriter.Close()
546                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
547         })
548
549         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
550         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
551
552         log.Print(api.Logs["stdout"].String())
553
554         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/\n"), Equals, true)
555 }
556
557 func (s *TestSuite) TestFullRunSetCwd(c *C) {
558         api, _ := FullRunHelper(c, `{
559     "command": ["pwd"],
560     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
561     "cwd": "/bin",
562     "environment": {},
563     "mounts": {"/tmp": {"kind": "tmp"} },
564     "output_path": "/tmp",
565     "priority": 1,
566     "runtime_constraints": {}
567 }`, func(t *TestDockerClient) {
568                 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
569                 t.logWriter.Close()
570                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
571         })
572
573         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
574         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
575
576         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/bin\n"), Equals, true)
577 }
578
579 func (s *TestSuite) TestCancel(c *C) {
580         record := `{
581     "command": ["/bin/sh", "-c", "echo foo && sleep 30 && echo bar"],
582     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
583     "cwd": ".",
584     "environment": {},
585     "mounts": {"/tmp": {"kind": "tmp"} },
586     "output_path": "/tmp",
587     "priority": 1,
588     "runtime_constraints": {}
589 }`
590
591         rec := ContainerRecord{}
592         err := json.NewDecoder(strings.NewReader(record)).Decode(&rec)
593         c.Check(err, IsNil)
594
595         docker := NewTestDockerClient()
596         docker.fn = func(t *TestDockerClient) {
597                 <-t.stop
598                 t.logWriter.Write(dockerLog(1, "foo\n"))
599                 t.logWriter.Close()
600                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
601         }
602         docker.RemoveImage(hwImageId, true)
603
604         api := &ArvTestClient{ContainerRecord: rec}
605         cr := NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
606         am := &ArvMountCmdLine{}
607         cr.RunArvMount = am.ArvMountTest
608
609         go func() {
610                 for cr.ContainerID == "" {
611                         time.Sleep(1 * time.Second)
612                 }
613                 cr.SigChan <- syscall.SIGINT
614         }()
615
616         err = cr.Run()
617
618         c.Check(err, IsNil)
619
620         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
621
622         if err != nil {
623                 for k, v := range api.Logs {
624                         c.Log(k)
625                         c.Log(v.String())
626                 }
627         }
628
629         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
630
631         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "foo\n"), Equals, true)
632
633 }
634
635 func (s *TestSuite) TestFullRunSetEnv(c *C) {
636         api, _ := FullRunHelper(c, `{
637     "command": ["/bin/sh", "-c", "echo $FROBIZ"],
638     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
639     "cwd": "/bin",
640     "environment": {"FROBIZ": "bilbo"},
641     "mounts": {"/tmp": {"kind": "tmp"} },
642     "output_path": "/tmp",
643     "priority": 1,
644     "runtime_constraints": {}
645 }`, func(t *TestDockerClient) {
646                 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
647                 t.logWriter.Close()
648                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
649         })
650
651         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
652         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
653
654         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "bilbo\n"), Equals, true)
655 }
656
657 type ArvMountCmdLine struct {
658         Cmd []string
659 }
660
661 func (am *ArvMountCmdLine) ArvMountTest(c []string) (*exec.Cmd, error) {
662         am.Cmd = c
663         return nil, nil
664 }
665
666 func (s *TestSuite) TestSetupMounts(c *C) {
667         api := &ArvTestClient{}
668         kc := &KeepTestClient{}
669         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
670         am := &ArvMountCmdLine{}
671         cr.RunArvMount = am.ArvMountTest
672
673         i := 0
674         cr.MkTempDir = func(string, string) (string, error) {
675                 i += 1
676                 d := fmt.Sprintf("/tmp/mktmpdir%d", i)
677                 os.Mkdir(d, os.ModePerm)
678                 return d, nil
679         }
680
681         {
682                 cr.ContainerRecord.Mounts = make(map[string]Mount)
683                 cr.ContainerRecord.Mounts["/tmp"] = Mount{Kind: "tmp"}
684                 cr.OutputPath = "/tmp"
685
686                 err := cr.SetupMounts()
687                 c.Check(err, IsNil)
688                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
689                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir2:/tmp"})
690                 cr.CleanupDirs()
691         }
692
693         {
694                 i = 0
695                 cr.ContainerRecord.Mounts = make(map[string]Mount)
696                 cr.ContainerRecord.Mounts["/keeptmp"] = Mount{Kind: "collection", Writable: true}
697                 cr.OutputPath = "/keeptmp"
698
699                 os.MkdirAll("/tmp/mktmpdir1/tmp0", os.ModePerm)
700
701                 err := cr.SetupMounts()
702                 c.Check(err, IsNil)
703                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
704                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir1/tmp0:/keeptmp"})
705                 cr.CleanupDirs()
706         }
707
708         {
709                 i = 0
710                 cr.ContainerRecord.Mounts = make(map[string]Mount)
711                 cr.ContainerRecord.Mounts["/keepinp"] = Mount{Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"}
712                 cr.ContainerRecord.Mounts["/keepout"] = Mount{Kind: "collection", Writable: true}
713                 cr.OutputPath = "/keepout"
714
715                 os.MkdirAll("/tmp/mktmpdir1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
716                 os.MkdirAll("/tmp/mktmpdir1/tmp0", os.ModePerm)
717
718                 err := cr.SetupMounts()
719                 c.Check(err, IsNil)
720                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
721                 var ss sort.StringSlice = cr.Binds
722                 ss.Sort()
723                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir1/by_id/59389a8f9ee9d399be35462a0f92541c+53:/keepinp:ro",
724                         "/tmp/mktmpdir1/tmp0:/keepout"})
725                 cr.CleanupDirs()
726         }
727 }