1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
29 "git.curoverse.com/arvados.git/sdk/go/arvados"
30 "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
31 "git.curoverse.com/arvados.git/sdk/go/manifest"
33 dockertypes "github.com/docker/docker/api/types"
34 dockercontainer "github.com/docker/docker/api/types/container"
35 dockernetwork "github.com/docker/docker/api/types/network"
39 // Gocheck boilerplate
40 func TestCrunchExec(t *testing.T) {
44 type TestSuite struct{}
46 // Gocheck boilerplate
47 var _ = Suite(&TestSuite{})
49 type ArvTestClient struct {
52 Content []arvadosclient.Dict
54 Logs map[string]*bytes.Buffer
60 type KeepTestClient struct {
65 var hwManifest = ". 82ab40c24fc8df01798e57ba66795bb1+841216+Aa124ac75e5168396c73c0a18eda641a4f41791c0@569fa8c3 0:841216:9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7.tar\n"
66 var hwPDH = "a45557269dcb65a6b78f9ac061c0850b+120"
67 var hwImageId = "9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7"
69 var otherManifest = ". 68a84f561b1d1708c6baff5e019a9ab3+46+Ae5d0af96944a3690becb1decdf60cc1c937f556d@5693216f 0:46:md5sum.txt\n"
70 var otherPDH = "a3e8f74c6f101eae01fa08bfb4e49b3a+54"
72 var normalizedManifestWithSubdirs = `. 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0abcdefgh11234567890@569fa8c3 0:9:file1_in_main.txt 9:18:file2_in_main.txt 0:27:zzzzz-8i9sb-bcdefghijkdhvnk.log.txt
73 ./subdir1 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 0:9:file1_in_subdir1.txt 9:18:file2_in_subdir1.txt
74 ./subdir1/subdir2 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0bcdefghijk544332211@569fa8c5 0:9:file1_in_subdir2.txt 9:18:file2_in_subdir2.txt
77 var normalizedWithSubdirsPDH = "a0def87f80dd594d4675809e83bd4f15+367"
79 var denormalizedManifestWithSubdirs = ". 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0abcdefgh11234567890@569fa8c3 0:9:file1_in_main.txt 9:18:file2_in_main.txt 0:27:zzzzz-8i9sb-bcdefghijkdhvnk.log.txt 0:10:subdir1/file1_in_subdir1.txt 10:17:subdir1/file2_in_subdir1.txt\n"
80 var denormalizedWithSubdirsPDH = "b0def87f80dd594d4675809e83bd4f15+367"
82 var fakeAuthUUID = "zzzzz-gj3su-55pqoyepgi2glem"
83 var fakeAuthToken = "a3ltuwzqcu2u4sc0q7yhpc2w7s00fdcqecg5d6e0u3pfohmbjt"
85 type TestDockerClient struct {
87 logReader io.ReadCloser
88 logWriter io.WriteCloser
89 fn func(t *TestDockerClient)
98 func NewTestDockerClient(exitCode int) *TestDockerClient {
99 t := &TestDockerClient{}
100 t.logReader, t.logWriter = io.Pipe()
102 t.stop = make(chan bool, 1)
107 type MockConn struct {
111 func (m *MockConn) Write(b []byte) (int, error) {
115 func NewMockConn() *MockConn {
120 func (t *TestDockerClient) ContainerAttach(ctx context.Context, container string, options dockertypes.ContainerAttachOptions) (dockertypes.HijackedResponse, error) {
121 return dockertypes.HijackedResponse{Conn: NewMockConn(), Reader: bufio.NewReader(t.logReader)}, nil
124 func (t *TestDockerClient) ContainerCreate(ctx context.Context, config *dockercontainer.Config, hostConfig *dockercontainer.HostConfig, networkingConfig *dockernetwork.NetworkingConfig, containerName string) (dockercontainer.ContainerCreateCreatedBody, error) {
125 if config.WorkingDir != "" {
126 t.cwd = config.WorkingDir
129 return dockercontainer.ContainerCreateCreatedBody{ID: "abcde"}, nil
132 func (t *TestDockerClient) ContainerStart(ctx context.Context, container string, options dockertypes.ContainerStartOptions) error {
134 return errors.New(`Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused "process_linux.go:359: container init caused \"rootfs_linux.go:54: mounting \\\"/tmp/keep453790790/by_id/99999999999999999999999999999999+99999/myGenome\\\" to rootfs \\\"/tmp/docker/overlay2/9999999999999999999999999999999999999999999999999999999999999999/merged\\\" at \\\"/tmp/docker/overlay2/9999999999999999999999999999999999999999999999999999999999999999/merged/keep/99999999999999999999999999999999+99999/myGenome\\\" caused \\\"no such file or directory\\\"\""`)
137 return errors.New(`panic: standard_init_linux.go:175: exec user process caused "no such file or directory"`)
140 return errors.New(`Error response from daemon: Cannot start container 41f26cbc43bcc1280f4323efb1830a394ba8660c9d1c2b564ba42bf7f7694845: [8] System error: no such file or directory`)
143 return errors.New(`Error response from daemon: Cannot start container 58099cd76c834f3dc2a4fb76c8028f049ae6d4fdf0ec373e1f2cfea030670c2d: [8] System error: exec: "foobar": executable file not found in $PATH`)
146 if container == "abcde" {
147 // t.fn gets executed in ContainerWait
150 return errors.New("Invalid container id")
154 func (t *TestDockerClient) ContainerStop(ctx context.Context, container string, timeout *time.Duration) error {
159 func (t *TestDockerClient) ContainerWait(ctx context.Context, container string, condition dockercontainer.WaitCondition) (<-chan dockercontainer.ContainerWaitOKBody, <-chan error) {
160 body := make(chan dockercontainer.ContainerWaitOKBody)
161 err := make(chan error)
164 body <- dockercontainer.ContainerWaitOKBody{StatusCode: int64(t.finish)}
171 func (t *TestDockerClient) ImageInspectWithRaw(ctx context.Context, image string) (dockertypes.ImageInspect, []byte, error) {
173 return dockertypes.ImageInspect{}, nil, fmt.Errorf("Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?")
176 if t.imageLoaded == image {
177 return dockertypes.ImageInspect{}, nil, nil
179 return dockertypes.ImageInspect{}, nil, errors.New("")
183 func (t *TestDockerClient) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (dockertypes.ImageLoadResponse, error) {
185 return dockertypes.ImageLoadResponse{}, fmt.Errorf("Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?")
187 _, err := io.Copy(ioutil.Discard, input)
189 return dockertypes.ImageLoadResponse{}, err
191 t.imageLoaded = hwImageId
192 return dockertypes.ImageLoadResponse{Body: ioutil.NopCloser(input)}, nil
196 func (*TestDockerClient) ImageRemove(ctx context.Context, image string, options dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error) {
200 func (client *ArvTestClient) Create(resourceType string,
201 parameters arvadosclient.Dict,
202 output interface{}) error {
205 defer client.Mutex.Unlock()
208 client.Content = append(client.Content, parameters)
210 if resourceType == "logs" {
211 et := parameters["log"].(arvadosclient.Dict)["event_type"].(string)
212 if client.Logs == nil {
213 client.Logs = make(map[string]*bytes.Buffer)
215 if client.Logs[et] == nil {
216 client.Logs[et] = &bytes.Buffer{}
218 client.Logs[et].Write([]byte(parameters["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]))
221 if resourceType == "collections" && output != nil {
222 mt := parameters["collection"].(arvadosclient.Dict)["manifest_text"].(string)
223 outmap := output.(*arvados.Collection)
224 outmap.PortableDataHash = fmt.Sprintf("%x+%d", md5.Sum([]byte(mt)), len(mt))
230 func (client *ArvTestClient) Call(method, resourceType, uuid, action string, parameters arvadosclient.Dict, output interface{}) error {
232 case method == "GET" && resourceType == "containers" && action == "auth":
233 return json.Unmarshal([]byte(`{
234 "kind": "arvados#api_client_authorization",
235 "uuid": "`+fakeAuthUUID+`",
236 "api_token": "`+fakeAuthToken+`"
239 return fmt.Errorf("Not found")
243 func (client *ArvTestClient) CallRaw(method, resourceType, uuid, action string,
244 parameters arvadosclient.Dict) (reader io.ReadCloser, err error) {
246 if method == "GET" && resourceType == "containers" && action == "" && !client.callraw {
247 j, err = json.Marshal(client.Container)
250 "command": ["sleep", "1"],
251 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
254 "mounts": {"/tmp": {"kind": "tmp"}, "/json": {"kind": "json", "content": {"number": 123456789123456789}}},
255 "output_path": "/tmp",
257 "runtime_constraints": {}
260 return ioutil.NopCloser(bytes.NewReader(j)), err
263 func (client *ArvTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
264 if resourceType == "collections" {
266 output.(*arvados.Collection).ManifestText = hwManifest
267 } else if uuid == otherPDH {
268 output.(*arvados.Collection).ManifestText = otherManifest
269 } else if uuid == normalizedWithSubdirsPDH {
270 output.(*arvados.Collection).ManifestText = normalizedManifestWithSubdirs
271 } else if uuid == denormalizedWithSubdirsPDH {
272 output.(*arvados.Collection).ManifestText = denormalizedManifestWithSubdirs
275 if resourceType == "containers" {
276 (*output.(*arvados.Container)) = client.Container
281 func (client *ArvTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
283 defer client.Mutex.Unlock()
285 client.Content = append(client.Content, parameters)
286 if resourceType == "containers" {
287 if parameters["container"].(arvadosclient.Dict)["state"] == "Running" {
288 client.WasSetRunning = true
294 var discoveryMap = map[string]interface{}{
295 "defaultTrashLifetime": float64(1209600),
296 "crunchLimitLogBytesPerJob": float64(67108864),
297 "crunchLogThrottleBytes": float64(65536),
298 "crunchLogThrottlePeriod": float64(60),
299 "crunchLogThrottleLines": float64(1024),
300 "crunchLogPartialLineThrottlePeriod": float64(5),
301 "crunchLogBytesPerEvent": float64(4096),
302 "crunchLogSecondsBetweenEvents": float64(1),
305 func (client *ArvTestClient) Discovery(key string) (interface{}, error) {
306 return discoveryMap[key], nil
309 // CalledWith returns the parameters from the first API call whose
310 // parameters match jpath/string. E.g., CalledWith(c, "foo.bar",
311 // "baz") returns parameters with parameters["foo"]["bar"]=="baz". If
312 // no call matches, it returns nil.
313 func (client *ArvTestClient) CalledWith(jpath string, expect interface{}) arvadosclient.Dict {
315 for _, content := range client.Content {
316 var v interface{} = content
317 for _, k := range strings.Split(jpath, ".") {
318 if dict, ok := v.(arvadosclient.Dict); !ok {
331 func (client *KeepTestClient) PutHB(hash string, buf []byte) (string, int, error) {
333 return fmt.Sprintf("%s+%d", hash, len(buf)), len(buf), nil
336 func (*KeepTestClient) ClearBlockCache() {
339 type FileWrapper struct {
344 func (fw FileWrapper) Readdir(n int) ([]os.FileInfo, error) {
345 return nil, errors.New("not implemented")
348 func (fw FileWrapper) Seek(int64, int) (int64, error) {
349 return 0, errors.New("not implemented")
352 func (fw FileWrapper) Size() int64 {
356 func (fw FileWrapper) Stat() (os.FileInfo, error) {
357 return nil, errors.New("not implemented")
360 func (fw FileWrapper) Truncate(int64) error {
361 return errors.New("not implemented")
364 func (fw FileWrapper) Write([]byte) (int, error) {
365 return 0, errors.New("not implemented")
368 func (client *KeepTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
369 if filename == hwImageId+".tar" {
370 rdr := ioutil.NopCloser(&bytes.Buffer{})
372 return FileWrapper{rdr, 1321984}, nil
373 } else if filename == "/file1_in_main.txt" {
374 rdr := ioutil.NopCloser(strings.NewReader("foo"))
376 return FileWrapper{rdr, 3}, nil
381 func (s *TestSuite) TestLoadImage(c *C) {
382 kc := &KeepTestClient{}
383 docker := NewTestDockerClient(0)
384 cr := NewContainerRunner(&ArvTestClient{}, kc, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
386 _, err := cr.Docker.ImageRemove(nil, hwImageId, dockertypes.ImageRemoveOptions{})
388 _, _, err = cr.Docker.ImageInspectWithRaw(nil, hwImageId)
391 cr.Container.ContainerImage = hwPDH
393 // (1) Test loading image from keep
394 c.Check(kc.Called, Equals, false)
395 c.Check(cr.ContainerConfig.Image, Equals, "")
401 cr.Docker.ImageRemove(nil, hwImageId, dockertypes.ImageRemoveOptions{})
404 c.Check(kc.Called, Equals, true)
405 c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
407 _, _, err = cr.Docker.ImageInspectWithRaw(nil, hwImageId)
410 // (2) Test using image that's already loaded
412 cr.ContainerConfig.Image = ""
416 c.Check(kc.Called, Equals, false)
417 c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
421 type ArvErrorTestClient struct{}
423 func (ArvErrorTestClient) Create(resourceType string,
424 parameters arvadosclient.Dict,
425 output interface{}) error {
429 func (ArvErrorTestClient) Call(method, resourceType, uuid, action string, parameters arvadosclient.Dict, output interface{}) error {
430 return errors.New("ArvError")
433 func (ArvErrorTestClient) CallRaw(method, resourceType, uuid, action string,
434 parameters arvadosclient.Dict) (reader io.ReadCloser, err error) {
435 return nil, errors.New("ArvError")
438 func (ArvErrorTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
439 return errors.New("ArvError")
442 func (ArvErrorTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
446 func (ArvErrorTestClient) Discovery(key string) (interface{}, error) {
447 return discoveryMap[key], nil
450 type KeepErrorTestClient struct{}
452 func (KeepErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
453 return "", 0, errors.New("KeepError")
456 func (KeepErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
457 return nil, errors.New("KeepError")
460 func (KeepErrorTestClient) ClearBlockCache() {
463 type KeepReadErrorTestClient struct{}
465 func (KeepReadErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
469 func (KeepReadErrorTestClient) ClearBlockCache() {
472 type ErrorReader struct {
476 func (ErrorReader) Read(p []byte) (n int, err error) {
477 return 0, errors.New("ErrorReader")
480 func (ErrorReader) Seek(int64, int) (int64, error) {
481 return 0, errors.New("ErrorReader")
484 func (KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
485 return ErrorReader{}, nil
488 func (s *TestSuite) TestLoadImageArvError(c *C) {
490 cr := NewContainerRunner(ArvErrorTestClient{}, &KeepTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
491 cr.Container.ContainerImage = hwPDH
493 err := cr.LoadImage()
494 c.Check(err.Error(), Equals, "While getting container image collection: ArvError")
497 func (s *TestSuite) TestLoadImageKeepError(c *C) {
499 docker := NewTestDockerClient(0)
500 cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
501 cr.Container.ContainerImage = hwPDH
503 err := cr.LoadImage()
504 c.Check(err.Error(), Equals, "While creating ManifestFileReader for container image: KeepError")
507 func (s *TestSuite) TestLoadImageCollectionError(c *C) {
508 // (3) Collection doesn't contain image
509 cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
510 cr.Container.ContainerImage = otherPDH
512 err := cr.LoadImage()
513 c.Check(err.Error(), Equals, "First file in the container image collection does not end in .tar")
516 func (s *TestSuite) TestLoadImageKeepReadError(c *C) {
517 // (4) Collection doesn't contain image
518 docker := NewTestDockerClient(0)
519 cr := NewContainerRunner(&ArvTestClient{}, KeepReadErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
520 cr.Container.ContainerImage = hwPDH
522 err := cr.LoadImage()
526 type ClosableBuffer struct {
530 func (*ClosableBuffer) Close() error {
534 type TestLogs struct {
535 Stdout ClosableBuffer
536 Stderr ClosableBuffer
539 func (tl *TestLogs) NewTestLoggingWriter(logstr string) io.WriteCloser {
540 if logstr == "stdout" {
543 if logstr == "stderr" {
549 func dockerLog(fd byte, msg string) []byte {
551 header := make([]byte, 8+len(by))
553 header[7] = byte(len(by))
558 func (s *TestSuite) TestRunContainer(c *C) {
559 docker := NewTestDockerClient(0)
560 docker.fn = func(t *TestDockerClient) {
561 t.logWriter.Write(dockerLog(1, "Hello world\n"))
564 cr := NewContainerRunner(&ArvTestClient{}, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
567 cr.NewLogWriter = logs.NewTestLoggingWriter
568 cr.Container.ContainerImage = hwPDH
569 cr.Container.Command = []string{"./hw"}
570 err := cr.LoadImage()
573 err = cr.CreateContainer()
576 err = cr.StartContainer()
579 err = cr.WaitFinish()
582 c.Check(strings.HasSuffix(logs.Stdout.String(), "Hello world\n"), Equals, true)
583 c.Check(logs.Stderr.String(), Equals, "")
586 func (s *TestSuite) TestCommitLogs(c *C) {
587 api := &ArvTestClient{}
588 kc := &KeepTestClient{}
589 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
590 cr.CrunchLog.Timestamper = (&TestTimestamper{}).Timestamp
592 cr.CrunchLog.Print("Hello world!")
593 cr.CrunchLog.Print("Goodbye")
594 cr.finalState = "Complete"
596 err := cr.CommitLogs()
599 c.Check(api.Calls, Equals, 2)
600 c.Check(api.Content[1]["ensure_unique_name"], Equals, true)
601 c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
602 c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
603 c.Check(*cr.LogsPDH, Equals, "63da7bdacf08c40f604daad80c261e9a+60")
606 func (s *TestSuite) TestUpdateContainerRunning(c *C) {
607 api := &ArvTestClient{}
608 kc := &KeepTestClient{}
609 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
611 err := cr.UpdateContainerRunning()
614 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Running")
617 func (s *TestSuite) TestUpdateContainerComplete(c *C) {
618 api := &ArvTestClient{}
619 kc := &KeepTestClient{}
620 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
622 cr.LogsPDH = new(string)
623 *cr.LogsPDH = "d3a229d2fe3690c2c3e75a71a153c6a3+60"
625 cr.ExitCode = new(int)
627 cr.finalState = "Complete"
629 err := cr.UpdateContainerFinal()
632 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
633 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
634 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
637 func (s *TestSuite) TestUpdateContainerCancelled(c *C) {
638 api := &ArvTestClient{}
639 kc := &KeepTestClient{}
640 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
642 cr.finalState = "Cancelled"
644 err := cr.UpdateContainerFinal()
647 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], IsNil)
648 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], IsNil)
649 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
652 // Used by the TestFullRun*() test below to DRY up boilerplate setup to do full
653 // dress rehearsal of the Run() function, starting from a JSON container record.
654 func FullRunHelper(c *C, record string, extraMounts []string, exitCode int, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner, realTemp string) {
655 rec := arvados.Container{}
656 err := json.Unmarshal([]byte(record), &rec)
659 docker := NewTestDockerClient(exitCode)
661 docker.ImageRemove(nil, hwImageId, dockertypes.ImageRemoveOptions{})
663 api = &ArvTestClient{Container: rec}
665 cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
666 cr.statInterval = 100 * time.Millisecond
667 am := &ArvMountCmdLine{}
668 cr.RunArvMount = am.ArvMountTest
670 realTemp, err = ioutil.TempDir("", "crunchrun_test1-")
672 defer os.RemoveAll(realTemp)
674 docker.realTemp = realTemp
677 cr.MkTempDir = func(_ string, prefix string) (string, error) {
679 d := fmt.Sprintf("%s/%s%d", realTemp, prefix, tempcount)
680 err := os.Mkdir(d, os.ModePerm)
681 if err != nil && strings.Contains(err.Error(), ": file exists") {
682 // Test case must have pre-populated the tempdir
688 if extraMounts != nil && len(extraMounts) > 0 {
689 err := cr.SetupArvMountPoint("keep")
692 for _, m := range extraMounts {
693 os.MkdirAll(cr.ArvMountPoint+"/by_id/"+m, os.ModePerm)
698 if api.CalledWith("container.state", "Complete") != nil {
702 c.Check(api.WasSetRunning, Equals, true)
703 c.Check(api.Content[api.Calls-2]["container"].(arvadosclient.Dict)["log"], NotNil)
707 for k, v := range api.Logs {
716 func (s *TestSuite) TestFullRunHello(c *C) {
717 api, _, _ := FullRunHelper(c, `{
718 "command": ["echo", "hello world"],
719 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
722 "mounts": {"/tmp": {"kind": "tmp"} },
723 "output_path": "/tmp",
725 "runtime_constraints": {}
726 }`, nil, 0, func(t *TestDockerClient) {
727 t.logWriter.Write(dockerLog(1, "hello world\n"))
731 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
732 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
733 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello world\n"), Equals, true)
737 func (s *TestSuite) TestCrunchstat(c *C) {
738 api, _, _ := FullRunHelper(c, `{
739 "command": ["sleep", "1"],
740 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
743 "mounts": {"/tmp": {"kind": "tmp"} },
744 "output_path": "/tmp",
746 "runtime_constraints": {}
747 }`, nil, 0, func(t *TestDockerClient) {
748 time.Sleep(time.Second)
752 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
753 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
755 // We didn't actually start a container, so crunchstat didn't
756 // find accounting files and therefore didn't log any stats.
757 // It should have logged a "can't find accounting files"
758 // message after one poll interval, though, so we can confirm
760 c.Assert(api.Logs["crunchstat"], NotNil)
761 c.Check(api.Logs["crunchstat"].String(), Matches, `(?ms).*cgroup stats files have not appeared after 100ms.*`)
763 // The "files never appeared" log assures us that we called
764 // (*crunchstat.Reporter)Stop(), and that we set it up with
765 // the correct container ID "abcde":
766 c.Check(api.Logs["crunchstat"].String(), Matches, `(?ms).*cgroup stats files never appeared for abcde\n`)
769 func (s *TestSuite) TestNodeInfoLog(c *C) {
770 api, _, _ := FullRunHelper(c, `{
771 "command": ["sleep", "1"],
772 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
775 "mounts": {"/tmp": {"kind": "tmp"} },
776 "output_path": "/tmp",
778 "runtime_constraints": {}
780 func(t *TestDockerClient) {
781 time.Sleep(time.Second)
785 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
786 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
788 c.Assert(api.Logs["node-info"], NotNil)
789 c.Check(api.Logs["node-info"].String(), Matches, `(?ms).*Host Information.*`)
790 c.Check(api.Logs["node-info"].String(), Matches, `(?ms).*CPU Information.*`)
791 c.Check(api.Logs["node-info"].String(), Matches, `(?ms).*Memory Information.*`)
792 c.Check(api.Logs["node-info"].String(), Matches, `(?ms).*Disk Space.*`)
793 c.Check(api.Logs["node-info"].String(), Matches, `(?ms).*Disk INodes.*`)
796 func (s *TestSuite) TestContainerRecordLog(c *C) {
797 api, _, _ := FullRunHelper(c, `{
798 "command": ["sleep", "1"],
799 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
802 "mounts": {"/tmp": {"kind": "tmp"} },
803 "output_path": "/tmp",
805 "runtime_constraints": {}
807 func(t *TestDockerClient) {
808 time.Sleep(time.Second)
812 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
813 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
815 c.Assert(api.Logs["container"], NotNil)
816 c.Check(api.Logs["container"].String(), Matches, `(?ms).*container_image.*`)
819 func (s *TestSuite) TestFullRunStderr(c *C) {
820 api, _, _ := FullRunHelper(c, `{
821 "command": ["/bin/sh", "-c", "echo hello ; echo world 1>&2 ; exit 1"],
822 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
825 "mounts": {"/tmp": {"kind": "tmp"} },
826 "output_path": "/tmp",
828 "runtime_constraints": {}
829 }`, nil, 1, func(t *TestDockerClient) {
830 t.logWriter.Write(dockerLog(1, "hello\n"))
831 t.logWriter.Write(dockerLog(2, "world\n"))
835 final := api.CalledWith("container.state", "Complete")
836 c.Assert(final, NotNil)
837 c.Check(final["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
838 c.Check(final["container"].(arvadosclient.Dict)["log"], NotNil)
840 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello\n"), Equals, true)
841 c.Check(strings.HasSuffix(api.Logs["stderr"].String(), "world\n"), Equals, true)
844 func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
845 api, _, _ := FullRunHelper(c, `{
847 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
850 "mounts": {"/tmp": {"kind": "tmp"} },
851 "output_path": "/tmp",
853 "runtime_constraints": {}
854 }`, nil, 0, func(t *TestDockerClient) {
855 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
859 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
860 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
861 c.Log(api.Logs["stdout"])
862 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/\n"), Equals, true)
865 func (s *TestSuite) TestFullRunSetCwd(c *C) {
866 api, _, _ := FullRunHelper(c, `{
868 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
871 "mounts": {"/tmp": {"kind": "tmp"} },
872 "output_path": "/tmp",
874 "runtime_constraints": {}
875 }`, nil, 0, func(t *TestDockerClient) {
876 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
880 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
881 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
882 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/bin\n"), Equals, true)
885 func (s *TestSuite) TestStopOnSignal(c *C) {
886 s.testStopContainer(c, func(cr *ContainerRunner) {
889 time.Sleep(time.Millisecond)
891 cr.SigChan <- syscall.SIGINT
896 func (s *TestSuite) TestStopOnArvMountDeath(c *C) {
897 s.testStopContainer(c, func(cr *ContainerRunner) {
898 cr.ArvMountExit = make(chan error)
900 cr.ArvMountExit <- exec.Command("true").Run()
901 close(cr.ArvMountExit)
906 func (s *TestSuite) testStopContainer(c *C, setup func(cr *ContainerRunner)) {
908 "command": ["/bin/sh", "-c", "echo foo && sleep 30 && echo bar"],
909 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
912 "mounts": {"/tmp": {"kind": "tmp"} },
913 "output_path": "/tmp",
915 "runtime_constraints": {}
918 rec := arvados.Container{}
919 err := json.Unmarshal([]byte(record), &rec)
922 docker := NewTestDockerClient(0)
923 docker.fn = func(t *TestDockerClient) {
925 t.logWriter.Write(dockerLog(1, "foo\n"))
928 docker.ImageRemove(nil, hwImageId, dockertypes.ImageRemoveOptions{})
930 api := &ArvTestClient{Container: rec}
931 cr := NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
932 cr.RunArvMount = func([]string, string) (*exec.Cmd, error) { return nil, nil }
935 done := make(chan error)
940 case <-time.After(20 * time.Second):
941 pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
946 for k, v := range api.Logs {
951 c.Check(api.CalledWith("container.log", nil), NotNil)
952 c.Check(api.CalledWith("container.state", "Cancelled"), NotNil)
953 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "foo\n"), Equals, true)
956 func (s *TestSuite) TestFullRunSetEnv(c *C) {
957 api, _, _ := FullRunHelper(c, `{
958 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
959 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
961 "environment": {"FROBIZ": "bilbo"},
962 "mounts": {"/tmp": {"kind": "tmp"} },
963 "output_path": "/tmp",
965 "runtime_constraints": {}
966 }`, nil, 0, func(t *TestDockerClient) {
967 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
971 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
972 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
973 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "bilbo\n"), Equals, true)
976 type ArvMountCmdLine struct {
981 func (am *ArvMountCmdLine) ArvMountTest(c []string, token string) (*exec.Cmd, error) {
987 func stubCert(temp string) string {
988 path := temp + "/ca-certificates.crt"
989 crt, _ := os.Create(path)
991 arvadosclient.CertFiles = []string{path}
995 func (s *TestSuite) TestSetupMounts(c *C) {
996 api := &ArvTestClient{}
997 kc := &KeepTestClient{}
998 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
999 am := &ArvMountCmdLine{}
1000 cr.RunArvMount = am.ArvMountTest
1002 realTemp, err := ioutil.TempDir("", "crunchrun_test1-")
1003 c.Assert(err, IsNil)
1004 certTemp, err := ioutil.TempDir("", "crunchrun_test2-")
1005 c.Assert(err, IsNil)
1006 stubCertPath := stubCert(certTemp)
1008 defer os.RemoveAll(realTemp)
1009 defer os.RemoveAll(certTemp)
1012 cr.MkTempDir = func(_ string, prefix string) (string, error) {
1014 d := fmt.Sprintf("%s/%s%d", realTemp, prefix, i)
1015 err := os.Mkdir(d, os.ModePerm)
1016 if err != nil && strings.Contains(err.Error(), ": file exists") {
1017 // Test case must have pre-populated the tempdir
1023 checkEmpty := func() {
1024 filepath.Walk(realTemp, func(path string, _ os.FileInfo, err error) error {
1025 c.Check(path, Equals, realTemp)
1033 cr.ArvMountPoint = ""
1034 cr.Container.Mounts = make(map[string]arvados.Mount)
1035 cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
1036 cr.OutputPath = "/tmp"
1037 cr.statInterval = 5 * time.Second
1038 err := cr.SetupMounts()
1040 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
1041 "--read-write", "--crunchstat-interval=5",
1042 "--mount-by-pdh", "by_id", realTemp + "/keep1"})
1043 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/2:/tmp"})
1044 os.RemoveAll(cr.ArvMountPoint)
1051 cr.ArvMountPoint = ""
1052 cr.Container.Mounts = make(map[string]arvados.Mount)
1053 cr.Container.Mounts["/out"] = arvados.Mount{Kind: "tmp"}
1054 cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
1055 cr.OutputPath = "/out"
1057 err := cr.SetupMounts()
1059 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
1060 "--read-write", "--crunchstat-interval=5",
1061 "--mount-by-pdh", "by_id", realTemp + "/keep1"})
1062 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/2:/out", realTemp + "/3:/tmp"})
1063 os.RemoveAll(cr.ArvMountPoint)
1070 cr.ArvMountPoint = ""
1071 cr.Container.Mounts = make(map[string]arvados.Mount)
1072 cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
1073 cr.OutputPath = "/tmp"
1076 cr.Container.RuntimeConstraints.API = &apiflag
1078 err := cr.SetupMounts()
1080 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
1081 "--read-write", "--crunchstat-interval=5",
1082 "--mount-by-pdh", "by_id", realTemp + "/keep1"})
1083 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/2:/tmp", stubCertPath + ":/etc/arvados/ca-certificates.crt:ro"})
1084 os.RemoveAll(cr.ArvMountPoint)
1093 cr.ArvMountPoint = ""
1094 cr.Container.Mounts = map[string]arvados.Mount{
1095 "/keeptmp": {Kind: "collection", Writable: true},
1097 cr.OutputPath = "/keeptmp"
1099 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1101 err := cr.SetupMounts()
1103 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
1104 "--read-write", "--crunchstat-interval=5",
1105 "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
1106 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/keep1/tmp0:/keeptmp"})
1107 os.RemoveAll(cr.ArvMountPoint)
1114 cr.ArvMountPoint = ""
1115 cr.Container.Mounts = map[string]arvados.Mount{
1116 "/keepinp": {Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"},
1117 "/keepout": {Kind: "collection", Writable: true},
1119 cr.OutputPath = "/keepout"
1121 os.MkdirAll(realTemp+"/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
1122 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1124 err := cr.SetupMounts()
1126 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
1127 "--read-write", "--crunchstat-interval=5",
1128 "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
1129 sort.StringSlice(cr.Binds).Sort()
1130 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53:/keepinp:ro",
1131 realTemp + "/keep1/tmp0:/keepout"})
1132 os.RemoveAll(cr.ArvMountPoint)
1139 cr.ArvMountPoint = ""
1140 cr.Container.RuntimeConstraints.KeepCacheRAM = 512
1141 cr.Container.Mounts = map[string]arvados.Mount{
1142 "/keepinp": {Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"},
1143 "/keepout": {Kind: "collection", Writable: true},
1145 cr.OutputPath = "/keepout"
1147 os.MkdirAll(realTemp+"/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
1148 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1150 err := cr.SetupMounts()
1152 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
1153 "--read-write", "--crunchstat-interval=5",
1154 "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
1155 sort.StringSlice(cr.Binds).Sort()
1156 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53:/keepinp:ro",
1157 realTemp + "/keep1/tmp0:/keepout"})
1158 os.RemoveAll(cr.ArvMountPoint)
1163 for _, test := range []struct {
1167 {in: "foo", out: `"foo"`},
1168 {in: nil, out: `null`},
1169 {in: map[string]int64{"foo": 123456789123456789}, out: `{"foo":123456789123456789}`},
1172 cr.ArvMountPoint = ""
1173 cr.Container.Mounts = map[string]arvados.Mount{
1174 "/mnt/test.json": {Kind: "json", Content: test.in},
1176 err := cr.SetupMounts()
1178 sort.StringSlice(cr.Binds).Sort()
1179 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/2/mountdata.json:/mnt/test.json:ro"})
1180 content, err := ioutil.ReadFile(realTemp + "/2/mountdata.json")
1182 c.Check(content, DeepEquals, []byte(test.out))
1183 os.RemoveAll(cr.ArvMountPoint)
1188 // Read-only mount points are allowed underneath output_dir mount point
1191 cr.ArvMountPoint = ""
1192 cr.Container.Mounts = make(map[string]arvados.Mount)
1193 cr.Container.Mounts = map[string]arvados.Mount{
1194 "/tmp": {Kind: "tmp"},
1195 "/tmp/foo": {Kind: "collection"},
1197 cr.OutputPath = "/tmp"
1199 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1201 err := cr.SetupMounts()
1203 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
1204 "--read-write", "--crunchstat-interval=5",
1205 "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
1206 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/2:/tmp", realTemp + "/keep1/tmp0:/tmp/foo:ro"})
1207 os.RemoveAll(cr.ArvMountPoint)
1212 // Writable mount points are not allowed underneath output_dir mount point
1215 cr.ArvMountPoint = ""
1216 cr.Container.Mounts = make(map[string]arvados.Mount)
1217 cr.Container.Mounts = map[string]arvados.Mount{
1218 "/tmp": {Kind: "tmp"},
1219 "/tmp/foo": {Kind: "collection", Writable: true},
1221 cr.OutputPath = "/tmp"
1223 err := cr.SetupMounts()
1224 c.Check(err, NotNil)
1225 c.Check(err, ErrorMatches, `Writable mount points are not permitted underneath the output_path.*`)
1226 os.RemoveAll(cr.ArvMountPoint)
1231 // Only mount points of kind 'collection' are allowed underneath output_dir mount point
1234 cr.ArvMountPoint = ""
1235 cr.Container.Mounts = make(map[string]arvados.Mount)
1236 cr.Container.Mounts = map[string]arvados.Mount{
1237 "/tmp": {Kind: "tmp"},
1238 "/tmp/foo": {Kind: "json"},
1240 cr.OutputPath = "/tmp"
1242 err := cr.SetupMounts()
1243 c.Check(err, NotNil)
1244 c.Check(err, ErrorMatches, `Only mount points of kind 'collection' are supported underneath the output_path.*`)
1245 os.RemoveAll(cr.ArvMountPoint)
1250 // Only mount point of kind 'collection' is allowed for stdin
1253 cr.ArvMountPoint = ""
1254 cr.Container.Mounts = make(map[string]arvados.Mount)
1255 cr.Container.Mounts = map[string]arvados.Mount{
1256 "stdin": {Kind: "tmp"},
1259 err := cr.SetupMounts()
1260 c.Check(err, NotNil)
1261 c.Check(err, ErrorMatches, `Unsupported mount kind 'tmp' for stdin.*`)
1262 os.RemoveAll(cr.ArvMountPoint)
1268 func (s *TestSuite) TestStdout(c *C) {
1270 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1271 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1273 "environment": {"FROBIZ": "bilbo"},
1274 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"} },
1275 "output_path": "/tmp",
1277 "runtime_constraints": {}
1280 api, _, _ := FullRunHelper(c, helperRecord, nil, 0, func(t *TestDockerClient) {
1281 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
1285 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1286 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1287 c.Check(api.CalledWith("collection.manifest_text", "./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out\n"), NotNil)
1290 // Used by the TestStdoutWithWrongPath*()
1291 func StdoutErrorRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner, err error) {
1292 rec := arvados.Container{}
1293 err = json.Unmarshal([]byte(record), &rec)
1296 docker := NewTestDockerClient(0)
1298 docker.ImageRemove(nil, hwImageId, dockertypes.ImageRemoveOptions{})
1300 api = &ArvTestClient{Container: rec}
1301 cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
1302 am := &ArvMountCmdLine{}
1303 cr.RunArvMount = am.ArvMountTest
1309 func (s *TestSuite) TestStdoutWithWrongPath(c *C) {
1310 _, _, err := StdoutErrorRunHelper(c, `{
1311 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path":"/tmpa.out"} },
1312 "output_path": "/tmp"
1313 }`, func(t *TestDockerClient) {})
1315 c.Check(err, NotNil)
1316 c.Check(strings.Contains(err.Error(), "Stdout path does not start with OutputPath"), Equals, true)
1319 func (s *TestSuite) TestStdoutWithWrongKindTmp(c *C) {
1320 _, _, err := StdoutErrorRunHelper(c, `{
1321 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "tmp", "path":"/tmp/a.out"} },
1322 "output_path": "/tmp"
1323 }`, func(t *TestDockerClient) {})
1325 c.Check(err, NotNil)
1326 c.Check(strings.Contains(err.Error(), "Unsupported mount kind 'tmp' for stdout"), Equals, true)
1329 func (s *TestSuite) TestStdoutWithWrongKindCollection(c *C) {
1330 _, _, err := StdoutErrorRunHelper(c, `{
1331 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "collection", "path":"/tmp/a.out"} },
1332 "output_path": "/tmp"
1333 }`, func(t *TestDockerClient) {})
1335 c.Check(err, NotNil)
1336 c.Check(strings.Contains(err.Error(), "Unsupported mount kind 'collection' for stdout"), Equals, true)
1339 func (s *TestSuite) TestFullRunWithAPI(c *C) {
1340 os.Setenv("ARVADOS_API_HOST", "test.arvados.org")
1341 defer os.Unsetenv("ARVADOS_API_HOST")
1342 api, _, _ := FullRunHelper(c, `{
1343 "command": ["/bin/sh", "-c", "echo $ARVADOS_API_HOST"],
1344 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1347 "mounts": {"/tmp": {"kind": "tmp"} },
1348 "output_path": "/tmp",
1350 "runtime_constraints": {"API": true}
1351 }`, nil, 0, func(t *TestDockerClient) {
1352 t.logWriter.Write(dockerLog(1, t.env[1][17:]+"\n"))
1356 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1357 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1358 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "test.arvados.org\n"), Equals, true)
1359 c.Check(api.CalledWith("container.output", "d41d8cd98f00b204e9800998ecf8427e+0"), NotNil)
1362 func (s *TestSuite) TestFullRunSetOutput(c *C) {
1363 os.Setenv("ARVADOS_API_HOST", "test.arvados.org")
1364 defer os.Unsetenv("ARVADOS_API_HOST")
1365 api, _, _ := FullRunHelper(c, `{
1366 "command": ["/bin/sh", "-c", "echo $ARVADOS_API_HOST"],
1367 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1370 "mounts": {"/tmp": {"kind": "tmp"} },
1371 "output_path": "/tmp",
1373 "runtime_constraints": {"API": true}
1374 }`, nil, 0, func(t *TestDockerClient) {
1375 t.api.Container.Output = "d4ab34d3d4f8a72f5c4973051ae69fab+122"
1379 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1380 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1381 c.Check(api.CalledWith("container.output", "d4ab34d3d4f8a72f5c4973051ae69fab+122"), NotNil)
1384 func (s *TestSuite) TestStdoutWithExcludeFromOutputMountPointUnderOutputDir(c *C) {
1386 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1387 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1389 "environment": {"FROBIZ": "bilbo"},
1391 "/tmp": {"kind": "tmp"},
1392 "/tmp/foo": {"kind": "collection",
1393 "portable_data_hash": "a3e8f74c6f101eae01fa08bfb4e49b3a+54",
1394 "exclude_from_output": true
1396 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1398 "output_path": "/tmp",
1400 "runtime_constraints": {}
1403 extraMounts := []string{"a3e8f74c6f101eae01fa08bfb4e49b3a+54"}
1405 api, _, _ := FullRunHelper(c, helperRecord, extraMounts, 0, func(t *TestDockerClient) {
1406 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
1410 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1411 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1412 c.Check(api.CalledWith("collection.manifest_text", "./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out\n"), NotNil)
1415 func (s *TestSuite) TestStdoutWithMultipleMountPointsUnderOutputDir(c *C) {
1417 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1418 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1420 "environment": {"FROBIZ": "bilbo"},
1422 "/tmp": {"kind": "tmp"},
1423 "/tmp/foo/bar": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/file2_in_main.txt"},
1424 "/tmp/foo/sub1": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/subdir1"},
1425 "/tmp/foo/sub1file2": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/subdir1/file2_in_subdir1.txt"},
1426 "/tmp/foo/baz/sub2file2": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/subdir1/subdir2/file2_in_subdir2.txt"},
1427 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1429 "output_path": "/tmp",
1431 "runtime_constraints": {}
1434 extraMounts := []string{
1435 "a0def87f80dd594d4675809e83bd4f15+367/file2_in_main.txt",
1436 "a0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt",
1437 "a0def87f80dd594d4675809e83bd4f15+367/subdir1/subdir2/file2_in_subdir2.txt",
1440 api, runner, realtemp := FullRunHelper(c, helperRecord, extraMounts, 0, func(t *TestDockerClient) {
1441 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
1445 c.Check(runner.Binds, DeepEquals, []string{realtemp + "/2:/tmp",
1446 realtemp + "/keep1/by_id/a0def87f80dd594d4675809e83bd4f15+367/file2_in_main.txt:/tmp/foo/bar:ro",
1447 realtemp + "/keep1/by_id/a0def87f80dd594d4675809e83bd4f15+367/subdir1/subdir2/file2_in_subdir2.txt:/tmp/foo/baz/sub2file2:ro",
1448 realtemp + "/keep1/by_id/a0def87f80dd594d4675809e83bd4f15+367/subdir1:/tmp/foo/sub1:ro",
1449 realtemp + "/keep1/by_id/a0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt:/tmp/foo/sub1file2:ro",
1452 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1453 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1454 for _, v := range api.Content {
1455 if v["collection"] != nil {
1456 c.Check(v["ensure_unique_name"], Equals, true)
1457 collection := v["collection"].(arvadosclient.Dict)
1458 if strings.Index(collection["name"].(string), "output") == 0 {
1459 manifest := collection["manifest_text"].(string)
1461 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1462 ./foo 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0abcdefgh11234567890@569fa8c3 9:18:bar 9:18:sub1file2
1463 ./foo/baz 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0bcdefghijk544332211@569fa8c5 9:18:sub2file2
1464 ./foo/sub1 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 0:9:file1_in_subdir1.txt 9:18:file2_in_subdir1.txt
1465 ./foo/sub1/subdir2 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0bcdefghijk544332211@569fa8c5 0:9:file1_in_subdir2.txt 9:18:file2_in_subdir2.txt
1472 func (s *TestSuite) TestStdoutWithMountPointsUnderOutputDirDenormalizedManifest(c *C) {
1474 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1475 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1477 "environment": {"FROBIZ": "bilbo"},
1479 "/tmp": {"kind": "tmp"},
1480 "/tmp/foo/bar": {"kind": "collection", "portable_data_hash": "b0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt"},
1481 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1483 "output_path": "/tmp",
1485 "runtime_constraints": {}
1488 extraMounts := []string{
1489 "b0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt",
1492 api, _, _ := FullRunHelper(c, helperRecord, extraMounts, 0, func(t *TestDockerClient) {
1493 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
1497 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1498 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1499 for _, v := range api.Content {
1500 if v["collection"] != nil {
1501 collection := v["collection"].(arvadosclient.Dict)
1502 if strings.Index(collection["name"].(string), "output") == 0 {
1503 manifest := collection["manifest_text"].(string)
1505 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1506 ./foo 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0abcdefgh11234567890@569fa8c3 10:17:bar
1513 func (s *TestSuite) TestOutputSymlinkToInput(c *C) {
1515 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1516 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1518 "environment": {"FROBIZ": "bilbo"},
1520 "/tmp": {"kind": "tmp"},
1521 "/keep/foo/sub1file2": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path": "/subdir1/file2_in_subdir1.txt"},
1522 "/keep/foo2": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367"}
1524 "output_path": "/tmp",
1526 "runtime_constraints": {}
1529 extraMounts := []string{
1530 "a0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt",
1533 api, _, _ := FullRunHelper(c, helperRecord, extraMounts, 0, func(t *TestDockerClient) {
1534 os.Symlink("/keep/foo/sub1file2", t.realTemp+"/2/baz")
1535 os.Symlink("/keep/foo2/subdir1/file2_in_subdir1.txt", t.realTemp+"/2/baz2")
1536 os.Symlink("/keep/foo2/subdir1", t.realTemp+"/2/baz3")
1537 os.Mkdir(t.realTemp+"/2/baz4", 0700)
1538 os.Symlink("/keep/foo2/subdir1/file2_in_subdir1.txt", t.realTemp+"/2/baz4/baz5")
1542 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1543 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1544 for _, v := range api.Content {
1545 if v["collection"] != nil {
1546 collection := v["collection"].(arvadosclient.Dict)
1547 if strings.Index(collection["name"].(string), "output") == 0 {
1548 manifest := collection["manifest_text"].(string)
1549 c.Check(manifest, Equals, `. 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 9:18:baz 9:18:baz2
1550 ./baz3 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 0:9:file1_in_subdir1.txt 9:18:file2_in_subdir1.txt
1551 ./baz3/subdir2 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0bcdefghijk544332211@569fa8c5 0:9:file1_in_subdir2.txt 9:18:file2_in_subdir2.txt
1552 ./baz4 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 9:18:baz5
1559 func (s *TestSuite) TestOutputError(c *C) {
1561 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1562 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1564 "environment": {"FROBIZ": "bilbo"},
1566 "/tmp": {"kind": "tmp"}
1568 "output_path": "/tmp",
1570 "runtime_constraints": {}
1573 extraMounts := []string{}
1575 api, _, _ := FullRunHelper(c, helperRecord, extraMounts, 0, func(t *TestDockerClient) {
1576 os.Symlink("/etc/hosts", t.realTemp+"/2/baz")
1580 c.Check(api.CalledWith("container.state", "Cancelled"), NotNil)
1583 func (s *TestSuite) TestOutputSymlinkToOutput(c *C) {
1585 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1586 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1588 "environment": {"FROBIZ": "bilbo"},
1590 "/tmp": {"kind": "tmp"}
1592 "output_path": "/tmp",
1594 "runtime_constraints": {}
1597 extraMounts := []string{}
1599 api, _, _ := FullRunHelper(c, helperRecord, extraMounts, 0, func(t *TestDockerClient) {
1600 rf, _ := os.Create(t.realTemp + "/2/realfile")
1601 rf.Write([]byte("foo"))
1604 os.Mkdir(t.realTemp+"/2/realdir", 0700)
1605 rf, _ = os.Create(t.realTemp + "/2/realdir/subfile")
1606 rf.Write([]byte("bar"))
1609 os.Symlink("/tmp/realfile", t.realTemp+"/2/file1")
1610 os.Symlink("realfile", t.realTemp+"/2/file2")
1611 os.Symlink("/tmp/file1", t.realTemp+"/2/file3")
1612 os.Symlink("file2", t.realTemp+"/2/file4")
1613 os.Symlink("realdir", t.realTemp+"/2/dir1")
1614 os.Symlink("/tmp/realdir", t.realTemp+"/2/dir2")
1618 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1619 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1620 for _, v := range api.Content {
1621 if v["collection"] != nil {
1622 collection := v["collection"].(arvadosclient.Dict)
1623 if strings.Index(collection["name"].(string), "output") == 0 {
1624 manifest := collection["manifest_text"].(string)
1625 c.Check(manifest, Equals,
1626 `. 7a2c86e102dcc231bd232aad99686dfa+15 0:3:file1 3:3:file2 6:3:file3 9:3:file4 12:3:realfile
1627 ./dir1 37b51d194a7513e45b56f6524f2d51f2+3 0:3:subfile
1628 ./dir2 37b51d194a7513e45b56f6524f2d51f2+3 0:3:subfile
1629 ./realdir 37b51d194a7513e45b56f6524f2d51f2+3 0:3:subfile
1636 func (s *TestSuite) TestStdinCollectionMountPoint(c *C) {
1638 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1639 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1641 "environment": {"FROBIZ": "bilbo"},
1643 "/tmp": {"kind": "tmp"},
1644 "stdin": {"kind": "collection", "portable_data_hash": "b0def87f80dd594d4675809e83bd4f15+367", "path": "/file1_in_main.txt"},
1645 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1647 "output_path": "/tmp",
1649 "runtime_constraints": {}
1652 extraMounts := []string{
1653 "b0def87f80dd594d4675809e83bd4f15+367/file1_in_main.txt",
1656 api, _, _ := FullRunHelper(c, helperRecord, extraMounts, 0, func(t *TestDockerClient) {
1657 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
1661 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1662 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1663 for _, v := range api.Content {
1664 if v["collection"] != nil {
1665 collection := v["collection"].(arvadosclient.Dict)
1666 if strings.Index(collection["name"].(string), "output") == 0 {
1667 manifest := collection["manifest_text"].(string)
1668 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1675 func (s *TestSuite) TestStdinJsonMountPoint(c *C) {
1677 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1678 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1680 "environment": {"FROBIZ": "bilbo"},
1682 "/tmp": {"kind": "tmp"},
1683 "stdin": {"kind": "json", "content": "foo"},
1684 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1686 "output_path": "/tmp",
1688 "runtime_constraints": {}
1691 api, _, _ := FullRunHelper(c, helperRecord, nil, 0, func(t *TestDockerClient) {
1692 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
1696 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1697 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1698 for _, v := range api.Content {
1699 if v["collection"] != nil {
1700 collection := v["collection"].(arvadosclient.Dict)
1701 if strings.Index(collection["name"].(string), "output") == 0 {
1702 manifest := collection["manifest_text"].(string)
1703 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1710 func (s *TestSuite) TestStderrMount(c *C) {
1711 api, _, _ := FullRunHelper(c, `{
1712 "command": ["/bin/sh", "-c", "echo hello;exit 1"],
1713 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1716 "mounts": {"/tmp": {"kind": "tmp"},
1717 "stdout": {"kind": "file", "path": "/tmp/a/out.txt"},
1718 "stderr": {"kind": "file", "path": "/tmp/b/err.txt"}},
1719 "output_path": "/tmp",
1721 "runtime_constraints": {}
1722 }`, nil, 1, func(t *TestDockerClient) {
1723 t.logWriter.Write(dockerLog(1, "hello\n"))
1724 t.logWriter.Write(dockerLog(2, "oops\n"))
1728 final := api.CalledWith("container.state", "Complete")
1729 c.Assert(final, NotNil)
1730 c.Check(final["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
1731 c.Check(final["container"].(arvadosclient.Dict)["log"], NotNil)
1733 c.Check(api.CalledWith("collection.manifest_text", "./a b1946ac92492d2347c6235b4d2611184+6 0:6:out.txt\n./b 38af5c54926b620264ab1501150cf189+5 0:5:err.txt\n"), NotNil)
1736 func (s *TestSuite) TestNumberRoundTrip(c *C) {
1737 cr := NewContainerRunner(&ArvTestClient{callraw: true}, &KeepTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
1738 cr.fetchContainerRecord()
1740 jsondata, err := json.Marshal(cr.Container.Mounts["/json"].Content)
1743 c.Check(string(jsondata), Equals, `{"number":123456789123456789}`)
1746 func (s *TestSuite) TestEvalSymlinks(c *C) {
1747 cr := NewContainerRunner(&ArvTestClient{callraw: true}, &KeepTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
1749 realTemp, err := ioutil.TempDir("", "crunchrun_test-")
1750 c.Assert(err, IsNil)
1751 defer os.RemoveAll(realTemp)
1753 cr.HostOutputDir = realTemp
1755 // Absolute path outside output dir
1756 os.Symlink("/etc/passwd", realTemp+"/p1")
1758 // Relative outside output dir
1759 os.Symlink("../zip", realTemp+"/p2")
1761 // Circular references
1762 os.Symlink("p4", realTemp+"/p3")
1763 os.Symlink("p5", realTemp+"/p4")
1764 os.Symlink("p3", realTemp+"/p5")
1766 // Target doesn't exist
1767 os.Symlink("p99", realTemp+"/p6")
1769 for _, v := range []string{"p1", "p2", "p3", "p4", "p5"} {
1770 info, err := os.Lstat(realTemp + "/" + v)
1771 _, _, _, err = cr.derefOutputSymlink(realTemp+"/"+v, info)
1772 c.Assert(err, NotNil)
1776 func (s *TestSuite) TestEvalSymlinkDir(c *C) {
1777 cr := NewContainerRunner(&ArvTestClient{callraw: true}, &KeepTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
1779 realTemp, err := ioutil.TempDir("", "crunchrun_test-")
1780 c.Assert(err, IsNil)
1781 defer os.RemoveAll(realTemp)
1783 cr.HostOutputDir = realTemp
1785 // Absolute path outside output dir
1786 os.Symlink(".", realTemp+"/loop")
1789 info, err := os.Lstat(realTemp + "/" + v)
1790 _, err = cr.UploadOutputFile(realTemp+"/"+v, info, err, []string{}, nil, "", "", 0)
1791 c.Assert(err, NotNil)
1794 func (s *TestSuite) TestFullBrokenDocker1(c *C) {
1795 tf, err := ioutil.TempFile("", "brokenNodeHook-")
1796 c.Assert(err, IsNil)
1797 defer os.Remove(tf.Name())
1799 tf.Write([]byte(`#!/bin/sh
1803 os.Chmod(tf.Name(), 0700)
1806 brokenNodeHook = &ech
1808 api, _, _ := FullRunHelper(c, `{
1809 "command": ["echo", "hello world"],
1810 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1813 "mounts": {"/tmp": {"kind": "tmp"} },
1814 "output_path": "/tmp",
1816 "runtime_constraints": {}
1817 }`, nil, 2, func(t *TestDockerClient) {
1818 t.logWriter.Write(dockerLog(1, "hello world\n"))
1822 c.Check(api.CalledWith("container.state", "Queued"), NotNil)
1823 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*unable to run containers.*")
1824 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*Running broken node hook.*")
1825 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*killme.*")
1829 func (s *TestSuite) TestFullBrokenDocker2(c *C) {
1831 brokenNodeHook = &ech
1833 api, _, _ := FullRunHelper(c, `{
1834 "command": ["echo", "hello world"],
1835 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1838 "mounts": {"/tmp": {"kind": "tmp"} },
1839 "output_path": "/tmp",
1841 "runtime_constraints": {}
1842 }`, nil, 2, func(t *TestDockerClient) {
1843 t.logWriter.Write(dockerLog(1, "hello world\n"))
1847 c.Check(api.CalledWith("container.state", "Queued"), NotNil)
1848 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*unable to run containers.*")
1849 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*No broken node hook.*")
1852 func (s *TestSuite) TestFullBrokenDocker3(c *C) {
1854 brokenNodeHook = &ech
1856 api, _, _ := FullRunHelper(c, `{
1857 "command": ["echo", "hello world"],
1858 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1861 "mounts": {"/tmp": {"kind": "tmp"} },
1862 "output_path": "/tmp",
1864 "runtime_constraints": {}
1865 }`, nil, 3, func(t *TestDockerClient) {
1866 t.logWriter.Write(dockerLog(1, "hello world\n"))
1870 c.Check(api.CalledWith("container.state", "Cancelled"), NotNil)
1871 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*unable to run containers.*")
1874 func (s *TestSuite) TestBadCommand1(c *C) {
1876 brokenNodeHook = &ech
1878 api, _, _ := FullRunHelper(c, `{
1879 "command": ["echo", "hello world"],
1880 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1883 "mounts": {"/tmp": {"kind": "tmp"} },
1884 "output_path": "/tmp",
1886 "runtime_constraints": {}
1887 }`, nil, 4, func(t *TestDockerClient) {
1888 t.logWriter.Write(dockerLog(1, "hello world\n"))
1892 c.Check(api.CalledWith("container.state", "Cancelled"), NotNil)
1893 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*Possible causes:.*is missing.*")
1896 func (s *TestSuite) TestBadCommand2(c *C) {
1898 brokenNodeHook = &ech
1900 api, _, _ := FullRunHelper(c, `{
1901 "command": ["echo", "hello world"],
1902 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1905 "mounts": {"/tmp": {"kind": "tmp"} },
1906 "output_path": "/tmp",
1908 "runtime_constraints": {}
1909 }`, nil, 5, func(t *TestDockerClient) {
1910 t.logWriter.Write(dockerLog(1, "hello world\n"))
1914 c.Check(api.CalledWith("container.state", "Cancelled"), NotNil)
1915 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*Possible causes:.*is missing.*")
1918 func (s *TestSuite) TestBadCommand3(c *C) {
1920 brokenNodeHook = &ech
1922 api, _, _ := FullRunHelper(c, `{
1923 "command": ["echo", "hello world"],
1924 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
1927 "mounts": {"/tmp": {"kind": "tmp"} },
1928 "output_path": "/tmp",
1930 "runtime_constraints": {}
1931 }`, nil, 6, func(t *TestDockerClient) {
1932 t.logWriter.Write(dockerLog(1, "hello world\n"))
1936 c.Check(api.CalledWith("container.state", "Cancelled"), NotNil)
1937 c.Check(api.Logs["crunch-run"].String(), Matches, "(?ms).*Possible causes:.*is missing.*")