github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/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 }