Skip to content
This repository was archived by the owner on May 12, 2021. It is now read-only.

Commit 201c487

Browse files
committed
cgroup: ignore cpuset if can not be applied.
The way that kata works is hotplugging cpus to the VM based in cpu and qouta, cpuset request may not match with the cpus requested from the host. Apply only if possible. Fixes: #446 Signed-off-by: Jose Carlos Venegas Munoz <[email protected]>
1 parent ca9d520 commit 201c487

File tree

22 files changed

+3395
-18
lines changed

22 files changed

+3395
-18
lines changed

Gopkg.lock

Lines changed: 9 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cgroup.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// Copyright (c) 2019 Intel Corporation
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
//
6+
7+
package main
8+
9+
import (
10+
"io/ioutil"
11+
"strings"
12+
13+
"github.com/docker/docker/pkg/parsers"
14+
"github.com/sirupsen/logrus"
15+
)
16+
17+
// set function in variable to overwrite for testing.
18+
var getCpusetGuest = func() (string, error) {
19+
cpusetGuestByte, err := ioutil.ReadFile("/sys/fs/cgroup/cpuset/cpuset.cpus")
20+
if err != nil {
21+
return "", err
22+
}
23+
24+
return strings.TrimSpace(string(cpusetGuestByte)), nil
25+
}
26+
27+
// Return the best match for cpuset list in the guest.
28+
// The runtime caller may apply cpuset for specific CPUs in the host.
29+
// The CPUs may not exist on the guest as they are hotplugged based
30+
// on cpu and qouta.
31+
// This function return a working cpuset to apply on the guest.
32+
func getAvailableCpusetList(cpusetReq string) (string, error) {
33+
34+
cpusetGuest, err := getCpusetGuest()
35+
if err != nil {
36+
return "", err
37+
}
38+
39+
cpusetListReq, err := parsers.ParseUintList(cpusetReq)
40+
if err != nil {
41+
return "", err
42+
}
43+
44+
cpusetGuestList, err := parsers.ParseUintList(cpusetGuest)
45+
if err != nil {
46+
return "", err
47+
}
48+
49+
for k := range cpusetListReq {
50+
if !cpusetGuestList[k] {
51+
agentLog.WithFields(logrus.Fields{
52+
"cpuset": cpusetReq,
53+
"cpu": k,
54+
"guest-cpus": cpusetGuest,
55+
}).Warnf("cpu is not in guest cpu list, using guest cpus")
56+
return cpusetGuest, nil
57+
}
58+
}
59+
60+
// All the cpus are valid keep the same cpuset string
61+
agentLog.WithFields(logrus.Fields{
62+
"cpuset": cpusetReq,
63+
}).Debugf("the requested cpuset is valid, using it")
64+
return cpusetReq, nil
65+
}

cgroup_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2019 Intel Corporation
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
package main
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestGetAvailableCpusetList(t *testing.T) {
15+
fakeGuestCpuset := "0-3"
16+
getCpusetGuest = func() (string, error) {
17+
return fakeGuestCpuset, nil
18+
}
19+
20+
type testCase struct {
21+
input string
22+
expectedOutput string
23+
}
24+
25+
cases := []testCase{
26+
{"0", "0"},
27+
{"0,1", "0,1"},
28+
{"0,1,2", "0,1,2"},
29+
{"0,1,2,3", "0,1,2,3"},
30+
{"0,1,2,3,4", fakeGuestCpuset},
31+
{"0-3", "0-3"},
32+
{"0-3,4", fakeGuestCpuset},
33+
{"0-4", fakeGuestCpuset},
34+
{"1", "1"},
35+
{"1,3", "1,3"},
36+
{"2-3", "2-3"},
37+
{"2-4", fakeGuestCpuset},
38+
}
39+
40+
for _, c := range cases {
41+
out, err := getAvailableCpusetList(c.input)
42+
assert.Nil(t, err, "Failed to calculate : %v", err)
43+
assert.Equal(t, out, c.expectedOutput)
44+
}
45+
}

