daemon/daemon_unix_test.go
4785f1a7
 // +build !windows
ed39fbeb
 
4f0d95fa
 package daemon // import "github.com/docker/docker/daemon"
ed39fbeb
 
 import (
b6e5ea8e
 	"errors"
e0af23dc
 	"io/ioutil"
 	"os"
750e0ace
 	"path/filepath"
ed39fbeb
 	"testing"
 
750e0ace
 	"github.com/docker/docker/api/types/blkiodev"
91e197d6
 	containertypes "github.com/docker/docker/api/types/container"
42f2621b
 	"github.com/docker/docker/container"
db63f937
 	"github.com/docker/docker/daemon/config"
57f1305e
 	"github.com/docker/docker/pkg/sysinfo"
750e0ace
 	"golang.org/x/sys/unix"
57f1305e
 	"gotest.tools/assert"
 	is "gotest.tools/assert/cmp"
ed39fbeb
 )
 
b6e5ea8e
 type fakeContainerGetter struct {
 	containers map[string]*container.Container
 }
 
 func (f *fakeContainerGetter) GetContainer(cid string) (*container.Container, error) {
 	container, ok := f.containers[cid]
 	if !ok {
 		return nil, errors.New("container not found")
 	}
 	return container, nil
 }
 
 // Unix test as uses settings which are not available on Windows
 func TestAdjustSharedNamespaceContainerName(t *testing.T) {
 	fakeID := "abcdef1234567890"
 	hostConfig := &containertypes.HostConfig{
 		IpcMode:     containertypes.IpcMode("container:base"),
 		PidMode:     containertypes.PidMode("container:base"),
 		NetworkMode: containertypes.NetworkMode("container:base"),
 	}
 	containerStore := &fakeContainerGetter{}
 	containerStore.containers = make(map[string]*container.Container)
 	containerStore.containers["base"] = &container.Container{
 		ID: fakeID,
 	}
 
 	adaptSharedNamespaceContainer(containerStore, hostConfig)
 	if hostConfig.IpcMode != containertypes.IpcMode("container:"+fakeID) {
 		t.Errorf("Expected IpcMode to be container:%s", fakeID)
 	}
 	if hostConfig.PidMode != containertypes.PidMode("container:"+fakeID) {
 		t.Errorf("Expected PidMode to be container:%s", fakeID)
 	}
 	if hostConfig.NetworkMode != containertypes.NetworkMode("container:"+fakeID) {
 		t.Errorf("Expected NetworkMode to be container:%s", fakeID)
 	}
 }
 
42f2621b
 // Unix test as uses settings which are not available on Windows
e0af23dc
 func TestAdjustCPUShares(t *testing.T) {
 	tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-")
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer os.RemoveAll(tmp)
 	daemon := &Daemon{
 		repository: tmp,
 		root:       tmp,
 	}
 
42f2621b
 	hostConfig := &containertypes.HostConfig{
 		Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
ed39fbeb
 	}
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, true)
351f6b8e
 	if hostConfig.CPUShares != linuxMinCPUShares {
 		t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares)
ed39fbeb
 	}
 
351f6b8e
 	hostConfig.CPUShares = linuxMaxCPUShares + 1
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, true)
351f6b8e
 	if hostConfig.CPUShares != linuxMaxCPUShares {
 		t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares)
ed39fbeb
 	}
 
5170a2c0
 	hostConfig.CPUShares = 0
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, true)
5170a2c0
 	if hostConfig.CPUShares != 0 {
351f6b8e
 		t.Error("Expected CPUShares to be unchanged")
ed39fbeb
 	}
 
5170a2c0
 	hostConfig.CPUShares = 1024
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, true)
5170a2c0
 	if hostConfig.CPUShares != 1024 {
351f6b8e
 		t.Error("Expected CPUShares to be unchanged")
ed39fbeb
 	}
 }
 
42f2621b
 // Unix test as uses settings which are not available on Windows
