diff options
Diffstat (limited to 'cmd/podman/shared/create_cli.go')
-rw-r--r-- | cmd/podman/shared/create_cli.go | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/cmd/podman/shared/create_cli.go b/cmd/podman/shared/create_cli.go new file mode 100644 index 000000000..4f9cb1699 --- /dev/null +++ b/cmd/podman/shared/create_cli.go @@ -0,0 +1,394 @@ +package shared + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/libpod/cmd/podman/shared/parse" + cc "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/sysinfo" + "github.com/docker/go-units" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // It's not kernel limit, we want this 4M limit to supply a reasonable functional container + linuxMinMemory = 4194304 +) + +// GetAllLabels ... +func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { + labels := make(map[string]string) + labelErr := parse.ReadKVStrings(labels, labelFile, inputLabels) + if labelErr != nil { + return labels, errors.Wrapf(labelErr, "unable to process labels from --label and label-file") + } + return labels, nil +} + +// validateSysctl validates a sysctl and returns it. +func validateSysctl(strSlice []string) (map[string]string, error) { + sysctl := make(map[string]string) + validSysctlMap := map[string]bool{ + "kernel.msgmax": true, + "kernel.msgmnb": true, + "kernel.msgmni": true, + "kernel.sem": true, + "kernel.shmall": true, + "kernel.shmmax": true, + "kernel.shmmni": true, + "kernel.shm_rmid_forced": true, + } + validSysctlPrefixes := []string{ + "net.", + "fs.mqueue.", + } + + for _, val := range strSlice { + foundMatch := false + arr := strings.Split(val, "=") + if len(arr) < 2 { + return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val) + } + if validSysctlMap[arr[0]] { + sysctl[arr[0]] = arr[1] + continue + } + + for _, prefix := range validSysctlPrefixes { + if strings.HasPrefix(arr[0], prefix) { + sysctl[arr[0]] = arr[1] + foundMatch = true + break + } + } + if !foundMatch { + return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0]) + } + } + return sysctl, nil +} + +func addWarning(warnings []string, msg string) []string { + logrus.Warn(msg) + return append(warnings, msg) +} + +// Format supported. +// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... +// podman run --mount type=tmpfs,target=/dev/shm .. +func parseMounts(mounts []string) ([]spec.Mount, error) { + // TODO(vrothberg): the manual parsing can be replaced with a regular expression + // to allow a more robust parsing of the mount format and to give + // precise errors regarding supported format versus suppored options. + var mountList []spec.Mount + errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>[,options]") + for _, mount := range mounts { + var tokenCount int + var mountInfo spec.Mount + + arr := strings.SplitN(mount, ",", 2) + if len(arr) < 2 { + return nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + } + kv := strings.Split(arr[0], "=") + if kv[0] != "type" { + return nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + } + switch kv[1] { + case "bind": + mountInfo.Type = string(cc.TypeBind) + case "tmpfs": + mountInfo.Type = string(cc.TypeTmpfs) + mountInfo.Source = string(cc.TypeTmpfs) + mountInfo.Options = append(mountInfo.Options, []string{"rprivate", "noexec", "nosuid", "nodev", "size=65536k"}...) + + default: + return nil, errors.Errorf("invalid filesystem type %q", kv[1]) + } + + tokens := strings.Split(arr[1], ",") + for i, val := range tokens { + if i == (tokenCount - 1) { + //Parse tokens before options. + break + } + kv := strings.Split(val, "=") + switch kv[0] { + case "ro", "nosuid", "nodev", "noexec": + mountInfo.Options = append(mountInfo.Options, kv[0]) + case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": + if mountInfo.Type != "bind" { + return nil, errors.Errorf("%s can only be used with bind mounts", kv[0]) + } + mountInfo.Options = append(mountInfo.Options, kv[0]) + case "tmpfs-mode": + if mountInfo.Type != "tmpfs" { + return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0]) + } + mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("mode=%s", kv[1])) + case "tmpfs-size": + if mountInfo.Type != "tmpfs" { + return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0]) + } + shmSize, err := units.FromHumanSize(kv[1]) + if err != nil { + return nil, errors.Wrapf(err, "unable to translate tmpfs-size") + } + + mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("size=%d", shmSize)) + + case "bind-propagation": + if mountInfo.Type != "bind" { + return nil, errors.Errorf("%s can only be used with bind mounts", kv[0]) + } + mountInfo.Options = append(mountInfo.Options, kv[1]) + case "src", "source": + if mountInfo.Type == "tmpfs" { + return nil, errors.Errorf("cannot use src= on a tmpfs file system") + } + if err := ValidateVolumeHostDir(kv[1]); err != nil { + return nil, err + } + mountInfo.Source = kv[1] + case "target", "dst", "destination": + if err := ValidateVolumeCtrDir(kv[1]); err != nil { + return nil, err + } + mountInfo.Destination = kv[1] + default: + return nil, errors.Errorf("incorrect mount option : %s", kv[0]) + } + } + mountList = append(mountList, mountInfo) + } + return mountList, nil +} + +func parseVolumes(volumes []string) error { + for _, volume := range volumes { + arr := strings.SplitN(volume, ":", 3) + if len(arr) < 2 { + return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume) + } + if err := ValidateVolumeHostDir(arr[0]); err != nil { + return err + } + if err := ValidateVolumeCtrDir(arr[1]); err != nil { + return err + } + if len(arr) > 2 { + if err := validateVolumeOpts(arr[2]); err != nil { + return err + } + } + } + return nil +} + +func parseVolumesFrom(volumesFrom []string) error { + for _, vol := range volumesFrom { + arr := strings.SplitN(vol, ":", 2) + if len(arr) == 2 { + if strings.Contains(arr[1], "Z") || strings.Contains(arr[1], "private") || strings.Contains(arr[1], "slave") || strings.Contains(arr[1], "shared") { + return errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", arr[1]) + } + if err := validateVolumeOpts(arr[1]); err != nil { + return err + } + } + } + return nil +} + +// ValidateVolumeHostDir ... +func ValidateVolumeHostDir(hostDir string) error { + if len(hostDir) == 0 { + return errors.Errorf("host directory cannot be empty") + } + if filepath.IsAbs(hostDir) { + if _, err := os.Stat(hostDir); err != nil { + return errors.Wrapf(err, "error checking path %q", hostDir) + } + } + // If hostDir is not an absolute path, that means the user wants to create a + // named volume. This will be done later on in the code. + return nil +} + +// ValidateVolumeCtrDir ... +func ValidateVolumeCtrDir(ctrDir string) error { + if len(ctrDir) == 0 { + return errors.Errorf("container directory cannot be empty") + } + if !filepath.IsAbs(ctrDir) { + return errors.Errorf("invalid container path, must be an absolute path %q", ctrDir) + } + return nil +} + +func validateVolumeOpts(option string) error { + var foundRootPropagation, foundRWRO, foundLabelChange int + options := strings.Split(option, ",") + for _, opt := range options { + switch opt { + case "rw", "ro": + foundRWRO++ + if foundRWRO > 1 { + return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", option) + } + case "z", "Z": + foundLabelChange++ + if foundLabelChange > 1 { + return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option) + } + case "private", "rprivate", "shared", "rshared", "slave", "rslave": + foundRootPropagation++ + if foundRootPropagation > 1 { + return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", option) + } + default: + return errors.Errorf("invalid option type %q", option) + } + } + return nil +} + +func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { + warnings := []string{} + sysInfo := sysinfo.New(true) + + // memory subsystem checks and adjustments + if config.Resources.Memory != 0 && config.Resources.Memory < linuxMinMemory { + return warnings, fmt.Errorf("minimum memory limit allowed is 4MB") + } + if config.Resources.Memory > 0 && !sysInfo.MemoryLimit { + warnings = addWarning(warnings, "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.") + config.Resources.Memory = 0 + config.Resources.MemorySwap = -1 + } + if config.Resources.Memory > 0 && config.Resources.MemorySwap != -1 && !sysInfo.SwapLimit { + warnings = addWarning(warnings, "Your kernel does not support swap limit capabilities,or the cgroup is not mounted. Memory limited without swap.") + config.Resources.MemorySwap = -1 + } + if config.Resources.Memory > 0 && config.Resources.MemorySwap > 0 && config.Resources.MemorySwap < config.Resources.Memory { + return warnings, fmt.Errorf("minimum memoryswap limit should be larger than memory limit, see usage") + } + if config.Resources.Memory == 0 && config.Resources.MemorySwap > 0 && !update { + return warnings, fmt.Errorf("you should always set the memory limit when using memoryswap limit, see usage") + } + if config.Resources.MemorySwappiness != -1 { + if !sysInfo.MemorySwappiness { + msg := "Your kernel does not support memory swappiness capabilities, or the cgroup is not mounted. Memory swappiness discarded." + warnings = addWarning(warnings, msg) + config.Resources.MemorySwappiness = -1 + } else { + swappiness := config.Resources.MemorySwappiness + if swappiness < -1 || swappiness > 100 { + return warnings, fmt.Errorf("invalid value: %v, valid memory swappiness range is 0-100", swappiness) + } + } + } + if config.Resources.MemoryReservation > 0 && !sysInfo.MemoryReservation { + warnings = addWarning(warnings, "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded.") + config.Resources.MemoryReservation = 0 + } + if config.Resources.MemoryReservation > 0 && config.Resources.MemoryReservation < linuxMinMemory { + return warnings, fmt.Errorf("minimum memory reservation allowed is 4MB") + } + if config.Resources.Memory > 0 && config.Resources.MemoryReservation > 0 && config.Resources.Memory < config.Resources.MemoryReservation { + return warnings, fmt.Errorf("minimum memory limit cannot be less than memory reservation limit, see usage") + } + if config.Resources.KernelMemory > 0 && !sysInfo.KernelMemory { + warnings = addWarning(warnings, "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded.") + config.Resources.KernelMemory = 0 + } + if config.Resources.KernelMemory > 0 && config.Resources.KernelMemory < linuxMinMemory { + return warnings, fmt.Errorf("minimum kernel memory limit allowed is 4MB") + } + if config.Resources.DisableOomKiller == true && !sysInfo.OomKillDisable { + // only produce warnings if the setting wasn't to *disable* the OOM Kill; no point + // warning the caller if they already wanted the feature to be off + warnings = addWarning(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.") + config.Resources.DisableOomKiller = false + } + + if config.Resources.PidsLimit != 0 && !sysInfo.PidsLimit { + warnings = addWarning(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.") + config.Resources.PidsLimit = 0 + } + + if config.Resources.CPUShares > 0 && !sysInfo.CPUShares { + warnings = addWarning(warnings, "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded.") + config.Resources.CPUShares = 0 + } + if config.Resources.CPUPeriod > 0 && !sysInfo.CPUCfsPeriod { + warnings = addWarning(warnings, "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded.") + config.Resources.CPUPeriod = 0 + } + if config.Resources.CPUPeriod != 0 && (config.Resources.CPUPeriod < 1000 || config.Resources.CPUPeriod > 1000000) { + return warnings, fmt.Errorf("CPU cfs period cannot be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)") + } + if config.Resources.CPUQuota > 0 && !sysInfo.CPUCfsQuota { + warnings = addWarning(warnings, "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded.") + config.Resources.CPUQuota = 0 + } + if config.Resources.CPUQuota > 0 && config.Resources.CPUQuota < 1000 { + return warnings, fmt.Errorf("CPU cfs quota cannot be less than 1ms (i.e. 1000)") + } + // cpuset subsystem checks and adjustments + if (config.Resources.CPUsetCPUs != "" || config.Resources.CPUsetMems != "") && !sysInfo.Cpuset { + warnings = addWarning(warnings, "Your kernel does not support cpuset or the cgroup is not mounted. CPUset discarded.") + config.Resources.CPUsetCPUs = "" + config.Resources.CPUsetMems = "" + } + cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(config.Resources.CPUsetCPUs) + if err != nil { + return warnings, fmt.Errorf("invalid value %s for cpuset cpus", config.Resources.CPUsetCPUs) + } + if !cpusAvailable { + return warnings, fmt.Errorf("requested CPUs are not available - requested %s, available: %s", config.Resources.CPUsetCPUs, sysInfo.Cpus) + } + memsAvailable, err := sysInfo.IsCpusetMemsAvailable(config.Resources.CPUsetMems) + if err != nil { + return warnings, fmt.Errorf("invalid value %s for cpuset mems", config.Resources.CPUsetMems) + } + if !memsAvailable { + return warnings, fmt.Errorf("requested memory nodes are not available - requested %s, available: %s", config.Resources.CPUsetMems, sysInfo.Mems) + } + + // blkio subsystem checks and adjustments + if config.Resources.BlkioWeight > 0 && !sysInfo.BlkioWeight { + warnings = addWarning(warnings, "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.") + config.Resources.BlkioWeight = 0 + } + if config.Resources.BlkioWeight > 0 && (config.Resources.BlkioWeight < 10 || config.Resources.BlkioWeight > 1000) { + return warnings, fmt.Errorf("range of blkio weight is from 10 to 1000") + } + if len(config.Resources.BlkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice { + warnings = addWarning(warnings, "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded.") + config.Resources.BlkioWeightDevice = []string{} + } + if len(config.Resources.DeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice { + warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded") + config.Resources.DeviceReadBps = []string{} + } + if len(config.Resources.DeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice { + warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.") + config.Resources.DeviceWriteBps = []string{} + } + if len(config.Resources.DeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice { + warnings = addWarning(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.") + config.Resources.DeviceReadIOps = []string{} + } + if len(config.Resources.DeviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice { + warnings = addWarning(warnings, "Your kernel does not support IOPS Block I/O write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded.") + config.Resources.DeviceWriteIOps = []string{} + } + + return warnings, nil +} |