9213: Improve gem loading in `arv`.
[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 = append(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         this.Calls += 1
172         this.Content = append(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.Calls, Equals, 2)
403         c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
404         c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
405         c.Check(*cr.LogsPDH, Equals, "63da7bdacf08c40f604daad80c261e9a+60")
406 }
407
408 func (s *TestSuite) TestUpdateContainerRecordRunning(c *C) {
409         api := &ArvTestClient{}
410         kc := &KeepTestClient{}
411         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
412
413         err := cr.UpdateContainerRecordRunning()
414         c.Check(err, IsNil)
415
416         c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Running")
417 }
418
419 func (s *TestSuite) TestUpdateContainerRecordComplete(c *C) {
420         api := &ArvTestClient{}
421         kc := &KeepTestClient{}
422         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
423
424         cr.LogsPDH = new(string)
425         *cr.LogsPDH = "d3a229d2fe3690c2c3e75a71a153c6a3+60"
426
427         cr.ExitCode = new(int)
428         *cr.ExitCode = 42
429         cr.finalState = "Complete"
430
431         err := cr.UpdateContainerRecordComplete()
432         c.Check(err, IsNil)
433
434         c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
435         c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
436         c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
437 }
438
439 func (s *TestSuite) TestUpdateContainerRecordCancelled(c *C) {
440         api := &ArvTestClient{}
441         kc := &KeepTestClient{}
442         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
443         cr.Cancelled = true
444         cr.finalState = "Cancelled"
445
446         err := cr.UpdateContainerRecordComplete()
447         c.Check(err, IsNil)
448
449         c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], IsNil)
450         c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], IsNil)
451         c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
452 }
453
454 // Used by the TestFullRun*() test below to DRY up boilerplate setup to do full
455 // dress rehearsal of the Run() function, starting from a JSON container record.
456 func FullRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner) {
457         rec := ContainerRecord{}
458         err := json.NewDecoder(strings.NewReader(record)).Decode(&rec)
459         c.Check(err, IsNil)
460
461         docker := NewTestDockerClient()
462         docker.fn = fn
463         docker.RemoveImage(hwImageId, true)
464
465         api = &ArvTestClient{ContainerRecord: rec}
466         cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
467         am := &ArvMountCmdLine{}
468         cr.RunArvMount = am.ArvMountTest
469
470         err = cr.Run()
471         c.Check(err, IsNil)
472         c.Check(api.WasSetRunning, Equals, true)
473
474         c.Check(api.Content[api.Calls-1]["container"].(arvadosclient.Dict)["log"], NotNil)
475
476         if err != nil {
477                 for k, v := range api.Logs {
478                         c.Log(k)
479                         c.Log(v.String())
480                 }
481         }
482
483         return
484 }
485
486 func (s *TestSuite) TestFullRunHello(c *C) {
487         api, _ := FullRunHelper(c, `{
488     "command": ["echo", "hello world"],
489     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
490     "cwd": ".",
491     "environment": {},
492     "mounts": {"/tmp": {"kind": "tmp"} },
493     "output_path": "/tmp",
494     "priority": 1,
495     "runtime_constraints": {}
496 }`, func(t *TestDockerClient) {
497                 t.logWriter.Write(dockerLog(1, "hello world\n"))
498                 t.logWriter.Close()
499                 t.finish <- dockerclient.WaitResult{}
500         })
501
502         c.Check(api.Calls, Equals, 7)
503         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
504         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
505
506         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello world\n"), Equals, true)
507
508 }
509
510 func (s *TestSuite) TestFullRunStderr(c *C) {
511         api, _ := FullRunHelper(c, `{
512     "command": ["/bin/sh", "-c", "echo hello ; echo world 1>&2 ; exit 1"],
513     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
514     "cwd": ".",
515     "environment": {},
516     "mounts": {"/tmp": {"kind": "tmp"} },
517     "output_path": "/tmp",
518     "priority": 1,
519     "runtime_constraints": {}
520 }`, func(t *TestDockerClient) {
521                 t.logWriter.Write(dockerLog(1, "hello\n"))
522                 t.logWriter.Write(dockerLog(2, "world\n"))
523                 t.logWriter.Close()
524                 t.finish <- dockerclient.WaitResult{ExitCode: 1}
525         })
526
527         c.Assert(api.Calls, Equals, 8)
528         c.Check(api.Content[7]["container"].(arvadosclient.Dict)["log"], NotNil)
529         c.Check(api.Content[7]["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
530         c.Check(api.Content[7]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
531
532         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello\n"), Equals, true)
533         c.Check(strings.HasSuffix(api.Logs["stderr"].String(), "world\n"), Equals, true)
534 }
535
536 func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
537         api, _ := FullRunHelper(c, `{
538     "command": ["pwd"],
539     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
540     "cwd": ".",
541     "environment": {},
542     "mounts": {"/tmp": {"kind": "tmp"} },
543     "output_path": "/tmp",
544     "priority": 1,
545     "runtime_constraints": {}
546 }`, func(t *TestDockerClient) {
547                 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
548                 t.logWriter.Close()
549                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
550         })
551
552         c.Check(api.Calls, Equals, 7)
553         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
554         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
555
556         log.Print(api.Logs["stdout"].String())
557
558         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/\n"), Equals, true)
559 }
560
561 func (s *TestSuite) TestFullRunSetCwd(c *C) {
562         api, _ := FullRunHelper(c, `{
563     "command": ["pwd"],
564     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
565     "cwd": "/bin",
566     "environment": {},
567     "mounts": {"/tmp": {"kind": "tmp"} },
568     "output_path": "/tmp",
569     "priority": 1,
570     "runtime_constraints": {}
571 }`, func(t *TestDockerClient) {
572                 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
573                 t.logWriter.Close()
574                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
575         })
576
577         c.Check(api.Calls, Equals, 7)
578         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
579         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
580
581         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/bin\n"), Equals, true)
582 }
583
584 func (s *TestSuite) TestCancel(c *C) {
585         record := `{
586     "command": ["/bin/sh", "-c", "echo foo && sleep 30 && echo bar"],
587     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
588     "cwd": ".",
589     "environment": {},
590     "mounts": {"/tmp": {"kind": "tmp"} },
591     "output_path": "/tmp",
592     "priority": 1,
593     "runtime_constraints": {}
594 }`
595
596         rec := ContainerRecord{}
597         err := json.NewDecoder(strings.NewReader(record)).Decode(&rec)
598         c.Check(err, IsNil)
599
600         docker := NewTestDockerClient()
601         docker.fn = func(t *TestDockerClient) {
602                 <-t.stop
603                 t.logWriter.Write(dockerLog(1, "foo\n"))
604                 t.logWriter.Close()
605                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
606         }
607         docker.RemoveImage(hwImageId, true)
608
609         api := &ArvTestClient{ContainerRecord: rec}
610         cr := NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
611         am := &ArvMountCmdLine{}
612         cr.RunArvMount = am.ArvMountTest
613
614         go func() {
615                 for cr.ContainerID == "" {
616                         time.Sleep(1 * time.Second)
617                 }
618                 cr.SigChan <- syscall.SIGINT
619         }()
620
621         err = cr.Run()
622
623         c.Check(err, IsNil)
624
625         c.Check(api.Calls, Equals, 6)
626         c.Check(api.Content[5]["container"].(arvadosclient.Dict)["log"], NotNil)
627
628         if err != nil {
629                 for k, v := range api.Logs {
630                         c.Log(k)
631                         c.Log(v.String())
632                 }
633         }
634
635         c.Check(api.Content[5]["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
636
637         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "foo\n"), Equals, true)
638
639 }
640
641 func (s *TestSuite) TestFullRunSetEnv(c *C) {
642         api, _ := FullRunHelper(c, `{
643     "command": ["/bin/sh", "-c", "echo $FROBIZ"],
644     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
645     "cwd": "/bin",
646     "environment": {"FROBIZ": "bilbo"},
647     "mounts": {"/tmp": {"kind": "tmp"} },
648     "output_path": "/tmp",
649     "priority": 1,
650     "runtime_constraints": {}
651 }`, func(t *TestDockerClient) {
652                 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
653                 t.logWriter.Close()
654                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
655         })
656
657         c.Check(api.Calls, Equals, 7)
658         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
659         c.Check(api.Content[6]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
660
661         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "bilbo\n"), Equals, true)
662 }
663
664 type ArvMountCmdLine struct {
665         Cmd []string
666 }
667
668 func (am *ArvMountCmdLine) ArvMountTest(c []string) (*exec.Cmd, error) {
669         am.Cmd = c
670         return nil, nil
671 }
672
673 func (s *TestSuite) TestSetupMounts(c *C) {
674         api := &ArvTestClient{}
675         kc := &KeepTestClient{}
676         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
677         am := &ArvMountCmdLine{}
678         cr.RunArvMount = am.ArvMountTest
679
680         i := 0
681         cr.MkTempDir = func(string, string) (string, error) {
682                 i += 1
683                 d := fmt.Sprintf("/tmp/mktmpdir%d", i)
684                 os.Mkdir(d, os.ModePerm)
685                 return d, nil
686         }
687
688         {
689                 cr.ContainerRecord.Mounts = make(map[string]Mount)
690                 cr.ContainerRecord.Mounts["/tmp"] = Mount{Kind: "tmp"}
691                 cr.OutputPath = "/tmp"
692
693                 err := cr.SetupMounts()
694                 c.Check(err, IsNil)
695                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
696                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir2:/tmp"})
697                 cr.CleanupDirs()
698         }
699
700         {
701                 i = 0
702                 cr.ContainerRecord.Mounts = make(map[string]Mount)
703                 cr.ContainerRecord.Mounts["/keeptmp"] = Mount{Kind: "collection", Writable: true}
704                 cr.OutputPath = "/keeptmp"
705
706                 os.MkdirAll("/tmp/mktmpdir1/tmp0", os.ModePerm)
707
708                 err := cr.SetupMounts()
709                 c.Check(err, IsNil)
710                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
711                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir1/tmp0:/keeptmp"})
712                 cr.CleanupDirs()
713         }
714
715         {
716                 i = 0
717                 cr.ContainerRecord.Mounts = make(map[string]Mount)
718                 cr.ContainerRecord.Mounts["/keepinp"] = Mount{Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"}
719                 cr.ContainerRecord.Mounts["/keepout"] = Mount{Kind: "collection", Writable: true}
720                 cr.OutputPath = "/keepout"
721
722                 os.MkdirAll("/tmp/mktmpdir1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
723                 os.MkdirAll("/tmp/mktmpdir1/tmp0", os.ModePerm)
724
725                 err := cr.SetupMounts()
726                 c.Check(err, IsNil)
727                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
728                 var ss sort.StringSlice = cr.Binds
729                 ss.Sort()
730                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir1/by_id/59389a8f9ee9d399be35462a0f92541c+53:/keepinp:ro",
731                         "/tmp/mktmpdir1/tmp0:/keepout"})
732                 cr.CleanupDirs()
733         }
734 }
735
736 func (s *TestSuite) TestStdout(c *C) {
737         helperRecord := `{`
738         helperRecord += `"command": ["/bin/sh", "-c", "echo $FROBIZ"],`
739         helperRecord += `"container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",`
740         helperRecord += `"cwd": "/bin",`
741         helperRecord += `"environment": {"FROBIZ": "bilbo"},`
742         helperRecord += `"mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"} },`
743         helperRecord += `"output_path": "/tmp",`
744         helperRecord += `"priority": 1,`
745         helperRecord += `"runtime_constraints": {}`
746         helperRecord += `}`
747
748         api, _ := FullRunHelper(c, helperRecord, func(t *TestDockerClient) {
749                 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
750                 t.logWriter.Close()
751                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
752         })
753
754         c.Check(api.Calls, Equals, 6)
755         c.Check(api.Content[5]["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
756         c.Check(api.Content[5]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
757         c.Check(api.Content[2]["collection"].(arvadosclient.Dict)["manifest_text"], Equals, "./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out\n")
758 }
759
760 // Used by the TestStdoutWithWrongPath*()
761 func StdoutErrorRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner, err error) {
762         rec := ContainerRecord{}
763         err = json.NewDecoder(strings.NewReader(record)).Decode(&rec)
764         c.Check(err, IsNil)
765
766         docker := NewTestDockerClient()
767         docker.fn = fn
768         docker.RemoveImage(hwImageId, true)
769
770         api = &ArvTestClient{ContainerRecord: rec}
771         cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
772         am := &ArvMountCmdLine{}
773         cr.RunArvMount = am.ArvMountTest
774
775         err = cr.Run()
776         return
777 }
778
779 func (s *TestSuite) TestStdoutWithWrongPath(c *C) {
780         _, _, err := StdoutErrorRunHelper(c, `{
781     "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path":"/tmpa.out"} },
782     "output_path": "/tmp"
783 }`, func(t *TestDockerClient) {})
784
785         c.Check(err, NotNil)
786         c.Check(strings.Contains(err.Error(), "Stdout path does not start with OutputPath"), Equals, true)
787 }
788
789 func (s *TestSuite) TestStdoutWithWrongKindTmp(c *C) {
790         _, _, err := StdoutErrorRunHelper(c, `{
791     "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "tmp", "path":"/tmp/a.out"} },
792     "output_path": "/tmp"
793 }`, func(t *TestDockerClient) {})
794
795         c.Check(err, NotNil)
796         c.Check(strings.Contains(err.Error(), "Unsupported mount kind 'tmp' for stdout"), Equals, true)
797 }
798
799 func (s *TestSuite) TestStdoutWithWrongKindCollection(c *C) {
800         _, _, err := StdoutErrorRunHelper(c, `{
801     "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "collection", "path":"/tmp/a.out"} },
802     "output_path": "/tmp"
803 }`, func(t *TestDockerClient) {})
804
805         c.Check(err, NotNil)
806         c.Check(strings.Contains(err.Error(), "Unsupported mount kind 'collection' for stdout"), Equals, true)
807 }