351f6b8e
 func TestAdjustCPUSharesNoAdjustment(t *testing.T) {
e0af23dc
 	tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-")
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer os.RemoveAll(tmp)
 	daemon := &Daemon{
 		repository: tmp,
 		root:       tmp,
 	}
 
42f2621b
 	hostConfig := &containertypes.HostConfig{
 		Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
ed39fbeb
 	}
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, false)
351f6b8e
 	if hostConfig.CPUShares != linuxMinCPUShares-1 {
 		t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares-1)
ed39fbeb
 	}
 
351f6b8e
 	hostConfig.CPUShares = linuxMaxCPUShares + 1
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, false)
351f6b8e
 	if hostConfig.CPUShares != linuxMaxCPUShares+1 {
 		t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares+1)
ed39fbeb
 	}
 
5170a2c0
 	hostConfig.CPUShares = 0
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, false)
5170a2c0
 	if hostConfig.CPUShares != 0 {
351f6b8e
 		t.Error("Expected CPUShares to be unchanged")
ed39fbeb
 	}
 
5170a2c0
 	hostConfig.CPUShares = 1024
e0af23dc
 	daemon.adaptContainerSettings(hostConfig, false)
5170a2c0
 	if hostConfig.CPUShares != 1024 {
351f6b8e
 		t.Error("Expected CPUShares to be unchanged")
ed39fbeb
 	}
 }
42f2621b
 
 // Unix test as uses settings which are not available on Windows
cb9aeb04
 func TestParseSecurityOptWithDeprecatedColon(t *testing.T) {
42f2621b
 	container := &container.Container{}
 	config := &containertypes.HostConfig{}
 
 	// test apparmor
cb9aeb04
 	config.SecurityOpt = []string{"apparmor=test_profile"}
42f2621b
 	if err := parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
 	}
 	if container.AppArmorProfile != "test_profile" {
 		t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", container.AppArmorProfile)
 	}
 
 	// test seccomp
 	sp := "/path/to/seccomp_test.json"
cb9aeb04
 	config.SecurityOpt = []string{"seccomp=" + sp}
42f2621b
 	if err := parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
 	}
 	if container.SeccompProfile != sp {
 		t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, container.SeccompProfile)
 	}
 
 	// test valid label
cb9aeb04
 	config.SecurityOpt = []string{"label=user:USER"}
 	if err := parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
 	}
 
 	// test invalid label
 	config.SecurityOpt = []string{"label"}
 	if err := parseSecurityOpt(container, config); err == nil {
 		t.Fatal("Expected parseSecurityOpt error, got nil")
 	}
 
 	// test invalid opt
 	config.SecurityOpt = []string{"test"}
 	if err := parseSecurityOpt(container, config); err == nil {
 		t.Fatal("Expected parseSecurityOpt error, got nil")
 	}
 }
 
 func TestParseSecurityOpt(t *testing.T) {
 	container := &container.Container{}
 	config := &containertypes.HostConfig{}
 
 	// test apparmor
 	config.SecurityOpt = []string{"apparmor=test_profile"}
 	if err := parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
 	}
 	if container.AppArmorProfile != "test_profile" {
 		t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", container.AppArmorProfile)
 	}
 
 	// test seccomp
 	sp := "/path/to/seccomp_test.json"
 	config.SecurityOpt = []string{"seccomp=" + sp}
 	if err := parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
 	}
 	if container.SeccompProfile != sp {
 		t.Fatalf("Unexpected SeccompProfile, expected: %q, got %q", sp, container.SeccompProfile)
 	}
 
 	// test valid label
 	config.SecurityOpt = []string{"label=user:USER"}
42f2621b
 	if err := parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
 	}
 
 	// test invalid label
 	config.SecurityOpt = []string{"label"}
 	if err := parseSecurityOpt(container, config); err == nil {
 		t.Fatal("Expected parseSecurityOpt error, got nil")
 	}
 
 	// test invalid opt
 	config.SecurityOpt = []string{"test"}
 	if err := parseSecurityOpt(container, config); err == nil {
 		t.Fatal("Expected parseSecurityOpt error, got nil")
 	}
 }
 
