mirror of
https://github.com/containers/podman.git
synced 2025-05-20 08:36:23 +08:00

create a container in podmanv2 using specgen approach. this is the core implementation and still has quite a bit of code commented out specifically around volumes, devices, and namespaces. need contributions from smes on these parts. Signed-off-by: Brent Baude <bbaude@redhat.com>
648 lines
17 KiB
Go
648 lines
17 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/image/v5/manifest"
|
|
"github.com/containers/libpod/cmd/podmanV2/parse"
|
|
"github.com/containers/libpod/libpod"
|
|
ann "github.com/containers/libpod/pkg/annotations"
|
|
envLib "github.com/containers/libpod/pkg/env"
|
|
ns "github.com/containers/libpod/pkg/namespaces"
|
|
"github.com/containers/libpod/pkg/specgen"
|
|
systemdGen "github.com/containers/libpod/pkg/systemd/generate"
|
|
"github.com/containers/libpod/pkg/util"
|
|
"github.com/docker/go-units"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error {
|
|
var (
|
|
err error
|
|
//namespaces map[string]string
|
|
)
|
|
|
|
// validate flags as needed
|
|
if err := c.validate(); err != nil {
|
|
return nil
|
|
}
|
|
|
|
inputCommand := args[1:]
|
|
if len(c.HealthCmd) > 0 {
|
|
s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s.IDMappings, err = util.ParseIDMapping(ns.UsernsMode(c.UserNS), c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if m := c.Memory; len(m) > 0 {
|
|
ml, err := units.RAMInBytes(m)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid value for memory")
|
|
}
|
|
s.ResourceLimits.Memory.Limit = &ml
|
|
}
|
|
if m := c.MemoryReservation; len(m) > 0 {
|
|
mr, err := units.RAMInBytes(m)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid value for memory")
|
|
}
|
|
s.ResourceLimits.Memory.Reservation = &mr
|
|
}
|
|
if m := c.MemorySwap; len(m) > 0 {
|
|
var ms int64
|
|
if m == "-1" {
|
|
ms = int64(-1)
|
|
s.ResourceLimits.Memory.Swap = &ms
|
|
} else {
|
|
ms, err = units.RAMInBytes(m)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid value for memory")
|
|
}
|
|
}
|
|
s.ResourceLimits.Memory.Swap = &ms
|
|
}
|
|
if m := c.KernelMemory; len(m) > 0 {
|
|
mk, err := units.RAMInBytes(m)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid value for kernel-memory")
|
|
}
|
|
s.ResourceLimits.Memory.Kernel = &mk
|
|
}
|
|
if b := c.BlkIOWeight; len(b) > 0 {
|
|
u, err := strconv.ParseUint(b, 10, 16)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "invalid value for blkio-weight")
|
|
}
|
|
nu := uint16(u)
|
|
s.ResourceLimits.BlockIO.Weight = &nu
|
|
}
|
|
|
|
s.Terminal = c.TTY
|
|
ep, err := ExposedPorts(c.Expose, c.Net.PublishPorts, c.PublishAll, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.PortMappings = ep
|
|
s.Pod = c.Pod
|
|
|
|
//s.CgroupNS = specgen.Namespace{
|
|
// NSMode: ,
|
|
// Value: "",
|
|
//}
|
|
|
|
//s.UserNS = specgen.Namespace{}
|
|
|
|
// Kernel Namespaces
|
|
// TODO Fix handling of namespace from pod
|
|
// Instead of integrating here, should be done in libpod
|
|
// However, that also involves setting up security opts
|
|
// when the pod's namespace is integrated
|
|
//namespaces = map[string]string{
|
|
// "cgroup": c.CGroupsNS,
|
|
// "pid": c.PID,
|
|
// //"net": c.Net.Network.Value, // TODO need help here
|
|
// "ipc": c.IPC,
|
|
// "user": c.User,
|
|
// "uts": c.UTS,
|
|
//}
|
|
//
|
|
//if len(c.PID) > 0 {
|
|
// split := strings.SplitN(c.PID, ":", 2)
|
|
// // need a way to do thsi
|
|
// specgen.Namespace{
|
|
// NSMode: split[0],
|
|
// }
|
|
// //Value: split1 if len allows
|
|
//}
|
|
// TODO this is going to have be done after things like pod creation are done because
|
|
// pod creation changes these values.
|
|
//pidMode := ns.PidMode(namespaces["pid"])
|
|
//usernsMode := ns.UsernsMode(namespaces["user"])
|
|
//utsMode := ns.UTSMode(namespaces["uts"])
|
|
//cgroupMode := ns.CgroupMode(namespaces["cgroup"])
|
|
//ipcMode := ns.IpcMode(namespaces["ipc"])
|
|
//// Make sure if network is set to container namespace, port binding is not also being asked for
|
|
//netMode := ns.NetworkMode(namespaces["net"])
|
|
//if netMode.IsContainer() {
|
|
// if len(portBindings) > 0 {
|
|
// return nil, errors.Errorf("cannot set port bindings on an existing container network namespace")
|
|
// }
|
|
//}
|
|
|
|
// TODO Remove when done with namespaces for realz
|
|
// Setting a default for IPC to get this working
|
|
s.IpcNS = specgen.Namespace{
|
|
NSMode: specgen.Private,
|
|
Value: "",
|
|
}
|
|
|
|
// TODO this is going to have to be done the libpod/server end of things
|
|
// USER
|
|
//user := c.String("user")
|
|
//if user == "" {
|
|
// switch {
|
|
// case usernsMode.IsKeepID():
|
|
// user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
|
|
// case data == nil:
|
|
// user = "0"
|
|
// default:
|
|
// user = data.Config.User
|
|
// }
|
|
//}
|
|
|
|
// STOP SIGNAL
|
|
signalString := "TERM"
|
|
if sig := c.StopSignal; len(sig) > 0 {
|
|
signalString = sig
|
|
}
|
|
stopSignal, err := util.ParseSignal(signalString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.StopSignal = &stopSignal
|
|
|
|
// ENVIRONMENT VARIABLES
|
|
//
|
|
// Precedence order (higher index wins):
|
|
// 1) env-host, 2) image data, 3) env-file, 4) env
|
|
env := map[string]string{
|
|
"container": "podman",
|
|
}
|
|
|
|
// First transform the os env into a map. We need it for the labels later in
|
|
// any case.
|
|
osEnv, err := envLib.ParseSlice(os.Environ())
|
|
if err != nil {
|
|
return errors.Wrap(err, "error parsing host environment variables")
|
|
}
|
|
|
|
if c.EnvHost {
|
|
env = envLib.Join(env, osEnv)
|
|
}
|
|
// env-file overrides any previous variables
|
|
for _, f := range c.EnvFile {
|
|
fileEnv, err := envLib.ParseFile(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// File env is overridden by env.
|
|
env = envLib.Join(env, fileEnv)
|
|
}
|
|
|
|
// env overrides any previous variables
|
|
if cmdLineEnv := c.env; len(cmdLineEnv) > 0 {
|
|
parsedEnv, err := envLib.ParseSlice(cmdLineEnv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
env = envLib.Join(env, parsedEnv)
|
|
}
|
|
s.Env = env
|
|
|
|
// LABEL VARIABLES
|
|
labels, err := parse.GetAllLabels(c.LabelFile, c.Label)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to process labels")
|
|
}
|
|
|
|
if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists {
|
|
labels[systemdGen.EnvVariable] = systemdUnit
|
|
}
|
|
|
|
s.Labels = labels
|
|
|
|
// ANNOTATIONS
|
|
annotations := make(map[string]string)
|
|
|
|
// First, add our default annotations
|
|
annotations[ann.TTY] = "false"
|
|
if c.TTY {
|
|
annotations[ann.TTY] = "true"
|
|
}
|
|
|
|
// Last, add user annotations
|
|
for _, annotation := range c.Annotation {
|
|
splitAnnotation := strings.SplitN(annotation, "=", 2)
|
|
if len(splitAnnotation) < 2 {
|
|
return errors.Errorf("Annotations must be formatted KEY=VALUE")
|
|
}
|
|
annotations[splitAnnotation[0]] = splitAnnotation[1]
|
|
}
|
|
s.Annotations = annotations
|
|
|
|
workDir := "/"
|
|
if wd := c.Workdir; len(wd) > 0 {
|
|
workDir = wd
|
|
}
|
|
s.WorkDir = workDir
|
|
entrypoint := []string{}
|
|
userCommand := []string{}
|
|
if ep := c.Entrypoint; len(ep) > 0 {
|
|
// Check if entrypoint specified is json
|
|
if err := json.Unmarshal([]byte(c.Entrypoint), &entrypoint); err != nil {
|
|
entrypoint = append(entrypoint, ep)
|
|
}
|
|
}
|
|
|
|
var command []string
|
|
|
|
// Build the command
|
|
// If we have an entry point, it goes first
|
|
if len(entrypoint) > 0 {
|
|
command = entrypoint
|
|
}
|
|
if len(inputCommand) > 0 {
|
|
// User command overrides data CMD
|
|
command = append(command, inputCommand...)
|
|
userCommand = append(userCommand, inputCommand...)
|
|
}
|
|
|
|
if len(inputCommand) > 0 {
|
|
s.Command = userCommand
|
|
} else {
|
|
s.Command = command
|
|
}
|
|
|
|
// SHM Size
|
|
shmSize, err := units.FromHumanSize(c.ShmSize)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to translate --shm-size")
|
|
}
|
|
s.ShmSize = &shmSize
|
|
s.HostAdd = c.Net.AddHosts
|
|
s.DNSServer = c.Net.DNSServers
|
|
s.DNSSearch = c.Net.DNSSearch
|
|
s.DNSOption = c.Net.DNSOptions
|
|
|
|
// deferred, must be added on libpod side
|
|
//var ImageVolumes map[string]struct{}
|
|
//if data != nil && c.String("image-volume") != "ignore" {
|
|
// ImageVolumes = data.Config.Volumes
|
|
//}
|
|
|
|
s.ImageVolumeMode = c.ImageVolume
|
|
systemd := c.SystemdD == "always"
|
|
if !systemd && command != nil {
|
|
x, err := strconv.ParseBool(c.SystemdD)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "cannot parse bool %s", c.SystemdD)
|
|
}
|
|
if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) {
|
|
systemd = true
|
|
}
|
|
}
|
|
if systemd {
|
|
if s.StopSignal == nil {
|
|
stopSignal, err = util.ParseSignal("RTMIN+3")
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing systemd signal")
|
|
}
|
|
s.StopSignal = &stopSignal
|
|
}
|
|
}
|
|
swappiness := uint64(c.MemorySwappiness)
|
|
if s.ResourceLimits == nil {
|
|
s.ResourceLimits = &specs.LinuxResources{}
|
|
}
|
|
if s.ResourceLimits.Memory == nil {
|
|
s.ResourceLimits.Memory = &specs.LinuxMemory{}
|
|
}
|
|
s.ResourceLimits.Memory.Swappiness = &swappiness
|
|
|
|
if s.LogConfiguration == nil {
|
|
s.LogConfiguration = &specgen.LogConfig{}
|
|
}
|
|
s.LogConfiguration.Driver = libpod.KubernetesLogging
|
|
if ld := c.LogDriver; len(ld) > 0 {
|
|
s.LogConfiguration.Driver = ld
|
|
}
|
|
if s.ResourceLimits.Pids == nil {
|
|
s.ResourceLimits.Pids = &specs.LinuxPids{}
|
|
}
|
|
s.ResourceLimits.Pids.Limit = c.PIDsLimit
|
|
if c.CGroups == "disabled" && c.PIDsLimit > 0 {
|
|
s.ResourceLimits.Pids.Limit = -1
|
|
}
|
|
// TODO WTF
|
|
//cgroup := &cc.CgroupConfig{
|
|
// Cgroups: c.String("cgroups"),
|
|
// Cgroupns: c.String("cgroupns"),
|
|
// CgroupParent: c.String("cgroup-parent"),
|
|
// CgroupMode: cgroupMode,
|
|
//}
|
|
//
|
|
//userns := &cc.UserConfig{
|
|
// GroupAdd: c.StringSlice("group-add"),
|
|
// IDMappings: idmappings,
|
|
// UsernsMode: usernsMode,
|
|
// User: user,
|
|
//}
|
|
//
|
|
//uts := &cc.UtsConfig{
|
|
// UtsMode: utsMode,
|
|
// NoHosts: c.Bool("no-hosts"),
|
|
// HostAdd: c.StringSlice("add-host"),
|
|
// Hostname: c.String("hostname"),
|
|
//}
|
|
|
|
sysctl := map[string]string{}
|
|
if ctl := c.Sysctl; len(ctl) > 0 {
|
|
sysctl, err = util.ValidateSysctls(ctl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.Sysctl = sysctl
|
|
|
|
s.CapAdd = c.CapAdd
|
|
s.CapDrop = c.CapDrop
|
|
s.Privileged = c.Privileged
|
|
s.ReadOnlyFilesystem = c.ReadOnly
|
|
|
|
// TODO
|
|
// ouitside of specgen and oci though
|
|
// defaults to true, check spec/storage
|
|
//s.readon = c.ReadOnlyTmpFS
|
|
// TODO convert to map?
|
|
// check if key=value and convert
|
|
sysmap := make(map[string]string)
|
|
for _, ctl := range c.Sysctl {
|
|
splitCtl := strings.SplitN(ctl, "=", 2)
|
|
if len(splitCtl) < 2 {
|
|
return errors.Errorf("invalid sysctl value %q", ctl)
|
|
}
|
|
sysmap[splitCtl[0]] = splitCtl[1]
|
|
}
|
|
s.Sysctl = sysmap
|
|
|
|
for _, opt := range c.SecurityOpt {
|
|
if opt == "no-new-privileges" {
|
|
s.ContainerSecurityConfig.NoNewPrivileges = true
|
|
} else {
|
|
con := strings.SplitN(opt, "=", 2)
|
|
if len(con) != 2 {
|
|
return fmt.Errorf("invalid --security-opt 1: %q", opt)
|
|
}
|
|
|
|
switch con[0] {
|
|
case "label":
|
|
// TODO selinux opts and label opts are the same thing
|
|
s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
|
|
case "apparmor":
|
|
s.ContainerSecurityConfig.ApparmorProfile = con[1]
|
|
case "seccomp":
|
|
s.SeccompProfilePath = con[1]
|
|
default:
|
|
return fmt.Errorf("invalid --security-opt 2: %q", opt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO any idea why this was done
|
|
// storage.go from spec/
|
|
// grab it
|
|
//volumes := rtc.Containers.Volumes
|
|
// TODO conflict on populate?
|
|
//if v := c.Volume; len(v)> 0 {
|
|
// s.Volumes = append(volumes, c.StringSlice("volume")...)
|
|
//}
|
|
//s.volu
|
|
|
|
//s.Mounts = c.Mount
|
|
s.VolumesFrom = c.VolumesFrom
|
|
|
|
// TODO any idea why this was done
|
|
//devices := rtc.Containers.Devices
|
|
// TODO conflict on populate?
|
|
//
|
|
//if c.Changed("device") {
|
|
// devices = append(devices, c.StringSlice("device")...)
|
|
//}
|
|
|
|
// TODO things i cannot find in spec
|
|
// we dont think these are in the spec
|
|
// init - initbinary
|
|
// initpath
|
|
s.Stdin = c.Interactive
|
|
// quiet
|
|
//DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
|
|
|
|
if bps := c.DeviceReadBPs; len(bps) > 0 {
|
|
if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if bps := c.DeviceWriteBPs; len(bps) > 0 {
|
|
if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if iops := c.DeviceReadIOPs; len(iops) > 0 {
|
|
if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if iops := c.DeviceWriteIOPs; len(iops) > 0 {
|
|
if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s.ResourceLimits.Memory.DisableOOMKiller = &c.OOMKillDisable
|
|
|
|
// Rlimits/Ulimits
|
|
for _, u := range c.Ulimit {
|
|
if u == "host" {
|
|
s.Rlimits = nil
|
|
break
|
|
}
|
|
ul, err := units.ParseUlimit(u)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
|
|
}
|
|
rl := specs.POSIXRlimit{
|
|
Type: ul.Name,
|
|
Hard: uint64(ul.Hard),
|
|
Soft: uint64(ul.Soft),
|
|
}
|
|
s.Rlimits = append(s.Rlimits, rl)
|
|
}
|
|
|
|
//Tmpfs: c.StringArray("tmpfs"),
|
|
|
|
// TODO how to handle this?
|
|
//Syslog: c.Bool("syslog"),
|
|
|
|
logOpts := make(map[string]string)
|
|
for _, o := range c.LogOptions {
|
|
split := strings.SplitN(o, "=", 2)
|
|
if len(split) < 2 {
|
|
return errors.Errorf("invalid log option %q", o)
|
|
}
|
|
logOpts[split[0]] = split[1]
|
|
}
|
|
s.LogConfiguration.Options = logOpts
|
|
s.Name = c.Name
|
|
|
|
if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil {
|
|
return err
|
|
}
|
|
|
|
if s.ResourceLimits.CPU == nil {
|
|
s.ResourceLimits.CPU = &specs.LinuxCPU{}
|
|
}
|
|
s.ResourceLimits.CPU.Shares = &c.CPUShares
|
|
s.ResourceLimits.CPU.Period = &c.CPUPeriod
|
|
|
|
// TODO research these
|
|
//s.ResourceLimits.CPU.Cpus = c.CPUS
|
|
//s.ResourceLimits.CPU.Cpus = c.CPUSetCPUs
|
|
|
|
//s.ResourceLimits.CPU. = c.CPUSetCPUs
|
|
s.ResourceLimits.CPU.Mems = c.CPUSetMems
|
|
s.ResourceLimits.CPU.Quota = &c.CPUQuota
|
|
s.ResourceLimits.CPU.RealtimePeriod = &c.CPURTPeriod
|
|
s.ResourceLimits.CPU.RealtimeRuntime = &c.CPURTRuntime
|
|
s.OOMScoreAdj = &c.OOMScoreAdj
|
|
s.RestartPolicy = c.Restart
|
|
s.Remove = c.Rm
|
|
s.StopTimeout = &c.StopTimeout
|
|
|
|
// TODO where should we do this?
|
|
//func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
|
|
return nil
|
|
}
|
|
|
|
func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) {
|
|
// Every healthcheck requires a command
|
|
if len(inCmd) == 0 {
|
|
return nil, errors.New("Must define a healthcheck command for all healthchecks")
|
|
}
|
|
|
|
// first try to parse option value as JSON array of strings...
|
|
cmd := []string{}
|
|
err := json.Unmarshal([]byte(inCmd), &cmd)
|
|
if err != nil {
|
|
// ...otherwise pass it to "/bin/sh -c" inside the container
|
|
cmd = []string{"CMD-SHELL", inCmd}
|
|
}
|
|
hc := manifest.Schema2HealthConfig{
|
|
Test: cmd,
|
|
}
|
|
|
|
if interval == "disable" {
|
|
interval = "0"
|
|
}
|
|
intervalDuration, err := time.ParseDuration(interval)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", interval)
|
|
}
|
|
|
|
hc.Interval = intervalDuration
|
|
|
|
if retries < 1 {
|
|
return nil, errors.New("healthcheck-retries must be greater than 0.")
|
|
}
|
|
hc.Retries = int(retries)
|
|
timeoutDuration, err := time.ParseDuration(timeout)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", timeout)
|
|
}
|
|
if timeoutDuration < time.Duration(1) {
|
|
return nil, errors.New("healthcheck-timeout must be at least 1 second")
|
|
}
|
|
hc.Timeout = timeoutDuration
|
|
|
|
startPeriodDuration, err := time.ParseDuration(startPeriod)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", startPeriod)
|
|
}
|
|
if startPeriodDuration < time.Duration(0) {
|
|
return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
|
|
}
|
|
hc.StartPeriod = startPeriodDuration
|
|
|
|
return &hc, nil
|
|
}
|
|
|
|
func parseWeightDevices(weightDevs []string, s *specgen.SpecGenerator) error {
|
|
for _, val := range weightDevs {
|
|
split := strings.SplitN(val, ":", 2)
|
|
if len(split) != 2 {
|
|
return fmt.Errorf("bad format: %s", val)
|
|
}
|
|
if !strings.HasPrefix(split[0], "/dev/") {
|
|
return fmt.Errorf("bad format for device path: %s", val)
|
|
}
|
|
weight, err := strconv.ParseUint(split[1], 10, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid weight for device: %s", val)
|
|
}
|
|
if weight > 0 && (weight < 10 || weight > 1000) {
|
|
return fmt.Errorf("invalid weight for device: %s", val)
|
|
}
|
|
w := uint16(weight)
|
|
s.WeightDevice[split[0]] = specs.LinuxWeightDevice{
|
|
Weight: &w,
|
|
LeafWeight: nil,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
|
|
td := make(map[string]specs.LinuxThrottleDevice)
|
|
for _, val := range bpsDevices {
|
|
split := strings.SplitN(val, ":", 2)
|
|
if len(split) != 2 {
|
|
return nil, fmt.Errorf("bad format: %s", val)
|
|
}
|
|
if !strings.HasPrefix(split[0], "/dev/") {
|
|
return nil, fmt.Errorf("bad format for device path: %s", val)
|
|
}
|
|
rate, err := units.RAMInBytes(split[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
|
|
}
|
|
if rate < 0 {
|
|
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
|
|
}
|
|
td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)}
|
|
}
|
|
return td, nil
|
|
}
|
|
|
|
func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
|
|
td := make(map[string]specs.LinuxThrottleDevice)
|
|
for _, val := range iopsDevices {
|
|
split := strings.SplitN(val, ":", 2)
|
|
if len(split) != 2 {
|
|
return nil, fmt.Errorf("bad format: %s", val)
|
|
}
|
|
if !strings.HasPrefix(split[0], "/dev/") {
|
|
return nil, fmt.Errorf("bad format for device path: %s", val)
|
|
}
|
|
rate, err := strconv.ParseUint(split[1], 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
|
|
}
|
|
td[split[0]] = specs.LinuxThrottleDevice{Rate: rate}
|
|
}
|
|
return td, nil
|
|
}
|