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