pkg/sysinfo: internalize parsing cpusets

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2024-12-31 15:37:03 +01:00
parent aa696ffbb1
commit 2282279180
2 changed files with 112 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"strconv"
"strings"
"sync"
@@ -12,7 +13,6 @@ import (
"github.com/containerd/cgroups/v3/cgroup1"
"github.com/containerd/containerd/pkg/seccomp"
"github.com/containerd/log"
"github.com/docker/docker/pkg/parsers"
"github.com/moby/sys/mountinfo"
)
@@ -320,7 +320,7 @@ func readProcBool(path string) bool {
const defaultMaxCPUs = 8192
func isCpusetListAvailable(requested, available string) (bool, error) {
parsedAvailable, err := parsers.ParseUintList(available)
parsedAvailable, err := parseUintList(available, 0)
if err != nil {
return false, err
}
@@ -341,7 +341,7 @@ func isCpusetListAvailable(requested, available string) (bool, error) {
maxCPUs = m
}
}
parsedRequested, err := parsers.ParseUintListMaximum(requested, maxCPUs)
parsedRequested, err := parseUintList(requested, maxCPUs)
if err != nil {
return false, err
}
@@ -352,3 +352,61 @@ func isCpusetListAvailable(requested, available string) (bool, error) {
}
return true, nil
}
// parseUintList parses and validates the specified string as the value
// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be
// one of the formats below. Note that duplicates are actually allowed in the
// input string. It returns a `map[int]bool` with available elements from `val`
// set to `true`. Values larger than `maximum` cause an error if max is non zero,
// in order to stop the map becoming excessively large.
// Supported formats:
//
// 7
// 1-6
// 0,3-4,7,8-10
// 0-0,0,1-7
// 03,1-3 <- this is gonna get parsed as [1,2,3]
// 3,2,1
// 0-2,3,1
func parseUintList(val string, maximum int) (map[int]bool, error) {
if val == "" {
return map[int]bool{}, nil
}
availableInts := make(map[int]bool)
split := strings.Split(val, ",")
errInvalidFormat := fmt.Errorf("invalid format: %s", val)
for _, r := range split {
if !strings.Contains(r, "-") {
v, err := strconv.Atoi(r)
if err != nil {
return nil, errInvalidFormat
}
if maximum != 0 && v > maximum {
return nil, fmt.Errorf("value of out range, maximum is %d", maximum)
}
availableInts[v] = true
} else {
minS, maxS, _ := strings.Cut(r, "-")
minAvailable, err := strconv.Atoi(minS)
if err != nil {
return nil, errInvalidFormat
}
maxAvailable, err := strconv.Atoi(maxS)
if err != nil {
return nil, errInvalidFormat
}
if maxAvailable < minAvailable {
return nil, errInvalidFormat
}
if maximum != 0 && maxAvailable > maximum {
return nil, fmt.Errorf("value of out range, maximum is %d", maximum)
}
for i := minAvailable; i <= maxAvailable; i++ {
availableInts[i] = true
}
}
}
return availableInts, nil
}

View File

@@ -3,6 +3,7 @@ package sysinfo // import "github.com/docker/docker/pkg/sysinfo"
import (
"os"
"path/filepath"
"reflect"
"testing"
"github.com/containerd/containerd/pkg/seccomp"
@@ -93,3 +94,53 @@ func TestIsCpusetListAvailable(t *testing.T) {
}
}
}
func TestParseUintList(t *testing.T) {
valids := map[string]map[int]bool{
"": {},
"7": {7: true},
"1-6": {1: true, 2: true, 3: true, 4: true, 5: true, 6: true},
"0-7": {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true},
"0,3-4,7,8-10": {0: true, 3: true, 4: true, 7: true, 8: true, 9: true, 10: true},
"0-0,0,1-4": {0: true, 1: true, 2: true, 3: true, 4: true},
"03,1-3": {1: true, 2: true, 3: true},
"3,2,1": {1: true, 2: true, 3: true},
"0-2,3,1": {0: true, 1: true, 2: true, 3: true},
}
for k, v := range valids {
out, err := parseUintList(k, 0)
if err != nil {
t.Fatalf("Expected not to fail, got %v", err)
}
if !reflect.DeepEqual(out, v) {
t.Fatalf("Expected %v, got %v", v, out)
}
}
invalids := []string{
"this",
"1--",
"1-10,,10",
"10-1",
"-1",
"-1,0",
}
for _, v := range invalids {
if out, err := parseUintList(v, 0); err == nil {
t.Fatalf("Expected failure with %s but got %v", v, out)
}
}
}
func TestParseUintListMaximumLimits(t *testing.T) {
v := "10,1000"
if _, err := parseUintList(v, 0); err != nil {
t.Fatalf("Expected not to fail, got %v", err)
}
if _, err := parseUintList(v, 1000); err != nil {
t.Fatalf("Expected not to fail, got %v", err)
}
if out, err := parseUintList(v, 100); err == nil {
t.Fatalf("Expected failure with %s but got %v", v, out)
}
}