From 551092f9c0da2244c60b75d893ef847f915ca604 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 15 Aug 2013 23:35:03 +0000 Subject: [PATCH] Add lxc-conf flag to allow custom lxc options --- container.go | 27 +++++++++++++++-- container_test.go | 32 ++++++++++++++++++++- docs/sources/api/docker_remote_api_v1.4.rst | 3 +- docs/sources/commandline/command/run.rst | 2 +- lxc_template.go | 13 +++++++++ utils.go | 21 ++++++++++++++ utils_test.go | 17 +++++++++++ 7 files changed, 109 insertions(+), 6 deletions(-) diff --git a/container.go b/container.go index 62007ed624..7de3539b2f 100644 --- a/container.go +++ b/container.go @@ -86,6 +86,7 @@ type Config struct { type HostConfig struct { Binds []string ContainerIDFile string + LxcConf []KeyValuePair } type BindMap struct { @@ -98,6 +99,11 @@ var ( ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.") ) +type KeyValuePair struct { + Key string + Value string +} + func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { @@ -140,6 +146,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container") flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image") + var flLxcOpts ListOpts + cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err } @@ -187,6 +196,12 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, entrypoint = []string{*flEntrypoint} } + var lxcConf []KeyValuePair + lxcConf, err := parseLxcConfOpts(flLxcOpts) + if err != nil { + return nil, nil, cmd, err + } + config := &Config{ Hostname: *flHostname, PortSpecs: flPorts, @@ -212,6 +227,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, + LxcConf: lxcConf, } if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { @@ -315,7 +331,7 @@ func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) { return ioutil.WriteFile(container.hostConfigPath(), data, 0666) } -func (container *Container) generateLXCConfig() error { +func (container *Container) generateLXCConfig(hostConfig *HostConfig) error { fo, err := os.Create(container.lxcConfigPath()) if err != nil { return err @@ -324,6 +340,11 @@ func (container *Container) generateLXCConfig() error { if err := LxcTemplateCompiled.Execute(fo, container); err != nil { return err } + if hostConfig != nil { + if err := LxcHostConfigTemplateCompiled.Execute(fo, hostConfig); err != nil { + return err + } + } return nil } @@ -520,7 +541,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { container.State.Lock() defer container.State.Unlock() - if len(hostConfig.Binds) == 0 { + if len(hostConfig.Binds) == 0 && len(hostConfig.LxcConf) == 0 { hostConfig, _ = container.ReadHostConfig() } @@ -645,7 +666,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } } - if err := container.generateLXCConfig(); err != nil { + if err := container.generateLXCConfig(hostConfig); err != nil { return err } diff --git a/container_test.go b/container_test.go index 3752615a3c..ba48ceb47a 100644 --- a/container_test.go +++ b/container_test.go @@ -1070,7 +1070,7 @@ func TestLXCConfig(t *testing.T) { t.Fatal(err) } defer runtime.Destroy(container) - container.generateLXCConfig() + container.generateLXCConfig(nil) grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar") grepFile(t, container.lxcConfigPath(), fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem)) @@ -1078,6 +1078,36 @@ func TestLXCConfig(t *testing.T) { fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2)) } +func TestCustomLxcConfig(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"/bin/true"}, + + Hostname: "foobar", + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + hostConfig := &HostConfig{LxcConf: []KeyValuePair{ + { + Key: "lxc.utsname", + Value: "docker", + }, + { + Key: "lxc.cgroup.cpuset.cpus", + Value: "0,1", + }, + }} + + container.generateLXCConfig(hostConfig) + grepFile(t, container.lxcConfigPath(), "lxc.utsname = docker") + grepFile(t, container.lxcConfigPath(), "lxc.cgroup.cpuset.cpus = 0,1") +} + func BenchmarkRunSequencial(b *testing.B) { runtime := mkRuntime(b) defer nuke(runtime) diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 14dbfd7c3f..d512de9ca3 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -356,7 +356,8 @@ Start a container Content-Type: application/json { - "Binds":["/tmp:/tmp"] + "Binds":["/tmp:/tmp"], + "LxcConf":{"lxc.utsname":"docker"} } **Example response**: diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index bd78ea473f..cd283669e6 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -30,7 +30,7 @@ -volumes-from="": Mount all volumes from the given container. -entrypoint="": Overwrite the default entrypoint set by the image. -w="": Working directory inside the container - + -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" Examples -------- diff --git a/lxc_template.go b/lxc_template.go index 2ed05ad168..d357c02b43 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -121,7 +121,16 @@ lxc.cgroup.cpu.shares = {{.Config.CpuShares}} {{end}} ` +const LxcHostConfigTemplate = ` +{{if .LxcConf}} +{{range $pair := .LxcConf}} +{{$pair.Key}} = {{$pair.Value}} +{{end}} +{{end}} +` + var LxcTemplateCompiled *template.Template +var LxcHostConfigTemplateCompiled *template.Template func getMemorySwap(config *Config) int64 { // By default, MemorySwap is set to twice the size of RAM. @@ -141,4 +150,8 @@ func init() { if err != nil { panic(err) } + LxcHostConfigTemplateCompiled, err = template.New("lxc-hostconfig").Funcs(funcMap).Parse(LxcHostConfigTemplate) + if err != nil { + panic(err) + } } diff --git a/utils.go b/utils.go index b8f264fdb4..aed8ffdd76 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,7 @@ package docker import ( + "fmt" "strings" ) @@ -146,3 +147,23 @@ func MergeConfig(userConf, imageConf *Config) { } } } + +func parseLxcConfOpts(opts ListOpts) ([]KeyValuePair, error) { + out := make([]KeyValuePair, len(opts)) + for i, o := range opts { + k, v, err := parseLxcOpt(o) + if err != nil { + return nil, err + } + out[i] = KeyValuePair{Key: k, Value: v} + } + return out, nil +} + +func parseLxcOpt(opt string) (string, string, error) { + parts := strings.SplitN(opt, "=", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt) + } + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil +} diff --git a/utils_test.go b/utils_test.go index 5c37e9e8ec..e8aae17186 100644 --- a/utils_test.go +++ b/utils_test.go @@ -301,3 +301,20 @@ func TestMergeConfigPublicPortNotHonored(t *testing.T) { t.Fail() } } + +func TestParseLxcConfOpt(t *testing.T) { + opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} + + for _, o := range opts { + k, v, err := parseLxcOpt(o) + if err != nil { + t.FailNow() + } + if k != "lxc.utsname" { + t.Fail() + } + if v != "docker" { + t.Fail() + } + } +}