github.com/dmaizel/tests@v0.0.0-20210728163746-cae6a2d9cee8/integration/docker/cgroups_test.go (about) 1 // Copyright (c) 2019 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package docker 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "runtime" 14 "strings" 15 16 "github.com/kata-containers/tests" 17 . "github.com/onsi/ginkgo" 18 . "github.com/onsi/ginkgo/extensions/table" 19 . "github.com/onsi/gomega" 20 ) 21 22 type cgroupType string 23 24 const ( 25 cgroupCPU cgroupType = "cpu" 26 cgroupCpuset cgroupType = "cpuset" 27 ) 28 29 const ( 30 sysCgroupPath = "/sys/fs/cgroup/" 31 dockerCgroupName = "docker" 32 cgroupPathPrefix = "kata" 33 sysCPUSharesFile = "cpu.shares" 34 sysCPUQuotaFile = "cpu.cfs_quota_us" 35 sysCPUPeriodFile = "cpu.cfs_period_us" 36 sysCpusetCpusFile = "cpuset.cpus" 37 ) 38 39 type expectedCPUValues struct { 40 shares string 41 quota string 42 period string 43 cpuset string 44 } 45 46 func containerID(name string) (string, error) { 47 stdout, stderr, exitCode := dockerInspect("--format", "{{.Id}}", name) 48 if exitCode != 0 { 49 return "", fmt.Errorf("Could not get container ID: %v", stderr) 50 } 51 return strings.Trim(stdout, "\n\t "), nil 52 } 53 54 func containerCgroupParent(name string) (string, error) { 55 stdout, stderr, exitCode := dockerInspect("--format", "{{.HostConfig.CgroupParent}}", name) 56 if exitCode != 0 { 57 return "", fmt.Errorf("Could not get container cgroup parent: %v", stderr) 58 } 59 return strings.Trim(stdout, "\n\t "), nil 60 } 61 62 func isPodCgroupOnlyEnabled() (bool, error) { 63 type RuntimeEnv struct { 64 SandboxCgroupOnly bool 65 } 66 type KataEnvSandboxCgroupOnly struct { 67 Runtime RuntimeEnv 68 } 69 strCmd := "kata-env --json" 70 cmd := tests.NewCommand(tests.Runtime, strings.Fields(strCmd)...) 71 72 stdout, stderr, exitCode := cmd.Run() 73 if exitCode != 0 { 74 return false, fmt.Errorf("Failed to run '%s %s' exit code: %d output '%s'", 75 tests.Runtime, 76 strCmd, 77 exitCode, 78 stdout+stderr) 79 } 80 kenv := KataEnvSandboxCgroupOnly{} 81 if err := json.Unmarshal([]byte(stdout), &kenv); err != nil { 82 return false, err 83 84 } 85 86 return kenv.Runtime.SandboxCgroupOnly, nil 87 } 88 89 func containerCgroupPath(name string, t cgroupType, SandboxCgroupOnly bool) (string, error) { 90 parentCgroup := dockerCgroupName 91 92 if path, err := containerCgroupParent(name); err != nil && path != "" { 93 parentCgroup = path 94 } 95 96 if id, err := containerID(name); err == nil && id != "" { 97 98 cgroupPath := fmt.Sprintf("%s_%s", cgroupPathPrefix, id) 99 return filepath.Join(sysCgroupPath, string(t), parentCgroup, cgroupPath), nil 100 } 101 102 return "", fmt.Errorf("Could not get container cgroup path") 103 } 104 105 func addProcessToCgroup(pid int, cgroupPath string) error { 106 return ioutil.WriteFile(filepath.Join(cgroupPath, "cgroup.procs"), 107 []byte(fmt.Sprintf("%v", pid)), os.FileMode(0775)) 108 } 109 110 func checkCPUCgroups(name string, expected expectedCPUValues, SandboxCgroupOnly bool) error { 111 cpuCgroupPath, err := containerCgroupPath(name, cgroupCPU, SandboxCgroupOnly) 112 if err != nil { 113 return err 114 } 115 116 cpusetCgroupPath, err := containerCgroupPath(name, cgroupCpuset, SandboxCgroupOnly) 117 if err != nil { 118 return err 119 } 120 121 for r, v := range map[string]string{ 122 filepath.Join(cpuCgroupPath, sysCPUQuotaFile): expected.quota, 123 filepath.Join(cpuCgroupPath, sysCPUPeriodFile): expected.period, 124 filepath.Join(cpuCgroupPath, sysCPUSharesFile): expected.shares, 125 filepath.Join(cpusetCgroupPath, sysCpusetCpusFile): expected.cpuset, 126 } { 127 c, err := ioutil.ReadFile(r) 128 if err != nil { 129 return err 130 } 131 132 if SandboxCgroupOnly { 133 // Just return and not skip we still want to check the cgroup exist 134 fmt.Fprintf(GinkgoWriter, "PodCgroupOnly enabled, cgroup is managed by caller, will not check values") 135 continue 136 } 137 138 cv := strings.Trim(string(c), "\n\t ") 139 if cv != v { 140 return fmt.Errorf("Cgroup %v, expected: %v, got: %v", r, v, cv) 141 } 142 } 143 144 return nil 145 } 146 147 var _ = Describe("Checking CPU cgroups in the host", func() { 148 var ( 149 args []string 150 id string 151 cpuCgroupPath string 152 cpusetCgroupPath string 153 err error 154 exitCode int 155 expected expectedCPUValues 156 SandboxCgroupOnly bool 157 ) 158 159 BeforeEach(func() { 160 SandboxCgroupOnly, err = isPodCgroupOnlyEnabled() 161 if err != nil { 162 Expect(err).ToNot(HaveOccurred()) 163 } 164 165 id = randomDockerName() 166 args = []string{"--cpus=1", "--cpu-shares=800", "--cpuset-cpus=0", "-dt", "--name", id, Image, "sh"} 167 }) 168 169 AfterEach(func() { 170 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 171 }) 172 173 Describe("checking whether cgroups can be deleted", func() { 174 Context("with a running process", func() { 175 It("should be deleted", func() { 176 if os.Getuid() != 0 { 177 Skip("only root user can modify cgroups") 178 } 179 180 _, _, exitCode = dockerRun(args...) 181 Expect(exitCode).To(BeZero()) 182 183 // check that cpu cgroups exist 184 cpuCgroupPath, err = containerCgroupPath(id, cgroupCPU, SandboxCgroupOnly) 185 Expect(err).ToNot(HaveOccurred()) 186 Expect(cpuCgroupPath).Should(BeADirectory()) 187 188 cpusetCgroupPath, err = containerCgroupPath(id, cgroupCpuset, SandboxCgroupOnly) 189 Expect(err).ToNot(HaveOccurred()) 190 Expect(cpusetCgroupPath).Should(BeADirectory()) 191 192 // Add current process to cgroups 193 err = addProcessToCgroup(os.Getpid(), cpuCgroupPath) 194 Expect(err).ToNot(HaveOccurred()) 195 196 err = addProcessToCgroup(os.Getpid(), cpusetCgroupPath) 197 Expect(err).ToNot(HaveOccurred()) 198 199 // remove container 200 Expect(RemoveDockerContainer(id)).To(BeTrue()) 201 202 // cgroups shouldn't exist 203 Expect(cpuCgroupPath).ShouldNot(BeADirectory()) 204 Expect(cpusetCgroupPath).ShouldNot(BeADirectory()) 205 }) 206 }) 207 }) 208 209 Describe("checking whether cgroups are updated", func() { 210 Context("updating container cpu and cpuset cgroup", func() { 211 It("should be updated", func() { 212 if SandboxCgroupOnly { 213 Skip("PodCgroupOnly enabled, host cgroup should be managed by caller") 214 } 215 _, _, exitCode = dockerRun(args...) 216 Expect(exitCode).To(BeZero()) 217 218 expected.shares = "738" 219 expected.quota = "250000" 220 expected.period = "100000" 221 expected.cpuset = "1" 222 223 if runtime.GOARCH == "ppc64le" { 224 expected.cpuset = "8" 225 } 226 _, _, exitCode = dockerUpdate("--cpus=2.5", "--cpu-shares", expected.shares, "--cpuset-cpus", expected.cpuset, id) 227 Expect(exitCode).To(BeZero()) 228 229 err = checkCPUCgroups(id, expected, SandboxCgroupOnly) 230 Expect(err).ToNot(HaveOccurred()) 231 232 Expect(RemoveDockerContainer(id)).To(BeTrue()) 233 }) 234 }) 235 }) 236 237 Describe("checking hosts's cpu cgroups", func() { 238 Context("container with cpu and cpuset constraints", func() { 239 It("shold have its cgroup set correctly", func() { 240 _, _, exitCode = dockerRun(args...) 241 Expect(exitCode).To(BeZero()) 242 243 expected.shares = "800" 244 expected.quota = "100000" 245 expected.period = "100000" 246 expected.cpuset = "0" 247 248 err = checkCPUCgroups(id, expected, SandboxCgroupOnly) 249 Expect(err).ToNot(HaveOccurred()) 250 251 Expect(RemoveDockerContainer(id)).To(BeTrue()) 252 }) 253 }) 254 }) 255 }) 256 257 var _ = Describe("Check cgroup paths", func() { 258 var ( 259 args []string 260 id string 261 ) 262 263 BeforeEach(func() { 264 id = randomDockerName() 265 args = []string{"-d", "--name", id} 266 }) 267 268 AfterEach(func() { 269 Expect(RemoveDockerContainer(id)).To(BeTrue()) 270 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 271 }) 272 273 DescribeTable("with a parent cgroup", 274 func(parentCgroup string) { 275 args = append(args, "--cgroup-parent", parentCgroup, Image) 276 _, _, exitCode := dockerRun(args...) 277 Expect(exitCode).To(BeZero()) 278 }, 279 withParentCgroup("../"), 280 withParentCgroup("../../"), 281 withParentCgroup("../../../"), 282 withParentCgroup("../../../../"), 283 withParentCgroup("~"), 284 withParentCgroup("/../../../../hi"), 285 ) 286 })