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 }