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