1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
25 "git.arvados.org/arvados.git/lib/cmd"
26 "git.arvados.org/arvados.git/sdk/go/arvados"
27 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
28 "git.arvados.org/arvados.git/sdk/go/arvadostest"
29 "git.arvados.org/arvados.git/sdk/go/manifest"
30 "golang.org/x/net/context"
35 // Gocheck boilerplate
36 func TestCrunchExec(t *testing.T) {
40 var _ = Suite(&TestSuite{})
42 type TestSuite struct {
43 client *arvados.Client
45 runner *ContainerRunner
46 executor *stubExecutor
49 testDispatcherKeepClient KeepTestClient
50 testContainerKeepClient KeepTestClient
53 func (s *TestSuite) SetUpTest(c *C) {
54 s.client = arvados.NewClientFromEnv()
55 s.executor = &stubExecutor{}
57 s.api = &ArvTestClient{}
58 s.runner, err = NewContainerRunner(s.client, s.api, &s.testDispatcherKeepClient, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
60 s.runner.executor = s.executor
61 s.runner.MkArvClient = func(token string) (IArvadosClient, IKeepClient, *arvados.Client, error) {
62 return s.api, &s.testContainerKeepClient, s.client, nil
64 s.runner.RunArvMount = func(cmd []string, tok string) (*exec.Cmd, error) {
65 s.runner.ArvMountPoint = s.keepmount
66 for i, opt := range cmd {
67 if opt == "--mount-tmp" {
68 err := os.Mkdir(s.keepmount+"/"+cmd[i+1], 0700)
72 s.keepmountTmp = append(s.keepmountTmp, cmd[i+1])
77 s.keepmount = c.MkDir()
78 err = os.Mkdir(s.keepmount+"/by_id", 0755)
81 err = os.Mkdir(s.keepmount+"/by_id/"+arvadostest.DockerImage112PDH, 0755)
83 err = ioutil.WriteFile(s.keepmount+"/by_id/"+arvadostest.DockerImage112PDH+"/"+arvadostest.DockerImage112Filename, []byte("#notarealtarball"), 0644)
84 err = os.Mkdir(s.keepmount+"/by_id/"+fakeInputCollectionPDH, 0755)
86 err = ioutil.WriteFile(s.keepmount+"/by_id/"+fakeInputCollectionPDH+"/input.json", []byte(`{"input":true}`), 0644)
88 s.runner.ArvMountPoint = s.keepmount
91 type ArvTestClient struct {
94 Content []arvadosclient.Dict
97 Logs map[string]*bytes.Buffer
103 type KeepTestClient struct {
106 StorageClasses []string
109 type stubExecutor struct {
115 created containerSpec
117 waitSleep time.Duration
126 func (e *stubExecutor) LoadImage(imageId string, tarball string, container arvados.Container, keepMount string,
127 containerClient *arvados.Client) error {
131 func (e *stubExecutor) Runtime() string { return "stub" }
132 func (e *stubExecutor) Version() string { return "stub " + cmd.Version.String() }
133 func (e *stubExecutor) Create(spec containerSpec) error { e.created = spec; return e.createErr }
134 func (e *stubExecutor) Start() error { e.exit = make(chan int, 1); go e.runFunc(); return e.startErr }
135 func (e *stubExecutor) CgroupID() string { return "cgroupid" }
136 func (e *stubExecutor) Stop() error { e.stopped = true; go func() { e.exit <- -1 }(); return e.stopErr }
137 func (e *stubExecutor) Close() { e.closed = true }
138 func (e *stubExecutor) Wait(context.Context) (int, error) {
139 return <-e.exit, e.waitErr
141 func (e *stubExecutor) InjectCommand(ctx context.Context, _, _ string, _ bool, _ []string) (*exec.Cmd, error) {
142 return nil, errors.New("unimplemented")
144 func (e *stubExecutor) IPAddress() (string, error) { return "", errors.New("unimplemented") }
146 const fakeInputCollectionPDH = "ffffffffaaaaaaaa88888888eeeeeeee+1234"
148 var hwManifest = ". 82ab40c24fc8df01798e57ba66795bb1+841216+Aa124ac75e5168396c73c0a18eda641a4f41791c0@569fa8c3 0:841216:9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7.tar\n"
149 var hwPDH = "a45557269dcb65a6b78f9ac061c0850b+120"
150 var hwImageID = "9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7"
152 var otherManifest = ". 68a84f561b1d1708c6baff5e019a9ab3+46+Ae5d0af96944a3690becb1decdf60cc1c937f556d@5693216f 0:46:md5sum.txt\n"
153 var otherPDH = "a3e8f74c6f101eae01fa08bfb4e49b3a+54"
155 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
156 ./subdir1 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 0:9:file1_in_subdir1.txt 9:18:file2_in_subdir1.txt
157 ./subdir1/subdir2 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0bcdefghijk544332211@569fa8c5 0:9:file1_in_subdir2.txt 9:18:file2_in_subdir2.txt
160 var normalizedWithSubdirsPDH = "a0def87f80dd594d4675809e83bd4f15+367"
162 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"
163 var denormalizedWithSubdirsPDH = "b0def87f80dd594d4675809e83bd4f15+367"
165 var fakeAuthUUID = "zzzzz-gj3su-55pqoyepgi2glem"
166 var fakeAuthToken = "a3ltuwzqcu2u4sc0q7yhpc2w7s00fdcqecg5d6e0u3pfohmbjt"
168 func (client *ArvTestClient) Create(resourceType string,
169 parameters arvadosclient.Dict,
170 output interface{}) error {
173 defer client.Mutex.Unlock()
176 client.Content = append(client.Content, parameters)
178 if resourceType == "logs" {
179 et := parameters["log"].(arvadosclient.Dict)["event_type"].(string)
180 if client.Logs == nil {
181 client.Logs = make(map[string]*bytes.Buffer)
183 if client.Logs[et] == nil {
184 client.Logs[et] = &bytes.Buffer{}
186 client.Logs[et].Write([]byte(parameters["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]))
189 if resourceType == "collections" && output != nil {
190 mt := parameters["collection"].(arvadosclient.Dict)["manifest_text"].(string)
191 outmap := output.(*arvados.Collection)
192 outmap.PortableDataHash = fmt.Sprintf("%x+%d", md5.Sum([]byte(mt)), len(mt))
193 outmap.UUID = fmt.Sprintf("zzzzz-4zz18-%15.15x", md5.Sum([]byte(mt)))
199 func (client *ArvTestClient) Call(method, resourceType, uuid, action string, parameters arvadosclient.Dict, output interface{}) error {
201 case method == "GET" && resourceType == "containers" && action == "auth":
202 return json.Unmarshal([]byte(`{
203 "kind": "arvados#api_client_authorization",
204 "uuid": "`+fakeAuthUUID+`",
205 "api_token": "`+fakeAuthToken+`"
207 case method == "GET" && resourceType == "containers" && action == "secret_mounts":
208 if client.secretMounts != nil {
209 return json.Unmarshal(client.secretMounts, output)
211 return json.Unmarshal([]byte(`{"secret_mounts":{}}`), output)
213 return fmt.Errorf("Not found")
217 func (client *ArvTestClient) CallRaw(method, resourceType, uuid, action string,
218 parameters arvadosclient.Dict) (reader io.ReadCloser, err error) {
220 if method == "GET" && resourceType == "nodes" && uuid == "" && action == "" {
222 "kind": "arvados#nodeList",
224 "uuid": "zzzzz-7ekkf-2z3mc76g2q73aio",
225 "hostname": "compute2",
226 "properties": {"total_cpu_cores": 16}
228 } else if method == "GET" && resourceType == "containers" && action == "" && !client.callraw {
230 j, err = json.Marshal(map[string]interface{}{
231 "items": []interface{}{client.Container},
232 "kind": "arvados#nodeList",
235 j, err = json.Marshal(client.Container)
239 "command": ["sleep", "1"],
240 "container_image": "` + arvadostest.DockerImage112PDH + `",
243 "mounts": {"/tmp": {"kind": "tmp"}, "/json": {"kind": "json", "content": {"number": 123456789123456789}}},
244 "output_path": "/tmp",
246 "runtime_constraints": {}
249 return ioutil.NopCloser(bytes.NewReader(j)), err
252 func (client *ArvTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
253 if resourceType == "collections" {
255 output.(*arvados.Collection).ManifestText = hwManifest
256 } else if uuid == otherPDH {
257 output.(*arvados.Collection).ManifestText = otherManifest
258 } else if uuid == normalizedWithSubdirsPDH {
259 output.(*arvados.Collection).ManifestText = normalizedManifestWithSubdirs
260 } else if uuid == denormalizedWithSubdirsPDH {
261 output.(*arvados.Collection).ManifestText = denormalizedManifestWithSubdirs
264 if resourceType == "containers" {
265 (*output.(*arvados.Container)) = client.Container
270 func (client *ArvTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
272 defer client.Mutex.Unlock()
274 client.Content = append(client.Content, parameters)
275 if resourceType == "containers" {
276 if parameters["container"].(arvadosclient.Dict)["state"] == "Running" {
277 client.WasSetRunning = true
279 } else if resourceType == "collections" && output != nil {
280 mt := parameters["collection"].(arvadosclient.Dict)["manifest_text"].(string)
281 output.(*arvados.Collection).UUID = uuid
282 output.(*arvados.Collection).PortableDataHash = fmt.Sprintf("%x", md5.Sum([]byte(mt)))
287 var discoveryMap = map[string]interface{}{
288 "defaultTrashLifetime": float64(1209600),
289 "crunchLimitLogBytesPerJob": float64(67108864),
290 "crunchLogThrottleBytes": float64(65536),
291 "crunchLogThrottlePeriod": float64(60),
292 "crunchLogThrottleLines": float64(1024),
293 "crunchLogPartialLineThrottlePeriod": float64(5),
294 "crunchLogBytesPerEvent": float64(4096),
295 "crunchLogSecondsBetweenEvents": float64(1),
298 func (client *ArvTestClient) Discovery(key string) (interface{}, error) {
299 return discoveryMap[key], nil
302 // CalledWith returns the parameters from the first API call whose
303 // parameters match jpath/string. E.g., CalledWith(c, "foo.bar",
304 // "baz") returns parameters with parameters["foo"]["bar"]=="baz". If
305 // no call matches, it returns nil.
306 func (client *ArvTestClient) CalledWith(jpath string, expect interface{}) arvadosclient.Dict {
308 for _, content := range client.Content {
309 var v interface{} = content
310 for _, k := range strings.Split(jpath, ".") {
311 if dict, ok := v.(arvadosclient.Dict); !ok {
324 func (client *KeepTestClient) LocalLocator(locator string) (string, error) {
328 func (client *KeepTestClient) BlockWrite(_ context.Context, opts arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
329 client.Content = opts.Data
330 return arvados.BlockWriteResponse{
331 Locator: fmt.Sprintf("%x+%d", md5.Sum(opts.Data), len(opts.Data)),
335 func (client *KeepTestClient) ReadAt(string, []byte, int) (int, error) {
336 return 0, errors.New("not implemented")
339 func (client *KeepTestClient) ClearBlockCache() {
342 func (client *KeepTestClient) Close() {
346 func (client *KeepTestClient) SetStorageClasses(sc []string) {
347 client.StorageClasses = sc
350 type FileWrapper struct {
355 func (fw FileWrapper) Readdir(n int) ([]os.FileInfo, error) {
356 return nil, errors.New("not implemented")
359 func (fw FileWrapper) Seek(int64, int) (int64, error) {
360 return 0, errors.New("not implemented")
363 func (fw FileWrapper) Size() int64 {
367 func (fw FileWrapper) Stat() (os.FileInfo, error) {
368 return nil, errors.New("not implemented")
371 func (fw FileWrapper) Truncate(int64) error {
372 return errors.New("not implemented")
375 func (fw FileWrapper) Write([]byte) (int, error) {
376 return 0, errors.New("not implemented")
379 func (fw FileWrapper) Sync() error {
380 return errors.New("not implemented")
383 func (fw FileWrapper) Snapshot() (*arvados.Subtree, error) {
384 return nil, errors.New("not implemented")
387 func (fw FileWrapper) Splice(*arvados.Subtree) error {
388 return errors.New("not implemented")
391 func (client *KeepTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
392 if filename == hwImageID+".tar" {
393 rdr := ioutil.NopCloser(&bytes.Buffer{})
395 return FileWrapper{rdr, 1321984}, nil
396 } else if filename == "/file1_in_main.txt" {
397 rdr := ioutil.NopCloser(strings.NewReader("foo"))
399 return FileWrapper{rdr, 3}, nil
404 func (s *TestSuite) TestLoadImage(c *C) {
405 s.runner.Container.ContainerImage = arvadostest.DockerImage112PDH
406 s.runner.Container.Mounts = map[string]arvados.Mount{
407 "/out": {Kind: "tmp", Writable: true},
409 s.runner.Container.OutputPath = "/out"
411 _, err := s.runner.SetupMounts()
414 imageID, err := s.runner.LoadImage()
416 c.Check(s.executor.loaded, Matches, ".*"+regexp.QuoteMeta(arvadostest.DockerImage112Filename))
417 c.Check(imageID, Equals, strings.TrimSuffix(arvadostest.DockerImage112Filename, ".tar"))
419 s.runner.Container.ContainerImage = arvadostest.DockerImage112PDH
420 s.executor.imageLoaded = false
421 s.executor.loaded = ""
422 s.executor.loadErr = errors.New("bork")
423 imageID, err = s.runner.LoadImage()
424 c.Check(err, ErrorMatches, ".*bork")
425 c.Check(s.executor.loaded, Matches, ".*"+regexp.QuoteMeta(arvadostest.DockerImage112Filename))
427 s.runner.Container.ContainerImage = fakeInputCollectionPDH
428 s.executor.imageLoaded = false
429 s.executor.loaded = ""
430 s.executor.loadErr = nil
431 imageID, err = s.runner.LoadImage()
432 c.Check(err, ErrorMatches, "image collection does not include a \\.tar image file")
433 c.Check(s.executor.loaded, Equals, "")
436 type ArvErrorTestClient struct{}
438 func (ArvErrorTestClient) Create(resourceType string,
439 parameters arvadosclient.Dict,
440 output interface{}) error {
444 func (ArvErrorTestClient) Call(method, resourceType, uuid, action string, parameters arvadosclient.Dict, output interface{}) error {
445 if method == "GET" && resourceType == "containers" && action == "auth" {
448 return errors.New("ArvError")
451 func (ArvErrorTestClient) CallRaw(method, resourceType, uuid, action string,
452 parameters arvadosclient.Dict) (reader io.ReadCloser, err error) {
453 return nil, errors.New("ArvError")
456 func (ArvErrorTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
457 return errors.New("ArvError")
460 func (ArvErrorTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
464 func (ArvErrorTestClient) Discovery(key string) (interface{}, error) {
465 return discoveryMap[key], nil
468 type KeepErrorTestClient struct {
472 func (*KeepErrorTestClient) ManifestFileReader(manifest.Manifest, string) (arvados.File, error) {
473 return nil, errors.New("KeepError")
476 func (*KeepErrorTestClient) BlockWrite(context.Context, arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
477 return arvados.BlockWriteResponse{}, errors.New("KeepError")
480 func (*KeepErrorTestClient) LocalLocator(string) (string, error) {
481 return "", errors.New("KeepError")
484 type KeepReadErrorTestClient struct {
488 func (*KeepReadErrorTestClient) ReadAt(string, []byte, int) (int, error) {
489 return 0, errors.New("KeepError")
492 type ErrorReader struct {
496 func (ErrorReader) Read(p []byte) (n int, err error) {
497 return 0, errors.New("ErrorReader")
500 func (ErrorReader) Seek(int64, int) (int64, error) {
501 return 0, errors.New("ErrorReader")
504 func (KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
505 return ErrorReader{}, nil
508 type ClosableBuffer struct {
512 func (*ClosableBuffer) Close() error {
516 type TestLogs struct {
517 Stdout ClosableBuffer
518 Stderr ClosableBuffer
521 func (tl *TestLogs) NewTestLoggingWriter(logstr string) (io.WriteCloser, error) {
522 if logstr == "stdout" {
523 return &tl.Stdout, nil
525 if logstr == "stderr" {
526 return &tl.Stderr, nil
528 return nil, errors.New("???")
531 func dockerLog(fd byte, msg string) []byte {
533 header := make([]byte, 8+len(by))
535 header[7] = byte(len(by))
540 func (s *TestSuite) TestRunContainer(c *C) {
541 s.executor.runFunc = func() {
542 fmt.Fprintf(s.executor.created.Stdout, "Hello world\n")
547 s.runner.NewLogWriter = logs.NewTestLoggingWriter
548 s.runner.Container.ContainerImage = arvadostest.DockerImage112PDH
549 s.runner.Container.Command = []string{"./hw"}
550 s.runner.Container.OutputStorageClasses = []string{"default"}
552 imageID, err := s.runner.LoadImage()
555 err = s.runner.CreateContainer(imageID, nil)
558 err = s.runner.StartContainer()
561 err = s.runner.WaitFinish()
564 c.Check(logs.Stdout.String(), Matches, ".*Hello world\n")
565 c.Check(logs.Stderr.String(), Equals, "")
568 func (s *TestSuite) TestCommitLogs(c *C) {
569 api := &ArvTestClient{}
570 kc := &KeepTestClient{}
572 cr, err := NewContainerRunner(s.client, api, kc, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
574 cr.CrunchLog.Timestamper = (&TestTimestamper{}).Timestamp
576 cr.CrunchLog.Print("Hello world!")
577 cr.CrunchLog.Print("Goodbye")
578 cr.finalState = "Complete"
580 err = cr.CommitLogs()
583 c.Check(api.Calls, Equals, 2)
584 c.Check(api.Content[1]["ensure_unique_name"], Equals, true)
585 c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
586 c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
587 c.Check(*cr.LogsPDH, Equals, "63da7bdacf08c40f604daad80c261e9a+60")
590 func (s *TestSuite) TestUpdateContainerRunning(c *C) {
591 api := &ArvTestClient{}
592 kc := &KeepTestClient{}
594 cr, err := NewContainerRunner(s.client, api, kc, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
597 err = cr.UpdateContainerRunning()
600 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Running")
603 func (s *TestSuite) TestUpdateContainerComplete(c *C) {
604 api := &ArvTestClient{}
605 kc := &KeepTestClient{}
607 cr, err := NewContainerRunner(s.client, api, kc, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
610 cr.LogsPDH = new(string)
611 *cr.LogsPDH = "d3a229d2fe3690c2c3e75a71a153c6a3+60"
613 cr.ExitCode = new(int)
615 cr.finalState = "Complete"
617 err = cr.UpdateContainerFinal()
620 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
621 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
622 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
625 func (s *TestSuite) TestUpdateContainerCancelled(c *C) {
626 api := &ArvTestClient{}
627 kc := &KeepTestClient{}
629 cr, err := NewContainerRunner(s.client, api, kc, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
632 cr.finalState = "Cancelled"
634 err = cr.UpdateContainerFinal()
637 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], IsNil)
638 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], IsNil)
639 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
642 // Used by the TestFullRun*() test below to DRY up boilerplate setup to do full
643 // dress rehearsal of the Run() function, starting from a JSON container record.
644 func (s *TestSuite) fullRunHelper(c *C, record string, extraMounts []string, exitCode int, fn func()) (*ArvTestClient, *ContainerRunner, string) {
645 err := json.Unmarshal([]byte(record), &s.api.Container)
647 initialState := s.api.Container.State
650 SecretMounts map[string]arvados.Mount `json:"secret_mounts"`
652 err = json.Unmarshal([]byte(record), &sm)
654 secretMounts, err := json.Marshal(sm)
656 c.Logf("SecretMounts decoded %v json %q", sm, secretMounts)
658 s.executor.runFunc = func() {
660 s.executor.exit <- exitCode
663 s.runner.statInterval = 100 * time.Millisecond
664 s.runner.containerWatchdogInterval = time.Second
666 realTemp := c.MkDir()
668 s.runner.MkTempDir = func(_, prefix string) (string, error) {
670 d := fmt.Sprintf("%s/%s%d", realTemp, prefix, tempcount)
671 err := os.Mkdir(d, os.ModePerm)
672 if err != nil && strings.Contains(err.Error(), ": file exists") {
673 // Test case must have pre-populated the tempdir
678 s.runner.MkArvClient = func(token string) (IArvadosClient, IKeepClient, *arvados.Client, error) {
679 return &ArvTestClient{secretMounts: secretMounts}, &s.testContainerKeepClient, nil, nil
682 if extraMounts != nil && len(extraMounts) > 0 {
683 err := s.runner.SetupArvMountPoint("keep")
686 for _, m := range extraMounts {
687 os.MkdirAll(s.runner.ArvMountPoint+"/by_id/"+m, os.ModePerm)
692 if s.api.CalledWith("container.state", "Complete") != nil {
695 if s.executor.loadErr == nil && s.executor.createErr == nil && initialState != "Running" {
696 c.Check(s.api.WasSetRunning, Equals, true)
697 var lastupdate arvadosclient.Dict
698 for _, content := range s.api.Content {
699 if content["container"] != nil {
700 lastupdate = content["container"].(arvadosclient.Dict)
703 if lastupdate["log"] == nil {
704 c.Errorf("no container update with non-nil log -- updates were: %v", s.api.Content)
709 for k, v := range s.api.Logs {
715 return s.api, s.runner, realTemp
718 func (s *TestSuite) TestFullRunHello(c *C) {
719 s.runner.enableMemoryLimit = true
720 s.runner.networkMode = "default"
721 s.fullRunHelper(c, `{
722 "command": ["echo", "hello world"],
723 "container_image": "`+arvadostest.DockerImage112PDH+`",
725 "environment": {"foo":"bar","baz":"waz"},
726 "mounts": {"/tmp": {"kind": "tmp"} },
727 "output_path": "/tmp",
729 "runtime_constraints": {"vcpus":1,"ram":1000000},
731 "output_storage_classes": ["default"]
733 c.Check(s.executor.created.Command, DeepEquals, []string{"echo", "hello world"})
734 c.Check(s.executor.created.Image, Equals, "sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678")
735 c.Check(s.executor.created.Env, DeepEquals, map[string]string{"foo": "bar", "baz": "waz"})
736 c.Check(s.executor.created.VCPUs, Equals, 1)
737 c.Check(s.executor.created.RAM, Equals, int64(1000000))
738 c.Check(s.executor.created.NetworkMode, Equals, "default")
739 c.Check(s.executor.created.EnableNetwork, Equals, false)
740 c.Check(s.executor.created.CUDADeviceCount, Equals, 0)
741 fmt.Fprintln(s.executor.created.Stdout, "hello world")
744 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
745 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
746 c.Check(s.api.Logs["stdout"].String(), Matches, ".*hello world\n")
747 c.Check(s.testDispatcherKeepClient.StorageClasses, DeepEquals, []string{"default"})
748 c.Check(s.testContainerKeepClient.StorageClasses, DeepEquals, []string{"default"})
751 func (s *TestSuite) TestRunAlreadyRunning(c *C) {
753 s.fullRunHelper(c, `{
754 "command": ["sleep", "3"],
755 "container_image": "`+arvadostest.DockerImage112PDH+`",
758 "mounts": {"/tmp": {"kind": "tmp"} },
759 "output_path": "/tmp",
761 "runtime_constraints": {},
762 "scheduling_parameters":{"max_run_time": 1},
767 c.Check(s.api.CalledWith("container.state", "Cancelled"), IsNil)
768 c.Check(s.api.CalledWith("container.state", "Complete"), IsNil)
769 c.Check(ran, Equals, false)
772 func (s *TestSuite) TestRunTimeExceeded(c *C) {
773 s.fullRunHelper(c, `{
774 "command": ["sleep", "3"],
775 "container_image": "`+arvadostest.DockerImage112PDH+`",
778 "mounts": {"/tmp": {"kind": "tmp"} },
779 "output_path": "/tmp",
781 "runtime_constraints": {},
782 "scheduling_parameters":{"max_run_time": 1},
785 time.Sleep(3 * time.Second)
788 c.Check(s.api.CalledWith("container.state", "Cancelled"), NotNil)
789 c.Check(s.api.Logs["crunch-run"].String(), Matches, "(?ms).*maximum run time exceeded.*")
792 func (s *TestSuite) TestContainerWaitFails(c *C) {
793 s.fullRunHelper(c, `{
794 "command": ["sleep", "3"],
795 "container_image": "`+arvadostest.DockerImage112PDH+`",
797 "mounts": {"/tmp": {"kind": "tmp"} },
798 "output_path": "/tmp",
802 s.executor.waitErr = errors.New("Container is not running")
805 c.Check(s.api.CalledWith("container.state", "Cancelled"), NotNil)
806 c.Check(s.api.Logs["crunch-run"].String(), Matches, "(?ms).*Container is not running.*")
809 func (s *TestSuite) TestCrunchstat(c *C) {
810 s.fullRunHelper(c, `{
811 "command": ["sleep", "1"],
812 "container_image": "`+arvadostest.DockerImage112PDH+`",
815 "mounts": {"/tmp": {"kind": "tmp"} },
816 "output_path": "/tmp",
818 "runtime_constraints": {},
821 time.Sleep(time.Second)
824 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
825 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
827 // We didn't actually start a container, so crunchstat didn't
828 // find accounting files and therefore didn't log any stats.
829 // It should have logged a "can't find accounting files"
830 // message after one poll interval, though, so we can confirm
832 c.Assert(s.api.Logs["crunchstat"], NotNil)
833 c.Check(s.api.Logs["crunchstat"].String(), Matches, `(?ms).*cgroup stats files have not appeared after 100ms.*`)
835 // The "files never appeared" log assures us that we called
836 // (*crunchstat.Reporter)Stop(), and that we set it up with
837 // the correct container ID "abcde":
838 c.Check(s.api.Logs["crunchstat"].String(), Matches, `(?ms).*cgroup stats files never appeared for cgroupid\n`)
841 func (s *TestSuite) TestNodeInfoLog(c *C) {
842 os.Setenv("SLURMD_NODENAME", "compute2")
843 s.fullRunHelper(c, `{
844 "command": ["sleep", "1"],
845 "container_image": "`+arvadostest.DockerImage112PDH+`",
848 "mounts": {"/tmp": {"kind": "tmp"} },
849 "output_path": "/tmp",
851 "runtime_constraints": {},
855 time.Sleep(time.Second)
858 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
859 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
861 c.Assert(s.api.Logs["node"], NotNil)
862 json := s.api.Logs["node"].String()
863 c.Check(json, Matches, `(?ms).*"uuid": *"zzzzz-7ekkf-2z3mc76g2q73aio".*`)
864 c.Check(json, Matches, `(?ms).*"total_cpu_cores": *16.*`)
865 c.Check(json, Not(Matches), `(?ms).*"info":.*`)
867 c.Assert(s.api.Logs["node-info"], NotNil)
868 json = s.api.Logs["node-info"].String()
869 c.Check(json, Matches, `(?ms).*Host Information.*`)
870 c.Check(json, Matches, `(?ms).*CPU Information.*`)
871 c.Check(json, Matches, `(?ms).*Memory Information.*`)
872 c.Check(json, Matches, `(?ms).*Disk Space.*`)
873 c.Check(json, Matches, `(?ms).*Disk INodes.*`)
876 func (s *TestSuite) TestLogVersionAndRuntime(c *C) {
877 s.fullRunHelper(c, `{
878 "command": ["sleep", "1"],
879 "container_image": "`+arvadostest.DockerImage112PDH+`",
882 "mounts": {"/tmp": {"kind": "tmp"} },
883 "output_path": "/tmp",
885 "runtime_constraints": {},
891 c.Assert(s.api.Logs["crunch-run"], NotNil)
892 c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*crunch-run \S+ \(go\S+\) start.*`)
893 c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*crunch-run process has uid=\d+\(.+\) gid=\d+\(.+\) groups=\d+\(.+\)(,\d+\(.+\))*\n.*`)
894 c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*Executing container: zzzzz-zzzzz-zzzzzzzzzzzzzzz.*`)
895 c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*Using container runtime: stub.*`)
898 func (s *TestSuite) TestContainerRecordLog(c *C) {
899 s.fullRunHelper(c, `{
900 "command": ["sleep", "1"],
901 "container_image": "`+arvadostest.DockerImage112PDH+`",
904 "mounts": {"/tmp": {"kind": "tmp"} },
905 "output_path": "/tmp",
907 "runtime_constraints": {},
911 time.Sleep(time.Second)
914 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
915 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
917 c.Assert(s.api.Logs["container"], NotNil)
918 c.Check(s.api.Logs["container"].String(), Matches, `(?ms).*container_image.*`)
921 func (s *TestSuite) TestFullRunStderr(c *C) {
922 s.fullRunHelper(c, `{
923 "command": ["/bin/sh", "-c", "echo hello ; echo world 1>&2 ; exit 1"],
924 "container_image": "`+arvadostest.DockerImage112PDH+`",
927 "mounts": {"/tmp": {"kind": "tmp"} },
928 "output_path": "/tmp",
930 "runtime_constraints": {},
933 fmt.Fprintln(s.executor.created.Stdout, "hello")
934 fmt.Fprintln(s.executor.created.Stderr, "world")
937 final := s.api.CalledWith("container.state", "Complete")
938 c.Assert(final, NotNil)
939 c.Check(final["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
940 c.Check(final["container"].(arvadosclient.Dict)["log"], NotNil)
942 c.Check(s.api.Logs["stdout"].String(), Matches, ".*hello\n")
943 c.Check(s.api.Logs["stderr"].String(), Matches, ".*world\n")
946 func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
947 s.fullRunHelper(c, `{
949 "container_image": "`+arvadostest.DockerImage112PDH+`",
952 "mounts": {"/tmp": {"kind": "tmp"} },
953 "output_path": "/tmp",
955 "runtime_constraints": {},
958 fmt.Fprintf(s.executor.created.Stdout, "workdir=%q", s.executor.created.WorkingDir)
961 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
962 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
963 c.Log(s.api.Logs["stdout"])
964 c.Check(s.api.Logs["stdout"].String(), Matches, `.*workdir=""\n`)
967 func (s *TestSuite) TestFullRunSetCwd(c *C) {
968 s.fullRunHelper(c, `{
970 "container_image": "`+arvadostest.DockerImage112PDH+`",
973 "mounts": {"/tmp": {"kind": "tmp"} },
974 "output_path": "/tmp",
976 "runtime_constraints": {},
979 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.WorkingDir)
982 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
983 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
984 c.Check(s.api.Logs["stdout"].String(), Matches, ".*/bin\n")
987 func (s *TestSuite) TestFullRunSetOutputStorageClasses(c *C) {
988 s.fullRunHelper(c, `{
990 "container_image": "`+arvadostest.DockerImage112PDH+`",
993 "mounts": {"/tmp": {"kind": "tmp"} },
994 "output_path": "/tmp",
996 "runtime_constraints": {},
998 "output_storage_classes": ["foo", "bar"]
1000 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.WorkingDir)
1003 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
1004 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1005 c.Check(s.api.Logs["stdout"].String(), Matches, ".*/bin\n")
1006 c.Check(s.testDispatcherKeepClient.StorageClasses, DeepEquals, []string{"foo", "bar"})
1007 c.Check(s.testContainerKeepClient.StorageClasses, DeepEquals, []string{"foo", "bar"})
1010 func (s *TestSuite) TestEnableCUDADeviceCount(c *C) {
1011 s.fullRunHelper(c, `{
1013 "container_image": "`+arvadostest.DockerImage112PDH+`",
1016 "mounts": {"/tmp": {"kind": "tmp"} },
1017 "output_path": "/tmp",
1019 "runtime_constraints": {"cuda": {"device_count": 2}},
1021 "output_storage_classes": ["foo", "bar"]
1022 }`, nil, 0, func() {
1023 fmt.Fprintln(s.executor.created.Stdout, "ok")
1025 c.Check(s.executor.created.CUDADeviceCount, Equals, 2)
1028 func (s *TestSuite) TestEnableCUDAHardwareCapability(c *C) {
1029 s.fullRunHelper(c, `{
1031 "container_image": "`+arvadostest.DockerImage112PDH+`",
1034 "mounts": {"/tmp": {"kind": "tmp"} },
1035 "output_path": "/tmp",
1037 "runtime_constraints": {"cuda": {"hardware_capability": "foo"}},
1039 "output_storage_classes": ["foo", "bar"]
1040 }`, nil, 0, func() {
1041 fmt.Fprintln(s.executor.created.Stdout, "ok")
1043 c.Check(s.executor.created.CUDADeviceCount, Equals, 0)
1046 func (s *TestSuite) TestStopOnSignal(c *C) {
1047 s.executor.runFunc = func() {
1048 s.executor.created.Stdout.Write([]byte("foo\n"))
1049 s.runner.SigChan <- syscall.SIGINT
1051 s.testStopContainer(c)
1054 func (s *TestSuite) TestStopOnArvMountDeath(c *C) {
1055 s.executor.runFunc = func() {
1056 s.executor.created.Stdout.Write([]byte("foo\n"))
1057 s.runner.ArvMountExit <- nil
1058 close(s.runner.ArvMountExit)
1060 s.runner.ArvMountExit = make(chan error)
1061 s.testStopContainer(c)
1064 func (s *TestSuite) testStopContainer(c *C) {
1066 "command": ["/bin/sh", "-c", "echo foo && sleep 30 && echo bar"],
1067 "container_image": "` + arvadostest.DockerImage112PDH + `",
1070 "mounts": {"/tmp": {"kind": "tmp"} },
1071 "output_path": "/tmp",
1073 "runtime_constraints": {},
1077 err := json.Unmarshal([]byte(record), &s.api.Container)
1078 c.Assert(err, IsNil)
1080 s.runner.RunArvMount = func([]string, string) (*exec.Cmd, error) { return nil, nil }
1081 s.runner.MkArvClient = func(token string) (IArvadosClient, IKeepClient, *arvados.Client, error) {
1082 return &ArvTestClient{}, &KeepTestClient{}, nil, nil
1085 done := make(chan error)
1087 done <- s.runner.Run()
1090 case <-time.After(20 * time.Second):
1091 pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
1092 c.Fatal("timed out")
1096 for k, v := range s.api.Logs {
1098 c.Log(v.String(), "\n")
1101 c.Check(s.api.CalledWith("container.log", nil), NotNil)
1102 c.Check(s.api.CalledWith("container.state", "Cancelled"), NotNil)
1103 c.Check(s.api.Logs["stdout"].String(), Matches, "(?ms).*foo\n$")
1106 func (s *TestSuite) TestFullRunSetEnv(c *C) {
1107 s.fullRunHelper(c, `{
1108 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1109 "container_image": "`+arvadostest.DockerImage112PDH+`",
1111 "environment": {"FROBIZ": "bilbo"},
1112 "mounts": {"/tmp": {"kind": "tmp"} },
1113 "output_path": "/tmp",
1115 "runtime_constraints": {},
1117 }`, nil, 0, func() {
1118 fmt.Fprintf(s.executor.created.Stdout, "%v", s.executor.created.Env)
1121 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
1122 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1123 c.Check(s.api.Logs["stdout"].String(), Matches, `.*map\[FROBIZ:bilbo\]\n`)
1126 type ArvMountCmdLine struct {
1131 func (am *ArvMountCmdLine) ArvMountTest(c []string, token string) (*exec.Cmd, error) {
1137 func stubCert(temp string) string {
1138 path := temp + "/ca-certificates.crt"
1139 crt, _ := os.Create(path)
1141 arvadosclient.CertFiles = []string{path}
1145 func (s *TestSuite) TestSetupMounts(c *C) {
1147 am := &ArvMountCmdLine{}
1148 cr.RunArvMount = am.ArvMountTest
1149 cr.ContainerArvClient = &ArvTestClient{}
1150 cr.ContainerKeepClient = &KeepTestClient{}
1151 cr.Container.OutputStorageClasses = []string{"default"}
1153 realTemp := c.MkDir()
1154 certTemp := c.MkDir()
1155 stubCertPath := stubCert(certTemp)
1156 cr.parentTemp = realTemp
1159 cr.MkTempDir = func(_ string, prefix string) (string, error) {
1161 d := fmt.Sprintf("%s/%s%d", realTemp, prefix, i)
1162 err := os.Mkdir(d, os.ModePerm)
1163 if err != nil && strings.Contains(err.Error(), ": file exists") {
1164 // Test case must have pre-populated the tempdir
1170 checkEmpty := func() {
1171 // Should be deleted.
1172 _, err := os.Stat(realTemp)
1173 c.Assert(os.IsNotExist(err), Equals, true)
1175 // Now recreate it for the next test.
1176 c.Assert(os.Mkdir(realTemp, 0777), IsNil)
1181 cr.ArvMountPoint = ""
1182 cr.Container.Mounts = make(map[string]arvados.Mount)
1183 cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
1184 cr.Container.OutputPath = "/tmp"
1185 cr.statInterval = 5 * time.Second
1186 bindmounts, err := cr.SetupMounts()
1188 c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
1189 "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
1190 "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
1191 c.Check(bindmounts, DeepEquals, map[string]bindmount{"/tmp": {realTemp + "/tmp2", false}})
1192 os.RemoveAll(cr.ArvMountPoint)
1199 cr.ArvMountPoint = ""
1200 cr.Container.Mounts = make(map[string]arvados.Mount)
1201 cr.Container.Mounts["/out"] = arvados.Mount{Kind: "tmp"}
1202 cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
1203 cr.Container.OutputPath = "/out"
1204 cr.Container.OutputStorageClasses = []string{"foo", "bar"}
1206 bindmounts, err := cr.SetupMounts()
1208 c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
1209 "--read-write", "--storage-classes", "foo,bar", "--crunchstat-interval=5",
1210 "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
1211 c.Check(bindmounts, DeepEquals, map[string]bindmount{"/out": {realTemp + "/tmp2", false}, "/tmp": {realTemp + "/tmp3", false}})
1212 os.RemoveAll(cr.ArvMountPoint)
1219 cr.ArvMountPoint = ""
1220 cr.Container.Mounts = make(map[string]arvados.Mount)
1221 cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
1222 cr.Container.OutputPath = "/tmp"
1223 cr.Container.RuntimeConstraints.API = true
1224 cr.Container.OutputStorageClasses = []string{"default"}
1226 bindmounts, err := cr.SetupMounts()
1228 c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
1229 "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
1230 "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
1231 c.Check(bindmounts, DeepEquals, map[string]bindmount{"/tmp": {realTemp + "/tmp2", false}, "/etc/arvados/ca-certificates.crt": {stubCertPath, true}})
1232 os.RemoveAll(cr.ArvMountPoint)
1236 cr.Container.RuntimeConstraints.API = false
1241 cr.ArvMountPoint = ""
1242 cr.Container.Mounts = map[string]arvados.Mount{
1243 "/keeptmp": {Kind: "collection", Writable: true},
1245 cr.Container.OutputPath = "/keeptmp"
1247 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1249 bindmounts, err := cr.SetupMounts()
1251 c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
1252 "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
1253 "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
1254 c.Check(bindmounts, DeepEquals, map[string]bindmount{"/keeptmp": {realTemp + "/keep1/tmp0", false}})
1255 os.RemoveAll(cr.ArvMountPoint)
1262 cr.ArvMountPoint = ""
1263 cr.Container.Mounts = map[string]arvados.Mount{
1264 "/keepinp": {Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"},
1265 "/keepout": {Kind: "collection", Writable: true},
1267 cr.Container.OutputPath = "/keepout"
1269 os.MkdirAll(realTemp+"/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
1270 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1272 bindmounts, err := cr.SetupMounts()
1274 c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
1275 "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
1276 "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
1277 c.Check(bindmounts, DeepEquals, map[string]bindmount{
1278 "/keepinp": {realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", true},
1279 "/keepout": {realTemp + "/keep1/tmp0", false},
1281 os.RemoveAll(cr.ArvMountPoint)
1288 cr.ArvMountPoint = ""
1289 cr.Container.RuntimeConstraints.KeepCacheRAM = 512
1290 cr.Container.Mounts = map[string]arvados.Mount{
1291 "/keepinp": {Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"},
1292 "/keepout": {Kind: "collection", Writable: true},
1294 cr.Container.OutputPath = "/keepout"
1296 os.MkdirAll(realTemp+"/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
1297 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1299 bindmounts, err := cr.SetupMounts()
1301 c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
1302 "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
1303 "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
1304 c.Check(bindmounts, DeepEquals, map[string]bindmount{
1305 "/keepinp": {realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", true},
1306 "/keepout": {realTemp + "/keep1/tmp0", false},
1308 os.RemoveAll(cr.ArvMountPoint)
1313 for _, test := range []struct {
1317 {in: "foo", out: `"foo"`},
1318 {in: nil, out: `null`},
1319 {in: map[string]int64{"foo": 123456789123456789}, out: `{"foo":123456789123456789}`},
1322 cr.ArvMountPoint = ""
1323 cr.Container.Mounts = map[string]arvados.Mount{
1324 "/mnt/test.json": {Kind: "json", Content: test.in},
1326 bindmounts, err := cr.SetupMounts()
1328 c.Check(bindmounts, DeepEquals, map[string]bindmount{
1329 "/mnt/test.json": {realTemp + "/json2/mountdata.json", true},
1331 content, err := ioutil.ReadFile(realTemp + "/json2/mountdata.json")
1333 c.Check(content, DeepEquals, []byte(test.out))
1334 os.RemoveAll(cr.ArvMountPoint)
1339 for _, test := range []struct {
1343 {in: "foo", out: `foo`},
1344 {in: nil, out: "error"},
1345 {in: map[string]int64{"foo": 123456789123456789}, out: "error"},
1348 cr.ArvMountPoint = ""
1349 cr.Container.Mounts = map[string]arvados.Mount{
1350 "/mnt/test.txt": {Kind: "text", Content: test.in},
1352 bindmounts, err := cr.SetupMounts()
1353 if test.out == "error" {
1354 c.Check(err.Error(), Equals, "content for mount \"/mnt/test.txt\" must be a string")
1357 c.Check(bindmounts, DeepEquals, map[string]bindmount{
1358 "/mnt/test.txt": {realTemp + "/text2/mountdata.text", true},
1360 content, err := ioutil.ReadFile(realTemp + "/text2/mountdata.text")
1362 c.Check(content, DeepEquals, []byte(test.out))
1364 os.RemoveAll(cr.ArvMountPoint)
1369 // Read-only mount points are allowed underneath output_dir mount point
1372 cr.ArvMountPoint = ""
1373 cr.Container.Mounts = make(map[string]arvados.Mount)
1374 cr.Container.Mounts = map[string]arvados.Mount{
1375 "/tmp": {Kind: "tmp"},
1376 "/tmp/foo": {Kind: "collection"},
1378 cr.Container.OutputPath = "/tmp"
1380 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
1382 bindmounts, err := cr.SetupMounts()
1384 c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
1385 "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
1386 "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
1387 c.Check(bindmounts, DeepEquals, map[string]bindmount{
1388 "/tmp": {realTemp + "/tmp2", false},
1389 "/tmp/foo": {realTemp + "/keep1/tmp0", true},
1391 os.RemoveAll(cr.ArvMountPoint)
1396 // Writable mount points copied to output_dir mount point
1399 cr.ArvMountPoint = ""
1400 cr.Container.Mounts = make(map[string]arvados.Mount)
1401 cr.Container.Mounts = map[string]arvados.Mount{
1402 "/tmp": {Kind: "tmp"},
1403 "/tmp/foo": {Kind: "collection",
1404 PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53",
1406 "/tmp/bar": {Kind: "collection",
1407 PortableDataHash: "59389a8f9ee9d399be35462a0f92541d+53",
1411 cr.Container.OutputPath = "/tmp"
1413 os.MkdirAll(realTemp+"/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
1414 os.MkdirAll(realTemp+"/keep1/by_id/59389a8f9ee9d399be35462a0f92541d+53/baz", os.ModePerm)
1416 rf, _ := os.Create(realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541d+53/baz/quux")
1417 rf.Write([]byte("bar"))
1420 _, err := cr.SetupMounts()
1422 _, err = os.Stat(cr.HostOutputDir + "/foo")
1424 _, err = os.Stat(cr.HostOutputDir + "/bar/quux")
1426 os.RemoveAll(cr.ArvMountPoint)
1431 // Only mount points of kind 'collection' are allowed underneath output_dir mount point
1434 cr.ArvMountPoint = ""
1435 cr.Container.Mounts = make(map[string]arvados.Mount)
1436 cr.Container.Mounts = map[string]arvados.Mount{
1437 "/tmp": {Kind: "tmp"},
1438 "/tmp/foo": {Kind: "tmp"},
1440 cr.Container.OutputPath = "/tmp"
1442 _, err := cr.SetupMounts()
1443 c.Check(err, NotNil)
1444 c.Check(err, ErrorMatches, `only mount points of kind 'collection', 'text' or 'json' are supported underneath the output_path.*`)
1445 os.RemoveAll(cr.ArvMountPoint)
1450 // Only mount point of kind 'collection' is allowed for stdin
1453 cr.ArvMountPoint = ""
1454 cr.Container.Mounts = make(map[string]arvados.Mount)
1455 cr.Container.Mounts = map[string]arvados.Mount{
1456 "stdin": {Kind: "tmp"},
1459 _, err := cr.SetupMounts()
1460 c.Check(err, NotNil)
1461 c.Check(err, ErrorMatches, `unsupported mount kind 'tmp' for stdin.*`)
1462 os.RemoveAll(cr.ArvMountPoint)
1470 cr.ArvMountPoint = ""
1471 (*GitMountSuite)(nil).useTestGitServer(c)
1472 cr.token = arvadostest.ActiveToken
1473 cr.Container.Mounts = make(map[string]arvados.Mount)
1474 cr.Container.Mounts = map[string]arvados.Mount{
1477 UUID: arvadostest.Repository2UUID,
1478 Commit: "fd3531f42995344f36c30b79f55f27b502f3d344",
1483 UUID: arvadostest.Repository2UUID,
1484 Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
1488 cr.Container.OutputPath = "/tmp"
1490 bindmounts, err := cr.SetupMounts()
1493 for path, mount := range bindmounts {
1494 c.Check(mount.ReadOnly, Equals, !cr.Container.Mounts[path].Writable, Commentf("%s %#v", path, mount))
1497 data, err := ioutil.ReadFile(bindmounts["/tip"].HostPath + "/dir1/dir2/file with mode 0644")
1499 c.Check(string(data), Equals, "\000\001\002\003")
1500 _, err = ioutil.ReadFile(bindmounts["/tip"].HostPath + "/file only on testbranch")
1501 c.Check(err, FitsTypeOf, &os.PathError{})
1502 c.Check(os.IsNotExist(err), Equals, true)
1504 data, err = ioutil.ReadFile(bindmounts["/non-tip"].HostPath + "/dir1/dir2/file with mode 0644")
1506 c.Check(string(data), Equals, "\000\001\002\003")
1507 data, err = ioutil.ReadFile(bindmounts["/non-tip"].HostPath + "/file only on testbranch")
1509 c.Check(string(data), Equals, "testfile\n")
1516 func (s *TestSuite) TestStdout(c *C) {
1518 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1519 "container_image": "` + arvadostest.DockerImage112PDH + `",
1521 "environment": {"FROBIZ": "bilbo"},
1522 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"} },
1523 "output_path": "/tmp",
1525 "runtime_constraints": {},
1529 s.fullRunHelper(c, helperRecord, nil, 0, func() {
1530 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.Env["FROBIZ"])
1533 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
1534 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1535 c.Check(s.runner.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", "./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out\n"), NotNil)
1538 // Used by the TestStdoutWithWrongPath*()
1539 func (s *TestSuite) stdoutErrorRunHelper(c *C, record string, fn func()) (*ArvTestClient, *ContainerRunner, error) {
1540 err := json.Unmarshal([]byte(record), &s.api.Container)
1541 c.Assert(err, IsNil)
1542 s.executor.runFunc = fn
1543 s.runner.RunArvMount = (&ArvMountCmdLine{}).ArvMountTest
1544 s.runner.MkArvClient = func(token string) (IArvadosClient, IKeepClient, *arvados.Client, error) {
1545 return s.api, &KeepTestClient{}, nil, nil
1547 return s.api, s.runner, s.runner.Run()
1550 func (s *TestSuite) TestStdoutWithWrongPath(c *C) {
1551 _, _, err := s.stdoutErrorRunHelper(c, `{
1552 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path":"/tmpa.out"} },
1553 "output_path": "/tmp",
1556 c.Check(err, ErrorMatches, ".*Stdout path does not start with OutputPath.*")
1559 func (s *TestSuite) TestStdoutWithWrongKindTmp(c *C) {
1560 _, _, err := s.stdoutErrorRunHelper(c, `{
1561 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "tmp", "path":"/tmp/a.out"} },
1562 "output_path": "/tmp",
1565 c.Check(err, ErrorMatches, ".*unsupported mount kind 'tmp' for stdout.*")
1568 func (s *TestSuite) TestStdoutWithWrongKindCollection(c *C) {
1569 _, _, err := s.stdoutErrorRunHelper(c, `{
1570 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "collection", "path":"/tmp/a.out"} },
1571 "output_path": "/tmp",
1574 c.Check(err, ErrorMatches, ".*unsupported mount kind 'collection' for stdout.*")
1577 func (s *TestSuite) TestFullRunWithAPI(c *C) {
1578 s.fullRunHelper(c, `{
1579 "command": ["/bin/sh", "-c", "true $ARVADOS_API_HOST"],
1580 "container_image": "`+arvadostest.DockerImage112PDH+`",
1583 "mounts": {"/tmp": {"kind": "tmp"} },
1584 "output_path": "/tmp",
1586 "runtime_constraints": {"API": true},
1588 }`, nil, 0, func() {
1589 c.Check(s.executor.created.Env["ARVADOS_API_HOST"], Equals, os.Getenv("ARVADOS_API_HOST"))
1590 s.executor.exit <- 3
1592 c.Check(s.api.CalledWith("container.exit_code", 3), NotNil)
1593 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1594 c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*status code 3\n.*`)
1597 func (s *TestSuite) TestFullRunSetOutput(c *C) {
1598 defer os.Setenv("ARVADOS_API_HOST", os.Getenv("ARVADOS_API_HOST"))
1599 os.Setenv("ARVADOS_API_HOST", "test.arvados.org")
1600 s.fullRunHelper(c, `{
1601 "command": ["/bin/sh", "-c", "echo $ARVADOS_API_HOST"],
1602 "container_image": "`+arvadostest.DockerImage112PDH+`",
1605 "mounts": {"/tmp": {"kind": "tmp"} },
1606 "output_path": "/tmp",
1608 "runtime_constraints": {"API": true},
1610 }`, nil, 0, func() {
1611 s.api.Container.Output = arvadostest.DockerImage112PDH
1614 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
1615 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1616 c.Check(s.api.CalledWith("container.output", arvadostest.DockerImage112PDH), NotNil)
1619 func (s *TestSuite) TestArvMountRuntimeStatusWarning(c *C) {
1620 s.runner.RunArvMount = func([]string, string) (*exec.Cmd, error) {
1621 os.Mkdir(s.runner.ArvMountPoint+"/by_id", 0666)
1622 ioutil.WriteFile(s.runner.ArvMountPoint+"/by_id/README", nil, 0666)
1623 return s.runner.ArvMountCmd([]string{"bash", "-c", "echo >&2 Test: Keep write error: I am a teapot; sleep 3"}, "")
1625 s.executor.runFunc = func() {
1626 time.Sleep(time.Second)
1627 s.executor.exit <- 137
1630 "command": ["sleep", "1"],
1631 "container_image": "` + arvadostest.DockerImage112PDH + `",
1634 "mounts": {"/tmp": {"kind": "tmp"} },
1635 "output_path": "/tmp",
1637 "runtime_constraints": {"API": true},
1640 err := json.Unmarshal([]byte(record), &s.api.Container)
1641 c.Assert(err, IsNil)
1642 err = s.runner.Run()
1643 c.Assert(err, IsNil)
1644 c.Check(s.api.CalledWith("container.exit_code", 137), NotNil)
1645 c.Check(s.api.CalledWith("container.runtime_status.warning", "arv-mount: Keep write error"), NotNil)
1646 c.Check(s.api.CalledWith("container.runtime_status.warningDetail", "Test: Keep write error: I am a teapot"), NotNil)
1647 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1648 c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*Container exited with status code 137 \(signal 9, SIGKILL\).*`)
1651 func (s *TestSuite) TestStdoutWithExcludeFromOutputMountPointUnderOutputDir(c *C) {
1653 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1654 "container_image": "` + arvadostest.DockerImage112PDH + `",
1656 "environment": {"FROBIZ": "bilbo"},
1658 "/tmp": {"kind": "tmp"},
1659 "/tmp/foo": {"kind": "collection",
1660 "portable_data_hash": "a3e8f74c6f101eae01fa08bfb4e49b3a+54",
1661 "exclude_from_output": true
1663 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1665 "output_path": "/tmp",
1667 "runtime_constraints": {},
1671 extraMounts := []string{"a3e8f74c6f101eae01fa08bfb4e49b3a+54"}
1673 s.fullRunHelper(c, helperRecord, extraMounts, 0, func() {
1674 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.Env["FROBIZ"])
1677 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
1678 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1679 c.Check(s.runner.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", "./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out\n"), NotNil)
1682 func (s *TestSuite) TestStdoutWithMultipleMountPointsUnderOutputDir(c *C) {
1684 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1685 "container_image": "` + arvadostest.DockerImage112PDH + `",
1687 "environment": {"FROBIZ": "bilbo"},
1689 "/tmp": {"kind": "tmp"},
1690 "/tmp/foo/bar": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/file2_in_main.txt"},
1691 "/tmp/foo/sub1": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/subdir1"},
1692 "/tmp/foo/sub1file2": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/subdir1/file2_in_subdir1.txt"},
1693 "/tmp/foo/baz/sub2file2": {"kind": "collection", "portable_data_hash": "a0def87f80dd594d4675809e83bd4f15+367", "path":"/subdir1/subdir2/file2_in_subdir2.txt"},
1694 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1696 "output_path": "/tmp",
1698 "runtime_constraints": {},
1702 extraMounts := []string{
1703 "a0def87f80dd594d4675809e83bd4f15+367/file2_in_main.txt",
1704 "a0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt",
1705 "a0def87f80dd594d4675809e83bd4f15+367/subdir1/subdir2/file2_in_subdir2.txt",
1708 api, _, realtemp := s.fullRunHelper(c, helperRecord, extraMounts, 0, func() {
1709 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.Env["FROBIZ"])
1712 c.Check(s.executor.created.BindMounts, DeepEquals, map[string]bindmount{
1713 "/tmp": {realtemp + "/tmp1", false},
1714 "/tmp/foo/bar": {s.keepmount + "/by_id/a0def87f80dd594d4675809e83bd4f15+367/file2_in_main.txt", true},
1715 "/tmp/foo/baz/sub2file2": {s.keepmount + "/by_id/a0def87f80dd594d4675809e83bd4f15+367/subdir1/subdir2/file2_in_subdir2.txt", true},
1716 "/tmp/foo/sub1": {s.keepmount + "/by_id/a0def87f80dd594d4675809e83bd4f15+367/subdir1", true},
1717 "/tmp/foo/sub1file2": {s.keepmount + "/by_id/a0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt", true},
1720 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1721 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1722 for _, v := range api.Content {
1723 if v["collection"] != nil {
1724 c.Check(v["ensure_unique_name"], Equals, true)
1725 collection := v["collection"].(arvadosclient.Dict)
1726 if strings.Index(collection["name"].(string), "output") == 0 {
1727 manifest := collection["manifest_text"].(string)
1729 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1730 ./foo 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0abcdefgh11234567890@569fa8c3 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 9:18:bar 36:18:sub1file2
1731 ./foo/baz 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0bcdefghijk544332211@569fa8c5 9:18:sub2file2
1732 ./foo/sub1 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396cabcdefghij6419876543234@569fa8c4 0:9:file1_in_subdir1.txt 9:18:file2_in_subdir1.txt
1733 ./foo/sub1/subdir2 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0bcdefghijk544332211@569fa8c5 0:9:file1_in_subdir2.txt 9:18:file2_in_subdir2.txt
1740 func (s *TestSuite) TestStdoutWithMountPointsUnderOutputDirDenormalizedManifest(c *C) {
1742 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1743 "container_image": "` + arvadostest.DockerImage112PDH + `",
1745 "environment": {"FROBIZ": "bilbo"},
1747 "/tmp": {"kind": "tmp"},
1748 "/tmp/foo/bar": {"kind": "collection", "portable_data_hash": "b0def87f80dd594d4675809e83bd4f15+367", "path": "/subdir1/file2_in_subdir1.txt"},
1749 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1751 "output_path": "/tmp",
1753 "runtime_constraints": {},
1757 extraMounts := []string{
1758 "b0def87f80dd594d4675809e83bd4f15+367/subdir1/file2_in_subdir1.txt",
1761 s.fullRunHelper(c, helperRecord, extraMounts, 0, func() {
1762 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.Env["FROBIZ"])
1765 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
1766 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
1767 for _, v := range s.api.Content {
1768 if v["collection"] != nil {
1769 collection := v["collection"].(arvadosclient.Dict)
1770 if strings.Index(collection["name"].(string), "output") == 0 {
1771 manifest := collection["manifest_text"].(string)
1773 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1774 ./foo 3e426d509afffb85e06c4c96a7c15e91+27+Aa124ac75e5168396c73c0abcdefgh11234567890@569fa8c3 10:17:bar
1781 func (s *TestSuite) TestOutputError(c *C) {
1783 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1784 "container_image": "` + arvadostest.DockerImage112PDH + `",
1786 "environment": {"FROBIZ": "bilbo"},
1788 "/tmp": {"kind": "tmp"}
1790 "output_path": "/tmp",
1792 "runtime_constraints": {},
1795 s.fullRunHelper(c, helperRecord, nil, 0, func() {
1796 os.Symlink("/etc/hosts", s.runner.HostOutputDir+"/baz")
1799 c.Check(s.api.CalledWith("container.state", "Cancelled"), NotNil)
1802 func (s *TestSuite) TestStdinCollectionMountPoint(c *C) {
1804 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1805 "container_image": "` + arvadostest.DockerImage112PDH + `",
1807 "environment": {"FROBIZ": "bilbo"},
1809 "/tmp": {"kind": "tmp"},
1810 "stdin": {"kind": "collection", "portable_data_hash": "b0def87f80dd594d4675809e83bd4f15+367", "path": "/file1_in_main.txt"},
1811 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1813 "output_path": "/tmp",
1815 "runtime_constraints": {},
1819 extraMounts := []string{
1820 "b0def87f80dd594d4675809e83bd4f15+367/file1_in_main.txt",
1823 api, _, _ := s.fullRunHelper(c, helperRecord, extraMounts, 0, func() {
1824 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.Env["FROBIZ"])
1827 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1828 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1829 for _, v := range api.Content {
1830 if v["collection"] != nil {
1831 collection := v["collection"].(arvadosclient.Dict)
1832 if strings.Index(collection["name"].(string), "output") == 0 {
1833 manifest := collection["manifest_text"].(string)
1834 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1841 func (s *TestSuite) TestStdinJsonMountPoint(c *C) {
1843 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
1844 "container_image": "` + arvadostest.DockerImage112PDH + `",
1846 "environment": {"FROBIZ": "bilbo"},
1848 "/tmp": {"kind": "tmp"},
1849 "stdin": {"kind": "json", "content": "foo"},
1850 "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"}
1852 "output_path": "/tmp",
1854 "runtime_constraints": {},
1858 api, _, _ := s.fullRunHelper(c, helperRecord, nil, 0, func() {
1859 fmt.Fprintln(s.executor.created.Stdout, s.executor.created.Env["FROBIZ"])
1862 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
1863 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
1864 for _, v := range api.Content {
1865 if v["collection"] != nil {
1866 collection := v["collection"].(arvadosclient.Dict)
1867 if strings.Index(collection["name"].(string), "output") == 0 {
1868 manifest := collection["manifest_text"].(string)
1869 c.Check(manifest, Equals, `./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out
1876 func (s *TestSuite) TestStderrMount(c *C) {
1877 api, cr, _ := s.fullRunHelper(c, `{
1878 "command": ["/bin/sh", "-c", "echo hello;exit 1"],
1879 "container_image": "`+arvadostest.DockerImage112PDH+`",
1882 "mounts": {"/tmp": {"kind": "tmp"},
1883 "stdout": {"kind": "file", "path": "/tmp/a/out.txt"},
1884 "stderr": {"kind": "file", "path": "/tmp/b/err.txt"}},
1885 "output_path": "/tmp",
1887 "runtime_constraints": {},
1889 }`, nil, 1, func() {
1890 fmt.Fprintln(s.executor.created.Stdout, "hello")
1891 fmt.Fprintln(s.executor.created.Stderr, "oops")
1894 final := api.CalledWith("container.state", "Complete")
1895 c.Assert(final, NotNil)
1896 c.Check(final["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
1897 c.Check(final["container"].(arvadosclient.Dict)["log"], NotNil)
1899 c.Check(cr.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", "./a b1946ac92492d2347c6235b4d2611184+6 0:6:out.txt\n./b 38af5c54926b620264ab1501150cf189+5 0:5:err.txt\n"), NotNil)
1902 func (s *TestSuite) TestNumberRoundTrip(c *C) {
1903 s.api.callraw = true
1904 err := s.runner.fetchContainerRecord()
1905 c.Assert(err, IsNil)
1906 jsondata, err := json.Marshal(s.runner.Container.Mounts["/json"].Content)
1907 c.Logf("%#v", s.runner.Container)
1909 c.Check(string(jsondata), Equals, `{"number":123456789123456789}`)
1912 func (s *TestSuite) TestFullBrokenDocker(c *C) {
1914 for _, setup := range []func(){
1916 c.Log("// waitErr = ocl runtime error")
1917 s.executor.waitErr = 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\\\"\""`)
1918 nextState = "Cancelled"
1921 c.Log("// loadErr = cannot connect")
1922 s.executor.loadErr = errors.New("Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?")
1923 s.runner.brokenNodeHook = c.MkDir() + "/broken-node-hook"
1924 err := ioutil.WriteFile(s.runner.brokenNodeHook, []byte("#!/bin/sh\nexec echo killme\n"), 0700)
1925 c.Assert(err, IsNil)
1926 nextState = "Queued"
1931 s.fullRunHelper(c, `{
1932 "command": ["echo", "hello world"],
1933 "container_image": "`+arvadostest.DockerImage112PDH+`",
1936 "mounts": {"/tmp": {"kind": "tmp"} },
1937 "output_path": "/tmp",
1939 "runtime_constraints": {},
1941 }`, nil, 0, func() {})
1942 c.Check(s.api.CalledWith("container.state", nextState), NotNil)
1943 c.Check(s.api.Logs["crunch-run"].String(), Matches, "(?ms).*unable to run containers.*")
1944 if s.runner.brokenNodeHook != "" {
1945 c.Check(s.api.Logs["crunch-run"].String(), Matches, "(?ms).*Running broken node hook.*")
1946 c.Check(s.api.Logs["crunch-run"].String(), Matches, "(?ms).*killme.*")
1947 c.Check(s.api.Logs["crunch-run"].String(), Not(Matches), "(?ms).*Writing /var/lock/crunch-run-broken to mark node as broken.*")
1949 c.Check(s.api.Logs["crunch-run"].String(), Matches, "(?ms).*Writing /var/lock/crunch-run-broken to mark node as broken.*")
1954 func (s *TestSuite) TestBadCommand(c *C) {
1955 for _, startError := range []string{
1956 `panic: standard_init_linux.go:175: exec user process caused "no such file or directory"`,
1957 `Error response from daemon: Cannot start container 41f26cbc43bcc1280f4323efb1830a394ba8660c9d1c2b564ba42bf7f7694845: [8] System error: no such file or directory`,
1958 `Error response from daemon: Cannot start container 58099cd76c834f3dc2a4fb76c8028f049ae6d4fdf0ec373e1f2cfea030670c2d: [8] System error: exec: "foobar": executable file not found in $PATH`,
1961 s.executor.startErr = errors.New(startError)
1962 s.fullRunHelper(c, `{
1963 "command": ["echo", "hello world"],
1964 "container_image": "`+arvadostest.DockerImage112PDH+`",
1967 "mounts": {"/tmp": {"kind": "tmp"} },
1968 "output_path": "/tmp",
1970 "runtime_constraints": {},
1972 }`, nil, 0, func() {})
1973 c.Check(s.api.CalledWith("container.state", "Cancelled"), NotNil)
1974 c.Check(s.api.Logs["crunch-run"].String(), Matches, "(?ms).*Possible causes:.*is missing.*")
1978 func (s *TestSuite) TestSecretTextMountPoint(c *C) {
1980 "command": ["true"],
1981 "container_image": "` + arvadostest.DockerImage112PDH + `",
1984 "/tmp": {"kind": "tmp"},
1985 "/tmp/secret.conf": {"kind": "text", "content": "mypassword"}
1989 "output_path": "/tmp",
1991 "runtime_constraints": {},
1995 s.fullRunHelper(c, helperRecord, nil, 0, func() {
1996 content, err := ioutil.ReadFile(s.runner.HostOutputDir + "/secret.conf")
1998 c.Check(string(content), Equals, "mypassword")
2001 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
2002 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
2003 c.Check(s.runner.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", ". 34819d7beeabb9260a5c854bc85b3e44+10 0:10:secret.conf\n"), NotNil)
2004 c.Check(s.runner.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", ""), IsNil)
2006 // under secret mounts, not captured in output
2008 "command": ["true"],
2009 "container_image": "` + arvadostest.DockerImage112PDH + `",
2012 "/tmp": {"kind": "tmp"}
2015 "/tmp/secret.conf": {"kind": "text", "content": "mypassword"}
2017 "output_path": "/tmp",
2019 "runtime_constraints": {},
2024 s.fullRunHelper(c, helperRecord, nil, 0, func() {
2025 content, err := ioutil.ReadFile(s.runner.HostOutputDir + "/secret.conf")
2027 c.Check(string(content), Equals, "mypassword")
2030 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
2031 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
2032 c.Check(s.runner.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", ". 34819d7beeabb9260a5c854bc85b3e44+10 0:10:secret.conf\n"), IsNil)
2033 c.Check(s.runner.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", ""), NotNil)
2035 // under secret mounts, output dir is a collection, not captured in output
2037 "command": ["true"],
2038 "container_image": "` + arvadostest.DockerImage112PDH + `",
2041 "/tmp": {"kind": "collection", "writable": true}
2044 "/tmp/secret.conf": {"kind": "text", "content": "mypassword"}
2046 "output_path": "/tmp",
2048 "runtime_constraints": {},
2053 _, _, realtemp := s.fullRunHelper(c, helperRecord, nil, 0, func() {
2054 // secret.conf should be provisioned as a separate
2055 // bind mount, i.e., it should not appear in the
2056 // (fake) fuse filesystem as viewed from the host.
2057 content, err := ioutil.ReadFile(s.runner.HostOutputDir + "/secret.conf")
2058 if !c.Check(errors.Is(err, os.ErrNotExist), Equals, true) {
2059 c.Logf("secret.conf: content %q, err %#v", content, err)
2061 err = ioutil.WriteFile(s.runner.HostOutputDir+"/.arvados#collection", []byte(`{"manifest_text":". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n"}`), 0700)
2065 content, err := ioutil.ReadFile(realtemp + "/text1/mountdata.text")
2067 c.Check(string(content), Equals, "mypassword")
2068 c.Check(s.executor.created.BindMounts["/tmp/secret.conf"], DeepEquals, bindmount{realtemp + "/text1/mountdata.text", true})
2069 c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
2070 c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
2071 c.Check(s.runner.ContainerArvClient.(*ArvTestClient).CalledWith("collection.manifest_text", ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:foo.txt\n"), NotNil)
2074 type FakeProcess struct {
2078 func (fp FakeProcess) CmdlineSlice() ([]string, error) {
2079 return fp.cmdLine, nil