documentation/features/cpuset.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Cpuset cgroup.
2+
3+
From Kernel documentation:
4+
5+
_" Cpusets provide a mechanism for assigning a set of CPUs and Memory Nodes to
6+
a set of tasks."_
7+
8+
The Kata agent brings compatibility to the cgroup cpuset CPU on the guest side.
9+
10+
The cpuset CPU cgroup will be applied on two events:
11+
12+
- containers creation
13+
14+
- container update
15+
16+
When the runtime requests to apply cpuset cgroup to the agent, the amount of
17+
vcpus available might not be the same to the required vcpus in the request.
18+
19+
This is because the request from the agent client (i.e. the Kata runtime)
20+
passes cpusets that are requested to be placed on the host. This isolates the
21+
container workload on some specific host CPUs. The runtime passes the requested
22+
cpuset to the agent, which tries to apply the cgroup cpuset on the guest.
23+
24+
The runtime only calculates and hot-plugs the CPUSs based on the container
25+
period and quota. This is why the VM will not have the same amount of CPUs as
26+
the host.
27+
28+
Example:
29+
30+
docker run -ti --cpus 2 --cpuset 0,1 busybox
31+
32+
This should result with the container limited to the time of 2 CPUs, but is
33+
only allowed to be scheduled on CPUs 0 and 1.
34+
35+
The following is an example of a similar case with a valid traditional container:
36+
37+
docker run -ti --cpus 2 --cpuset 2,3,4 busybox
38+
39+
Here, the container is limited to 2 CPUs and can be scheduled on CPU 2, 3, and
40+
4.
41+
42+
The Kata runtime only hotplugs 2 CPUs, making it impossible to request that the
43+
guest kernel schedules the workload on vcpu 3 and 4.
44+
45+
## cpuset best effort application.
46+
47+
The Kata agent evaluates the request to see if it is possible to apply the
48+
cpuset request onto the guest.
49+
50+
- If the CPUSs requested are not available in the guest, the request is ignored.
51+
- If the CPUs requested are available, the request is applied by the agent.
52+

grpc.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ func updateContainerCpuset(cgroupPath string, newCpuset string, cookies cookie)
179179
// Don't use c.container.Set because of it will modify container's config.
180180
// c.container.Set MUST BE used only on update.
181181
cpusetCpusPath := filepath.Join(cpusetPath, "cpuset.cpus")
182-
agentLog.WithField("path", cpusetPath).Debug("updating cpuset cgroup")
182+
183183
if err := ioutil.WriteFile(cpusetCpusPath, []byte(newCpuset), cpusetMode); err != nil {
184184
return fmt.Errorf("Could not update cpuset cgroup '%s': %v", newCpuset, err)
185185
}
@@ -625,6 +625,15 @@ func (a *agentGRPC) CreateContainer(ctx context.Context, req *pb.CreateContainer
625625
return emptyResp, err
626626
}
627627

628+
if ociSpec.Linux.Resources.CPU != nil && ociSpec.Linux.Resources.CPU.Cpus != "" {
629+
availableCpuset, err := getAvailableCpusetList(ociSpec.Linux.Resources.CPU.Cpus)
630+
if err != nil {
631+
return emptyResp, err
632+
}
633+
634+
ociSpec.Linux.Resources.CPU.Cpus = availableCpuset
635+
}
636+
628637
if a.sandbox.guestHooksPresent {
629638
// Add any custom OCI hooks to the spec
630639
a.sandbox.addGuestHooks(ociSpec)
@@ -1017,6 +1026,11 @@ func (a *agentGRPC) UpdateContainer(ctx context.Context, req *pb.UpdateContainer
10171026

10181027
// cpuset is a special case where container's cpuset cgroup MUST BE updated
10191028
if resources.CpusetCpus != "" {
1029+
resources.CpusetCpus, err = getAvailableCpusetList(resources.CpusetCpus)
1030+
if err != nil {
1031+
return emptyResp, err
1032+
}
1033+
10201034
cookies := make(cookie)
10211035
if err = updateContainerCpuset(contConfig.Cgroups.Path, resources.CpusetCpus, cookies); err != nil {
10221036
agentLog.WithError(err).Warn("Could not update container cpuset cgroup")

0 commit comments

Comments
 (0)