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 }