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  })