17417: Merge branch 'main' into 17417-add-arm64
[arvados.git] / lib / crunchrun / crunchrun_test.go
index 5f7e71d95793e304c49dbd92ba5ac5fb9534ad93..67b18857b0ea3cf5321e529a8984ed5e89017628 100644 (file)
@@ -39,11 +39,13 @@ func TestCrunchExec(t *testing.T) {
 var _ = Suite(&TestSuite{})
 
 type TestSuite struct {
-       client    *arvados.Client
-       api       *ArvTestClient
-       runner    *ContainerRunner
-       executor  *stubExecutor
-       keepmount string
+       client                   *arvados.Client
+       api                      *ArvTestClient
+       runner                   *ContainerRunner
+       executor                 *stubExecutor
+       keepmount                string
+       testDispatcherKeepClient KeepTestClient
+       testContainerKeepClient  KeepTestClient
 }
 
 func (s *TestSuite) SetUpTest(c *C) {
@@ -52,11 +54,11 @@ func (s *TestSuite) SetUpTest(c *C) {
        s.executor = &stubExecutor{}
        var err error
        s.api = &ArvTestClient{}
-       s.runner, err = NewContainerRunner(s.client, s.api, &KeepTestClient{}, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
+       s.runner, err = NewContainerRunner(s.client, s.api, &s.testDispatcherKeepClient, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
        c.Assert(err, IsNil)
        s.runner.executor = s.executor
        s.runner.MkArvClient = func(token string) (IArvadosClient, IKeepClient, *arvados.Client, error) {
-               return s.api, &KeepTestClient{}, s.client, nil
+               return s.api, &s.testContainerKeepClient, s.client, nil
        }
        s.runner.RunArvMount = func(cmd []string, tok string) (*exec.Cmd, error) {
                s.runner.ArvMountPoint = s.keepmount
@@ -88,8 +90,9 @@ type ArvTestClient struct {
 }
 
 type KeepTestClient struct {
-       Called  bool
-       Content []byte
+       Called         bool
+       Content        []byte
+       StorageClasses []string
 }
 
 type stubExecutor struct {
@@ -109,16 +112,18 @@ type stubExecutor struct {
        exit        chan int
 }
 
-func (e *stubExecutor) ImageLoaded(imageID string) bool { return e.imageLoaded }
-func (e *stubExecutor) LoadImage(filename string) error { e.loaded = filename; return e.loadErr }
+func (e *stubExecutor) LoadImage(imageId string, tarball string, container arvados.Container, keepMount string,
+       containerClient *arvados.Client) error {
+       e.loaded = tarball
+       return e.loadErr
+}
+func (e *stubExecutor) Runtime() string                 { return "stub" }
 func (e *stubExecutor) Create(spec containerSpec) error { e.created = spec; return e.createErr }
 func (e *stubExecutor) Start() error                    { e.exit = make(chan int, 1); go e.runFunc(); return e.startErr }
 func (e *stubExecutor) CgroupID() string                { return "cgroupid" }
 func (e *stubExecutor) Stop() error                     { e.stopped = true; go func() { e.exit <- -1 }(); return e.stopErr }
 func (e *stubExecutor) Close()                          { e.closed = true }
 func (e *stubExecutor) Wait(context.Context) (int, error) {
-       defer e.created.Stdout.Close()
-       defer e.created.Stderr.Close()
        return <-e.exit, e.waitErr
 }
 
@@ -304,9 +309,11 @@ func (client *KeepTestClient) LocalLocator(locator string) (string, error) {
        return locator, nil
 }
 
-func (client *KeepTestClient) PutB(buf []byte) (string, int, error) {
-       client.Content = buf
-       return fmt.Sprintf("%x+%d", md5.Sum(buf), len(buf)), len(buf), nil
+func (client *KeepTestClient) BlockWrite(_ context.Context, opts arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
+       client.Content = opts.Data
+       return arvados.BlockWriteResponse{
+               Locator: fmt.Sprintf("%x+%d", md5.Sum(opts.Data), len(opts.Data)),
+       }, nil
 }
 
 func (client *KeepTestClient) ReadAt(string, []byte, int) (int, error) {
@@ -320,6 +327,10 @@ func (client *KeepTestClient) Close() {
        client.Content = nil
 }
 
+func (client *KeepTestClient) SetStorageClasses(sc []string) {
+       client.StorageClasses = sc
+}
+
 type FileWrapper struct {
        io.ReadCloser
        len int64
@@ -396,16 +407,6 @@ func (s *TestSuite) TestLoadImage(c *C) {
        imageID, err = s.runner.LoadImage()
        c.Check(err, ErrorMatches, "image collection does not include a \\.tar image file")
        c.Check(s.executor.loaded, Equals, "")
-
-       // if executor reports image is already loaded, LoadImage should not be called
-       s.runner.Container.ContainerImage = arvadostest.DockerImage112PDH
-       s.executor.imageLoaded = true
-       s.executor.loaded = ""
-       s.executor.loadErr = nil
-       imageID, err = s.runner.LoadImage()
-       c.Check(err, IsNil)
-       c.Check(s.executor.loaded, Equals, "")
-       c.Check(imageID, Equals, strings.TrimSuffix(arvadostest.DockerImage112Filename, ".tar"))
 }
 
 type ArvErrorTestClient struct{}
@@ -448,8 +449,8 @@ func (*KeepErrorTestClient) ManifestFileReader(manifest.Manifest, string) (arvad
        return nil, errors.New("KeepError")
 }
 
-func (*KeepErrorTestClient) PutB(buf []byte) (string, int, error) {
-       return "", 0, errors.New("KeepError")
+func (*KeepErrorTestClient) BlockWrite(context.Context, arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
+       return arvados.BlockWriteResponse{}, errors.New("KeepError")
 }
 
 func (*KeepErrorTestClient) LocalLocator(string) (string, error) {
@@ -515,8 +516,6 @@ func dockerLog(fd byte, msg string) []byte {
 func (s *TestSuite) TestRunContainer(c *C) {
        s.executor.runFunc = func() {
                fmt.Fprintf(s.executor.created.Stdout, "Hello world\n")
-               s.executor.created.Stdout.Close()
-               s.executor.created.Stderr.Close()
                s.executor.exit <- 0
        }
 
@@ -524,6 +523,7 @@ func (s *TestSuite) TestRunContainer(c *C) {
        s.runner.NewLogWriter = logs.NewTestLoggingWriter
        s.runner.Container.ContainerImage = arvadostest.DockerImage112PDH
        s.runner.Container.Command = []string{"./hw"}
+       s.runner.Container.OutputStorageClasses = []string{"default"}
 
        imageID, err := s.runner.LoadImage()
        c.Assert(err, IsNil)
@@ -654,7 +654,7 @@ func (s *TestSuite) fullRunHelper(c *C, record string, extraMounts []string, exi
                return d, err
        }
        s.runner.MkArvClient = func(token string) (IArvadosClient, IKeepClient, *arvados.Client, error) {
-               return &ArvTestClient{secretMounts: secretMounts}, &KeepTestClient{}, nil, nil
+               return &ArvTestClient{secretMounts: secretMounts}, &s.testContainerKeepClient, nil, nil
        }
 
        if extraMounts != nil && len(extraMounts) > 0 {
@@ -705,7 +705,8 @@ func (s *TestSuite) TestFullRunHello(c *C) {
     "output_path": "/tmp",
     "priority": 1,
     "runtime_constraints": {"vcpus":1,"ram":1000000},
-    "state": "Locked"
+    "state": "Locked",
+    "output_storage_classes": ["default"]
 }`, nil, 0, func() {
                c.Check(s.executor.created.Command, DeepEquals, []string{"echo", "hello world"})
                c.Check(s.executor.created.Image, Equals, "sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678")
@@ -714,13 +715,15 @@ func (s *TestSuite) TestFullRunHello(c *C) {
                c.Check(s.executor.created.RAM, Equals, int64(1000000))
                c.Check(s.executor.created.NetworkMode, Equals, "default")
                c.Check(s.executor.created.EnableNetwork, Equals, false)
+               c.Check(s.executor.created.CUDADeviceCount, Equals, 0)
                fmt.Fprintln(s.executor.created.Stdout, "hello world")
        })
 
        c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
        c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
        c.Check(s.api.Logs["stdout"].String(), Matches, ".*hello world\n")
-
+       c.Check(s.testDispatcherKeepClient.StorageClasses, DeepEquals, []string{"default"})
+       c.Check(s.testContainerKeepClient.StorageClasses, DeepEquals, []string{"default"})
 }
 
 func (s *TestSuite) TestRunAlreadyRunning(c *C) {
@@ -848,6 +851,26 @@ func (s *TestSuite) TestNodeInfoLog(c *C) {
        c.Check(json, Matches, `(?ms).*Disk INodes.*`)
 }
 
+func (s *TestSuite) TestLogVersionAndRuntime(c *C) {
+       s.fullRunHelper(c, `{
+               "command": ["sleep", "1"],
+               "container_image": "`+arvadostest.DockerImage112PDH+`",
+               "cwd": ".",
+               "environment": {},
+               "mounts": {"/tmp": {"kind": "tmp"} },
+               "output_path": "/tmp",
+               "priority": 1,
+               "runtime_constraints": {},
+               "state": "Locked"
+       }`, nil, 0,
+               func() {
+               })
+
+       c.Assert(s.api.Logs["crunch-run"], NotNil)
+       c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*crunch-run \S+ \(go\S+\) start.*`)
+       c.Check(s.api.Logs["crunch-run"].String(), Matches, `(?ms).*Executing container 'zzzzz-zzzzz-zzzzzzzzzzzzzzz' using stub runtime.*`)
+}
+
 func (s *TestSuite) TestContainerRecordLog(c *C) {
        s.fullRunHelper(c, `{
                "command": ["sleep", "1"],
@@ -937,6 +960,65 @@ func (s *TestSuite) TestFullRunSetCwd(c *C) {
        c.Check(s.api.Logs["stdout"].String(), Matches, ".*/bin\n")
 }
 
+func (s *TestSuite) TestFullRunSetOutputStorageClasses(c *C) {
+       s.fullRunHelper(c, `{
+    "command": ["pwd"],
+    "container_image": "`+arvadostest.DockerImage112PDH+`",
+    "cwd": "/bin",
+    "environment": {},
+    "mounts": {"/tmp": {"kind": "tmp"} },
+    "output_path": "/tmp",
+    "priority": 1,
+    "runtime_constraints": {},
+    "state": "Locked",
+    "output_storage_classes": ["foo", "bar"]
+}`, nil, 0, func() {
+               fmt.Fprintln(s.executor.created.Stdout, s.executor.created.WorkingDir)
+       })
+
+       c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
+       c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
+       c.Check(s.api.Logs["stdout"].String(), Matches, ".*/bin\n")
+       c.Check(s.testDispatcherKeepClient.StorageClasses, DeepEquals, []string{"foo", "bar"})
+       c.Check(s.testContainerKeepClient.StorageClasses, DeepEquals, []string{"foo", "bar"})
+}
+
+func (s *TestSuite) TestEnableCUDADeviceCount(c *C) {
+       s.fullRunHelper(c, `{
+    "command": ["pwd"],
+    "container_image": "`+arvadostest.DockerImage112PDH+`",
+    "cwd": "/bin",
+    "environment": {},
+    "mounts": {"/tmp": {"kind": "tmp"} },
+    "output_path": "/tmp",
+    "priority": 1,
+    "runtime_constraints": {"cuda_device_count": 2},
+    "state": "Locked",
+    "output_storage_classes": ["foo", "bar"]
+}`, nil, 0, func() {
+               fmt.Fprintln(s.executor.created.Stdout, "ok")
+       })
+       c.Check(s.executor.created.CUDADeviceCount, Equals, 2)
+}
+
+func (s *TestSuite) TestEnableCUDAHardwareCapability(c *C) {
+       s.fullRunHelper(c, `{
+    "command": ["pwd"],
+    "container_image": "`+arvadostest.DockerImage112PDH+`",
+    "cwd": "/bin",
+    "environment": {},
+    "mounts": {"/tmp": {"kind": "tmp"} },
+    "output_path": "/tmp",
+    "priority": 1,
+    "runtime_constraints": {"cuda_hardware_capability": "foo"},
+    "state": "Locked",
+    "output_storage_classes": ["foo", "bar"]
+}`, nil, 0, func() {
+               fmt.Fprintln(s.executor.created.Stdout, "ok")
+       })
+       c.Check(s.executor.created.CUDADeviceCount, Equals, 1)
+}
+
 func (s *TestSuite) TestStopOnSignal(c *C) {
        s.executor.runFunc = func() {
                s.executor.created.Stdout.Write([]byte("foo\n"))
@@ -1042,6 +1124,7 @@ func (s *TestSuite) TestSetupMounts(c *C) {
        cr.RunArvMount = am.ArvMountTest
        cr.ContainerArvClient = &ArvTestClient{}
        cr.ContainerKeepClient = &KeepTestClient{}
+       cr.Container.OutputStorageClasses = []string{"default"}
 
        realTemp := c.MkDir()
        certTemp := c.MkDir()
@@ -1078,9 +1161,9 @@ func (s *TestSuite) TestSetupMounts(c *C) {
                cr.statInterval = 5 * time.Second
                bindmounts, err := cr.SetupMounts()
                c.Check(err, IsNil)
-               c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
-                       "--read-write", "--crunchstat-interval=5",
-                       "--mount-by-pdh", "by_id", realTemp + "/keep1"})
+               c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
+                       "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
+                       "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
                c.Check(bindmounts, DeepEquals, map[string]bindmount{"/tmp": {realTemp + "/tmp2", false}})
                os.RemoveAll(cr.ArvMountPoint)
                cr.CleanupDirs()
@@ -1094,12 +1177,13 @@ func (s *TestSuite) TestSetupMounts(c *C) {
                cr.Container.Mounts["/out"] = arvados.Mount{Kind: "tmp"}
                cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
                cr.Container.OutputPath = "/out"
+               cr.Container.OutputStorageClasses = []string{"foo", "bar"}
 
                bindmounts, err := cr.SetupMounts()
                c.Check(err, IsNil)
-               c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
-                       "--read-write", "--crunchstat-interval=5",
-                       "--mount-by-pdh", "by_id", realTemp + "/keep1"})
+               c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
+                       "--read-write", "--storage-classes", "foo,bar", "--crunchstat-interval=5",
+                       "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
                c.Check(bindmounts, DeepEquals, map[string]bindmount{"/out": {realTemp + "/tmp2", false}, "/tmp": {realTemp + "/tmp3", false}})
                os.RemoveAll(cr.ArvMountPoint)
                cr.CleanupDirs()
@@ -1113,12 +1197,13 @@ func (s *TestSuite) TestSetupMounts(c *C) {
                cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
                cr.Container.OutputPath = "/tmp"
                cr.Container.RuntimeConstraints.API = true
+               cr.Container.OutputStorageClasses = []string{"default"}
 
                bindmounts, err := cr.SetupMounts()
                c.Check(err, IsNil)
-               c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
-                       "--read-write", "--crunchstat-interval=5",
-                       "--mount-by-pdh", "by_id", realTemp + "/keep1"})
+               c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
+                       "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
+                       "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
                c.Check(bindmounts, DeepEquals, map[string]bindmount{"/tmp": {realTemp + "/tmp2", false}, "/etc/arvados/ca-certificates.crt": {stubCertPath, true}})
                os.RemoveAll(cr.ArvMountPoint)
                cr.CleanupDirs()
@@ -1139,9 +1224,9 @@ func (s *TestSuite) TestSetupMounts(c *C) {
 
                bindmounts, err := cr.SetupMounts()
                c.Check(err, IsNil)
-               c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
-                       "--read-write", "--crunchstat-interval=5",
-                       "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
+               c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
+                       "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
+                       "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
                c.Check(bindmounts, DeepEquals, map[string]bindmount{"/keeptmp": {realTemp + "/keep1/tmp0", false}})
                os.RemoveAll(cr.ArvMountPoint)
                cr.CleanupDirs()
@@ -1162,9 +1247,9 @@ func (s *TestSuite) TestSetupMounts(c *C) {
 
                bindmounts, err := cr.SetupMounts()
                c.Check(err, IsNil)
-               c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
-                       "--read-write", "--crunchstat-interval=5",
-                       "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
+               c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
+                       "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
+                       "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
                c.Check(bindmounts, DeepEquals, map[string]bindmount{
                        "/keepinp": {realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", true},
                        "/keepout": {realTemp + "/keep1/tmp0", false},
@@ -1189,9 +1274,9 @@ func (s *TestSuite) TestSetupMounts(c *C) {
 
                bindmounts, err := cr.SetupMounts()
                c.Check(err, IsNil)
-               c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
-                       "--read-write", "--crunchstat-interval=5",
-                       "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
+               c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
+                       "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
+                       "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
                c.Check(bindmounts, DeepEquals, map[string]bindmount{
                        "/keepinp": {realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", true},
                        "/keepout": {realTemp + "/keep1/tmp0", false},
@@ -1272,9 +1357,9 @@ func (s *TestSuite) TestSetupMounts(c *C) {
 
                bindmounts, err := cr.SetupMounts()
                c.Check(err, IsNil)
-               c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other",
-                       "--read-write", "--crunchstat-interval=5",
-                       "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
+               c.Check(am.Cmd, DeepEquals, []string{"arv-mount", "--foreground",
+                       "--read-write", "--storage-classes", "default", "--crunchstat-interval=5",
+                       "--file-cache", "512", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", "--disable-event-listening", "--mount-by-id", "by_uuid", realTemp + "/keep1"})
                c.Check(bindmounts, DeepEquals, map[string]bindmount{
                        "/tmp":     {realTemp + "/tmp2", false},
                        "/tmp/foo": {realTemp + "/keep1/tmp0", true},
@@ -1506,6 +1591,37 @@ func (s *TestSuite) TestFullRunSetOutput(c *C) {
        c.Check(s.api.CalledWith("container.output", arvadostest.DockerImage112PDH), NotNil)
 }
 
+func (s *TestSuite) TestArvMountRuntimeStatusWarning(c *C) {
+       s.runner.RunArvMount = func([]string, string) (*exec.Cmd, error) {
+               os.Mkdir(s.runner.ArvMountPoint+"/by_id", 0666)
+               ioutil.WriteFile(s.runner.ArvMountPoint+"/by_id/README", nil, 0666)
+               return s.runner.ArvMountCmd([]string{"bash", "-c", "echo >&2 Test: Keep write error: I am a teapot; sleep 3"}, "")
+       }
+       s.executor.runFunc = func() {
+               time.Sleep(time.Second)
+               s.executor.exit <- 0
+       }
+       record := `{
+    "command": ["sleep", "1"],
+    "container_image": "` + arvadostest.DockerImage112PDH + `",
+    "cwd": "/bin",
+    "environment": {},
+    "mounts": {"/tmp": {"kind": "tmp"} },
+    "output_path": "/tmp",
+    "priority": 1,
+    "runtime_constraints": {"API": true},
+    "state": "Locked"
+}`
+       err := json.Unmarshal([]byte(record), &s.api.Container)
+       c.Assert(err, IsNil)
+       err = s.runner.Run()
+       c.Assert(err, IsNil)
+       c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
+       c.Check(s.api.CalledWith("container.runtime_status.warning", "arv-mount: Keep write error"), NotNil)
+       c.Check(s.api.CalledWith("container.runtime_status.warningDetail", "Test: Keep write error: I am a teapot"), NotNil)
+       c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
+}
+
 func (s *TestSuite) TestStdoutWithExcludeFromOutputMountPointUnderOutputDir(c *C) {
        helperRecord := `{
                "command": ["/bin/sh", "-c", "echo $FROBIZ"],