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 }