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