github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/drivers/docker/config_test.go (about) 1 package docker 2 3 import ( 4 "testing" 5 6 "github.com/hashicorp/nomad/helper/pluginutils/hclutils" 7 "github.com/hashicorp/nomad/plugins/drivers" 8 "github.com/stretchr/testify/require" 9 ) 10 11 func TestConfig_ParseHCL(t *testing.T) { 12 cases := []struct { 13 name string 14 15 input string 16 expected *TaskConfig 17 }{ 18 { 19 "basic image", 20 `config { 21 image = "redis:3.2" 22 }`, 23 &TaskConfig{ 24 Image: "redis:3.2", 25 Devices: []DockerDevice{}, 26 Mounts: []DockerMount{}, 27 MountsList: []DockerMount{}, 28 CPUCFSPeriod: 100000, 29 ImagePullTimeout: "5m", 30 }, 31 }, 32 } 33 34 parser := hclutils.NewConfigParser(taskConfigSpec) 35 for _, c := range cases { 36 c := c 37 t.Run(c.name, func(t *testing.T) { 38 var tc *TaskConfig 39 40 parser.ParseHCL(t, c.input, &tc) 41 42 require.EqualValues(t, c.expected, tc) 43 44 }) 45 } 46 } 47 48 func TestConfig_ParseJSON(t *testing.T) { 49 cases := []struct { 50 name string 51 input string 52 expected TaskConfig 53 }{ 54 { 55 name: "nil values for blocks are safe", 56 input: `{"Config": {"image": "bash:3", "mounts": null}}`, 57 expected: TaskConfig{ 58 Image: "bash:3", 59 Mounts: []DockerMount{}, 60 MountsList: []DockerMount{}, 61 Devices: []DockerDevice{}, 62 CPUCFSPeriod: 100000, 63 ImagePullTimeout: "5m", 64 }, 65 }, 66 { 67 name: "nil values for 'volumes' field are safe", 68 input: `{"Config": {"image": "bash:3", "volumes": null}}`, 69 expected: TaskConfig{ 70 Image: "bash:3", 71 Mounts: []DockerMount{}, 72 MountsList: []DockerMount{}, 73 Devices: []DockerDevice{}, 74 CPUCFSPeriod: 100000, 75 ImagePullTimeout: "5m", 76 }, 77 }, 78 { 79 name: "nil values for 'args' field are safe", 80 input: `{"Config": {"image": "bash:3", "args": null}}`, 81 expected: TaskConfig{ 82 Image: "bash:3", 83 Mounts: []DockerMount{}, 84 MountsList: []DockerMount{}, 85 Devices: []DockerDevice{}, 86 CPUCFSPeriod: 100000, 87 ImagePullTimeout: "5m", 88 }, 89 }, 90 { 91 name: "nil values for string fields are safe", 92 input: `{"Config": {"image": "bash:3", "command": null}}`, 93 expected: TaskConfig{ 94 Image: "bash:3", 95 Mounts: []DockerMount{}, 96 MountsList: []DockerMount{}, 97 Devices: []DockerDevice{}, 98 CPUCFSPeriod: 100000, 99 ImagePullTimeout: "5m", 100 }, 101 }, 102 } 103 104 for _, c := range cases { 105 c := c 106 t.Run(c.name, func(t *testing.T) { 107 var tc TaskConfig 108 hclutils.NewConfigParser(taskConfigSpec).ParseJson(t, c.input, &tc) 109 110 require.Equal(t, c.expected, tc) 111 }) 112 } 113 } 114 115 func TestConfig_PortMap_Deserialization(t *testing.T) { 116 parser := hclutils.NewConfigParser(taskConfigSpec) 117 118 expectedMap := map[string]int{ 119 "ssh": 25, 120 "http": 80, 121 "https": 443, 122 } 123 124 t.Run("parsing hcl block case", func(t *testing.T) { 125 validHCL := ` 126 config { 127 image = "redis" 128 port_map { 129 ssh = 25 130 http = 80 131 https = 443 132 } 133 }` 134 135 var tc *TaskConfig 136 parser.ParseHCL(t, validHCL, &tc) 137 138 require.EqualValues(t, expectedMap, tc.PortMap) 139 }) 140 141 t.Run("parsing hcl assignment case", func(t *testing.T) { 142 validHCL := ` 143 config { 144 image = "redis" 145 port_map = { 146 ssh = 25 147 http = 80 148 https = 443 149 } 150 }` 151 152 var tc *TaskConfig 153 parser.ParseHCL(t, validHCL, &tc) 154 155 require.EqualValues(t, expectedMap, tc.PortMap) 156 }) 157 158 validJsons := []struct { 159 name string 160 json string 161 }{ 162 { 163 "single map in an array", 164 `{"Config": {"image": "redis", "port_map": [{"ssh": 25, "http": 80, "https": 443}]}}`, 165 }, 166 { 167 "array of single map entries", 168 `{"Config": {"image": "redis", "port_map": [{"ssh": 25}, {"http": 80}, {"https": 443}]}}`, 169 }, 170 { 171 "array of maps", 172 `{"Config": {"image": "redis", "port_map": [{"ssh": 25, "http": 80}, {"https": 443}]}}`, 173 }, 174 } 175 176 for _, c := range validJsons { 177 t.Run("json:"+c.name, func(t *testing.T) { 178 var tc *TaskConfig 179 parser.ParseJson(t, c.json, &tc) 180 181 require.EqualValues(t, expectedMap, tc.PortMap) 182 }) 183 } 184 185 } 186 187 func TestConfig_ParseAllHCL(t *testing.T) { 188 cfgStr := ` 189 config { 190 image = "redis:3.2" 191 image_pull_timeout = "15m" 192 advertise_ipv6_address = true 193 args = ["command_arg1", "command_arg2"] 194 auth { 195 username = "myusername" 196 password = "mypassword" 197 email = "myemail@example.com" 198 server_address = "https://example.com" 199 } 200 201 auth_soft_fail = true 202 cap_add = ["CAP_SYS_NICE"] 203 cap_drop = ["CAP_SYS_ADMIN", "CAP_SYS_TIME"] 204 command = "/bin/bash" 205 cpu_hard_limit = true 206 cpu_cfs_period = 20 207 devices = [ 208 {"host_path"="/dev/null", "container_path"="/tmp/container-null", cgroup_permissions="rwm"}, 209 {"host_path"="/dev/random", "container_path"="/tmp/container-random"}, 210 ] 211 dns_search_domains = ["sub.example.com", "sub2.example.com"] 212 dns_options = ["debug", "attempts:10"] 213 dns_servers = ["8.8.8.8", "1.1.1.1"] 214 entrypoint = ["/bin/bash", "-c"] 215 extra_hosts = ["127.0.0.1 localhost.example.com"] 216 force_pull = true 217 hostname = "self.example.com" 218 interactive = true 219 ipc_mode = "host" 220 ipv4_address = "10.0.2.1" 221 ipv6_address = "2601:184:407f:b37c:d834:412e:1f86:7699" 222 labels { 223 owner = "hashicorp-nomad" 224 key = "val" 225 } 226 load = "/tmp/image.tar.gz" 227 logging { 228 driver = "json-file-driver" 229 type = "json-file" 230 config { 231 "max-file" = "3" 232 "max-size" = "10m" 233 } 234 } 235 mac_address = "02:42:ac:11:00:02" 236 memory_hard_limit = 512 237 238 mount { 239 type = "bind" 240 target ="/mount-bind-target" 241 source = "/bind-source-mount" 242 readonly = true 243 bind_options { 244 propagation = "rshared" 245 } 246 } 247 248 mount { 249 type = "tmpfs" 250 target ="/mount-tmpfs-target" 251 readonly = true 252 tmpfs_options { 253 size = 30000 254 mode = 0777 255 } 256 } 257 258 mounts = [ 259 { 260 type = "bind" 261 target = "/bind-target", 262 source = "/bind-source" 263 readonly = true 264 bind_options { 265 propagation = "rshared" 266 } 267 }, 268 { 269 type = "tmpfs" 270 target = "/tmpfs-target", 271 readonly = true 272 tmpfs_options { 273 size = 30000 274 mode = 0777 275 } 276 }, 277 { 278 type = "volume" 279 target = "/volume-target" 280 source = "/volume-source" 281 readonly = true 282 volume_options { 283 no_copy = true 284 labels { 285 label_key = "label_value" 286 } 287 driver_config { 288 name = "nfs" 289 options { 290 option_key = "option_value" 291 } 292 } 293 } 294 }, 295 ] 296 network_aliases = ["redis"] 297 network_mode = "host" 298 pids_limit = 2000 299 pid_mode = "host" 300 ports = ["http", "https"] 301 port_map { 302 http = 80 303 redis = 6379 304 } 305 privileged = true 306 readonly_rootfs = true 307 runtime = "runc" 308 security_opt = [ 309 "credentialspec=file://gmsaUser.json" 310 ], 311 shm_size = 30000 312 storage_opt { 313 dm.thinpooldev = "dev/mapper/thin-pool" 314 dm.use_deferred_deletion = "true" 315 dm.use_deferred_removal = "true" 316 317 } 318 sysctl { 319 net.core.somaxconn = "16384" 320 } 321 tty = true 322 ulimit { 323 nproc = "4242" 324 nofile = "2048:4096" 325 } 326 uts_mode = "host" 327 userns_mode = "host" 328 volumes = [ 329 "/host-path:/container-path:rw", 330 ] 331 volume_driver = "host" 332 work_dir = "/tmp/workdir" 333 }` 334 335 expected := &TaskConfig{ 336 Image: "redis:3.2", 337 ImagePullTimeout: "15m", 338 AdvertiseIPv6Addr: true, 339 Args: []string{"command_arg1", "command_arg2"}, 340 Auth: DockerAuth{ 341 Username: "myusername", 342 Password: "mypassword", 343 Email: "myemail@example.com", 344 ServerAddr: "https://example.com", 345 }, 346 AuthSoftFail: true, 347 CapAdd: []string{"CAP_SYS_NICE"}, 348 CapDrop: []string{"CAP_SYS_ADMIN", "CAP_SYS_TIME"}, 349 Command: "/bin/bash", 350 CPUHardLimit: true, 351 CPUCFSPeriod: 20, 352 Devices: []DockerDevice{ 353 { 354 HostPath: "/dev/null", 355 ContainerPath: "/tmp/container-null", 356 CgroupPermissions: "rwm", 357 }, 358 { 359 HostPath: "/dev/random", 360 ContainerPath: "/tmp/container-random", 361 CgroupPermissions: "", 362 }, 363 }, 364 DNSSearchDomains: []string{"sub.example.com", "sub2.example.com"}, 365 DNSOptions: []string{"debug", "attempts:10"}, 366 DNSServers: []string{"8.8.8.8", "1.1.1.1"}, 367 Entrypoint: []string{"/bin/bash", "-c"}, 368 ExtraHosts: []string{"127.0.0.1 localhost.example.com"}, 369 ForcePull: true, 370 Hostname: "self.example.com", 371 Interactive: true, 372 IPCMode: "host", 373 IPv4Address: "10.0.2.1", 374 IPv6Address: "2601:184:407f:b37c:d834:412e:1f86:7699", 375 Labels: map[string]string{ 376 "owner": "hashicorp-nomad", 377 "key": "val", 378 }, 379 LoadImage: "/tmp/image.tar.gz", 380 Logging: DockerLogging{ 381 Driver: "json-file-driver", 382 Type: "json-file", 383 Config: map[string]string{ 384 "max-file": "3", 385 "max-size": "10m", 386 }}, 387 MacAddress: "02:42:ac:11:00:02", 388 MemoryHardLimit: 512, 389 Mounts: []DockerMount{ 390 { 391 Type: "bind", 392 Target: "/mount-bind-target", 393 Source: "/bind-source-mount", 394 ReadOnly: true, 395 BindOptions: DockerBindOptions{ 396 Propagation: "rshared", 397 }, 398 }, 399 { 400 Type: "tmpfs", 401 Target: "/mount-tmpfs-target", 402 Source: "", 403 ReadOnly: true, 404 TmpfsOptions: DockerTmpfsOptions{ 405 SizeBytes: 30000, 406 Mode: 511, 407 }, 408 }, 409 }, 410 MountsList: []DockerMount{ 411 { 412 Type: "bind", 413 Target: "/bind-target", 414 Source: "/bind-source", 415 ReadOnly: true, 416 BindOptions: DockerBindOptions{ 417 Propagation: "rshared", 418 }, 419 }, 420 { 421 Type: "tmpfs", 422 Target: "/tmpfs-target", 423 Source: "", 424 ReadOnly: true, 425 TmpfsOptions: DockerTmpfsOptions{ 426 SizeBytes: 30000, 427 Mode: 511, 428 }, 429 }, 430 { 431 Type: "volume", 432 Target: "/volume-target", 433 Source: "/volume-source", 434 ReadOnly: true, 435 VolumeOptions: DockerVolumeOptions{ 436 NoCopy: true, 437 Labels: map[string]string{ 438 "label_key": "label_value", 439 }, 440 DriverConfig: DockerVolumeDriverConfig{ 441 Name: "nfs", 442 Options: map[string]string{ 443 "option_key": "option_value", 444 }, 445 }, 446 }, 447 }, 448 }, 449 NetworkAliases: []string{"redis"}, 450 NetworkMode: "host", 451 PidsLimit: 2000, 452 PidMode: "host", 453 Ports: []string{"http", "https"}, 454 PortMap: map[string]int{ 455 "http": 80, 456 "redis": 6379, 457 }, 458 Privileged: true, 459 ReadonlyRootfs: true, 460 Runtime: "runc", 461 SecurityOpt: []string{ 462 "credentialspec=file://gmsaUser.json", 463 }, 464 ShmSize: 30000, 465 StorageOpt: map[string]string{ 466 "dm.thinpooldev": "dev/mapper/thin-pool", 467 "dm.use_deferred_deletion": "true", 468 "dm.use_deferred_removal": "true", 469 }, 470 Sysctl: map[string]string{ 471 "net.core.somaxconn": "16384", 472 }, 473 TTY: true, 474 Ulimit: map[string]string{ 475 "nofile": "2048:4096", 476 "nproc": "4242", 477 }, 478 UTSMode: "host", 479 UsernsMode: "host", 480 Volumes: []string{ 481 "/host-path:/container-path:rw", 482 }, 483 VolumeDriver: "host", 484 WorkDir: "/tmp/workdir", 485 } 486 487 var tc *TaskConfig 488 hclutils.NewConfigParser(taskConfigSpec).ParseHCL(t, cfgStr, &tc) 489 490 require.EqualValues(t, expected, tc) 491 } 492 493 // TestConfig_DriverConfig_GC asserts that gc is parsed 494 // and populated with defaults as expected 495 func TestConfig_DriverConfig_GC(t *testing.T) { 496 cases := []struct { 497 name string 498 config string 499 expected GCConfig 500 }{ 501 { 502 name: "pure default", 503 config: `{}`, 504 expected: GCConfig{ 505 Image: true, ImageDelay: "3m", Container: true, 506 DanglingContainers: ContainerGCConfig{ 507 Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"}, 508 }, 509 }, 510 { 511 name: "partial gc", 512 config: `{ gc { } }`, 513 expected: GCConfig{ 514 Image: true, ImageDelay: "3m", Container: true, 515 DanglingContainers: ContainerGCConfig{ 516 Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"}, 517 }, 518 }, 519 { 520 name: "partial gc", 521 config: `{ gc { dangling_containers { } } }`, 522 expected: GCConfig{ 523 Image: true, ImageDelay: "3m", Container: true, 524 DanglingContainers: ContainerGCConfig{ 525 Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"}, 526 }, 527 }, 528 { 529 name: "partial image", 530 config: `{ gc { image = false } }`, 531 expected: GCConfig{ 532 Image: false, ImageDelay: "3m", Container: true, 533 DanglingContainers: ContainerGCConfig{ 534 Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"}, 535 }, 536 }, 537 { 538 name: "partial image_delay", 539 config: `{ gc { image_delay = "1d"} }`, 540 expected: GCConfig{ 541 Image: true, ImageDelay: "1d", Container: true, 542 DanglingContainers: ContainerGCConfig{ 543 Enabled: true, PeriodStr: "5m", CreationGraceStr: "5m"}, 544 }, 545 }, 546 { 547 name: "partial dangling_containers", 548 config: `{ gc { dangling_containers { enabled = false } } }`, 549 expected: GCConfig{ 550 Image: true, ImageDelay: "3m", Container: true, 551 DanglingContainers: ContainerGCConfig{ 552 Enabled: false, PeriodStr: "5m", CreationGraceStr: "5m"}, 553 }, 554 }, 555 { 556 name: "incomplete dangling_containers 2", 557 config: `{ gc { dangling_containers { period = "10m" } } }`, 558 expected: GCConfig{ 559 Image: true, ImageDelay: "3m", Container: true, 560 DanglingContainers: ContainerGCConfig{ 561 Enabled: true, PeriodStr: "10m", CreationGraceStr: "5m"}, 562 }, 563 }, 564 { 565 name: "full default", 566 config: `{ gc { 567 image = false 568 image_delay = "5m" 569 container = false 570 dangling_containers { 571 enabled = false 572 dry_run = true 573 period = "10m" 574 creation_grace = "20m" 575 }}}`, 576 expected: GCConfig{ 577 Image: false, 578 ImageDelay: "5m", 579 Container: false, 580 DanglingContainers: ContainerGCConfig{ 581 Enabled: false, 582 DryRun: true, 583 PeriodStr: "10m", 584 CreationGraceStr: "20m", 585 }, 586 }, 587 }, 588 } 589 590 for _, c := range cases { 591 t.Run(c.name, func(t *testing.T) { 592 var tc DriverConfig 593 hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc) 594 require.EqualValues(t, c.expected, tc.GC) 595 596 }) 597 } 598 } 599 600 func TestConfig_InternalCapabilities(t *testing.T) { 601 cases := []struct { 602 name string 603 config string 604 expected drivers.InternalCapabilities 605 }{ 606 { 607 name: "pure default", 608 config: `{}`, 609 expected: drivers.InternalCapabilities{}, 610 }, 611 { 612 name: "disabled", 613 config: `{ disable_log_collection = true }`, 614 expected: drivers.InternalCapabilities{DisableLogCollection: true}, 615 }, 616 { 617 name: "enabled explicitly", 618 config: `{ disable_log_collection = false }`, 619 expected: drivers.InternalCapabilities{}, 620 }, 621 } 622 623 for _, c := range cases { 624 t.Run(c.name, func(t *testing.T) { 625 var tc DriverConfig 626 hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc) 627 628 d := &Driver{config: &tc} 629 require.Equal(t, c.expected, d.InternalCapabilities()) 630 }) 631 } 632 } 633 634 func TestConfig_DriverConfig_InfraImagePullTimeout(t *testing.T) { 635 cases := []struct { 636 name string 637 config string 638 expected string 639 }{ 640 { 641 name: "default", 642 config: `{}`, 643 expected: "5m", 644 }, 645 { 646 name: "set explicitly", 647 config: `{ infra_image_pull_timeout = "1m" }`, 648 expected: "1m", 649 }, 650 } 651 652 for _, c := range cases { 653 t.Run(c.name, func(t *testing.T) { 654 var tc DriverConfig 655 hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc) 656 require.Equal(t, c.expected, tc.InfraImagePullTimeout) 657 }) 658 } 659 } 660 661 func TestConfig_DriverConfig_PullActivityTimeout(t *testing.T) { 662 cases := []struct { 663 name string 664 config string 665 expected string 666 }{ 667 { 668 name: "default", 669 config: `{}`, 670 expected: "2m", 671 }, 672 { 673 name: "set explicitly", 674 config: `{ pull_activity_timeout = "5m" }`, 675 expected: "5m", 676 }, 677 } 678 679 for _, c := range cases { 680 t.Run(c.name, func(t *testing.T) { 681 var tc DriverConfig 682 hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc) 683 require.Equal(t, c.expected, tc.PullActivityTimeout) 684 }) 685 } 686 } 687 688 func TestConfig_DriverConfig_AllowRuntimes(t *testing.T) { 689 cases := []struct { 690 name string 691 config string 692 expected map[string]struct{} 693 }{ 694 { 695 name: "pure default", 696 config: `{}`, 697 expected: map[string]struct{}{"runc": struct{}{}, "nvidia": struct{}{}}, 698 }, 699 { 700 name: "custom", 701 config: `{ allow_runtimes = ["runc", "firecracker"]}`, 702 expected: map[string]struct{}{"runc": struct{}{}, "firecracker": struct{}{}}, 703 }, 704 } 705 706 for _, c := range cases { 707 t.Run(c.name, func(t *testing.T) { 708 var tc map[string]interface{} 709 hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc) 710 711 dh := dockerDriverHarness(t, tc) 712 d := dh.Impl().(*Driver) 713 require.Equal(t, c.expected, d.config.allowRuntimes) 714 }) 715 } 716 }