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