github.com/rish1988/moby@v25.0.2+incompatible/daemon/daemon_unix_test.go (about)

     1  //go:build !windows
     2  
     3  package daemon // import "github.com/docker/docker/daemon"
     4  
     5  import (
     6  	"errors"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/docker/docker/api/types/blkiodev"
    12  	containertypes "github.com/docker/docker/api/types/container"
    13  	"github.com/docker/docker/container"
    14  	"github.com/docker/docker/daemon/config"
    15  	"github.com/docker/docker/pkg/sysinfo"
    16  	"github.com/opencontainers/selinux/go-selinux"
    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  	cfg := &config.Config{}
    72  	muteLogs(t)
    73  
    74  	hostConfig := &containertypes.HostConfig{
    75  		Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
    76  	}
    77  	daemon.adaptContainerSettings(cfg, hostConfig, true)
    78  	if hostConfig.CPUShares != linuxMinCPUShares {
    79  		t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares)
    80  	}
    81  
    82  	hostConfig.CPUShares = linuxMaxCPUShares + 1
    83  	daemon.adaptContainerSettings(cfg, hostConfig, true)
    84  	if hostConfig.CPUShares != linuxMaxCPUShares {
    85  		t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares)
    86  	}
    87  
    88  	hostConfig.CPUShares = 0
    89  	daemon.adaptContainerSettings(cfg, hostConfig, true)
    90  	if hostConfig.CPUShares != 0 {
    91  		t.Error("Expected CPUShares to be unchanged")
    92  	}
    93  
    94  	hostConfig.CPUShares = 1024
    95  	daemon.adaptContainerSettings(cfg, hostConfig, true)
    96  	if hostConfig.CPUShares != 1024 {
    97  		t.Error("Expected CPUShares to be unchanged")
    98  	}
    99  }
   100  
   101  // Unix test as uses settings which are not available on Windows
   102  func TestAdjustCPUSharesNoAdjustment(t *testing.T) {
   103  	tmp, err := os.MkdirTemp("", "docker-daemon-unix-test-")
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  	defer os.RemoveAll(tmp)
   108  	daemon := &Daemon{
   109  		repository: tmp,
   110  		root:       tmp,
   111  	}
   112  	cfg := &config.Config{}
   113  
   114  	hostConfig := &containertypes.HostConfig{
   115  		Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
   116  	}
   117  	daemon.adaptContainerSettings(cfg, hostConfig, false)
   118  	if hostConfig.CPUShares != linuxMinCPUShares-1 {
   119  		t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares-1)
   120  	}
   121  
   122  	hostConfig.CPUShares = linuxMaxCPUShares + 1
   123  	daemon.adaptContainerSettings(cfg, hostConfig, false)
   124  	if hostConfig.CPUShares != linuxMaxCPUShares+1 {
   125  		t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares+1)
   126  	}
   127  
   128  	hostConfig.CPUShares = 0
   129  	daemon.adaptContainerSettings(cfg, hostConfig, false)
   130  	if hostConfig.CPUShares != 0 {
   131  		t.Error("Expected CPUShares to be unchanged")
   132  	}
   133  
   134  	hostConfig.CPUShares = 1024
   135  	daemon.adaptContainerSettings(cfg, hostConfig, false)
   136  	if hostConfig.CPUShares != 1024 {
   137  		t.Error("Expected CPUShares to be unchanged")
   138  	}
   139  }
   140  
   141  // Unix test as uses settings which are not available on Windows
   142  func TestParseSecurityOptWithDeprecatedColon(t *testing.T) {
   143  	opts := &container.SecurityOptions{}
   144  	cfg := &containertypes.HostConfig{}
   145  
   146  	// test apparmor
   147  	cfg.SecurityOpt = []string{"apparmor=test_profile"}
   148  	if err := parseSecurityOpt(opts, cfg); err != nil {
   149  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   150  	}
   151  	if opts.AppArmorProfile != "test_profile" {
   152  		t.Fatalf(`Unexpected AppArmorProfile, expected: "test_profile", got %q`, opts.AppArmorProfile)
   153  	}
   154  
   155  	// test seccomp
   156  	sp := "/path/to/seccomp_test.json"
   157  	cfg.SecurityOpt = []string{"seccomp=" + sp}
   158  	if err := parseSecurityOpt(opts, cfg); err != nil {
   159  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   160  	}
   161  	if opts.SeccompProfile != sp {
   162  		t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, opts.SeccompProfile)
   163  	}
   164  
   165  	// test valid label
   166  	cfg.SecurityOpt = []string{"label=user:USER"}
   167  	if err := parseSecurityOpt(opts, cfg); err != nil {
   168  		t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
   169  	}
   170  
   171  	// test invalid label
   172  	cfg.SecurityOpt = []string{"label"}
   173  	if err := parseSecurityOpt(opts, cfg); err == nil {
   174  		t.Fatal("Expected parseSecurityOpt error, got nil")
   175  	}
   176  
   177  	// test invalid opt
   178  	cfg.SecurityOpt = []string{"test"}
   179  	if err := parseSecurityOpt(opts, cfg); err == nil {
   180  		t.Fatal("Expected parseSecurityOpt error, got nil")
   181  	}
   182  }
   183  
   184  func TestParseSecurityOpt(t *testing.T) {
   185  	t.Run("apparmor", func(t *testing.T) {
   186  		secOpts := &container.SecurityOptions{}
   187  		err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
   188  			SecurityOpt: []string{"apparmor=test_profile"},
   189  		})
   190  		assert.Check(t, err)
   191  		assert.Equal(t, secOpts.AppArmorProfile, "test_profile")
   192  	})
   193  	t.Run("apparmor using legacy separator", func(t *testing.T) {
   194  		secOpts := &container.SecurityOptions{}
   195  		err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
   196  			SecurityOpt: []string{"apparmor:test_profile"},
   197  		})
   198  		assert.Check(t, err)
   199  		assert.Equal(t, secOpts.AppArmorProfile, "test_profile")
   200  	})
   201  	t.Run("seccomp", func(t *testing.T) {
   202  		secOpts := &container.SecurityOptions{}
   203  		err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
   204  			SecurityOpt: []string{"seccomp=/path/to/seccomp_test.json"},
   205  		})
   206  		assert.Check(t, err)
   207  		assert.Equal(t, secOpts.SeccompProfile, "/path/to/seccomp_test.json")
   208  	})
   209  	t.Run("valid label", func(t *testing.T) {
   210  		secOpts := &container.SecurityOptions{}
   211  		err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
   212  			SecurityOpt: []string{"label=user:USER"},
   213  		})
   214  		assert.Check(t, err)
   215  		if selinux.GetEnabled() {
   216  			// TODO(thaJeztah): set expected labels here (or "partial" if depends on host)
   217  			// assert.Check(t, is.Equal(secOpts.MountLabel, ""))
   218  			// assert.Check(t, is.Equal(secOpts.ProcessLabel, ""))
   219  		} else {
   220  			assert.Check(t, is.Equal(secOpts.MountLabel, ""))
   221  			assert.Check(t, is.Equal(secOpts.ProcessLabel, ""))
   222  		}
   223  	})
   224  	t.Run("invalid label", func(t *testing.T) {
   225  		secOpts := &container.SecurityOptions{}
   226  		err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
   227  			SecurityOpt: []string{"label"},
   228  		})
   229  		assert.Error(t, err, `invalid --security-opt 1: "label"`)
   230  	})
   231  	t.Run("invalid option (no value)", func(t *testing.T) {
   232  		secOpts := &container.SecurityOptions{}
   233  		err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
   234  			SecurityOpt: []string{"unknown"},
   235  		})
   236  		assert.Error(t, err, `invalid --security-opt 1: "unknown"`)
   237  	})
   238  	t.Run("unknown option", func(t *testing.T) {
   239  		secOpts := &container.SecurityOptions{}
   240  		err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
   241  			SecurityOpt: []string{"unknown=something"},
   242  		})
   243  		assert.Error(t, err, `invalid --security-opt 2: "unknown=something"`)
   244  	})
   245  }
   246  
   247  func TestParseNNPSecurityOptions(t *testing.T) {
   248  	daemonCfg := &configStore{Config: config.Config{NoNewPrivileges: true}}
   249  	daemon := &Daemon{}
   250  	daemon.configStore.Store(daemonCfg)
   251  	opts := &container.SecurityOptions{}
   252  	cfg := &containertypes.HostConfig{}
   253  
   254  	// test NNP when "daemon:true" and "no-new-privileges=false""
   255  	cfg.SecurityOpt = []string{"no-new-privileges=false"}
   256  
   257  	if err := daemon.parseSecurityOpt(&daemonCfg.Config, opts, cfg); err != nil {
   258  		t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
   259  	}
   260  	if opts.NoNewPrivileges {
   261  		t.Fatalf("container.NoNewPrivileges should be FALSE: %v", opts.NoNewPrivileges)
   262  	}
   263  
   264  	// test NNP when "daemon:false" and "no-new-privileges=true""
   265  	daemonCfg.NoNewPrivileges = false
   266  	cfg.SecurityOpt = []string{"no-new-privileges=true"}
   267  
   268  	if err := daemon.parseSecurityOpt(&daemonCfg.Config, opts, cfg); err != nil {
   269  		t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
   270  	}
   271  	if !opts.NoNewPrivileges {
   272  		t.Fatalf("container.NoNewPrivileges should be TRUE: %v", opts.NoNewPrivileges)
   273  	}
   274  }
   275  
   276  func TestVerifyPlatformContainerResources(t *testing.T) {
   277  	t.Parallel()
   278  	var (
   279  		no  = false
   280  		yes = true
   281  	)
   282  
   283  	withMemoryLimit := func(si *sysinfo.SysInfo) {
   284  		si.MemoryLimit = true
   285  	}
   286  	withSwapLimit := func(si *sysinfo.SysInfo) {
   287  		si.SwapLimit = true
   288  	}
   289  	withOomKillDisable := func(si *sysinfo.SysInfo) {
   290  		si.OomKillDisable = true
   291  	}
   292  
   293  	tests := []struct {
   294  		name             string
   295  		resources        containertypes.Resources
   296  		sysInfo          sysinfo.SysInfo
   297  		update           bool
   298  		expectedWarnings []string
   299  	}{
   300  		{
   301  			name:             "no-oom-kill-disable",
   302  			resources:        containertypes.Resources{},
   303  			sysInfo:          sysInfo(t, withMemoryLimit),
   304  			expectedWarnings: []string{},
   305  		},
   306  		{
   307  			name: "oom-kill-disable-disabled",
   308  			resources: containertypes.Resources{
   309  				OomKillDisable: &no,
   310  			},
   311  			sysInfo:          sysInfo(t, withMemoryLimit),
   312  			expectedWarnings: []string{},
   313  		},
   314  		{
   315  			name: "oom-kill-disable-not-supported",
   316  			resources: containertypes.Resources{
   317  				OomKillDisable: &yes,
   318  			},
   319  			sysInfo: sysInfo(t, withMemoryLimit),
   320  			expectedWarnings: []string{
   321  				"Your kernel does not support OomKillDisable. OomKillDisable discarded.",
   322  			},
   323  		},
   324  		{
   325  			name: "oom-kill-disable-without-memory-constraints",
   326  			resources: containertypes.Resources{
   327  				OomKillDisable: &yes,
   328  				Memory:         0,
   329  			},
   330  			sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
   331  			expectedWarnings: []string{
   332  				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
   333  			},
   334  		},
   335  		{
   336  			name: "oom-kill-disable-with-memory-constraints-but-no-memory-limit-support",
   337  			resources: containertypes.Resources{
   338  				OomKillDisable: &yes,
   339  				Memory:         linuxMinMemory,
   340  			},
   341  			sysInfo: sysInfo(t, withOomKillDisable),
   342  			expectedWarnings: []string{
   343  				"Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.",
   344  				"OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
   345  			},
   346  		},
   347  		{
   348  			name: "oom-kill-disable-with-memory-constraints",
   349  			resources: containertypes.Resources{
   350  				OomKillDisable: &yes,
   351  				Memory:         linuxMinMemory,
   352  			},
   353  			sysInfo:          sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
   354  			expectedWarnings: []string{},
   355  		},
   356  	}
   357  	for _, tc := range tests {
   358  		tc := tc
   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 := os.MkdirTemp("", "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  }