From 10fdbc0467d1be6c7c731d3f35590d87ee42f96f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 07:16:40 +0000 Subject: [PATCH] Add unit test for lxc conf merge and native opts Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/container.go | 10 +- runtime/execdriver/lxc/lxc_template.go | 2 +- .../execdriver/native/configuration/parse.go | 14 -- .../native/configuration/parse_test.go | 166 ++++++++++++++++++ runtime/execdriver/native/create.go | 66 ++++--- .../native/{ => template}/default_template.go | 7 +- runtime/utils.go | 20 +++ runtime/utils_test.go | 27 +++ 8 files changed, 265 insertions(+), 47 deletions(-) create mode 100644 runtime/execdriver/native/configuration/parse_test.go rename runtime/execdriver/native/{ => template}/default_template.go (88%) create mode 100644 runtime/utils_test.go diff --git a/runtime/container.go b/runtime/container.go index ee5045e374..be162c7a21 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -383,14 +383,8 @@ func populateCommand(c *Container) { } } - // merge in the lxc conf options into the generic config map - if lxcConf := c.hostConfig.LxcConf; lxcConf != nil { - lxc := driverConfig["lxc"] - for _, pair := range lxcConf { - lxc = append(lxc, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) - } - driverConfig["lxc"] = lxc - } + // TODO: this can be removed after lxc-conf is fully deprecated + mergeLxcConfIntoOptions(c.hostConfig, driverConfig) resources := &execdriver.Resources{ Memory: c.Config.Memory, diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index 7979e4f284..c5ba876dce 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -120,7 +120,7 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{if .Config.lxc}} {{range $value := .Config.lxc}} -{{$value}} +lxc.{{$value}} {{end}} {{end}} ` diff --git a/runtime/execdriver/native/configuration/parse.go b/runtime/execdriver/native/configuration/parse.go index 090cb29660..6d6c643919 100644 --- a/runtime/execdriver/native/configuration/parse.go +++ b/runtime/execdriver/native/configuration/parse.go @@ -31,20 +31,6 @@ var actions = map[string]Action{ "fs.readonly": readonlyFs, // make the rootfs of the container read only } -// GetSupportedActions returns a list of all the avaliable actions supported by the driver -// TODO: this should return a description also -func GetSupportedActions() []string { - var ( - i int - out = make([]string, len(actions)) - ) - for k := range actions { - out[i] = k - i++ - } - return out -} - func cpusetCpus(container *libcontainer.Container, context interface{}, value string) error { if container.Cgroups == nil { return fmt.Errorf("cannot set cgroups when they are disabled") diff --git a/runtime/execdriver/native/configuration/parse_test.go b/runtime/execdriver/native/configuration/parse_test.go new file mode 100644 index 0000000000..8001358766 --- /dev/null +++ b/runtime/execdriver/native/configuration/parse_test.go @@ -0,0 +1,166 @@ +package configuration + +import ( + "github.com/dotcloud/docker/runtime/execdriver/native/template" + "testing" +) + +func TestSetReadonlyRootFs(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "fs.readonly=true", + } + ) + + if container.ReadonlyFs { + t.Fatal("container should not have a readonly rootfs by default") + } + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if !container.ReadonlyFs { + t.Fatal("container should have a readonly rootfs") + } +} + +func TestConfigurationsDoNotConflict(t *testing.T) { + var ( + container1 = template.New() + container2 = template.New() + opts = []string{ + "cap.add=NET_ADMIN", + } + ) + + if err := ParseConfiguration(container1, nil, opts); err != nil { + t.Fatal(err) + } + + if !container1.CapabilitiesMask.Get("NET_ADMIN").Enabled { + t.Fatal("container one should have NET_ADMIN enabled") + } + if container2.CapabilitiesMask.Get("NET_ADMIN").Enabled { + t.Fatal("container two should not have NET_ADMIN enabled") + } +} + +func TestCpusetCpus(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cgroups.cpuset.cpus=1,2", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if expected := "1,2"; container.Cgroups.CpusetCpus != expected { + t.Fatalf("expected %s got %s for cpuset.cpus", expected, container.Cgroups.CpusetCpus) + } +} + +func TestAppArmorProfile(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "apparmor_profile=koye-the-protector", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + if expected := "koye-the-protector"; container.Context["apparmor_profile"] != expected { + t.Fatalf("expected profile %s got %s", expected, container.Context["apparmor_profile"]) + } +} + +func TestCpuShares(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cgroups.cpu_shares=1048", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if expected := int64(1048); container.Cgroups.CpuShares != expected { + t.Fatalf("expected cpu shares %d got %d", expected, container.Cgroups.CpuShares) + } +} + +func TestCgroupMemory(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cgroups.memory=500m", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if expected := int64(500 * 1024 * 1024); container.Cgroups.Memory != expected { + t.Fatalf("expected memory %d got %d", expected, container.Cgroups.Memory) + } +} + +func TestAddCap(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cap.add=MKNOD", + "cap.add=SYS_ADMIN", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if !container.CapabilitiesMask.Get("MKNOD").Enabled { + t.Fatal("container should have MKNOD enabled") + } + if !container.CapabilitiesMask.Get("SYS_ADMIN").Enabled { + t.Fatal("container should have SYS_ADMIN enabled") + } +} + +func TestDropCap(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "cap.drop=MKNOD", + } + ) + // enabled all caps like in privileged mode + for _, c := range container.CapabilitiesMask { + c.Enabled = true + } + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if container.CapabilitiesMask.Get("MKNOD").Enabled { + t.Fatal("container should not have MKNOD enabled") + } +} + +func TestDropNamespace(t *testing.T) { + var ( + container = template.New() + opts = []string{ + "ns.drop=NEWNET", + } + ) + if err := ParseConfiguration(container, nil, opts); err != nil { + t.Fatal(err) + } + + if container.Namespaces.Get("NEWNET").Enabled { + t.Fatal("container should not have NEWNET enabled") + } +} diff --git a/runtime/execdriver/native/create.go b/runtime/execdriver/native/create.go index 7118edc91e..7e663f0555 100644 --- a/runtime/execdriver/native/create.go +++ b/runtime/execdriver/native/create.go @@ -5,30 +5,53 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/runtime/execdriver/native/configuration" + "github.com/dotcloud/docker/runtime/execdriver/native/template" "os" ) // createContainer populates and configures the container type with the // data provided by the execdriver.Command func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container, error) { - container := getDefaultTemplate() + container := template.New() container.Hostname = getEnv("HOSTNAME", c.Env) container.Tty = c.Tty container.User = c.User container.WorkingDir = c.WorkingDir container.Env = c.Env + container.Cgroups.Name = c.ID + // check to see if we are running in ramdisk to disable pivot root + container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" - loopbackNetwork := libcontainer.Network{ - Mtu: c.Network.Mtu, - Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), - Gateway: "localhost", - Type: "loopback", - Context: libcontainer.Context{}, + if err := d.createNetwork(container, c); err != nil { + return nil, err } + if c.Privileged { + if err := d.setPrivileged(container); err != nil { + return nil, err + } + } + if err := d.setupCgroups(container, c); err != nil { + return nil, err + } + if err := d.setupMounts(container, c); err != nil { + return nil, err + } + if err := configuration.ParseConfiguration(container, d.activeContainers, c.Config["native"]); err != nil { + return nil, err + } + return container, nil +} +func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.Command) error { container.Networks = []*libcontainer.Network{ - &loopbackNetwork, + { + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0), + Gateway: "localhost", + Type: "loopback", + Context: libcontainer.Context{}, + }, } if c.Network.Interface != nil { @@ -44,27 +67,30 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container } container.Networks = append(container.Networks, &vethNetwork) } + return nil +} - container.Cgroups.Name = c.ID - if c.Privileged { - container.CapabilitiesMask = nil - container.Cgroups.DeviceAccess = true - container.Context["apparmor_profile"] = "unconfined" +func (d *driver) setPrivileged(container *libcontainer.Container) error { + for _, c := range container.CapabilitiesMask { + c.Enabled = true } + container.Cgroups.DeviceAccess = true + container.Context["apparmor_profile"] = "unconfined" + return nil +} + +func (d *driver) setupCgroups(container *libcontainer.Container, c *execdriver.Command) error { if c.Resources != nil { container.Cgroups.CpuShares = c.Resources.CpuShares container.Cgroups.Memory = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap } - // check to see if we are running in ramdisk to disable pivot root - container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + return nil +} +func (d *driver) setupMounts(container *libcontainer.Container, c *execdriver.Command) error { for _, m := range c.Mounts { container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) } - - if err := configuration.ParseConfiguration(container, d.activeContainers, c.Config["native"]); err != nil { - return nil, err - } - return container, nil + return nil } diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/template/default_template.go similarity index 88% rename from runtime/execdriver/native/default_template.go rename to runtime/execdriver/native/template/default_template.go index 0dcd7db356..b9eb87713e 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/template/default_template.go @@ -1,13 +1,12 @@ -package native +package template import ( "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" ) -// getDefaultTemplate returns the docker default for -// the libcontainer configuration file -func getDefaultTemplate() *libcontainer.Container { +// New returns the docker default configuration for libcontainer +func New() *libcontainer.Container { return &libcontainer.Container{ CapabilitiesMask: libcontainer.Capabilities{ libcontainer.GetCapability("SETPCAP"), diff --git a/runtime/utils.go b/runtime/utils.go index b343b5b10e..b983e67d41 100644 --- a/runtime/utils.go +++ b/runtime/utils.go @@ -1,9 +1,11 @@ package runtime import ( + "fmt" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/runconfig" + "strings" ) func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { @@ -30,6 +32,24 @@ func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostCon return nil } +func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[string][]string) { + if hostConfig == nil { + return + } + + // merge in the lxc conf options into the generic config map + if lxcConf := hostConfig.LxcConf; lxcConf != nil { + lxc := driverConfig["lxc"] + for _, pair := range lxcConf { + // because lxc conf gets the driver name lxc.XXXX we need to trim it off + // and let the lxc driver add it back later if needed + parts := strings.SplitN(pair.Key, ".", 2) + lxc = append(lxc, fmt.Sprintf("%s=%s", parts[1], pair.Value)) + } + driverConfig["lxc"] = lxc + } +} + type checker struct { runtime *Runtime } diff --git a/runtime/utils_test.go b/runtime/utils_test.go new file mode 100644 index 0000000000..81c745c0d5 --- /dev/null +++ b/runtime/utils_test.go @@ -0,0 +1,27 @@ +package runtime + +import ( + "github.com/dotcloud/docker/runconfig" + "testing" +) + +func TestMergeLxcConfig(t *testing.T) { + var ( + hostConfig = &runconfig.HostConfig{ + LxcConf: []runconfig.KeyValuePair{ + {Key: "lxc.cgroups.cpuset", Value: "1,2"}, + }, + } + driverConfig = make(map[string][]string) + ) + + mergeLxcConfIntoOptions(hostConfig, driverConfig) + if l := len(driverConfig["lxc"]); l > 1 { + t.Fatalf("expected lxc options len of 1 got %d", l) + } + + cpuset := driverConfig["lxc"][0] + if expected := "cgroups.cpuset=1,2"; cpuset != expected { + t.Fatalf("expected %s got %s", expected, cpuset) + } +}