github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/sandbox/cgroup/cgroup_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  package cgroup_test
    20  
    21  import (
    22  	"errors"
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"testing"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/sandbox/cgroup"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type cgroupSuite struct {
    36  	testutil.BaseTest
    37  	rootDir string
    38  }
    39  
    40  var _ = Suite(&cgroupSuite{})
    41  
    42  func TestCgroup(t *testing.T) { TestingT(t) }
    43  
    44  func (s *cgroupSuite) SetUpTest(c *C) {
    45  	s.BaseTest.SetUpTest(c)
    46  
    47  	s.rootDir = c.MkDir()
    48  	dirs.SetRootDir(s.rootDir)
    49  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    50  }
    51  
    52  func (s *cgroupSuite) TestIsUnified(c *C) {
    53  	restore := cgroup.MockVersion(cgroup.V2, nil)
    54  	defer restore()
    55  	c.Assert(cgroup.IsUnified(), Equals, true)
    56  
    57  	restore = cgroup.MockVersion(cgroup.V1, nil)
    58  	defer restore()
    59  	c.Assert(cgroup.IsUnified(), Equals, false)
    60  
    61  	restore = cgroup.MockVersion(cgroup.Unknown, nil)
    62  	defer restore()
    63  	c.Assert(cgroup.IsUnified(), Equals, false)
    64  }
    65  
    66  func (s *cgroupSuite) TestProbeVersion2(c *C) {
    67  	restore := cgroup.MockFsTypeForPath(func(p string) (int64, error) {
    68  		c.Assert(p, Equals, filepath.Join(s.rootDir, "/sys/fs/cgroup"))
    69  		return int64(cgroup.Cgroup2SuperMagic), nil
    70  	})
    71  	defer restore()
    72  	v, err := cgroup.ProbeCgroupVersion()
    73  	c.Assert(err, IsNil)
    74  	c.Assert(v, Equals, cgroup.V2)
    75  }
    76  
    77  func (s *cgroupSuite) TestProbeVersion1(c *C) {
    78  	const TMPFS_MAGIC = 0x1021994
    79  	restore := cgroup.MockFsTypeForPath(func(p string) (int64, error) {
    80  		c.Assert(p, Equals, filepath.Join(s.rootDir, "/sys/fs/cgroup"))
    81  		return TMPFS_MAGIC, nil
    82  	})
    83  	defer restore()
    84  	v, err := cgroup.ProbeCgroupVersion()
    85  	c.Assert(err, IsNil)
    86  	c.Assert(v, Equals, cgroup.V1)
    87  }
    88  
    89  func (s *cgroupSuite) TestProbeVersionUnhappy(c *C) {
    90  	restore := cgroup.MockFsTypeForPath(func(p string) (int64, error) {
    91  		c.Assert(p, Equals, filepath.Join(s.rootDir, "/sys/fs/cgroup"))
    92  		return 0, errors.New("statfs fail")
    93  	})
    94  	defer restore()
    95  	v, err := cgroup.ProbeCgroupVersion()
    96  	c.Assert(err, ErrorMatches, "cannot determine filesystem type: statfs fail")
    97  	c.Assert(v, Equals, cgroup.Unknown)
    98  }
    99  
   100  func (s *cgroupSuite) TestVersion(c *C) {
   101  	restore := cgroup.MockVersion(cgroup.V2, nil)
   102  	defer restore()
   103  	v, err := cgroup.Version()
   104  	c.Assert(v, Equals, cgroup.V2)
   105  	c.Assert(err, IsNil)
   106  
   107  	restore = cgroup.MockVersion(cgroup.V1, nil)
   108  	defer restore()
   109  	v, err = cgroup.Version()
   110  	c.Assert(v, Equals, cgroup.V1)
   111  	c.Assert(err, IsNil)
   112  
   113  	restore = cgroup.MockVersion(cgroup.Unknown, nil)
   114  	defer restore()
   115  	v, err = cgroup.Version()
   116  	c.Assert(v, Equals, cgroup.Unknown)
   117  	c.Assert(err, IsNil)
   118  
   119  	restore = cgroup.MockVersion(cgroup.Unknown, errors.New("foo"))
   120  	defer restore()
   121  	v, err = cgroup.Version()
   122  	c.Assert(v, Equals, cgroup.Unknown)
   123  	c.Assert(err, ErrorMatches, "foo")
   124  }
   125  
   126  func (s *cgroupSuite) TestProcPidPath(c *C) {
   127  	c.Assert(cgroup.ProcPidPath(1), Equals, filepath.Join(s.rootDir, "/proc/1/cgroup"))
   128  	c.Assert(cgroup.ProcPidPath(1234), Equals, filepath.Join(s.rootDir, "/proc/1234/cgroup"))
   129  }
   130  
   131  var mockCgroup = []byte(`
   132  10:devices:/user.slice
   133  9:cpuset:/
   134  8:net_cls,net_prio:/
   135  7:freezer:/snap.hello-world
   136  6:perf_event:/
   137  5:pids:/user.slice/user-1000.slice/user@1000.service
   138  4:cpu,cpuacct:/
   139  3:memory:/memory/group
   140  2:blkio:/
   141  1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service
   142  0:foo:/illegal/unified/entry
   143  0::/systemd/unified
   144  11:name=snapd:/snap.foo.bar
   145  `)
   146  
   147  func (s *cgroupSuite) TestProgGroupHappy(c *C) {
   148  	err := os.MkdirAll(filepath.Join(s.rootDir, "proc/333"), 0755)
   149  	c.Assert(err, IsNil)
   150  	err = ioutil.WriteFile(filepath.Join(s.rootDir, "proc/333/cgroup"), mockCgroup, 0755)
   151  	c.Assert(err, IsNil)
   152  
   153  	group, err := cgroup.ProcGroup(333, cgroup.MatchV1Controller("freezer"))
   154  	c.Assert(err, IsNil)
   155  	c.Check(group, Equals, "/snap.hello-world")
   156  
   157  	group, err = cgroup.ProcGroup(333, cgroup.MatchV1Controller("memory"))
   158  	c.Assert(err, IsNil)
   159  	c.Check(group, Equals, "/memory/group")
   160  
   161  	group, err = cgroup.ProcGroup(333, cgroup.MatchV1NamedHierarchy("systemd"))
   162  	c.Assert(err, IsNil)
   163  	c.Check(group, Equals, "/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service")
   164  
   165  	group, err = cgroup.ProcGroup(333, cgroup.MatchV1NamedHierarchy("snapd"))
   166  	c.Assert(err, IsNil)
   167  	c.Check(group, Equals, "/snap.foo.bar")
   168  
   169  	group, err = cgroup.ProcGroup(333, cgroup.MatchUnifiedHierarchy())
   170  	c.Assert(err, IsNil)
   171  	c.Check(group, Equals, "/systemd/unified")
   172  }
   173  
   174  func (s *cgroupSuite) TestProgGroupMissingFile(c *C) {
   175  	err := os.MkdirAll(filepath.Join(s.rootDir, "proc/333"), 0755)
   176  	c.Assert(err, IsNil)
   177  
   178  	group, err := cgroup.ProcGroup(333, cgroup.MatchV1Controller("freezer"))
   179  	c.Assert(err, ErrorMatches, "open .*/proc/333/cgroup: no such file or directory")
   180  	c.Check(group, Equals, "")
   181  }
   182  
   183  func (s *cgroupSuite) TestProgGroupMissingGroup(c *C) {
   184  	var noFreezerCgroup = []byte(`
   185  10:devices:/user.slice
   186  `)
   187  
   188  	err := os.MkdirAll(filepath.Join(s.rootDir, "proc/333"), 0755)
   189  	c.Assert(err, IsNil)
   190  	err = ioutil.WriteFile(filepath.Join(s.rootDir, "proc/333/cgroup"), noFreezerCgroup, 0755)
   191  	c.Assert(err, IsNil)
   192  
   193  	group, err := cgroup.ProcGroup(333, cgroup.MatchV1Controller("freezer"))
   194  	c.Assert(err, ErrorMatches, `cannot find controller "freezer" cgroup path for pid 333`)
   195  	c.Check(group, Equals, "")
   196  
   197  	group, err = cgroup.ProcGroup(333, cgroup.MatchUnifiedHierarchy())
   198  	c.Assert(err, ErrorMatches, `cannot find unified hierarchy cgroup path for pid 333`)
   199  	c.Check(group, Equals, "")
   200  
   201  	group, err = cgroup.ProcGroup(333, cgroup.MatchV1NamedHierarchy("snapd"))
   202  	c.Assert(err, ErrorMatches, `cannot find named hierarchy "snapd" cgroup path for pid 333`)
   203  	c.Check(group, Equals, "")
   204  }
   205  
   206  var mockCgroupConfusingCpu = []byte(`
   207  8:cpuacct:/foo.cpuacct
   208  7:cpuset,cpu,cpuacct:/foo.many-cpu
   209  `)
   210  
   211  func (s *cgroupSuite) TestProgGroupConfusingCpu(c *C) {
   212  	err := os.MkdirAll(filepath.Join(s.rootDir, "proc/333"), 0755)
   213  	c.Assert(err, IsNil)
   214  	err = ioutil.WriteFile(filepath.Join(s.rootDir, "proc/333/cgroup"), mockCgroupConfusingCpu, 0755)
   215  	c.Assert(err, IsNil)
   216  
   217  	group, err := cgroup.ProcGroup(333, cgroup.MatchV1Controller("cpu"))
   218  	c.Assert(err, IsNil)
   219  	c.Check(group, Equals, "/foo.many-cpu")
   220  
   221  	group, err = cgroup.ProcGroup(333, cgroup.MatchV1Controller("cpuacct"))
   222  	c.Assert(err, IsNil)
   223  	c.Check(group, Equals, "/foo.cpuacct")
   224  
   225  	group, err = cgroup.ProcGroup(333, cgroup.MatchV1Controller("cpuset"))
   226  	c.Assert(err, IsNil)
   227  	c.Check(group, Equals, "/foo.many-cpu")
   228  }
   229  
   230  func (s *cgroupSuite) TestProgGroupBadSelector(c *C) {
   231  	group, err := cgroup.ProcGroup(333, nil)
   232  	c.Assert(err, ErrorMatches, `internal error: cgroup matcher is nil`)
   233  	c.Check(group, Equals, "")
   234  }
   235  
   236  func (s *cgroupSuite) TestProcessPathInTrackingCgroup(c *C) {
   237  	const noise = `12:cpuset:/
   238  11:rdma:/
   239  10:blkio:/
   240  9:freezer:/
   241  8:cpu,cpuacct:/
   242  7:perf_event:/
   243  6:net_cls,net_prio:/
   244  5:devices:/user.slice
   245  4:hugetlb:/
   246  3:memory:/user.slice/user-1000.slice/user@1000.service
   247  2:pids:/user.slice/user-1000.slice/user@1000.service
   248  `
   249  
   250  	d := c.MkDir()
   251  	defer dirs.SetRootDir(dirs.GlobalRootDir)
   252  	dirs.SetRootDir(d)
   253  
   254  	restore := cgroup.MockVersion(cgroup.V2, nil)
   255  	defer restore()
   256  
   257  	f := filepath.Join(d, "proc", "1234", "cgroup")
   258  	c.Assert(os.MkdirAll(filepath.Dir(f), 0755), IsNil)
   259  
   260  	for _, scenario := range []struct{ cgroups, path, errMsg string }{
   261  		{cgroups: "", path: "", errMsg: "cannot find tracking cgroup"},
   262  		{cgroups: noise + "", path: "", errMsg: "cannot find tracking cgroup"},
   263  		{cgroups: noise + "0::/foo", path: "/foo"},
   264  		{cgroups: noise + "1:name=systemd:/bar", path: "/bar"},
   265  		// First match wins (normally they are in sync).
   266  		{cgroups: noise + "1:name=systemd:/bar\n0::/foo", path: "/bar"},
   267  		{cgroups: "0::/tricky:path", path: "/tricky:path"},
   268  		{cgroups: "1:ctrl" /* no path */, errMsg: `cannot parse proc cgroup entry ".*": expected three fields`},
   269  		{cgroups: "potato:foo:/bar" /* bad ID number */, errMsg: `cannot parse proc cgroup entry ".*": cannot parse cgroup id "potato"`},
   270  	} {
   271  		c.Assert(ioutil.WriteFile(f, []byte(scenario.cgroups), 0644), IsNil)
   272  		path, err := cgroup.ProcessPathInTrackingCgroup(1234)
   273  		if scenario.errMsg != "" {
   274  			c.Assert(err, ErrorMatches, scenario.errMsg)
   275  		} else {
   276  			c.Assert(path, Equals, scenario.path)
   277  		}
   278  	}
   279  }
   280  
   281  func (s *cgroupSuite) TestProcessPathInTrackingCgroupV2SpecialCase(c *C) {
   282  	const text = `0::/
   283  1:name=systemd:/user.slice/user-0.slice/session-1.scope
   284  `
   285  	d := c.MkDir()
   286  	defer dirs.SetRootDir(dirs.GlobalRootDir)
   287  	dirs.SetRootDir(d)
   288  
   289  	restore := cgroup.MockVersion(cgroup.V1, nil)
   290  	defer restore()
   291  
   292  	f := filepath.Join(d, "proc", "1234", "cgroup")
   293  	c.Assert(os.MkdirAll(filepath.Dir(f), 0755), IsNil)
   294  
   295  	c.Assert(ioutil.WriteFile(f, []byte(text), 0644), IsNil)
   296  	path, err := cgroup.ProcessPathInTrackingCgroup(1234)
   297  	c.Assert(err, IsNil)
   298  	// Because v2 is not really mounted, we ignore the entry 0::/
   299  	// and return the v1 version instead.
   300  	c.Assert(path, Equals, "/user.slice/user-0.slice/session-1.scope")
   301  }