Skip to content

Commit 2282279

Browse files
committed
pkg/sysinfo: internalize parsing cpusets
Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent aa696ff commit 2282279

2 files changed

Lines changed: 112 additions & 3 deletions

File tree

pkg/sysinfo/sysinfo_linux.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import (
55
"fmt"
66
"os"
77
"path"
8+
"strconv"
89
"strings"
910
"sync"
1011

1112
"github.com/containerd/cgroups/v3"
1213
"github.com/containerd/cgroups/v3/cgroup1"
1314
"github.com/containerd/containerd/pkg/seccomp"
1415
"github.com/containerd/log"
15-
"github.com/docker/docker/pkg/parsers"
1616
"github.com/moby/sys/mountinfo"
1717
)
1818

@@ -320,7 +320,7 @@ func readProcBool(path string) bool {
320320
const defaultMaxCPUs = 8192
321321

322322
func isCpusetListAvailable(requested, available string) (bool, error) {
323-
parsedAvailable, err := parsers.ParseUintList(available)
323+
parsedAvailable, err := parseUintList(available, 0)
324324
if err != nil {
325325
return false, err
326326
}
@@ -341,7 +341,7 @@ func isCpusetListAvailable(requested, available string) (bool, error) {
341341
maxCPUs = m
342342
}
343343
}
344-
parsedRequested, err := parsers.ParseUintListMaximum(requested, maxCPUs)
344+
parsedRequested, err := parseUintList(requested, maxCPUs)
345345
if err != nil {
346346
return false, err
347347
}
@@ -352,3 +352,61 @@ func isCpusetListAvailable(requested, available string) (bool, error) {
352352
}
353353
return true, nil
354354
}
355+
356+
// parseUintList parses and validates the specified string as the value
357+
// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be
358+
// one of the formats below. Note that duplicates are actually allowed in the
359+
// input string. It returns a `map[int]bool` with available elements from `val`
360+
// set to `true`. Values larger than `maximum` cause an error if max is non zero,
361+
// in order to stop the map becoming excessively large.
362+
// Supported formats:
363+
//
364+
// 7
365+
// 1-6
366+
// 0,3-4,7,8-10
367+
// 0-0,0,1-7
368+
// 03,1-3 <- this is gonna get parsed as [1,2,3]
369+
// 3,2,1
370+
// 0-2,3,1
371+
func parseUintList(val string, maximum int) (map[int]bool, error) {
372+
if val == "" {
373+
return map[int]bool{}, nil
374+
}
375+
376+
availableInts := make(map[int]bool)
377+
split := strings.Split(val, ",")
378+
errInvalidFormat := fmt.Errorf("invalid format: %s", val)
379+
380+
for _, r := range split {
381+
if !strings.Contains(r, "-") {
382+
v, err := strconv.Atoi(r)
383+
if err != nil {
384+
return nil, errInvalidFormat
385+
}
386+
if maximum != 0 && v > maximum {
387+
return nil, fmt.Errorf("value of out range, maximum is %d", maximum)
388+
}
389+
availableInts[v] = true
390+
} else {
391+
minS, maxS, _ := strings.Cut(r, "-")
392+
minAvailable, err := strconv.Atoi(minS)
393+
if err != nil {
394+
return nil, errInvalidFormat
395+
}
396+
maxAvailable, err := strconv.Atoi(maxS)
397+
if err != nil {
398+
return nil, errInvalidFormat
399+
}
400+
if maxAvailable < minAvailable {
401+
return nil, errInvalidFormat
402+
}
403+
if maximum != 0 && maxAvailable > maximum {
404+
return nil, fmt.Errorf("value of out range, maximum is %d", maximum)
405+
}
406+
for i := minAvailable; i <= maxAvailable; i++ {
407+
availableInts[i] = true
408+
}
409+
}
410+
}
411+
return availableInts, nil
412+
}

pkg/sysinfo/sysinfo_linux_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package sysinfo // import "github.com/docker/docker/pkg/sysinfo"
33
import (
44
"os"
55
"path/filepath"
6+
"reflect"
67
"testing"
78

89
"github.com/containerd/containerd/pkg/seccomp"
@@ -93,3 +94,53 @@ func TestIsCpusetListAvailable(t *testing.T) {
9394
}
9495
}
9596
}
97+
98+
func TestParseUintList(t *testing.T) {
99+
valids := map[string]map[int]bool{
100+
"": {},
101+
"7": {7: true},
102+
"1-6": {1: true, 2: true, 3: true, 4: true, 5: true, 6: true},
103+
"0-7": {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true},
104+
"0,3-4,7,8-10": {0: true, 3: true, 4: true, 7: true, 8: true, 9: true, 10: true},
105+
"0-0,0,1-4": {0: true, 1: true, 2: true, 3: true, 4: true},
106+
"03,1-3": {1: true, 2: true, 3: true},
107+
"3,2,1": {1: true, 2: true, 3: true},
108+
"0-2,3,1": {0: true, 1: true, 2: true, 3: true},
109+
}
110+
for k, v := range valids {
111+
out, err := parseUintList(k, 0)
112+
if err != nil {
113+
t.Fatalf("Expected not to fail, got %v", err)
114+
}
115+
if !reflect.DeepEqual(out, v) {
116+
t.Fatalf("Expected %v, got %v", v, out)
117+
}
118+
}
119+
120+
invalids := []string{
121+
"this",
122+
"1--",
123+
"1-10,,10",
124+
"10-1",
125+
"-1",
126+
"-1,0",
127+
}
128+
for _, v := range invalids {
129+
if out, err := parseUintList(v, 0); err == nil {
130+
t.Fatalf("Expected failure with %s but got %v", v, out)
131+
}
132+
}
133+
}
134+
135+
func TestParseUintListMaximumLimits(t *testing.T) {
136+
v := "10,1000"
137+
if _, err := parseUintList(v, 0); err != nil {
138+
t.Fatalf("Expected not to fail, got %v", err)
139+
}
140+
if _, err := parseUintList(v, 1000); err != nil {
141+
t.Fatalf("Expected not to fail, got %v", err)
142+
}
143+
if out, err := parseUintList(v, 100); err == nil {
144+
t.Fatalf("Expected failure with %s but got %v", v, out)
145+
}
146+
}

0 commit comments

Comments
 (0)