d7fda019
 func TestParseNNPSecurityOptions(t *testing.T) {
 	daemon := &Daemon{
 		configStore: &config.Config{NoNewPrivileges: true},
 	}
 	container := &container.Container{}
 	config := &containertypes.HostConfig{}
 
 	// test NNP when "daemon:true" and "no-new-privileges=false""
 	config.SecurityOpt = []string{"no-new-privileges=false"}
 
 	if err := daemon.parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
 	}
 	if container.NoNewPrivileges {
 		t.Fatalf("container.NoNewPrivileges should be FALSE: %v", container.NoNewPrivileges)
 	}
 
 	// test NNP when "daemon:false" and "no-new-privileges=true""
 	daemon.configStore.NoNewPrivileges = false
 	config.SecurityOpt = []string{"no-new-privileges=true"}
 
 	if err := daemon.parseSecurityOpt(container, config); err != nil {
 		t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
 	}
 	if !container.NoNewPrivileges {
 		t.Fatalf("container.NoNewPrivileges should be TRUE: %v", container.NoNewPrivileges)
 	}
 }
 
42f2621b
 func TestNetworkOptions(t *testing.T) {
 	daemon := &Daemon{}
db63f937
 	dconfigCorrect := &config.Config{
 		CommonConfig: config.CommonConfig{
42f2621b
 			ClusterStore:     "consul://localhost:8500",
 			ClusterAdvertise: "192.168.0.1:8000",
 		},
 	}
 
a00940f0
 	if _, err := daemon.networkOptions(dconfigCorrect, nil, nil); err != nil {
2eee6133
 		t.Fatalf("Expect networkOptions success, got error: %v", err)
42f2621b
 	}
 
db63f937
 	dconfigWrong := &config.Config{
 		CommonConfig: config.CommonConfig{
42f2621b
 			ClusterStore: "consul://localhost:8500://test://bbb",
 		},
 	}
 
a00940f0
 	if _, err := daemon.networkOptions(dconfigWrong, nil, nil); err == nil {
2a8d6368
 		t.Fatal("Expected networkOptions error, got nil")
42f2621b
 	}
 }
57f1305e
 
b6e373c5
 func TestVerifyPlatformContainerResources(t *testing.T) {
57f1305e
 	t.Parallel()
 	var (
 		no  = false
 		yes = true
 	)
 
 	withMemoryLimit := func(si *sysinfo.SysInfo) {
 		si.MemoryLimit = true
 	}
 	withSwapLimit := func(si *sysinfo.SysInfo) {
 		si.SwapLimit = true
 	}
 	withOomKillDisable := func(si *sysinfo.SysInfo) {
 		si.OomKillDisable = true
 	}
 
 	tests := []struct {
 		name             string
 		resources        containertypes.Resources
 		sysInfo          sysinfo.SysInfo
 		update           bool
 		expectedWarnings []string
 	}{
 		{
 			name:             "no-oom-kill-disable",
 			resources:        containertypes.Resources{},
 			sysInfo:          sysInfo(t, withMemoryLimit),
 			expectedWarnings: []string{},
 		},
 		{
 			name: "oom-kill-disable-disabled",
 			resources: containertypes.Resources{
 				OomKillDisable: &no,
 			},
 			sysInfo:          sysInfo(t, withMemoryLimit),
 			expectedWarnings: []string{},
 		},
 		{
 			name: "oom-kill-disable-not-supported",
 			resources: containertypes.Resources{
 				OomKillDisable: &yes,
 			},
 			sysInfo: sysInfo(t, withMemoryLimit),
 			expectedWarnings: []string{
 				"Your kernel does not support OomKillDisable. OomKillDisable discarded.",
 			},
 		},
 		{
 			name: "oom-kill-disable-without-memory-constraints",
 			resources: containertypes.Resources{
 				OomKillDisable: &yes,
 				Memory:         0,
 			},
 			sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
 			expectedWarnings: []string{
 				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
 			},
 		},
 		{
 			name: "oom-kill-disable-with-memory-constraints-but-no-memory-limit-support",
 			resources: containertypes.Resources{
 				OomKillDisable: &yes,
 				Memory:         linuxMinMemory,
 			},
 			sysInfo: sysInfo(t, withOomKillDisable),
 			expectedWarnings: []string{
 				"Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.",
 				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
 			},
 		},
 		{
 			name: "oom-kill-disable-with-memory-constraints",
 			resources: containertypes.Resources{
 				OomKillDisable: &yes,
 				Memory:         linuxMinMemory,
 			},
 			sysInfo:          sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
 			expectedWarnings: []string{},
 		},
 	}
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
b6e373c5
 			warnings, err := verifyPlatformContainerResources(&tc.resources, &tc.sysInfo, tc.update)
57f1305e
 			assert.NilError(t, err)
 			for _, w := range tc.expectedWarnings {
 				assert.Assert(t, is.Contains(warnings, w))
 			}
 		})
 	}
 }
 
 func sysInfo(t *testing.T, opts ...func(*sysinfo.SysInfo)) sysinfo.SysInfo {
 	t.Helper()
 	si := sysinfo.SysInfo{}
 
 	for _, opt := range opts {
 		opt(&si)
 	}
 
 	if si.OomKillDisable {
 		t.Log(t.Name(), "OOM disable supported")
 	}
 	return si
 }
