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