gopkg.in/docker/docker.v23@v23.0.11/daemon/daemon_unix_test.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package daemon // import "github.com/docker/docker/daemon"
     5  
     6  import (
     7  	"errors"
     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 := os.MkdirTemp("", "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 := os.MkdirTemp("", "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 TestVerifyPlatformContainerResources(t *testing.T) {
   254  	t.Parallel()
   255  	var (
   256  		no  = false
   257  		yes = true
   258  	)
   259  
   260  	withMemoryLimit := func(si *sysinfo.SysInfo) {
   261  		si.MemoryLimit = true
   262  	}
   263  	withSwapLimit := func(si *sysinfo.SysInfo) {
   264  		si.SwapLimit = true
   265  	}
   266  	withOomKillDisable := func(si *sysinfo.SysInfo) {
   267  		si.OomKillDisable = true
   268  	}
   269  
   270  	tests := []struct {
   271  		name             string
   272  		resources        containertypes.Resources
   273  		sysInfo          sysinfo.SysInfo
   274  		update           bool
   275  		expectedWarnings []string
   276  	}{
   277  		{
   278  			name:             "no-oom-kill-disable",
   279  			resources:        containertypes.Resources{},
   280  			sysInfo:          sysInfo(t, withMemoryLimit),
   281  			expectedWarnings: []string{},
   282  		},
   283  		{
   284  			name: "oom-kill-disable-disabled",
   285  			resources: containertypes.Resources{
   286  				OomKillDisable: &no,
   287  			},
   288  			sysInfo:          sysInfo(t, withMemoryLimit),
   289  			expectedWarnings: []string{},
   290  		},
   291  		{
   292  			name: "oom-kill-disable-not-supported",
   293  			resources: containertypes.Resources{
   294  				OomKillDisable: &yes,
   295  			},
   296  			sysInfo: sysInfo(t, withMemoryLimit),
   297  			expectedWarnings: []string{
   298  				"Your kernel does not support OomKillDisable. OomKillDisable discarded.",
   299  			},
   300  		},
   301  		{
   302  			name: "oom-kill-disable-without-memory-constraints",
   303  			resources: containertypes.Resources{
   304  				OomKillDisable: &yes,
   305  				Memory:         0,
   306  			},
   307  			sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
   308  			expectedWarnings: []string{
   309  				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
   310  			},
   311  		},
   312  		{
   313  			name: "oom-kill-disable-with-memory-constraints-but-no-memory-limit-support",
   314  			resources: containertypes.Resources{
   315  				OomKillDisable: &yes,
   316  				Memory:         linuxMinMemory,
   317  			},
   318  			sysInfo: sysInfo(t, withOomKillDisable),
   319  			expectedWarnings: []string{
   320  				"Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.",
   321  				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
   322  			},
   323  		},
   324  		{
   325  			name: "oom-kill-disable-with-memory-constraints",
   326  			resources: containertypes.Resources{
   327  				OomKillDisable: &yes,
   328  				Memory:         linuxMinMemory,
   329  			},
   330  			sysInfo:          sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
   331  			expectedWarnings: []string{},
   332  		},
   333  	}
   334  	for _, tc := range tests {
   335  		tc := tc
   336  		t.Run(tc.name, func(t *testing.T) {
   337  			t.Parallel()
   338  			warnings, err := verifyPlatformContainerResources(&tc.resources, &tc.sysInfo, tc.update)
   339  			assert.NilError(t, err)
   340  			for _, w := range tc.expectedWarnings {
   341  				assert.Assert(t, is.Contains(warnings, w))
   342  			}
   343  		})
   344  	}
   345  }
   346  
   347  func sysInfo(t *testing.T, opts ...func(*sysinfo.SysInfo)) sysinfo.SysInfo {
   348  	t.Helper()
   349  	si := sysinfo.SysInfo{}
   350  
   351  	for _, opt := range opts {
   352  		opt(&si)
   353  	}
   354  
   355  	if si.OomKillDisable {
   356  		t.Log(t.Name(), "OOM disable supported")
   357  	}
   358  	return si
   359  }
   360  
   361  const (
   362  	// prepare major 0x1FD(509 in decimal) and minor 0x130(304)
   363  	DEVNO  = 0x11FD30
   364  	MAJOR  = 509
   365  	MINOR  = 304
   366  	WEIGHT = 1024
   367  )
   368  
   369  func deviceTypeMock(t *testing.T, testAndCheck func(string)) {
   370  	if os.Getuid() != 0 {
   371  		t.Skip("root required") // for mknod
   372  	}
   373  
   374  	t.Parallel()
   375  
   376  	tempDir, err := os.MkdirTemp("", "tempDevDir"+t.Name())
   377  	assert.NilError(t, err, "create temp file")
   378  	tempFile := filepath.Join(tempDir, "dev")
   379  
   380  	defer os.RemoveAll(tempDir)
   381  
   382  	if err = unix.Mknod(tempFile, unix.S_IFCHR, DEVNO); err != nil {
   383  		t.Fatalf("mknod error %s(%x): %v", tempFile, DEVNO, err)
   384  	}
   385  
   386  	testAndCheck(tempFile)
   387  }
   388  
   389  func TestGetBlkioWeightDevices(t *testing.T) {
   390  	deviceTypeMock(t, func(tempFile string) {
   391  		mockResource := containertypes.Resources{
   392  			BlkioWeightDevice: []*blkiodev.WeightDevice{{Path: tempFile, Weight: WEIGHT}},
   393  		}
   394  
   395  		weightDevs, err := getBlkioWeightDevices(mockResource)
   396  
   397  		assert.NilError(t, err, "getBlkioWeightDevices")
   398  		assert.Check(t, is.Len(weightDevs, 1), "getBlkioWeightDevices")
   399  		assert.Check(t, weightDevs[0].Major == MAJOR, "get major device type")
   400  		assert.Check(t, weightDevs[0].Minor == MINOR, "get minor device type")
   401  		assert.Check(t, *weightDevs[0].Weight == WEIGHT, "get device weight")
   402  	})
   403  }
   404  
   405  func TestGetBlkioThrottleDevices(t *testing.T) {
   406  	deviceTypeMock(t, func(tempFile string) {
   407  		mockDevs := []*blkiodev.ThrottleDevice{{Path: tempFile, Rate: WEIGHT}}
   408  
   409  		retDevs, err := getBlkioThrottleDevices(mockDevs)
   410  
   411  		assert.NilError(t, err, "getBlkioThrottleDevices")
   412  		assert.Check(t, is.Len(retDevs, 1), "getBlkioThrottleDevices")
   413  		assert.Check(t, retDevs[0].Major == MAJOR, "get major device type")
   414  		assert.Check(t, retDevs[0].Minor == MINOR, "get minor device type")
   415  		assert.Check(t, retDevs[0].Rate == WEIGHT, "get device rate")
   416  	})
   417  }