750e0ace
 
 const (
 	// prepare major 0x1FD(509 in decimal) and minor 0x130(304)
 	DEVNO  = 0x11FD30
 	MAJOR  = 509
 	MINOR  = 304
 	WEIGHT = 1024
 )
 
 func deviceTypeMock(t *testing.T, testAndCheck func(string)) {
 	if os.Getuid() != 0 {
 		t.Skip("root required") // for mknod
 	}
 
 	t.Parallel()
 
 	tempDir, err := ioutil.TempDir("", "tempDevDir"+t.Name())
 	assert.NilError(t, err, "create temp file")
 	tempFile := filepath.Join(tempDir, "dev")
 
 	defer os.RemoveAll(tempDir)
 
 	if err = unix.Mknod(tempFile, unix.S_IFCHR, DEVNO); err != nil {
 		t.Fatalf("mknod error %s(%x): %v", tempFile, DEVNO, err)
 	}
 
 	testAndCheck(tempFile)
 }
 
 func TestGetBlkioWeightDevices(t *testing.T) {
 	deviceTypeMock(t, func(tempFile string) {
 		mockResource := containertypes.Resources{
 			BlkioWeightDevice: []*blkiodev.WeightDevice{{Path: tempFile, Weight: WEIGHT}},
 		}
 
 		weightDevs, err := getBlkioWeightDevices(mockResource)
 
 		assert.NilError(t, err, "getBlkioWeightDevices")
 		assert.Check(t, is.Len(weightDevs, 1), "getBlkioWeightDevices")
 		assert.Check(t, weightDevs[0].Major == MAJOR, "get major device type")
 		assert.Check(t, weightDevs[0].Minor == MINOR, "get minor device type")
 		assert.Check(t, *weightDevs[0].Weight == WEIGHT, "get device weight")
 	})
 }
 
 func TestGetBlkioThrottleDevices(t *testing.T) {
 	deviceTypeMock(t, func(tempFile string) {
 		mockDevs := []*blkiodev.ThrottleDevice{{Path: tempFile, Rate: WEIGHT}}
 
 		retDevs, err := getBlkioThrottleDevices(mockDevs)
 
 		assert.NilError(t, err, "getBlkioThrottleDevices")
 		assert.Check(t, is.Len(retDevs, 1), "getBlkioThrottleDevices")
 		assert.Check(t, retDevs[0].Major == MAJOR, "get major device type")
 		assert.Check(t, retDevs[0].Minor == MINOR, "get minor device type")
 		assert.Check(t, retDevs[0].Rate == WEIGHT, "get device rate")
 	})
 }