github.com/dmaizel/tests@v0.0.0-20210728163746-cae6a2d9cee8/integration/docker/cpu_test.go (about) 1 // Copyright (c) 2018 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package docker 6 7 import ( 8 "fmt" 9 "math" 10 "runtime" 11 "strings" 12 13 . "github.com/kata-containers/tests" 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/ginkgo/extensions/table" 16 . "github.com/onsi/gomega" 17 ) 18 19 const ( 20 sharesSysPath = "/sys/fs/cgroup/cpu/cpu.shares" 21 quotaSysPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" 22 periodSysPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" 23 cpusetCpusSysPath = "/sys/fs/cgroup/cpuset/cpuset.cpus" 24 cpusetMemsSysPath = "/sys/fs/cgroup/cpuset/cpuset.mems" 25 26 checkCpusCmdFmt = `for c in $(seq 1 %d); do if [ "$(nproc)" == "%d" ]; then nproc; exit 0; fi; sleep %d; done; exit 1` 27 ) 28 29 func withCPUPeriodAndQuota(quota, period, defaultVCPUs int, fail bool) TableEntry { 30 var msg string 31 32 if fail { 33 msg = "should fail" 34 } else { 35 msg = fmt.Sprintf("should have %d CPUs", ((quota+period-1)/period)+defaultVCPUs) 36 } 37 38 return Entry(msg, quota, period, fail) 39 } 40 41 func withCPUConstraint(cpus float64, defaultVCPUs int, fail bool) TableEntry { 42 var msg string 43 c := int(math.Ceil(cpus)) 44 45 if fail { 46 msg = "should fail" 47 } else { 48 msg = fmt.Sprintf("should have %d CPUs", c+defaultVCPUs) 49 } 50 51 return Entry(msg, c, fail) 52 } 53 54 var _ = Describe("Hot plug CPUs", func() { 55 var ( 56 args []string 57 id string 58 vCPUs int 59 defaultVCPUs int 60 waitTime int 61 maxTries int 62 exitCode int 63 stdout string 64 ) 65 66 BeforeEach(func() { 67 id = "" 68 waitTime = 5 69 maxTries = 5 70 args = []string{} 71 defaultVCPUs = int(KataConfig.Hypervisor[KataHypervisor].DefaultVCPUs) 72 Expect(defaultVCPUs).To(BeNumerically(">", 0)) 73 }) 74 75 DescribeTable("container with CPU period and quota", 76 func(quota, period int, fail bool) { 77 vCPUs = ((quota + period - 1) / period) + defaultVCPUs 78 79 for i := 0; i < maxTries; i++ { 80 id = randomDockerName() 81 args = []string{ 82 "--rm", "--name", id, 83 "--cpu-quota", fmt.Sprintf("%d", quota), 84 "--cpu-period", fmt.Sprintf("%d", period), 85 DebianImage, "bash", "-c", 86 fmt.Sprintf(checkCpusCmdFmt, maxTries, vCPUs, waitTime), 87 } 88 89 stdout, _, exitCode = dockerRun(args...) 90 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 91 92 if fail { 93 Expect(exitCode).ToNot(BeZero()) 94 return 95 } 96 97 if exitCode == 0 { 98 break 99 } 100 } 101 Expect(exitCode).To(BeZero()) 102 Expect(fmt.Sprintf("%d", vCPUs)).To(Equal(strings.TrimSpace(stdout))) 103 }, 104 withCPUPeriodAndQuota(30000, 20000, defaultVCPUs, false), 105 withCPUPeriodAndQuota(30000, 10000, defaultVCPUs, false), 106 withCPUPeriodAndQuota(10000, 10000, defaultVCPUs, false), 107 withCPUPeriodAndQuota(10000, 100, defaultVCPUs, true), 108 ) 109 110 DescribeTable("container with CPU constraint", 111 func(cpus int, fail bool) { 112 vCPUs = cpus + defaultVCPUs 113 114 for i := 0; i < maxTries; i++ { 115 id = randomDockerName() 116 args = []string{ 117 "--rm", "--name", id, 118 "--cpus", fmt.Sprintf("%d", cpus), 119 DebianImage, "bash", "-c", 120 fmt.Sprintf(checkCpusCmdFmt, maxTries, vCPUs, waitTime), 121 } 122 123 stdout, _, exitCode = dockerRun(args...) 124 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 125 126 if fail { 127 Expect(exitCode).ToNot(BeZero()) 128 return 129 } 130 if exitCode == 0 { 131 break 132 } 133 } 134 Expect(exitCode).To(BeZero()) 135 Expect(fmt.Sprintf("%d", vCPUs)).To(Equal(strings.TrimSpace(stdout))) 136 }, 137 withCPUConstraint(1, defaultVCPUs, false), 138 withCPUConstraint(1.5, defaultVCPUs, false), 139 withCPUConstraint(2, defaultVCPUs, false), 140 withCPUConstraint(2.5, defaultVCPUs, false), 141 withCPUConstraint(-5, defaultVCPUs, true), 142 ) 143 }) 144 145 var _ = Describe("CPU constraints", func() { 146 var ( 147 args []string 148 id string 149 shares int = 300 150 quota int = 20000 151 period int = 15000 152 cpusetCpus int = 0 153 cpusetMems int = 0 154 ) 155 156 BeforeEach(func() { 157 id = randomDockerName() 158 args = []string{"--rm", "--name", id} 159 }) 160 161 AfterEach(func() { 162 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 163 }) 164 165 Describe("checking container with CPU constraints", func() { 166 Context(fmt.Sprintf("with shares equal to %d", shares), func() { 167 It(fmt.Sprintf("%s should have %d", sharesSysPath, shares), func() { 168 args = append(args, "--cpu-shares", fmt.Sprintf("%d", shares), Image, "cat", sharesSysPath) 169 stdout, _, exitCode := dockerRun(args...) 170 Expect(exitCode).To(BeZero()) 171 Expect(fmt.Sprintf("%d", shares)).To(Equal(strings.Trim(stdout, "\n\t "))) 172 }) 173 }) 174 175 Context(fmt.Sprintf("with period equal to %d", period), func() { 176 It(fmt.Sprintf("%s should have %d", periodSysPath, period), func() { 177 args = append(args, "--cpu-period", fmt.Sprintf("%d", period), Image, "cat", periodSysPath) 178 stdout, _, exitCode := dockerRun(args...) 179 Expect(exitCode).To(BeZero()) 180 Expect(fmt.Sprintf("%d", period)).To(Equal(strings.Trim(stdout, "\n\t "))) 181 }) 182 }) 183 184 Context(fmt.Sprintf("with quota equal to %d", quota), func() { 185 It(fmt.Sprintf("%s should have %d", quotaSysPath, quota), func() { 186 args = append(args, "--cpu-quota", fmt.Sprintf("%d", quota), Image, "cat", quotaSysPath) 187 stdout, _, exitCode := dockerRun(args...) 188 Expect(exitCode).To(BeZero()) 189 Expect(fmt.Sprintf("%d", quota)).To(Equal(strings.Trim(stdout, "\n\t "))) 190 }) 191 }) 192 193 Context(fmt.Sprintf("with cpuset-cpus to %d", cpusetCpus), func() { 194 It(fmt.Sprintf("%s should have %d", cpusetCpusSysPath, cpusetCpus), func() { 195 args = append(args, "--cpuset-cpus", fmt.Sprintf("%d", cpusetCpus), Image, "cat", cpusetCpusSysPath) 196 stdout, _, exitCode := dockerRun(args...) 197 Expect(exitCode).To(BeZero()) 198 Expect(fmt.Sprintf("%d", cpusetCpus)).To(Equal(strings.Trim(stdout, "\n\t "))) 199 }) 200 }) 201 202 Context(fmt.Sprintf("with cpuset-mems to %d", cpusetMems), func() { 203 It(fmt.Sprintf("%s should have %d", cpusetMemsSysPath, cpusetMems), func() { 204 args = append(args, "--cpuset-mems", fmt.Sprintf("%d", cpusetMems), Image, "cat", cpusetMemsSysPath) 205 stdout, _, exitCode := dockerRun(args...) 206 Expect(exitCode).To(BeZero()) 207 Expect(fmt.Sprintf("%d", cpusetMems)).To(Equal(strings.Trim(stdout, "\n\t "))) 208 }) 209 }) 210 }) 211 }) 212 213 func withParentCgroup(parentCgroup string) TableEntry { 214 return Entry(fmt.Sprintf("should not fail with parent cgroup: %s", parentCgroup), parentCgroup) 215 } 216 217 var _ = Describe("[Serial Test] Hot plug CPUs", func() { 218 var ( 219 args []string 220 id string 221 cpus uint 222 ) 223 224 BeforeEach(func() { 225 id = randomDockerName() 226 args = []string{"--rm", "--name", id} 227 cpus = 2 228 }) 229 230 AfterEach(func() { 231 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 232 }) 233 234 DescribeTable("with a parent cgroup", 235 func(parentCgroup string) { 236 args = append(args, "--cgroup-parent", parentCgroup, "--cpus", fmt.Sprintf("%d", cpus), DebianImage, "bash", "-c", 237 fmt.Sprintf("echo $(($(cat %s)/$(cat %s)))", quotaSysPath, periodSysPath)) 238 stdout, _, exitCode := dockerRun(args...) 239 Expect(exitCode).To(BeZero()) 240 Expect(fmt.Sprintf("%d", cpus)).To(Equal(strings.Trim(stdout, "\n\t "))) 241 }, 242 withParentCgroup("0"), 243 withParentCgroup("systemd"), 244 withParentCgroup("/systemd/"), 245 withParentCgroup("///systemd////"), 246 withParentCgroup("systemd////"), 247 withParentCgroup("////systemd"), 248 withParentCgroup("docker"), 249 withParentCgroup("abc/xyz/rgb"), 250 withParentCgroup("/abc/xyz/rgb/"), 251 withParentCgroup("///abc///xyz////rgb///"), 252 ) 253 }) 254 255 var _ = Describe("Update number of CPUs", func() { 256 var ( 257 runArgs []string 258 updateArgs []string 259 execArgs []string 260 id string 261 vCPUs int 262 defaultVCPUs int 263 waitTime int 264 maxTries int 265 stdout string 266 exitCode int 267 ) 268 269 BeforeEach(func() { 270 id = "" 271 waitTime = 5 272 maxTries = 5 273 274 defaultVCPUs = int(KataConfig.Hypervisor[KataHypervisor].DefaultVCPUs) 275 Expect(defaultVCPUs).To(BeNumerically(">", 0)) 276 277 runArgs = []string{} 278 updateArgs = []string{} 279 execArgs = []string{} 280 }) 281 282 DescribeTable("Update CPU period and quota", 283 func(quota, period int, fail bool) { 284 vCPUs = ((quota + period - 1) / period) + defaultVCPUs 285 286 for i := 0; i < maxTries; i++ { 287 id = randomDockerName() 288 runArgs = []string{ 289 "-dt", "--name", id, 290 DebianImage, "bash", 291 } 292 updateArgs = []string{ 293 "--cpu-quota", fmt.Sprintf("%d", quota), 294 "--cpu-period", fmt.Sprintf("%d", period), 295 id, 296 } 297 execArgs = []string{ 298 id, "bash", "-c", 299 fmt.Sprintf(checkCpusCmdFmt, maxTries, vCPUs, waitTime), 300 } 301 302 _, _, exitCode = dockerRun(runArgs...) 303 Expect(exitCode).To(BeZero()) 304 305 stdout, _, exitCode = dockerUpdate(updateArgs...) 306 if fail { 307 Expect(RemoveDockerContainer(id)).To(BeTrue()) 308 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 309 Expect(exitCode).ToNot(BeZero()) 310 return 311 } 312 Expect(exitCode).To(BeZero()) 313 314 stdout, _, exitCode = dockerExec(execArgs...) 315 Expect(RemoveDockerContainer(id)).To(BeTrue()) 316 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 317 if exitCode == 0 { 318 break 319 } 320 } 321 Expect(exitCode).To(BeZero()) 322 Expect(fmt.Sprintf("%d", vCPUs)).To(Equal(strings.TrimSpace(stdout))) 323 }, 324 withCPUPeriodAndQuota(30000, 20000, defaultVCPUs, false), 325 withCPUPeriodAndQuota(30000, 10000, defaultVCPUs, false), 326 withCPUPeriodAndQuota(10000, 10000, defaultVCPUs, false), 327 withCPUPeriodAndQuota(10000, 100, defaultVCPUs, true), 328 ) 329 330 DescribeTable("Update CPU constraint", 331 func(cpus int, fail bool) { 332 vCPUs = cpus + defaultVCPUs 333 334 for i := 0; i < maxTries; i++ { 335 id = randomDockerName() 336 runArgs = []string{ 337 "-dt", "--name", id, 338 DebianImage, "bash", 339 } 340 execArgs = []string{ 341 id, "bash", "-c", 342 fmt.Sprintf(checkCpusCmdFmt, maxTries, vCPUs, waitTime), 343 } 344 updateArgs = []string{"--cpus", fmt.Sprintf("%d", cpus), id} 345 346 _, _, exitCode = dockerRun(runArgs...) 347 Expect(exitCode).To(BeZero()) 348 349 stdout, _, exitCode = dockerUpdate(updateArgs...) 350 if fail { 351 Expect(RemoveDockerContainer(id)).To(BeTrue()) 352 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 353 Expect(exitCode).ToNot(BeZero()) 354 return 355 } 356 Expect(exitCode).To(BeZero()) 357 358 stdout, _, exitCode = dockerExec(execArgs...) 359 Expect(RemoveDockerContainer(id)).To(BeTrue()) 360 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 361 if exitCode == 0 { 362 break 363 } 364 } 365 Expect(exitCode).To(BeZero()) 366 Expect(fmt.Sprintf("%d", vCPUs)).To(Equal(strings.TrimSpace(stdout))) 367 }, 368 withCPUConstraint(1, defaultVCPUs, false), 369 withCPUConstraint(1.3, defaultVCPUs, false), 370 withCPUConstraint(2, defaultVCPUs, false), 371 withCPUConstraint(2.5, defaultVCPUs, false), 372 withCPUConstraint(3, defaultVCPUs, false), 373 ) 374 }) 375 376 func withCPUConstraintCheckPeriodAndQuota(cpus float64, fail bool) TableEntry { 377 return Entry(fmt.Sprintf("quota/period should be equal to %.1f", cpus), cpus, fail) 378 } 379 380 func withCPUSetConstraint(cpuset string, minCpusNeeded int, fail bool) TableEntry { 381 // test should fail when the actual number of cpus is less than the minimum number 382 // of cpus needed to run the test, for example cpuset=0-2 requires 3 cpus(0,1,2) 383 if runtime.NumCPU() < minCpusNeeded { 384 fail = true 385 } 386 387 return Entry(fmt.Sprintf("cpuset should be equal to %s", cpuset), cpuset, fail) 388 } 389 390 var _ = Describe("Update CPU constraints", func() { 391 var ( 392 runArgs []string 393 updateArgs []string 394 execArgs []string 395 id string 396 exitCode int 397 stdout string 398 ) 399 400 BeforeEach(func() { 401 id = randomDockerName() 402 403 updateArgs = []string{} 404 execArgs = []string{} 405 runArgs = []string{} 406 }) 407 408 AfterEach(func() { 409 Expect(RemoveDockerContainer(id)).To(BeTrue()) 410 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 411 }) 412 413 DescribeTable("Update number of CPUs to check period and quota", 414 func(cpus float64, fail bool) { 415 runArgs = []string{"--rm", "--name", id, "-dt", DebianImage, "bash"} 416 _, _, exitCode = dockerRun(runArgs...) 417 Expect(exitCode).To(BeZero()) 418 419 updateArgs = append(updateArgs, "--cpus", fmt.Sprintf("%f", cpus), id) 420 stdout, _, exitCode = dockerUpdate(updateArgs...) 421 if fail { 422 Expect(exitCode).ToNot(BeZero()) 423 return 424 } 425 Expect(exitCode).To(BeZero()) 426 427 execArgs = append(execArgs, id, "bash", "-c", 428 fmt.Sprintf(`perl -e "printf ('%%.1f', $(cat %s)/$(cat %s))"`, quotaSysPath, periodSysPath)) 429 stdout, _, exitCode = dockerExec(execArgs...) 430 Expect(exitCode).To(BeZero()) 431 Expect(fmt.Sprintf("%.1f", cpus)).To(Equal(strings.Trim(stdout, "\n\t "))) 432 }, 433 withCPUConstraintCheckPeriodAndQuota(0.5, shouldNotFail), 434 withCPUConstraintCheckPeriodAndQuota(1, shouldNotFail), 435 withCPUConstraintCheckPeriodAndQuota(1.2, shouldNotFail), 436 withCPUConstraintCheckPeriodAndQuota(2, shouldNotFail), 437 withCPUConstraintCheckPeriodAndQuota(2.8, shouldNotFail), 438 withCPUConstraintCheckPeriodAndQuota(3, shouldNotFail), 439 withCPUConstraintCheckPeriodAndQuota(-3, shouldFail), 440 withCPUConstraintCheckPeriodAndQuota(-2.5, shouldFail), 441 ) 442 443 DescribeTable("Update CPU set", 444 func(cpuset string, fail bool) { 445 // Use the actual number of CPUs 446 runArgs = []string{"--rm", fmt.Sprintf("--cpus=%d", runtime.NumCPU()), 447 "--name", id, "-dt", DebianImage, "bash"} 448 _, _, exitCode = dockerRun(runArgs...) 449 Expect(exitCode).To(BeZero()) 450 451 updateArgs = append(updateArgs, "--cpuset-cpus", cpuset, id) 452 stdout, _, exitCode = dockerUpdate(updateArgs...) 453 if fail { 454 Expect(exitCode).ToNot(BeZero()) 455 return 456 } 457 Expect(exitCode).To(BeZero()) 458 459 execArgs = append(execArgs, id, "cat", cpusetCpusSysPath) 460 stdout, _, exitCode = dockerExec(execArgs...) 461 Expect(exitCode).To(BeZero()) 462 Expect(cpuset).To(Equal(strings.Trim(stdout, "\n\t "))) 463 }, 464 withCPUSetConstraint("0", 1, shouldNotFail), 465 withCPUSetConstraint("2", 3, shouldNotFail), 466 withCPUSetConstraint("0-1", 2, shouldNotFail), 467 withCPUSetConstraint("0-2", 3, shouldNotFail), 468 withCPUSetConstraint("0-3", 4, shouldNotFail), 469 withCPUSetConstraint("0,2", 3, shouldNotFail), 470 withCPUSetConstraint("0,3", 4, shouldNotFail), 471 withCPUSetConstraint("0,-2,3", 0, shouldFail), 472 withCPUSetConstraint("-1-3", 0, shouldFail), 473 ) 474 }) 475 476 var _ = Describe("CPUs and CPU set", func() { 477 type cpuTest struct { 478 cpus string 479 cpusetcpus string 480 expectedCpus string 481 } 482 483 var ( 484 args []string 485 id string 486 cpuTests []cpuTest 487 exitCode int 488 stdout string 489 updateCheckFn func(cpus, cpusetCpus, expectedCpus string) 490 ) 491 492 BeforeEach(func() { 493 id = randomDockerName() 494 args = []string{"--rm", "-dt", "--name", id, Image, "sh"} 495 cpuTests = []cpuTest{ 496 {"1", "0-1", "2"}, 497 {"3", "1,2", "2"}, 498 {"2", "1", "1"}, 499 } 500 _, _, exitCode = dockerRun(args...) 501 Expect(exitCode).To(BeZero()) 502 updateCheckFn = func(cpus, cpusetCpus, expectedCpus string) { 503 args = []string{"--cpus", cpus, "--cpuset-cpus", cpusetCpus, id} 504 _, _, exitCode = dockerUpdate(args...) 505 Expect(exitCode).To(BeZero()) 506 stdout, _, exitCode = dockerExec(id, "nproc") 507 Expect(expectedCpus).To(Equal(strings.Trim(stdout, "\n\t "))) 508 } 509 }) 510 511 AfterEach(func() { 512 Expect(RemoveDockerContainer(id)).To(BeTrue()) 513 Expect(ExistDockerContainer(id)).NotTo(BeTrue()) 514 }) 515 516 Describe("updating", func() { 517 Context("cpus and cpuset of a running container", func() { 518 It("should have the right number of vCPUs", func() { 519 for _, c := range cpuTests { 520 updateCheckFn(c.cpus, c.cpusetcpus, c.expectedCpus) 521 } 522 }) 523 }) 524 }) 525 })