17244: Test findCgroup on cgroup files in crunchstat testdata.
authorTom Clegg <tom@curii.com>
Fri, 21 Jul 2023 13:53:34 +0000 (09:53 -0400)
committerTom Clegg <tom@curii.com>
Fri, 21 Jul 2023 13:53:34 +0000 (09:53 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curii.com>

lib/crunchrun/cgroup.go
lib/crunchrun/cgroup_test.go
lib/crunchrun/crunchrun.go

index 34dc8e40f092981157555dc2c525cdabc3265105..a722e5f1423b715ebc8f65e9cc1afb1cdb847f2e 100644 (file)
@@ -7,16 +7,16 @@ package crunchrun
 import (
        "bytes"
        "fmt"
-       "io/ioutil"
+       "io/fs"
 )
 
 // Return the current process's cgroup for the given subsystem.
 //
 // If the host has cgroups v2 and not v1 (i.e., unified mode), return
 // the current process's cgroup.
-func findCgroup(subsystem string) (string, error) {
+func findCgroup(fsys fs.FS, subsystem string) (string, error) {
        subsys := []byte(subsystem)
-       cgroups, err := ioutil.ReadFile("/proc/self/cgroup")
+       cgroups, err := fs.ReadFile(fsys, "proc/self/cgroup")
        if err != nil {
                return "", err
        }
@@ -27,9 +27,18 @@ func findCgroup(subsystem string) (string, error) {
                }
                if len(toks[1]) == 0 && string(toks[0]) == "0" {
                        // cgroups v2: "0::$PATH"
+                       //
+                       // In "hybrid" mode, this entry is last, so we
+                       // use it when the specified subsystem doesn't
+                       // match a cgroups v1 entry.
+                       //
+                       // In "unified" mode, this is the only entry,
+                       // so we use it regardless of which subsystem
+                       // was specified.
                        return string(toks[2]), nil
                }
                for _, s := range bytes.Split(toks[1], []byte(",")) {
+                       // cgroups v1: "7:cpu,cpuacct:/user.slice"
                        if bytes.Compare(s, subsys) == 0 {
                                return string(toks[2]), nil
                        }
index eb87456d14b0d1e0e60245460009d75cfe4a01b2..a1acb6fb922910bc7a386c396ff3b4719d0b9eac 100644 (file)
@@ -5,6 +5,11 @@
 package crunchrun
 
 import (
+       "bytes"
+       "os"
+       "os/exec"
+       "strings"
+
        . "gopkg.in/check.v1"
 )
 
@@ -13,11 +18,57 @@ type CgroupSuite struct{}
 var _ = Suite(&CgroupSuite{})
 
 func (s *CgroupSuite) TestFindCgroup(c *C) {
-       for _, s := range []string{"devices", "cpu", "cpuset"} {
-               g, err := findCgroup(s)
-               if c.Check(err, IsNil) {
-                       c.Check(g, Not(Equals), "", Commentf("subsys %q", s))
+       var testfiles []string
+       buf, err := exec.Command("find", "../crunchstat/testdata", "-name", "cgroup", "-type", "f").Output()
+       c.Assert(err, IsNil)
+       for _, testfile := range bytes.Split(buf, []byte{'\n'}) {
+               if len(testfile) > 0 {
+                       testfiles = append(testfiles, string(testfile))
+               }
+       }
+       testfiles = append(testfiles, "/proc/self/cgroup")
+
+       tmpdir := c.MkDir()
+       err = os.MkdirAll(tmpdir+"/proc/self", 0777)
+       c.Assert(err, IsNil)
+       fsys := os.DirFS(tmpdir)
+
+       for _, trial := range []struct {
+               match  string // if non-empty, only check testfiles containing this string
+               subsys string
+               expect string // empty means "any" (we never actually expect empty string)
+       }{
+               {"debian11", "blkio", "/user.slice/user-1000.slice/session-5424.scope"},
+               {"debian12", "cpuacct", "/user.slice/user-1000.slice/session-4.scope"},
+               {"debian12", "bogus-does-not-matter", "/user.slice/user-1000.slice/session-4.scope"},
+               {"ubuntu1804", "blkio", "/user.slice"},
+               {"ubuntu1804", "cpuacct", "/user.slice"},
+               {"", "cpu", ""},
+               {"", "cpuset", ""},
+               {"", "devices", ""},
+               {"", "bogus-does-not-matter", ""},
+       } {
+               for _, testfile := range testfiles {
+                       if !strings.Contains(testfile, trial.match) {
+                               continue
+                       }
+                       c.Logf("trial %+v testfile %s", trial, testfile)
+
+                       // Copy cgroup file into our fake proc/self/ dir
+                       buf, err := os.ReadFile(testfile)
+                       c.Assert(err, IsNil)
+                       err = os.WriteFile(tmpdir+"/proc/self/cgroup", buf, 0777)
+                       c.Assert(err, IsNil)
+
+                       cgroup, err := findCgroup(fsys, trial.subsys)
+                       if !c.Check(err, IsNil) {
+                               continue
+                       }
+                       c.Logf("\tcgroup = %q", cgroup)
+                       c.Check(cgroup, Not(Equals), "")
+                       if trial.expect != "" {
+                               c.Check(cgroup, Equals, trial.expect)
+                       }
                }
-               c.Logf("cgroup(%q) == %q", s, g)
        }
 }
index de79a29efda027c329551987b85ce87b0dd4b095..1c0cf621dbf730e409193efc7cbe44a0e90daa8e 100644 (file)
@@ -2131,7 +2131,7 @@ func (command) RunCommand(prog string, args []string, stdin io.Reader, stdout, s
        cr.enableNetwork = *enableNetwork
        cr.networkMode = *networkMode
        if *cgroupParentSubsystem != "" {
-               p, err := findCgroup(*cgroupParentSubsystem)
+               p, err := findCgroup(os.DirFS("/"), *cgroupParentSubsystem)
                if err != nil {
                        log.Printf("fatal: cgroup parent subsystem: %s", err)
                        return 1