8015: Don't use Docker logging driver. Attach stdout/stderr before starting container.
[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[4] = 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
467         err = cr.Run()
468         c.Check(err, IsNil)
469         c.Check(api.WasSetRunning, Equals, true)
470
471         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
472
473         if err != nil {
474                 for k, v := range api.Logs {
475                         c.Log(k)
476                         c.Log(v.String())
477                 }
478         }
479
480         return
481 }
482
483 func (s *TestSuite) TestFullRunHello(c *C) {
484         api, _ := FullRunHelper(c, `{
485     "command": ["echo", "hello world"],
486     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
487     "cwd": ".",
488     "environment": {},
489     "mounts": {"/tmp": {"kind": "tmp"} },
490     "output_path": "/tmp",
491     "priority": 1,
492     "runtime_constraints": {}
493 }`, func(t *TestDockerClient) {
494                 t.logWriter.Write(dockerLog(1, "hello world\n"))
495                 t.logWriter.Close()
496                 t.finish <- dockerclient.WaitResult{}
497         })
498
499         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
500         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
501
502         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello world\n"), Equals, true)
503
504 }
505
506 func (s *TestSuite) TestFullRunStderr(c *C) {
507         api, _ := FullRunHelper(c, `{
508     "command": ["/bin/sh", "-c", "echo hello ; echo world 1>&2 ; exit 1"],
509     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
510     "cwd": ".",
511     "environment": {},
512     "mounts": {"/tmp": {"kind": "tmp"} },
513     "output_path": "/tmp",
514     "priority": 1,
515     "runtime_constraints": {}
516 }`, func(t *TestDockerClient) {
517                 t.logWriter.Write(dockerLog(1, "hello\n"))
518                 t.logWriter.Write(dockerLog(2, "world\n"))
519                 t.logWriter.Close()
520                 t.finish <- dockerclient.WaitResult{ExitCode: 1}
521         })
522
523         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
524         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
525         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
526
527         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello\n"), Equals, true)
528         c.Check(strings.HasSuffix(api.Logs["stderr"].String(), "world\n"), Equals, true)
529 }
530
531 func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
532         api, _ := FullRunHelper(c, `{
533     "command": ["pwd"],
534     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
535     "cwd": ".",
536     "environment": {},
537     "mounts": {"/tmp": {"kind": "tmp"} },
538     "output_path": "/tmp",
539     "priority": 1,
540     "runtime_constraints": {}
541 }`, func(t *TestDockerClient) {
542                 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
543                 t.logWriter.Close()
544                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
545         })
546
547         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
548         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
549
550         log.Print(api.Logs["stdout"].String())
551
552         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/\n"), Equals, true)
553 }
554
555 func (s *TestSuite) TestFullRunSetCwd(c *C) {
556         api, _ := FullRunHelper(c, `{
557     "command": ["pwd"],
558     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
559     "cwd": "/bin",
560     "environment": {},
561     "mounts": {"/tmp": {"kind": "tmp"} },
562     "output_path": "/tmp",
563     "priority": 1,
564     "runtime_constraints": {}
565 }`, func(t *TestDockerClient) {
566                 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
567                 t.logWriter.Close()
568                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
569         })
570
571         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
572         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
573
574         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/bin\n"), Equals, true)
575 }
576
577 func (s *TestSuite) TestCancel(c *C) {
578         record := `{
579     "command": ["/bin/sh", "-c", "echo foo && sleep 30 && echo bar"],
580     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
581     "cwd": ".",
582     "environment": {},
583     "mounts": {"/tmp": {"kind": "tmp"} },
584     "output_path": "/tmp",
585     "priority": 1,
586     "runtime_constraints": {}
587 }`
588
589         rec := ContainerRecord{}
590         err := json.NewDecoder(strings.NewReader(record)).Decode(&rec)
591         c.Check(err, IsNil)
592
593         docker := NewTestDockerClient()
594         docker.fn = func(t *TestDockerClient) {
595                 <-t.stop
596                 t.logWriter.Write(dockerLog(1, "foo\n"))
597                 t.logWriter.Close()
598                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
599         }
600         docker.RemoveImage(hwImageId, true)
601
602         api := &ArvTestClient{ContainerRecord: rec}
603         cr := NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
604
605         go func() {
606                 for cr.ContainerID == "" {
607                         time.Sleep(1 * time.Second)
608                 }
609                 cr.SigChan <- syscall.SIGINT
610         }()
611
612         err = cr.Run()
613
614         c.Check(err, IsNil)
615
616         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
617
618         if err != nil {
619                 for k, v := range api.Logs {
620                         c.Log(k)
621                         c.Log(v.String())
622                 }
623         }
624
625         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
626
627         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "foo\n"), Equals, true)
628
629 }
630
631 func (s *TestSuite) TestFullRunSetEnv(c *C) {
632         api, _ := FullRunHelper(c, `{
633     "command": ["/bin/sh", "-c", "echo $FROBIZ"],
634     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
635     "cwd": "/bin",
636     "environment": {"FROBIZ": "bilbo"},
637     "mounts": {"/tmp": {"kind": "tmp"} },
638     "output_path": "/tmp",
639     "priority": 1,
640     "runtime_constraints": {}
641 }`, func(t *TestDockerClient) {
642                 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
643                 t.logWriter.Close()
644                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
645         })
646
647         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
648         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
649
650         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "bilbo\n"), Equals, true)
651 }
652
653 type ArvMountCmdLine struct {
654         Cmd []string
655 }
656
657 func (am *ArvMountCmdLine) ArvMountTest(c []string) (*exec.Cmd, error) {
658         am.Cmd = c
659         return nil, nil
660 }
661
662 func (s *TestSuite) TestSetupMounts(c *C) {
663         api := &ArvTestClient{}
664         kc := &KeepTestClient{}
665         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
666         am := &ArvMountCmdLine{}
667         cr.RunArvMount = am.ArvMountTest
668
669         i := 0
670         cr.MkTempDir = func(string, string) (string, error) {
671                 i += 1
672                 d := fmt.Sprintf("/tmp/mktmpdir%d", i)
673                 os.Mkdir(d, os.ModePerm)
674                 return d, nil
675         }
676
677         {
678                 cr.ContainerRecord.Mounts = make(map[string]Mount)
679                 cr.ContainerRecord.Mounts["/tmp"] = Mount{Kind: "tmp"}
680                 cr.OutputPath = "/tmp"
681
682                 err := cr.SetupMounts()
683                 c.Check(err, IsNil)
684                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
685                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir2:/tmp"})
686                 cr.CleanupDirs()
687         }
688
689         {
690                 i = 0
691                 cr.ContainerRecord.Mounts = make(map[string]Mount)
692                 cr.ContainerRecord.Mounts["/keeptmp"] = Mount{Kind: "collection", Writable: true}
693                 cr.OutputPath = "/keeptmp"
694
695                 os.MkdirAll("/tmp/mktmpdir1/tmp0", os.ModePerm)
696
697                 err := cr.SetupMounts()
698                 c.Check(err, IsNil)
699                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
700                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir1/tmp0:/keeptmp"})
701                 cr.CleanupDirs()
702         }
703
704         {
705                 i = 0
706                 cr.ContainerRecord.Mounts = make(map[string]Mount)
707                 cr.ContainerRecord.Mounts["/keepinp"] = Mount{Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"}
708                 cr.ContainerRecord.Mounts["/keepout"] = Mount{Kind: "collection", Writable: true}
709                 cr.OutputPath = "/keepout"
710
711                 os.MkdirAll("/tmp/mktmpdir1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
712                 os.MkdirAll("/tmp/mktmpdir1/tmp0", os.ModePerm)
713
714                 err := cr.SetupMounts()
715                 c.Check(err, IsNil)
716                 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "/tmp/mktmpdir1"})
717                 var ss sort.StringSlice = cr.Binds
718                 ss.Sort()
719                 c.Check(cr.Binds, DeepEquals, []string{"/tmp/mktmpdir1/by_id/59389a8f9ee9d399be35462a0f92541c+53:/keepinp:ro",
720                         "/tmp/mktmpdir1/tmp0:/keepout"})
721                 cr.CleanupDirs()
722         }
723 }