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 }