Before this patch libcontainer badly errored out with `invalid
argument` or `numerical result out of range` while trying to write
to cpuset.cpus or cpuset.mems with an invalid value provided.
This patch adds validation to --cpuset-cpus and --cpuset-mems flag along with
validation based on system's available cpus/mems before starting a container.
Signed-off-by: Antonio Murdaca <runcom@linux.com>
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/autogen/dockerversion" |
| 16 | 16 |
"github.com/docker/docker/context" |
| 17 | 17 |
"github.com/docker/docker/daemon/graphdriver" |
| 18 |
+ derr "github.com/docker/docker/errors" |
|
| 18 | 19 |
"github.com/docker/docker/pkg/fileutils" |
| 19 | 20 |
"github.com/docker/docker/pkg/parsers" |
| 20 | 21 |
"github.com/docker/docker/pkg/parsers/kernel" |
| ... | ... |
@@ -197,6 +198,20 @@ func verifyPlatformContainerSettings(ctx context.Context, daemon *Daemon, hostCo |
| 197 | 197 |
hostConfig.CpusetCpus = "" |
| 198 | 198 |
hostConfig.CpusetMems = "" |
| 199 | 199 |
} |
| 200 |
+ cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(hostConfig.CpusetCpus) |
|
| 201 |
+ if err != nil {
|
|
| 202 |
+ return warnings, derr.ErrorCodeInvalidCpusetCpus.WithArgs(hostConfig.CpusetCpus) |
|
| 203 |
+ } |
|
| 204 |
+ if !cpusAvailable {
|
|
| 205 |
+ return warnings, derr.ErrorCodeNotAvailableCpusetCpus.WithArgs(hostConfig.CpusetCpus, sysInfo.Cpus) |
|
| 206 |
+ } |
|
| 207 |
+ memsAvailable, err := sysInfo.IsCpusetMemsAvailable(hostConfig.CpusetMems) |
|
| 208 |
+ if err != nil {
|
|
| 209 |
+ return warnings, derr.ErrorCodeInvalidCpusetMems.WithArgs(hostConfig.CpusetMems) |
|
| 210 |
+ } |
|
| 211 |
+ if !memsAvailable {
|
|
| 212 |
+ return warnings, derr.ErrorCodeNotAvailableCpusetMems.WithArgs(hostConfig.CpusetMems, sysInfo.Mems) |
|
| 213 |
+ } |
|
| 200 | 214 |
if hostConfig.BlkioWeight > 0 && !sysInfo.BlkioWeight {
|
| 201 | 215 |
warnings = append(warnings, "Your kernel does not support Block I/O weight. Weight discarded.") |
| 202 | 216 |
logrus.Warnf("Your kernel does not support Block I/O weight. Weight discarded.")
|
| ... | ... |
@@ -237,10 +252,7 @@ func checkSystem() error {
|
| 237 | 237 |
if os.Geteuid() != 0 {
|
| 238 | 238 |
return fmt.Errorf("The Docker daemon needs to be run as root")
|
| 239 | 239 |
} |
| 240 |
- if err := checkKernel(); err != nil {
|
|
| 241 |
- return err |
|
| 242 |
- } |
|
| 243 |
- return nil |
|
| 240 |
+ return checkKernel() |
|
| 244 | 241 |
} |
| 245 | 242 |
|
| 246 | 243 |
// configureKernelSecuritySupport configures and validate security support for the kernel |
| ... | ... |
@@ -827,4 +827,40 @@ var ( |
| 827 | 827 |
Description: "While trying to delete a container, there was an error trying to delete one of its volumes", |
| 828 | 828 |
HTTPStatusCode: http.StatusInternalServerError, |
| 829 | 829 |
}) |
| 830 |
+ |
|
| 831 |
+ // ErrorCodeInvalidCpusetCpus is generated when user provided cpuset CPUs |
|
| 832 |
+ // are invalid. |
|
| 833 |
+ ErrorCodeInvalidCpusetCpus = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
| 834 |
+ Value: "INVALIDCPUSETCPUS", |
|
| 835 |
+ Message: "Invalid value %s for cpuset cpus.", |
|
| 836 |
+ Description: "While verifying the container's 'HostConfig', CpusetCpus value was in an incorrect format", |
|
| 837 |
+ HTTPStatusCode: http.StatusInternalServerError, |
|
| 838 |
+ }) |
|
| 839 |
+ |
|
| 840 |
+ // ErrorCodeInvalidCpusetMems is generated when user provided cpuset mems |
|
| 841 |
+ // are invalid. |
|
| 842 |
+ ErrorCodeInvalidCpusetMems = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
| 843 |
+ Value: "INVALIDCPUSETMEMS", |
|
| 844 |
+ Message: "Invalid value %s for cpuset mems.", |
|
| 845 |
+ Description: "While verifying the container's 'HostConfig', CpusetMems value was in an incorrect format", |
|
| 846 |
+ HTTPStatusCode: http.StatusInternalServerError, |
|
| 847 |
+ }) |
|
| 848 |
+ |
|
| 849 |
+ // ErrorCodeNotAvailableCpusetCpus is generated when user provided cpuset |
|
| 850 |
+ // CPUs aren't available in the container's cgroup. |
|
| 851 |
+ ErrorCodeNotAvailableCpusetCpus = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
| 852 |
+ Value: "NOTAVAILABLECPUSETCPUS", |
|
| 853 |
+ Message: "Requested CPUs are not available - requested %s, available: %s.", |
|
| 854 |
+ Description: "While verifying the container's 'HostConfig', cpuset CPUs provided aren't available in the container's cgroup available set", |
|
| 855 |
+ HTTPStatusCode: http.StatusInternalServerError, |
|
| 856 |
+ }) |
|
| 857 |
+ |
|
| 858 |
+ // ErrorCodeNotAvailableCpusetMems is generated when user provided cpuset |
|
| 859 |
+ // memory nodes aren't available in the container's cgroup. |
|
| 860 |
+ ErrorCodeNotAvailableCpusetMems = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
| 861 |
+ Value: "NOTAVAILABLECPUSETMEMS", |
|
| 862 |
+ Message: "Requested memory nodes are not available - requested %s, available: %s.", |
|
| 863 |
+ Description: "While verifying the container's 'HostConfig', cpuset memory nodes provided aren't available in the container's cgroup available set", |
|
| 864 |
+ HTTPStatusCode: http.StatusInternalServerError, |
|
| 865 |
+ }) |
|
| 830 | 866 |
) |
| ... | ... |
@@ -1525,3 +1525,29 @@ func (s *DockerSuite) TestContainersApiGetContainersJSONEmpty(c *check.C) {
|
| 1525 | 1525 |
c.Fatalf("Expected empty response to be `[]`, got %q", string(body))
|
| 1526 | 1526 |
} |
| 1527 | 1527 |
} |
| 1528 |
+ |
|
| 1529 |
+func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C) {
|
|
| 1530 |
+ testRequires(c, DaemonIsLinux) |
|
| 1531 |
+ |
|
| 1532 |
+ c1 := struct {
|
|
| 1533 |
+ Image string |
|
| 1534 |
+ CpusetCpus string |
|
| 1535 |
+ }{"busybox", "1-42,,"}
|
|
| 1536 |
+ name := "wrong-cpuset-cpus" |
|
| 1537 |
+ status, body, err := sockRequest("POST", "/containers/create?name="+name, c1)
|
|
| 1538 |
+ c.Assert(err, check.IsNil) |
|
| 1539 |
+ c.Assert(status, check.Equals, http.StatusInternalServerError) |
|
| 1540 |
+ expected := "Invalid value 1-42,, for cpuset cpus.\n" |
|
| 1541 |
+ c.Assert(string(body), check.Equals, expected, check.Commentf("Expected output to contain %q, got %q", expected, string(body)))
|
|
| 1542 |
+ |
|
| 1543 |
+ c2 := struct {
|
|
| 1544 |
+ Image string |
|
| 1545 |
+ CpusetMems string |
|
| 1546 |
+ }{"busybox", "42-3,1--"}
|
|
| 1547 |
+ name = "wrong-cpuset-mems" |
|
| 1548 |
+ status, body, err = sockRequest("POST", "/containers/create?name="+name, c2)
|
|
| 1549 |
+ c.Assert(err, check.IsNil) |
|
| 1550 |
+ c.Assert(status, check.Equals, http.StatusInternalServerError) |
|
| 1551 |
+ expected = "Invalid value 42-3,1-- for cpuset mems.\n" |
|
| 1552 |
+ c.Assert(string(body), check.Equals, expected, check.Commentf("Expected output to contain %q, got %q", expected, string(body)))
|
|
| 1553 |
+} |
| ... | ... |
@@ -3407,3 +3407,17 @@ func (s *DockerSuite) TestRunStdinBlockedAfterContainerExit(c *check.C) {
|
| 3407 | 3407 |
c.Fatal("timeout waiting for command to exit")
|
| 3408 | 3408 |
} |
| 3409 | 3409 |
} |
| 3410 |
+ |
|
| 3411 |
+func (s *DockerSuite) TestRunWrongCpusetCpusFlagValue(c *check.C) {
|
|
| 3412 |
+ out, _, err := dockerCmdWithError("run", "--cpuset-cpus", "1-10,11--", "busybox", "true")
|
|
| 3413 |
+ c.Assert(err, check.NotNil) |
|
| 3414 |
+ expected := "Error response from daemon: Invalid value 1-10,11-- for cpuset cpus.\n" |
|
| 3415 |
+ c.Assert(out, check.Equals, expected, check.Commentf("Expected output to contain %q, got %q", expected, out))
|
|
| 3416 |
+} |
|
| 3417 |
+ |
|
| 3418 |
+func (s *DockerSuite) TestRunWrongCpusetMemsFlagValue(c *check.C) {
|
|
| 3419 |
+ out, _, err := dockerCmdWithError("run", "--cpuset-mems", "1-42--", "busybox", "true")
|
|
| 3420 |
+ c.Assert(err, check.NotNil) |
|
| 3421 |
+ expected := "Error response from daemon: Invalid value 1-42-- for cpuset mems.\n" |
|
| 3422 |
+ c.Assert(out, check.Equals, expected, check.Commentf("Expected output to contain %q, got %q", expected, out))
|
|
| 3423 |
+} |
| ... | ... |
@@ -9,11 +9,14 @@ import ( |
| 9 | 9 |
"os" |
| 10 | 10 |
"os/exec" |
| 11 | 11 |
"path/filepath" |
| 12 |
+ "strconv" |
|
| 12 | 13 |
"strings" |
| 13 | 14 |
"time" |
| 14 | 15 |
|
| 15 | 16 |
"github.com/docker/docker/pkg/integration/checker" |
| 16 | 17 |
"github.com/docker/docker/pkg/mount" |
| 18 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 19 |
+ "github.com/docker/docker/pkg/sysinfo" |
|
| 17 | 20 |
"github.com/go-check/check" |
| 18 | 21 |
"github.com/kr/pty" |
| 19 | 22 |
) |
| ... | ... |
@@ -370,3 +373,41 @@ func (s *DockerSuite) TestRunSwapLessThanMemoryLimit(c *check.C) {
|
| 370 | 370 |
c.Fatalf("Expected output to contain %q, not %q", out, expected)
|
| 371 | 371 |
} |
| 372 | 372 |
} |
| 373 |
+ |
|
| 374 |
+func (s *DockerSuite) TestRunInvalidCpusetCpusFlagValue(c *check.C) {
|
|
| 375 |
+ testRequires(c, cgroupCpuset) |
|
| 376 |
+ |
|
| 377 |
+ sysInfo := sysinfo.New(true) |
|
| 378 |
+ cpus, err := parsers.ParseUintList(sysInfo.Cpus) |
|
| 379 |
+ c.Assert(err, check.IsNil) |
|
| 380 |
+ var invalid int |
|
| 381 |
+ for i := 0; i <= len(cpus)+1; i++ {
|
|
| 382 |
+ if !cpus[i] {
|
|
| 383 |
+ invalid = i |
|
| 384 |
+ break |
|
| 385 |
+ } |
|
| 386 |
+ } |
|
| 387 |
+ out, _, err := dockerCmdWithError("run", "--cpuset-cpus", strconv.Itoa(invalid), "busybox", "true")
|
|
| 388 |
+ c.Assert(err, check.NotNil) |
|
| 389 |
+ expected := fmt.Sprintf("Error response from daemon: Requested CPUs are not available - requested %s, available: %s.\n", strconv.Itoa(invalid), sysInfo.Cpus)
|
|
| 390 |
+ c.Assert(out, check.Equals, expected, check.Commentf("Expected output to contain %q, got %q", expected, out))
|
|
| 391 |
+} |
|
| 392 |
+ |
|
| 393 |
+func (s *DockerSuite) TestRunInvalidCpusetMemsFlagValue(c *check.C) {
|
|
| 394 |
+ testRequires(c, cgroupCpuset) |
|
| 395 |
+ |
|
| 396 |
+ sysInfo := sysinfo.New(true) |
|
| 397 |
+ mems, err := parsers.ParseUintList(sysInfo.Mems) |
|
| 398 |
+ c.Assert(err, check.IsNil) |
|
| 399 |
+ var invalid int |
|
| 400 |
+ for i := 0; i <= len(mems)+1; i++ {
|
|
| 401 |
+ if !mems[i] {
|
|
| 402 |
+ invalid = i |
|
| 403 |
+ break |
|
| 404 |
+ } |
|
| 405 |
+ } |
|
| 406 |
+ out, _, err := dockerCmdWithError("run", "--cpuset-mems", strconv.Itoa(invalid), "busybox", "true")
|
|
| 407 |
+ c.Assert(err, check.NotNil) |
|
| 408 |
+ expected := fmt.Sprintf("Error response from daemon: Requested memory nodes are not available - requested %s, available: %s.\n", strconv.Itoa(invalid), sysInfo.Mems)
|
|
| 409 |
+ c.Assert(out, check.Equals, expected, check.Commentf("Expected output to contain %q, got %q", expected, out))
|
|
| 410 |
+} |
| ... | ... |
@@ -127,7 +127,7 @@ func PartParser(template, data string) (map[string]string, error) {
|
| 127 | 127 |
out = make(map[string]string, len(templateParts)) |
| 128 | 128 |
) |
| 129 | 129 |
if len(parts) != len(templateParts) {
|
| 130 |
- return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
|
|
| 130 |
+ return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
|
|
| 131 | 131 |
} |
| 132 | 132 |
|
| 133 | 133 |
for i, t := range templateParts {
|
| ... | ... |
@@ -196,3 +196,53 @@ func ParseLink(val string) (string, string, error) {
|
| 196 | 196 |
} |
| 197 | 197 |
return arr[0], arr[1], nil |
| 198 | 198 |
} |
| 199 |
+ |
|
| 200 |
+// ParseUintList parses and validates the specified string as the value |
|
| 201 |
+// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be |
|
| 202 |
+// one of the formats below. Note that duplicates are actually allowed in the |
|
| 203 |
+// input string. It returns a `map[int]bool` with available elements from `val` |
|
| 204 |
+// set to `true`. |
|
| 205 |
+// Supported formats: |
|
| 206 |
+// 7 |
|
| 207 |
+// 1-6 |
|
| 208 |
+// 0,3-4,7,8-10 |
|
| 209 |
+// 0-0,0,1-7 |
|
| 210 |
+// 03,1-3 <- this is gonna get parsed as [1,2,3] |
|
| 211 |
+// 3,2,1 |
|
| 212 |
+// 0-2,3,1 |
|
| 213 |
+func ParseUintList(val string) (map[int]bool, error) {
|
|
| 214 |
+ if val == "" {
|
|
| 215 |
+ return map[int]bool{}, nil
|
|
| 216 |
+ } |
|
| 217 |
+ |
|
| 218 |
+ availableInts := make(map[int]bool) |
|
| 219 |
+ split := strings.Split(val, ",") |
|
| 220 |
+ errInvalidFormat := fmt.Errorf("invalid format: %s", val)
|
|
| 221 |
+ |
|
| 222 |
+ for _, r := range split {
|
|
| 223 |
+ if !strings.Contains(r, "-") {
|
|
| 224 |
+ v, err := strconv.Atoi(r) |
|
| 225 |
+ if err != nil {
|
|
| 226 |
+ return nil, errInvalidFormat |
|
| 227 |
+ } |
|
| 228 |
+ availableInts[v] = true |
|
| 229 |
+ } else {
|
|
| 230 |
+ split := strings.SplitN(r, "-", 2) |
|
| 231 |
+ min, err := strconv.Atoi(split[0]) |
|
| 232 |
+ if err != nil {
|
|
| 233 |
+ return nil, errInvalidFormat |
|
| 234 |
+ } |
|
| 235 |
+ max, err := strconv.Atoi(split[1]) |
|
| 236 |
+ if err != nil {
|
|
| 237 |
+ return nil, errInvalidFormat |
|
| 238 |
+ } |
|
| 239 |
+ if max < min {
|
|
| 240 |
+ return nil, errInvalidFormat |
|
| 241 |
+ } |
|
| 242 |
+ for i := min; i <= max; i++ {
|
|
| 243 |
+ availableInts[i] = true |
|
| 244 |
+ } |
|
| 245 |
+ } |
|
| 246 |
+ } |
|
| 247 |
+ return availableInts, nil |
|
| 248 |
+} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package parsers |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "reflect" |
|
| 4 | 5 |
"runtime" |
| 5 | 6 |
"strings" |
| 6 | 7 |
"testing" |
| ... | ... |
@@ -238,3 +239,40 @@ func TestParseLink(t *testing.T) {
|
| 238 | 238 |
t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
|
| 239 | 239 |
} |
| 240 | 240 |
} |
| 241 |
+ |
|
| 242 |
+func TestParseUintList(t *testing.T) {
|
|
| 243 |
+ valids := map[string]map[int]bool{
|
|
| 244 |
+ "": {},
|
|
| 245 |
+ "7": {7: true},
|
|
| 246 |
+ "1-6": {1: true, 2: true, 3: true, 4: true, 5: true, 6: true},
|
|
| 247 |
+ "0-7": {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true},
|
|
| 248 |
+ "0,3-4,7,8-10": {0: true, 3: true, 4: true, 7: true, 8: true, 9: true, 10: true},
|
|
| 249 |
+ "0-0,0,1-4": {0: true, 1: true, 2: true, 3: true, 4: true},
|
|
| 250 |
+ "03,1-3": {1: true, 2: true, 3: true},
|
|
| 251 |
+ "3,2,1": {1: true, 2: true, 3: true},
|
|
| 252 |
+ "0-2,3,1": {0: true, 1: true, 2: true, 3: true},
|
|
| 253 |
+ } |
|
| 254 |
+ for k, v := range valids {
|
|
| 255 |
+ out, err := ParseUintList(k) |
|
| 256 |
+ if err != nil {
|
|
| 257 |
+ t.Fatalf("Expected not to fail, got %v", err)
|
|
| 258 |
+ } |
|
| 259 |
+ if !reflect.DeepEqual(out, v) {
|
|
| 260 |
+ t.Fatalf("Expected %v, got %v", v, out)
|
|
| 261 |
+ } |
|
| 262 |
+ } |
|
| 263 |
+ |
|
| 264 |
+ invalids := []string{
|
|
| 265 |
+ "this", |
|
| 266 |
+ "1--", |
|
| 267 |
+ "1-10,,10", |
|
| 268 |
+ "10-1", |
|
| 269 |
+ "-1", |
|
| 270 |
+ "-1,0", |
|
| 271 |
+ } |
|
| 272 |
+ for _, v := range invalids {
|
|
| 273 |
+ if out, err := ParseUintList(v); err == nil {
|
|
| 274 |
+ t.Fatalf("Expected failure with %s but got %v", v, out)
|
|
| 275 |
+ } |
|
| 276 |
+ } |
|
| 277 |
+} |
| ... | ... |
@@ -1,5 +1,7 @@ |
| 1 | 1 |
package sysinfo |
| 2 | 2 |
|
| 3 |
+import "github.com/docker/docker/pkg/parsers" |
|
| 4 |
+ |
|
| 3 | 5 |
// SysInfo stores information about which features a kernel supports. |
| 4 | 6 |
// TODO Windows: Factor out platform specific capabilities. |
| 5 | 7 |
type SysInfo struct {
|
| ... | ... |
@@ -63,4 +65,41 @@ type cgroupBlkioInfo struct {
|
| 63 | 63 |
type cgroupCpusetInfo struct {
|
| 64 | 64 |
// Whether Cpuset is supported or not |
| 65 | 65 |
Cpuset bool |
| 66 |
+ |
|
| 67 |
+ // Available Cpuset's cpus |
|
| 68 |
+ Cpus string |
|
| 69 |
+ |
|
| 70 |
+ // Available Cpuset's memory nodes |
|
| 71 |
+ Mems string |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+// IsCpusetCpusAvailable returns `true` if the provided string set is contained |
|
| 75 |
+// in cgroup's cpuset.cpus set, `false` otherwise. |
|
| 76 |
+// If error is not nil a parsing error occurred. |
|
| 77 |
+func (c cgroupCpusetInfo) IsCpusetCpusAvailable(provided string) (bool, error) {
|
|
| 78 |
+ return isCpusetListAvailable(provided, c.Cpus) |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+// IsCpusetMemsAvailable returns `true` if the provided string set is contained |
|
| 82 |
+// in cgroup's cpuset.mems set, `false` otherwise. |
|
| 83 |
+// If error is not nil a parsing error occurred. |
|
| 84 |
+func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) {
|
|
| 85 |
+ return isCpusetListAvailable(provided, c.Mems) |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+func isCpusetListAvailable(provided, available string) (bool, error) {
|
|
| 89 |
+ parsedProvided, err := parsers.ParseUintList(provided) |
|
| 90 |
+ if err != nil {
|
|
| 91 |
+ return false, err |
|
| 92 |
+ } |
|
| 93 |
+ parsedAvailable, err := parsers.ParseUintList(available) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ return false, err |
|
| 96 |
+ } |
|
| 97 |
+ for k := range parsedProvided {
|
|
| 98 |
+ if !parsedAvailable[k] {
|
|
| 99 |
+ return false, nil |
|
| 100 |
+ } |
|
| 101 |
+ } |
|
| 102 |
+ return true, nil |
|
| 66 | 103 |
} |
| ... | ... |
@@ -126,7 +126,7 @@ func checkCgroupBlkioInfo(quiet bool) cgroupBlkioInfo {
|
| 126 | 126 |
|
| 127 | 127 |
// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point. |
| 128 | 128 |
func checkCgroupCpusetInfo(quiet bool) cgroupCpusetInfo {
|
| 129 |
- _, err := cgroups.FindCgroupMountpoint("cpuset")
|
|
| 129 |
+ mountPoint, err := cgroups.FindCgroupMountpoint("cpuset")
|
|
| 130 | 130 |
if err != nil {
|
| 131 | 131 |
if !quiet {
|
| 132 | 132 |
logrus.Warn(err) |
| ... | ... |
@@ -134,7 +134,21 @@ func checkCgroupCpusetInfo(quiet bool) cgroupCpusetInfo {
|
| 134 | 134 |
return cgroupCpusetInfo{}
|
| 135 | 135 |
} |
| 136 | 136 |
|
| 137 |
- return cgroupCpusetInfo{Cpuset: true}
|
|
| 137 |
+ cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus")) |
|
| 138 |
+ if err != nil {
|
|
| 139 |
+ return cgroupCpusetInfo{}
|
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems")) |
|
| 143 |
+ if err != nil {
|
|
| 144 |
+ return cgroupCpusetInfo{}
|
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ return cgroupCpusetInfo{
|
|
| 148 |
+ Cpuset: true, |
|
| 149 |
+ Cpus: strings.TrimSpace(string(cpus)), |
|
| 150 |
+ Mems: strings.TrimSpace(string(mems)), |
|
| 151 |
+ } |
|
| 138 | 152 |
} |
| 139 | 153 |
|
| 140 | 154 |
func cgroupEnabled(mountPoint, name string) bool {
|
| 141 | 155 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,26 @@ |
| 0 |
+package sysinfo |
|
| 1 |
+ |
|
| 2 |
+import "testing" |
|
| 3 |
+ |
|
| 4 |
+func TestIsCpusetListAvailable(t *testing.T) {
|
|
| 5 |
+ cases := []struct {
|
|
| 6 |
+ provided string |
|
| 7 |
+ available string |
|
| 8 |
+ res bool |
|
| 9 |
+ err bool |
|
| 10 |
+ }{
|
|
| 11 |
+ {"1", "0-4", true, false},
|
|
| 12 |
+ {"01,3", "0-4", true, false},
|
|
| 13 |
+ {"", "0-7", true, false},
|
|
| 14 |
+ {"1--42", "0-7", false, true},
|
|
| 15 |
+ {"1-42", "00-1,8,,9", false, true},
|
|
| 16 |
+ {"1,41-42", "43,45", false, false},
|
|
| 17 |
+ {"0-3", "", false, false},
|
|
| 18 |
+ } |
|
| 19 |
+ for _, c := range cases {
|
|
| 20 |
+ r, err := isCpusetListAvailable(c.provided, c.available) |
|
| 21 |
+ if (c.err && err == nil) && r != c.res {
|
|
| 22 |
+ t.Fatalf("Expected pair: %v, %v for %s, %s. Got %v, %v instead", c.res, c.err, c.provided, c.available, (c.err && err == nil), r)
|
|
| 23 |
+ } |
|
| 24 |
+ } |
|
| 25 |
+} |