diff --git a/container.go b/container.go index f9247eccc8..05c3253520 100644 --- a/container.go +++ b/container.go @@ -171,7 +171,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached") flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") - flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") + flMemoryString := cmd.String("m", "", "Memory limit (format: , where unit = b, k, m or g)") flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") flNetwork := cmd.Bool("n", true, "Enable networking for this container") flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container") @@ -180,9 +180,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.String("name", "", "Assign a name to the container") flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces") - if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { + if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") - *flMemory = 0 + *flMemoryString = "" } flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") @@ -249,6 +249,18 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } } + var flMemory int64 + + if *flMemoryString != "" { + parsedMemory, err := utils.RAMInBytes(*flMemoryString) + + if err != nil { + return nil, nil, cmd, err + } + + flMemory = parsedMemory + } + var binds []string // add any bind targets to the list of container volumes @@ -319,7 +331,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Tty: *flTty, NetworkDisabled: !*flNetwork, OpenStdin: *flStdin, - Memory: *flMemory, + Memory: flMemory, CpuShares: *flCpuShares, AttachStdin: flAttach.Get("stdin"), AttachStdout: flAttach.Get("stdout"), @@ -344,7 +356,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, PublishAllPorts: *flPublishAll, } - if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { + if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index 4e3d369e14..b3906d05bf 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -652,7 +652,7 @@ network communication. -h="": Container host name -i=false: Keep stdin open even if not attached -privileged=false: Give extended privileges to this container - -m=0: Memory limit (in bytes) + -m="": Memory limit (format: , where unit = b, k, m or g) -n=true: Enable networking for this container -p=[]: Map a network port to the container -rm=false: Automatically remove the container when it exits (incompatible with -d) diff --git a/server.go b/server.go index 3441029027..2a9c2000d1 100644 --- a/server.go +++ b/server.go @@ -1023,7 +1023,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write func (srv *Server) ContainerCreate(config *Config, name string) (string, []string, error) { if config.Memory != 0 && config.Memory < 524288 { - return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") + return "", nil, fmt.Errorf("Minimum memory limit allowed is 512k") } if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { diff --git a/utils/utils.go b/utils/utils.go index 152c41d86c..03fdc1987a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -177,6 +177,40 @@ func HumanSize(size int64) string { return fmt.Sprintf("%.4g %s", sizef, units[i]) } +// Parses a human-readable string representing an amount of RAM +// in bytes, kibibytes, mebibytes or gibibytes, and returns the +// number of bytes, or -1 if the string is unparseable. +// Units are case-insensitive, and the 'b' suffix is optional. +func RAMInBytes(size string) (bytes int64, err error) { + re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$") + if error != nil { + return -1, error + } + + matches := re.FindStringSubmatch(size) + + if len(matches) != 3 { + return -1, fmt.Errorf("Invalid size: '%s'", size) + } + + memLimit, error := strconv.ParseInt(matches[1], 10, 0) + if error != nil { + return -1, error + } + + unit := strings.ToLower(matches[2]) + + if unit == "k" { + memLimit *= 1024 + } else if unit == "m" { + memLimit *= 1024 * 1024 + } else if unit == "g" { + memLimit *= 1024 * 1024 * 1024 + } + + return memLimit, nil +} + func Trunc(s string, maxlen int) string { if len(s) <= maxlen { return s diff --git a/utils/utils_test.go b/utils/utils_test.go index 5377c181c4..a6fcbf24bb 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -265,6 +265,39 @@ func TestHumanSize(t *testing.T) { } } +func TestRAMInBytes(t *testing.T) { + assertRAMInBytes(t, "32", false, 32) + assertRAMInBytes(t, "32b", false, 32) + assertRAMInBytes(t, "32B", false, 32) + assertRAMInBytes(t, "32k", false, 32*1024) + assertRAMInBytes(t, "32K", false, 32*1024) + assertRAMInBytes(t, "32kb", false, 32*1024) + assertRAMInBytes(t, "32Kb", false, 32*1024) + assertRAMInBytes(t, "32Mb", false, 32*1024*1024) + assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024) + + assertRAMInBytes(t, "", true, -1) + assertRAMInBytes(t, "hello", true, -1) + assertRAMInBytes(t, "-32", true, -1) + assertRAMInBytes(t, " 32 ", true, -1) + assertRAMInBytes(t, "32 mb", true, -1) + assertRAMInBytes(t, "32m b", true, -1) + assertRAMInBytes(t, "32bm", true, -1) +} + +func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) { + actualBytes, err := RAMInBytes(size) + if (err != nil) && !expectError { + t.Errorf("Unexpected error parsing '%s': %s", size, err) + } + if (err == nil) && expectError { + t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes) + } + if actualBytes != expectedBytes { + t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes) + } +} + func TestParseHost(t *testing.T) { if addr, err := ParseHost("127.0.0.1", 4243, "0.0.0.0"); err != nil || addr != "tcp://0.0.0.0:4243" { t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)