Files
kata-containers/virtcontainers/cgroups_test.go
Wei Zhang 34fe3b9d6d cgroups: add host cgroup support
Fixes #344

Add host cgroup support for kata.

This commits only adds cpu.cfs_period and cpu.cfs_quota support.

It will create 3-level hierarchy, take "cpu" cgroup as an example:

```
/sys/fs/cgroup
|---cpu
   |---kata
      |---<sandbox-id>
         |--vcpu
      |---<sandbox-id>
```

* `vc` cgroup is common parent for all kata-container sandbox, it won't be removed
after sandbox removed. This cgroup has no limitation.
* `<sandbox-id>` cgroup is the layer for each sandbox, it contains all other qemu
threads except for vcpu threads. In future, we can consider putting all shim
processes and proxy process here. This cgroup has no limitation yet.
* `vcpu` cgroup contains vcpu threads from qemu. Currently cpu quota and period
constraint applies to this cgroup.

Signed-off-by: Wei Zhang <zhangwei555@huawei.com>
Signed-off-by: Jingxiao Lu <lujingxiao@huawei.com>
2018-10-27 09:41:35 +08:00

212 lines
5.4 KiB
Go

// Copyright (c) 2018 Huawei Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package virtcontainers
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert"
"github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
)
func getCgroupDestination(subsystem string) (string, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return "", err
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
if err := s.Err(); err != nil {
return "", err
}
fields := strings.Fields(s.Text())
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if opt == subsystem {
return fields[4], nil
}
}
}
return "", fmt.Errorf("failed to find cgroup mountpoint for %q", subsystem)
}
func TestMergeSpecResource(t *testing.T) {
s := &Sandbox{
config: &SandboxConfig{
Containers: []ContainerConfig{
{
ID: "containerA",
Annotations: make(map[string]string),
},
{
ID: "containerA",
Annotations: make(map[string]string),
},
},
},
}
contA := s.config.Containers[0]
contB := s.config.Containers[1]
getIntP := func(x int64) *int64 { return &x }
getUintP := func(x uint64) *uint64 { return &x }
type testData struct {
first *specs.LinuxResources
second *specs.LinuxResources
expected *specs.LinuxResources
}
for _, testdata := range []testData{
{
nil,
nil,
&specs.LinuxResources{CPU: &specs.LinuxCPU{}},
},
{
nil,
&specs.LinuxResources{},
&specs.LinuxResources{CPU: &specs.LinuxCPU{}},
},
{
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(0), Period: getUintP(100000)}},
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(20000), Period: getUintP(100000)}},
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(20000), Period: getUintP(100000)}},
},
{
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(10000), Period: getUintP(0)}},
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(20000), Period: getUintP(100000)}},
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(20000), Period: getUintP(100000)}},
},
{
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(1000), Period: getUintP(2000)}},
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(20000), Period: getUintP(100000)}},
&specs.LinuxResources{CPU: &specs.LinuxCPU{Quota: getIntP(1400), Period: getUintP(2000)}},
},
} {
data, err := json.Marshal(&specs.Spec{
Linux: &specs.Linux{
Resources: testdata.first,
},
})
assert.Nil(t, err)
contA.Annotations[annotations.ConfigJSONKey] = string(data)
data, err = json.Marshal(&specs.Spec{
Linux: &specs.Linux{
Resources: testdata.second,
},
})
assert.Nil(t, err)
contB.Annotations[annotations.ConfigJSONKey] = string(data)
rc, err := s.mergeSpecResource()
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(rc, testdata.expected), "should be equal, got: %#v, expected: %#v", rc, testdata.expected)
}
}
func TestSetupCgroups(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("Test disabled as requires root privileges")
}
s := &Sandbox{
id: "test-sandbox",
hypervisor: &mockHypervisor{},
config: &SandboxConfig{
Containers: []ContainerConfig{
{
ID: "containerA",
Annotations: make(map[string]string),
},
{
ID: "containerA",
Annotations: make(map[string]string),
},
},
},
}
contA := s.config.Containers[0]
contB := s.config.Containers[1]
getIntP := func(x int64) *int64 { return &x }
getUintP := func(x uint64) *uint64 { return &x }
data, err := json.Marshal(&specs.Spec{
Linux: &specs.Linux{
Resources: &specs.LinuxResources{
CPU: &specs.LinuxCPU{
Quota: getIntP(5000),
Period: getUintP(10000),
},
},
},
})
assert.Nil(t, err)
contA.Annotations[annotations.ConfigJSONKey] = string(data)
data, err = json.Marshal(&specs.Spec{
Linux: &specs.Linux{
Resources: &specs.LinuxResources{
CPU: &specs.LinuxCPU{
Quota: getIntP(10000),
Period: getUintP(40000),
},
},
},
})
assert.Nil(t, err)
contB.Annotations[annotations.ConfigJSONKey] = string(data)
err = s.newCgroups()
assert.Nil(t, err, "failed to create cgroups")
defer s.destroyCgroups()
// test if function works without error
err = s.setupCgroups()
assert.Nil(t, err, "setup host cgroup failed")
// test if the quota and period value are written into cgroup files
cpu, err := getCgroupDestination("cpu")
assert.Nil(t, err, "failed to get cpu cgroup path")
assert.NotEqual(t, "", cpu, "cpu cgroup value can't be empty")
parentDir := filepath.Join(cpu, defaultCgroupParent, "test-sandbox", "vcpu")
quotaFile := filepath.Join(parentDir, "cpu.cfs_quota_us")
periodFile := filepath.Join(parentDir, "cpu.cfs_period_us")
expectedQuota := "7500\n"
expectedPeriod := "10000\n"
fquota, err := os.Open(quotaFile)
assert.Nil(t, err, "open file %q failed", quotaFile)
defer fquota.Close()
data, err = ioutil.ReadAll(fquota)
assert.Nil(t, err)
assert.Equal(t, expectedQuota, string(data), "failed to get expected cfs_quota")
fperiod, err := os.Open(periodFile)
assert.Nil(t, err, "open file %q failed", periodFile)
defer fperiod.Close()
data, err = ioutil.ReadAll(fperiod)
assert.Nil(t, err)
assert.Equal(t, expectedPeriod, string(data), "failed to get expected cfs_period")
}