github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/daemon_unix_test.go (about)

     1  // +build !windows
     2  
     3  package daemon // import "github.com/docker/docker/daemon"
     4  
     5  import (
     6  	"errors"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/docker/docker/api/types/blkiodev"
    13  	containertypes "github.com/docker/docker/api/types/container"
    14  	"github.com/docker/docker/container"
    15  	"github.com/docker/docker/daemon/config"
    16  	"github.com/docker/docker/pkg/sysinfo"
    17  	"golang.org/x/sys/unix"
    18  	"gotest.tools/v3/assert"
    19  	is "gotest.tools/v3/assert/cmp"
    20  )
    21  
    22  type fakeContainerGetter struct {
    23  	containers map[string]*container.Container
    24  }
    25  
    26  func (f *fakeContainerGetter) GetContainer(cid string) (*container.Container, error) {
    27  	ctr, ok := f.containers[cid]
    28  	if !ok {
    29  		return nil, errors.New("container not found")
    30  	}
    31  	return ctr, nil
    32  }
    33  
    34  // Unix test as uses settings which are not available on Windows
    35  func TestAdjustSharedNamespaceContainerName(t *testing.T) {
    36  	fakeID := "abcdef1234567890"
    37  	hostConfig := &containertypes.HostConfig{
    38  		IpcMode:     containertypes.IpcMode("container:base"),
    39  		PidMode:     containertypes.PidMode("container:base"),
    40  		NetworkMode: containertypes.NetworkMode("container:base"),
    41  	}
    42  	containerStore := &fakeContainerGetter{}
    43  	containerStore.containers = make(map[string]*container.Container)
    44  	containerStore.containers["base"] = &container.Container{
    45  		ID: fakeID,
    46  	}
    47  
    48  	adaptSharedNamespaceContainer(containerStore, hostConfig)
    49  	if hostConfig.IpcMode != containertypes.IpcMode("container:"+fakeID) {
    50  		t.Errorf("Expected IpcMode to be container:%s", fakeID)
    51  	}
    52  	if hostConfig.PidMode != containertypes.PidMode("container:"+fakeID) {
    53  		t.Errorf("Expected PidMode to be container:%s", fakeID)
    54  	}
    55  	if hostConfig.NetworkMode != containertypes.NetworkMode("container:"+fakeID) {
    56  		t.Errorf("Expected NetworkMode to be container:%s", fakeID)
    57  	}
    58  }
    59  
    60  // Unix test as uses settings which are not available on Windows
    61  func TestAdjustCPUShares(t *testing.T) {
    62  	tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-")
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  	defer os.RemoveAll(tmp)
    67  	daemon := &Daemon{
    68  		repository: tmp,
    69  		root:       tmp,
    70  	}
    71  	muteLogs()
    72  
    73  	hostConfig := &containertypes.HostConfig{
    74  		Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
    75  	}
    76  	daemon.adaptContainerSettings(hostConfig, true)
    77  	if hostConfig.CPUShares != linuxMinCPUShares {
    78  		t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares)
    79  	}
    80  
    81  	hostConfig.CPUShares = linuxMaxCPUShares + 1
    82  	daemon.adaptContainerSettings(hostConfig, true)
    83  	if hostConfig.CPUShares != linuxMaxCPUShares {
    84  		t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares)
    85  	}
    86  
    87  	hostConfig.CPUShares = 0
    88  	daemon.adaptContainerSettings(hostConfig, true)
    89  	if hostConfig.CPUShares != 0 {
    90  		t.Error("Expected CPUShares to be unchanged")
    91  	}
    92  
    93  	hostConfig.CPUShares = 1024
    94  	daemon.adaptContainerSettings(hostConfig, true)
    95  	if hostConfig.CPUShares != 1024 {
    96  		t.Error("Expected CPUShares to be unchanged")
    97  	}
    98  }
    99  
   100  // Unix test as uses settings which are not available on Windows
   101  func TestAdjustCPUSharesNoAdjustment(t *testing.T) {
   102  	tmp, err := ioutil.TempDir("", "docker-daemon-unix-test-")
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	defer os.RemoveAll(tmp)
   107  	daemon := &Daemon{
   108  		repository: tmp,
   109  		root:       tmp,
   110  	}
   111  
   112  	hostConfig := &containertypes.HostConfig{
   113  		Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
   114  	}
   115  	daemon.adaptContainerSettings(hostConfig, false)
   116  	if hostConfig.CPUShares != linuxMinCPUShares-1 {
   117  		t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares-1)
   118  	}
   119  
   120  	hostConfig.CPUShares = linuxMaxCPUShares + 1
   121  	daemon.adaptContainerSettings(hostConfig, false)
   122  	if hostConfig.CPUShares != linuxMaxCPUShares+1 {
   123  		t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares+1)
   124  	}
   125  
   126  	hostConfig.CPUShares = 0
   127  	daemon.adaptContainerSettings(hostConfig, false)
   128  	if hostConfig.CPUShares != 0 {
   129  		t.Error("Expected CPUShares to be unchanged")
   130  	}
   131  
   132  	hostConfig.CPUShares = 1024
   133  	daemon.adaptContainerSettings(hostConfig, false)
   134  	if hostConfig.CPUShares != 1024 {
   135  		t.Error("Expected CPUShares to be unchanged")
   136  	}
   137  }
   138  
   139  // Unix test as uses settings which are not available on Windows
   140  func TestParseSecurityOptWithDeprecatedColon(t *testing.T) {
   141  	ctr := &container.Container{}
   142  	cfg := &containertypes.HostConfig{}
   143  
   144  	// test apparmor
   145  	cfg.SecurityOpt = []string{"apparmor=test_profile"}
   146  	if err := parseSecurityOpt(ctr, cfg); err != nil {
   147  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   148  	}
   149  	if ctr.AppArmorProfile != "test_profile" {
   150  		t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", ctr.AppArmorProfile)
   151  	}
   152  
   153  	// test seccomp
   154  	sp := "/path/to/seccomp_test.json"
   155  	cfg.SecurityOpt = []string{"seccomp=" + sp}
   156  	if err := parseSecurityOpt(ctr, cfg); err != nil {
   157  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   158  	}
   159  	if ctr.SeccompProfile != sp {
   160  		t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, ctr.SeccompProfile)
   161  	}
   162  
   163  	// test valid label
   164  	cfg.SecurityOpt = []string{"label=user:USER"}
   165  	if err := parseSecurityOpt(ctr, cfg); err != nil {
   166  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   167  	}
   168  
   169  	// test invalid label
   170  	cfg.SecurityOpt = []string{"label"}
   171  	if err := parseSecurityOpt(ctr, cfg); err == nil {
   172  		t.Fatal("Expected parseSecurityOpt error, got nil")
   173  	}
   174  
   175  	// test invalid opt
   176  	cfg.SecurityOpt = []string{"test"}
   177  	if err := parseSecurityOpt(ctr, cfg); err == nil {
   178  		t.Fatal("Expected parseSecurityOpt error, got nil")
   179  	}
   180  }
   181  
   182  func TestParseSecurityOpt(t *testing.T) {
   183  	ctr := &container.Container{}
   184  	cfg := &containertypes.HostConfig{}
   185  
   186  	// test apparmor
   187  	cfg.SecurityOpt = []string{"apparmor=test_profile"}
   188  	if err := parseSecurityOpt(ctr, cfg); err != nil {
   189  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   190  	}
   191  	if ctr.AppArmorProfile != "test_profile" {
   192  		t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", ctr.AppArmorProfile)
   193  	}
   194  
   195  	// test seccomp
   196  	sp := "/path/to/seccomp_test.json"
   197  	cfg.SecurityOpt = []string{"seccomp=" + sp}
   198  	if err := parseSecurityOpt(ctr, cfg); err != nil {
   199  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   200  	}
   201  	if ctr.SeccompProfile != sp {
   202  		t.Fatalf("Unexpected SeccompProfile, expected: %q, got %q", sp, ctr.SeccompProfile)
   203  	}
   204  
   205  	// test valid label
   206  	cfg.SecurityOpt = []string{"label=user:USER"}
   207  	if err := parseSecurityOpt(ctr, cfg); err != nil {
   208  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   209  	}
   210  
   211  	// test invalid label
   212  	cfg.SecurityOpt = []string{"label"}
   213  	if err := parseSecurityOpt(ctr, cfg); err == nil {
   214  		t.Fatal("Expected parseSecurityOpt error, got nil")
   215  	}
   216  
   217  	// test invalid opt
   218  	cfg.SecurityOpt = []string{"test"}
   219  	if err := parseSecurityOpt(ctr, cfg); err == nil {
   220  		t.Fatal("Expected parseSecurityOpt error, got nil")
   221  	}
   222  }
   223  
   224  func TestParseNNPSecurityOptions(t *testing.T) {
   225  	daemon := &Daemon{
   226  		configStore: &config.Config{NoNewPrivileges: true},
   227  	}
   228  	ctr := &container.Container{}
   229  	cfg := &containertypes.HostConfig{}
   230  
   231  	// test NNP when "daemon:true" and "no-new-privileges=false""
   232  	cfg.SecurityOpt = []string{"no-new-privileges=false"}
   233  
   234  	if err := daemon.parseSecurityOpt(ctr, cfg); err != nil {
   235  		t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
   236  	}
   237  	if ctr.NoNewPrivileges {
   238  		t.Fatalf("container.NoNewPrivileges should be FALSE: %v", ctr.NoNewPrivileges)
   239  	}
   240  
   241  	// test NNP when "daemon:false" and "no-new-privileges=true""
   242  	daemon.configStore.NoNewPrivileges = false
   243  	cfg.SecurityOpt = []string{"no-new-privileges=true"}
   244  
   245  	if err := daemon.parseSecurityOpt(ctr, cfg); err != nil {
   246  		t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
   247  	}
   248  	if !ctr.NoNewPrivileges {
   249  		t.Fatalf("container.NoNewPrivileges should be TRUE: %v", ctr.NoNewPrivileges)
   250  	}
   251  }
   252  
   253  func TestNetworkOptions(t *testing.T) {
   254  	daemon := &Daemon{}
   255  	dconfigCorrect := &config.Config{
   256  		CommonConfig: config.CommonConfig{
   257  			ClusterStore:     "consul://localhost:8500",
   258  			ClusterAdvertise: "192.168.0.1:8000",
   259  		},
   260  	}
   261  
   262  	if _, err := daemon.networkOptions(dconfigCorrect, nil, nil); err != nil {
   263  		t.Fatalf("Expect networkOptions success, got error: %v", err)
   264  	}
   265  
   266  	dconfigWrong := &config.Config{
   267  		CommonConfig: config.CommonConfig{
   268  			ClusterStore: "consul://localhost:8500://test://bbb",
   269  		},
   270  	}
   271  
   272  	if _, err := daemon.networkOptions(dconfigWrong, nil, nil); err == nil {
   273  		t.Fatal("Expected networkOptions error, got nil")
   274  	}
   275  }
   276  
   277  func TestVerifyPlatformContainerResources(t *testing.T) {
   278  	t.Parallel()
   279  	var (
   280  		no  = false
   281  		yes = true
   282  	)
   283  
   284  	withMemoryLimit := func(si *sysinfo.SysInfo) {
   285  		si.MemoryLimit = true
   286  	}
   287  	withSwapLimit := func(si *sysinfo.SysInfo) {
   288  		si.SwapLimit = true
   289  	}
   290  	withOomKillDisable := func(si *sysinfo.SysInfo) {
   291  		si.OomKillDisable = true
   292  	}
   293  
   294  	tests := []struct {
   295  		name             string
   296  		resources        containertypes.Resources
   297  		sysInfo          sysinfo.SysInfo
   298  		update           bool
   299  		expectedWarnings []string
   300  	}{
   301  		{
   302  			name:             "no-oom-kill-disable",
   303  			resources:        containertypes.Resources{},
   304  			sysInfo:          sysInfo(t, withMemoryLimit),
   305  			expectedWarnings: []string{},
   306  		},
   307  		{
   308  			name: "oom-kill-disable-disabled",
   309  			resources: containertypes.Resources{
   310  				OomKillDisable: &no,
   311  			},
   312  			sysInfo:          sysInfo(t, withMemoryLimit),
   313  			expectedWarnings: []string{},
   314  		},
   315  		{
   316  			name: "oom-kill-disable-not-supported",
   317  			resources: containertypes.Resources{
   318  				OomKillDisable: &yes,
   319  			},
   320  			sysInfo: sysInfo(t, withMemoryLimit),
   321  			expectedWarnings: []string{
   322  				"Your kernel does not support OomKillDisable. OomKillDisable discarded.",
   323  			},
   324  		},
   325  		{
   326  			name: "oom-kill-disable-without-memory-constraints",
   327  			resources: containertypes.Resources{
   328  				OomKillDisable: &yes,
   329  				Memory:         0,
   330  			},
   331  			sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
   332  			expectedWarnings: []string{
   333  				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
   334  			},
   335  		},
   336  		{
   337  			name: "oom-kill-disable-with-memory-constraints-but-no-memory-limit-support",
   338  			resources: containertypes.Resources{
   339  				OomKillDisable: &yes,
   340  				Memory:         linuxMinMemory,
   341  			},
   342  			sysInfo: sysInfo(t, withOomKillDisable),
   343  			expectedWarnings: []string{
   344  				"Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.",
   345  				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
   346  			},
   347  		},
   348  		{
   349  			name: "oom-kill-disable-with-memory-constraints",
   350  			resources: containertypes.Resources{
   351  				OomKillDisable: &yes,
   352  				Memory:         linuxMinMemory,
   353  			},
   354  			sysInfo:          sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
   355  			expectedWarnings: []string{},
   356  		},
   357  	}
   358  	for _, tc := range tests {
   359  		t.Run(tc.name, func(t *testing.T) {
   360  			t.Parallel()
   361  			warnings, err := verifyPlatformContainerResources(&tc.resources, &tc.sysInfo, tc.update)
   362  			assert.NilError(t, err)
   363  			for _, w := range tc.expectedWarnings {
   364  				assert.Assert(t, is.Contains(warnings, w))
   365  			}
   366  		})
   367  	}
   368  }
   369  
   370  func sysInfo(t *testing.T, opts ...func(*sysinfo.SysInfo)) sysinfo.SysInfo {
   371  	t.Helper()
   372  	si := sysinfo.SysInfo{}
   373  
   374  	for _, opt := range opts {
   375  		opt(&si)
   376  	}
   377  
   378  	if si.OomKillDisable {
   379  		t.Log(t.Name(), "OOM disable supported")
   380  	}
   381  	return si
   382  }
   383  
   384  const (
   385  	// prepare major 0x1FD(509 in decimal) and minor 0x130(304)
   386  	DEVNO  = 0x11FD30
   387  	MAJOR  = 509
   388  	MINOR  = 304
   389  	WEIGHT = 1024
   390  )
   391  
   392  func deviceTypeMock(t *testing.T, testAndCheck func(string)) {
   393  	if os.Getuid() != 0 {
   394  		t.Skip("root required") // for mknod
   395  	}
   396  
   397  	t.Parallel()
   398  
   399  	tempDir, err := ioutil.TempDir("", "tempDevDir"+t.Name())
   400  	assert.NilError(t, err, "create temp file")
   401  	tempFile := filepath.Join(tempDir, "dev")
   402  
   403  	defer os.RemoveAll(tempDir)
   404  
   405  	if err = unix.Mknod(tempFile, unix.S_IFCHR, DEVNO); err != nil {
   406  		t.Fatalf("mknod error %s(%x): %v", tempFile, DEVNO, err)
   407  	}
   408  
   409  	testAndCheck(tempFile)
   410  }
   411  
   412  func TestGetBlkioWeightDevices(t *testing.T) {
   413  	deviceTypeMock(t, func(tempFile string) {
   414  		mockResource := containertypes.Resources{
   415  			BlkioWeightDevice: []*blkiodev.WeightDevice{{Path: tempFile, Weight: WEIGHT}},
   416  		}
   417  
   418  		weightDevs, err := getBlkioWeightDevices(mockResource)
   419  
   420  		assert.NilError(t, err, "getBlkioWeightDevices")
   421  		assert.Check(t, is.Len(weightDevs, 1), "getBlkioWeightDevices")
   422  		assert.Check(t, weightDevs[0].Major == MAJOR, "get major device type")
   423  		assert.Check(t, weightDevs[0].Minor == MINOR, "get minor device type")
   424  		assert.Check(t, *weightDevs[0].Weight == WEIGHT, "get device weight")
   425  	})
   426  }
   427  
   428  func TestGetBlkioThrottleDevices(t *testing.T) {
   429  	deviceTypeMock(t, func(tempFile string) {
   430  		mockDevs := []*blkiodev.ThrottleDevice{{Path: tempFile, Rate: WEIGHT}}
   431  
   432  		retDevs, err := getBlkioThrottleDevices(mockDevs)
   433  
   434  		assert.NilError(t, err, "getBlkioThrottleDevices")
   435  		assert.Check(t, is.Len(retDevs, 1), "getBlkioThrottleDevices")
   436  		assert.Check(t, retDevs[0].Major == MAJOR, "get major device type")
   437  		assert.Check(t, retDevs[0].Minor == MINOR, "get minor device type")
   438  		assert.Check(t, retDevs[0].Rate == WEIGHT, "get device rate")
   439  	})
   440  }