diff options
-rw-r--r-- | runsc/boot/loader.go | 18 | ||||
-rw-r--r-- | runsc/specutils/BUILD | 1 | ||||
-rw-r--r-- | runsc/specutils/cpu.go | 90 |
3 files changed, 102 insertions, 7 deletions
diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 623d04171..f906c9f95 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -20,7 +20,6 @@ import ( "math/rand" "os" "os/signal" - "runtime" "sync" "sync/atomic" "syscall" @@ -201,15 +200,20 @@ func New(spec *specs.Spec, conf *Config, controllerFD, deviceFD int, ioFDs []int caps, auth.NewRootUserNamespace()) + // Get CPU numbers from spec. + cpuNum, err := specutils.CalculateCPUNumber(spec) + if err != nil { + return nil, fmt.Errorf("cannot get cpus from spec: %v", err) + } + // Initiate the Kernel object, which is required by the Context passed // to createVFS in order to mount (among other things) procfs. if err = k.Init(kernel.InitKernelArgs{ - FeatureSet: cpuid.HostFeatureSet(), - Timekeeper: tk, - RootUserNamespace: creds.UserNamespace, - NetworkStack: networkStack, - // TODO: use number of logical processors from cgroups. - ApplicationCores: uint(runtime.NumCPU()), + FeatureSet: cpuid.HostFeatureSet(), + Timekeeper: tk, + RootUserNamespace: creds.UserNamespace, + NetworkStack: networkStack, + ApplicationCores: uint(cpuNum), Vdso: vdso, RootUTSNamespace: kernel.NewUTSNamespace(spec.Hostname, "", creds.UserNamespace), RootIPCNamespace: kernel.NewIPCNamespace(creds.UserNamespace), diff --git a/runsc/specutils/BUILD b/runsc/specutils/BUILD index e73b2293f..f1a99ce48 100644 --- a/runsc/specutils/BUILD +++ b/runsc/specutils/BUILD @@ -5,6 +5,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "specutils", srcs = [ + "cpu.go", "namespace.go", "specutils.go", ], diff --git a/runsc/specutils/cpu.go b/runsc/specutils/cpu.go new file mode 100644 index 000000000..9abe26b64 --- /dev/null +++ b/runsc/specutils/cpu.go @@ -0,0 +1,90 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package specutils + +import ( + "fmt" + "runtime" + "strconv" + "strings" + + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +// CalculateCPUNumber calculates the number of CPUs that should be exposed +// inside the sandbox. +func CalculateCPUNumber(spec *specs.Spec) (int, error) { + // If spec does not contain CPU field, then return the number of host CPUs. + if spec == nil || spec.Linux == nil || spec.Linux.Resources == nil || spec.Linux.Resources.CPU == nil { + return runtime.NumCPU(), nil + } + cpuSpec := spec.Linux.Resources.CPU + + // If cpuSpec.Cpus is specified, then parse and return that. They must be in + // the list format for cpusets, which is "a comma-separated list of CPU + // numbers and ranges of numbers, in ASCII decimal." --man 7 cpuset. + cpus := cpuSpec.Cpus + if cpus != "" { + cpuNum := 0 + for _, subs := range strings.Split(cpus, ",") { + result, err := parseCPUNumber(subs) + if err != nil { + return 0, err + } + cpuNum += result + } + return cpuNum, nil + } + + // If CPU.Quota and CPU.Period are specified, we can divide them to get an + // approximation of the number of CPUs needed. + if cpuSpec.Quota != nil && cpuSpec.Period != nil && *cpuSpec.Period != 0 { + cpuQuota := *cpuSpec.Quota + cpuPeriod := *cpuSpec.Period + return int(cpuQuota)/int(cpuPeriod) + 1, nil + } + + // Default to number of host cpus. + return runtime.NumCPU(), nil +} + +// parseCPUNumber converts a cpuset string into the number of cpus included in +// the string , e.g. "3-6" -> 4. +func parseCPUNumber(cpus string) (int, error) { + switch cpusSlice := strings.Split(cpus, "-"); len(cpusSlice) { + case 1: + // cpus is not a range. We must only check that it is a valid number. + if _, err := strconv.Atoi(cpus); err != nil { + return 0, fmt.Errorf("invalid individual cpu number %q", cpus) + } + return 1, nil + case 2: + // cpus is a range. We must check that start and end are valid numbers, + // and calculate their difference (inclusively). + first, err := strconv.Atoi(cpusSlice[0]) + if err != nil || first < 0 { + return 0, fmt.Errorf("invalid first cpu number %q in range %q", cpusSlice[0], cpus) + } + last, err := strconv.Atoi(cpusSlice[1]) + if err != nil || last < 0 { + return 0, fmt.Errorf("invalid last cpu number %q in range %q", cpusSlice[1], cpus) + } + cpuNum := last - first + 1 + if cpuNum <= 0 { + return 0, fmt.Errorf("cpu range %q does not include positive number of cpus", cpus) + } + } + return 0, fmt.Errorf("invalid cpu string %q", cpus) +} |