github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/command/agent/config_test.go (about) 1 package agent 2 3 import ( 4 "io/ioutil" 5 "net" 6 "os" 7 "path/filepath" 8 "reflect" 9 "testing" 10 "time" 11 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/nomad/nomad/structs/config" 14 ) 15 16 var ( 17 // trueValue/falseValue are used to get a pointer to a boolean 18 trueValue = true 19 falseValue = false 20 ) 21 22 func TestConfig_Merge(t *testing.T) { 23 c0 := &Config{} 24 25 c1 := &Config{ 26 Telemetry: &Telemetry{}, 27 Client: &ClientConfig{}, 28 Server: &ServerConfig{}, 29 Ports: &Ports{}, 30 Addresses: &Addresses{}, 31 AdvertiseAddrs: &AdvertiseAddrs{}, 32 Atlas: &AtlasConfig{}, 33 Vault: &config.VaultConfig{}, 34 Consul: &config.ConsulConfig{}, 35 } 36 37 c2 := &Config{ 38 Region: "global", 39 Datacenter: "dc1", 40 NodeName: "node1", 41 DataDir: "/tmp/dir1", 42 LogLevel: "INFO", 43 EnableDebug: false, 44 LeaveOnInt: false, 45 LeaveOnTerm: false, 46 EnableSyslog: false, 47 SyslogFacility: "local0.info", 48 DisableUpdateCheck: false, 49 DisableAnonymousSignature: false, 50 BindAddr: "127.0.0.1", 51 Telemetry: &Telemetry{ 52 StatsiteAddr: "127.0.0.1:8125", 53 StatsdAddr: "127.0.0.1:8125", 54 DataDogAddr: "127.0.0.1:8125", 55 DisableHostname: false, 56 CirconusAPIToken: "0", 57 CirconusAPIApp: "nomadic", 58 CirconusAPIURL: "http://api.circonus.com/v2", 59 CirconusSubmissionInterval: "60s", 60 CirconusCheckSubmissionURL: "https://someplace.com/metrics", 61 CirconusCheckID: "0", 62 CirconusCheckForceMetricActivation: "true", 63 CirconusCheckInstanceID: "node1:nomadic", 64 CirconusCheckSearchTag: "service:nomadic", 65 CirconusCheckDisplayName: "node1:nomadic", 66 CirconusCheckTags: "cat1:tag1,cat2:tag2", 67 CirconusBrokerID: "0", 68 CirconusBrokerSelectTag: "dc:dc1", 69 }, 70 Client: &ClientConfig{ 71 Enabled: false, 72 StateDir: "/tmp/state1", 73 AllocDir: "/tmp/alloc1", 74 NodeClass: "class1", 75 Options: map[string]string{ 76 "foo": "bar", 77 }, 78 NetworkSpeed: 100, 79 CpuCompute: 100, 80 MaxKillTimeout: "20s", 81 ClientMaxPort: 19996, 82 Reserved: &Resources{ 83 CPU: 10, 84 MemoryMB: 10, 85 DiskMB: 10, 86 IOPS: 10, 87 ReservedPorts: "1,10-30,55", 88 ParsedReservedPorts: []int{1, 2, 4}, 89 }, 90 }, 91 Server: &ServerConfig{ 92 Enabled: false, 93 BootstrapExpect: 1, 94 DataDir: "/tmp/data1", 95 ProtocolVersion: 1, 96 NumSchedulers: 1, 97 NodeGCThreshold: "1h", 98 HeartbeatGrace: "30s", 99 }, 100 Ports: &Ports{ 101 HTTP: 4646, 102 RPC: 4647, 103 Serf: 4648, 104 }, 105 Addresses: &Addresses{ 106 HTTP: "127.0.0.1", 107 RPC: "127.0.0.1", 108 Serf: "127.0.0.1", 109 }, 110 AdvertiseAddrs: &AdvertiseAddrs{ 111 RPC: "127.0.0.1", 112 Serf: "127.0.0.1", 113 }, 114 Atlas: &AtlasConfig{ 115 Infrastructure: "hashicorp/test1", 116 Token: "abc", 117 Join: false, 118 Endpoint: "foo", 119 }, 120 HTTPAPIResponseHeaders: map[string]string{ 121 "Access-Control-Allow-Origin": "*", 122 }, 123 Vault: &config.VaultConfig{ 124 Token: "1", 125 AllowUnauthenticated: &falseValue, 126 TaskTokenTTL: "1", 127 Addr: "1", 128 TLSCaFile: "1", 129 TLSCaPath: "1", 130 TLSCertFile: "1", 131 TLSKeyFile: "1", 132 TLSSkipVerify: &falseValue, 133 TLSServerName: "1", 134 }, 135 Consul: &config.ConsulConfig{ 136 ServerServiceName: "1", 137 ClientServiceName: "1", 138 AutoAdvertise: &falseValue, 139 Addr: "1", 140 Timeout: 1 * time.Second, 141 Token: "1", 142 Auth: "1", 143 EnableSSL: &falseValue, 144 VerifySSL: &falseValue, 145 CAFile: "1", 146 CertFile: "1", 147 KeyFile: "1", 148 ServerAutoJoin: &falseValue, 149 ClientAutoJoin: &falseValue, 150 ChecksUseAdvertise: &falseValue, 151 }, 152 } 153 154 c3 := &Config{ 155 Region: "region2", 156 Datacenter: "dc2", 157 NodeName: "node2", 158 DataDir: "/tmp/dir2", 159 LogLevel: "DEBUG", 160 EnableDebug: true, 161 LeaveOnInt: true, 162 LeaveOnTerm: true, 163 EnableSyslog: true, 164 SyslogFacility: "local0.debug", 165 DisableUpdateCheck: true, 166 DisableAnonymousSignature: true, 167 BindAddr: "127.0.0.2", 168 Telemetry: &Telemetry{ 169 StatsiteAddr: "127.0.0.2:8125", 170 StatsdAddr: "127.0.0.2:8125", 171 DataDogAddr: "127.0.0.1:8125", 172 DisableHostname: true, 173 PublishNodeMetrics: true, 174 PublishAllocationMetrics: true, 175 CirconusAPIToken: "1", 176 CirconusAPIApp: "nomad", 177 CirconusAPIURL: "https://api.circonus.com/v2", 178 CirconusSubmissionInterval: "10s", 179 CirconusCheckSubmissionURL: "https://example.com/metrics", 180 CirconusCheckID: "1", 181 CirconusCheckForceMetricActivation: "false", 182 CirconusCheckInstanceID: "node2:nomad", 183 CirconusCheckSearchTag: "service:nomad", 184 CirconusCheckDisplayName: "node2:nomad", 185 CirconusCheckTags: "cat1:tag1,cat2:tag2", 186 CirconusBrokerID: "1", 187 CirconusBrokerSelectTag: "dc:dc2", 188 }, 189 Client: &ClientConfig{ 190 Enabled: true, 191 StateDir: "/tmp/state2", 192 AllocDir: "/tmp/alloc2", 193 NodeClass: "class2", 194 Servers: []string{"server2"}, 195 Meta: map[string]string{ 196 "baz": "zip", 197 }, 198 Options: map[string]string{ 199 "foo": "bar", 200 "baz": "zip", 201 }, 202 ChrootEnv: map[string]string{}, 203 ClientMaxPort: 20000, 204 ClientMinPort: 22000, 205 NetworkSpeed: 105, 206 CpuCompute: 105, 207 MaxKillTimeout: "50s", 208 Reserved: &Resources{ 209 CPU: 15, 210 MemoryMB: 15, 211 DiskMB: 15, 212 IOPS: 15, 213 ReservedPorts: "2,10-30,55", 214 ParsedReservedPorts: []int{1, 2, 3}, 215 }, 216 GCInterval: 6 * time.Second, 217 GCParallelDestroys: 6, 218 GCDiskUsageThreshold: 71, 219 GCInodeUsageThreshold: 86, 220 }, 221 Server: &ServerConfig{ 222 Enabled: true, 223 BootstrapExpect: 2, 224 DataDir: "/tmp/data2", 225 ProtocolVersion: 2, 226 NumSchedulers: 2, 227 EnabledSchedulers: []string{structs.JobTypeBatch}, 228 NodeGCThreshold: "12h", 229 HeartbeatGrace: "2m", 230 RejoinAfterLeave: true, 231 StartJoin: []string{"1.1.1.1"}, 232 RetryJoin: []string{"1.1.1.1"}, 233 RetryInterval: "10s", 234 retryInterval: time.Second * 10, 235 }, 236 Ports: &Ports{ 237 HTTP: 20000, 238 RPC: 21000, 239 Serf: 22000, 240 }, 241 Addresses: &Addresses{ 242 HTTP: "127.0.0.2", 243 RPC: "127.0.0.2", 244 Serf: "127.0.0.2", 245 }, 246 AdvertiseAddrs: &AdvertiseAddrs{ 247 RPC: "127.0.0.2", 248 Serf: "127.0.0.2", 249 }, 250 Atlas: &AtlasConfig{ 251 Infrastructure: "hashicorp/test2", 252 Token: "xyz", 253 Join: true, 254 Endpoint: "bar", 255 }, 256 HTTPAPIResponseHeaders: map[string]string{ 257 "Access-Control-Allow-Origin": "*", 258 "Access-Control-Allow-Methods": "GET, POST, OPTIONS", 259 }, 260 Vault: &config.VaultConfig{ 261 Token: "2", 262 AllowUnauthenticated: &trueValue, 263 TaskTokenTTL: "2", 264 Addr: "2", 265 TLSCaFile: "2", 266 TLSCaPath: "2", 267 TLSCertFile: "2", 268 TLSKeyFile: "2", 269 TLSSkipVerify: &trueValue, 270 TLSServerName: "2", 271 }, 272 Consul: &config.ConsulConfig{ 273 ServerServiceName: "2", 274 ClientServiceName: "2", 275 AutoAdvertise: &trueValue, 276 Addr: "2", 277 Timeout: 2 * time.Second, 278 Token: "2", 279 Auth: "2", 280 EnableSSL: &trueValue, 281 VerifySSL: &trueValue, 282 CAFile: "2", 283 CertFile: "2", 284 KeyFile: "2", 285 ServerAutoJoin: &trueValue, 286 ClientAutoJoin: &trueValue, 287 ChecksUseAdvertise: &trueValue, 288 }, 289 } 290 291 result := c0.Merge(c1) 292 result = result.Merge(c2) 293 result = result.Merge(c3) 294 if !reflect.DeepEqual(result, c3) { 295 t.Fatalf("bad:\n%#v\n%#v", result, c3) 296 } 297 } 298 299 func TestConfig_ParseConfigFile(t *testing.T) { 300 // Fails if the file doesn't exist 301 if _, err := ParseConfigFile("/unicorns/leprechauns"); err == nil { 302 t.Fatalf("expected error, got nothing") 303 } 304 305 fh, err := ioutil.TempFile("", "nomad") 306 if err != nil { 307 t.Fatalf("err: %s", err) 308 } 309 defer os.RemoveAll(fh.Name()) 310 311 // Invalid content returns error 312 if _, err := fh.WriteString("nope;!!!"); err != nil { 313 t.Fatalf("err: %s", err) 314 } 315 if _, err := ParseConfigFile(fh.Name()); err == nil { 316 t.Fatalf("expected load error, got nothing") 317 } 318 319 // Valid content parses successfully 320 if err := fh.Truncate(0); err != nil { 321 t.Fatalf("err: %s", err) 322 } 323 if _, err := fh.Seek(0, 0); err != nil { 324 t.Fatalf("err: %s", err) 325 } 326 if _, err := fh.WriteString(`{"region":"west"}`); err != nil { 327 t.Fatalf("err: %s", err) 328 } 329 330 config, err := ParseConfigFile(fh.Name()) 331 if err != nil { 332 t.Fatalf("err: %s", err) 333 } 334 if config.Region != "west" { 335 t.Fatalf("bad region: %q", config.Region) 336 } 337 } 338 339 func TestConfig_LoadConfigDir(t *testing.T) { 340 // Fails if the dir doesn't exist. 341 if _, err := LoadConfigDir("/unicorns/leprechauns"); err == nil { 342 t.Fatalf("expected error, got nothing") 343 } 344 345 dir, err := ioutil.TempDir("", "nomad") 346 if err != nil { 347 t.Fatalf("err: %s", err) 348 } 349 defer os.RemoveAll(dir) 350 351 // Returns empty config on empty dir 352 config, err := LoadConfig(dir) 353 if err != nil { 354 t.Fatalf("err: %s", err) 355 } 356 if config == nil { 357 t.Fatalf("should not be nil") 358 } 359 360 file1 := filepath.Join(dir, "conf1.hcl") 361 err = ioutil.WriteFile(file1, []byte(`{"region":"west"}`), 0600) 362 if err != nil { 363 t.Fatalf("err: %s", err) 364 } 365 366 file2 := filepath.Join(dir, "conf2.hcl") 367 err = ioutil.WriteFile(file2, []byte(`{"datacenter":"sfo"}`), 0600) 368 if err != nil { 369 t.Fatalf("err: %s", err) 370 } 371 372 file3 := filepath.Join(dir, "conf3.hcl") 373 err = ioutil.WriteFile(file3, []byte(`nope;!!!`), 0600) 374 if err != nil { 375 t.Fatalf("err: %s", err) 376 } 377 378 // Fails if we have a bad config file 379 if _, err := LoadConfigDir(dir); err == nil { 380 t.Fatalf("expected load error, got nothing") 381 } 382 383 if err := os.Remove(file3); err != nil { 384 t.Fatalf("err: %s", err) 385 } 386 387 // Works if configs are valid 388 config, err = LoadConfigDir(dir) 389 if err != nil { 390 t.Fatalf("err: %s", err) 391 } 392 if config.Region != "west" || config.Datacenter != "sfo" { 393 t.Fatalf("bad: %#v", config) 394 } 395 } 396 397 func TestConfig_LoadConfig(t *testing.T) { 398 // Fails if the target doesn't exist 399 if _, err := LoadConfig("/unicorns/leprechauns"); err == nil { 400 t.Fatalf("expected error, got nothing") 401 } 402 403 fh, err := ioutil.TempFile("", "nomad") 404 if err != nil { 405 t.Fatalf("err: %s", err) 406 } 407 defer os.Remove(fh.Name()) 408 409 if _, err := fh.WriteString(`{"region":"west"}`); err != nil { 410 t.Fatalf("err: %s", err) 411 } 412 413 // Works on a config file 414 config, err := LoadConfig(fh.Name()) 415 if err != nil { 416 t.Fatalf("err: %s", err) 417 } 418 if config.Region != "west" { 419 t.Fatalf("bad: %#v", config) 420 } 421 422 expectedConfigFiles := []string{fh.Name()} 423 if !reflect.DeepEqual(config.Files, expectedConfigFiles) { 424 t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n", 425 expectedConfigFiles, config.Files) 426 } 427 428 dir, err := ioutil.TempDir("", "nomad") 429 if err != nil { 430 t.Fatalf("err: %s", err) 431 } 432 defer os.RemoveAll(dir) 433 434 file1 := filepath.Join(dir, "config1.hcl") 435 err = ioutil.WriteFile(file1, []byte(`{"datacenter":"sfo"}`), 0600) 436 if err != nil { 437 t.Fatalf("err: %s", err) 438 } 439 440 // Works on config dir 441 config, err = LoadConfig(dir) 442 if err != nil { 443 t.Fatalf("err: %s", err) 444 } 445 if config.Datacenter != "sfo" { 446 t.Fatalf("bad: %#v", config) 447 } 448 449 expectedConfigFiles = []string{file1} 450 if !reflect.DeepEqual(config.Files, expectedConfigFiles) { 451 t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n", 452 expectedConfigFiles, config.Files) 453 } 454 } 455 456 func TestConfig_LoadConfigsFileOrder(t *testing.T) { 457 config1, err := LoadConfigDir("test-resources/etcnomad") 458 if err != nil { 459 t.Fatalf("Failed to load config: %s", err) 460 } 461 462 config2, err := LoadConfig("test-resources/myconf") 463 if err != nil { 464 t.Fatalf("Failed to load config: %s", err) 465 } 466 467 expected := []string{ 468 // filepath.FromSlash changes these to backslash \ on Windows 469 filepath.FromSlash("test-resources/etcnomad/common.hcl"), 470 filepath.FromSlash("test-resources/etcnomad/server.json"), 471 filepath.FromSlash("test-resources/myconf"), 472 } 473 474 config := config1.Merge(config2) 475 476 if !reflect.DeepEqual(config.Files, expected) { 477 t.Errorf("Loaded configs don't match\nwant: %+v\n got: %+v\n", 478 expected, config.Files) 479 } 480 } 481 482 func TestConfig_Listener(t *testing.T) { 483 config := DefaultConfig() 484 485 // Fails on invalid input 486 if ln, err := config.Listener("tcp", "nope", 8080); err == nil { 487 ln.Close() 488 t.Fatalf("expected addr error") 489 } 490 if ln, err := config.Listener("nope", "127.0.0.1", 8080); err == nil { 491 ln.Close() 492 t.Fatalf("expected protocol err") 493 } 494 if ln, err := config.Listener("tcp", "127.0.0.1", -1); err == nil { 495 ln.Close() 496 t.Fatalf("expected port error") 497 } 498 499 // Works with valid inputs 500 ln, err := config.Listener("tcp", "127.0.0.1", 24000) 501 if err != nil { 502 t.Fatalf("err: %s", err) 503 } 504 ln.Close() 505 506 if net := ln.Addr().Network(); net != "tcp" { 507 t.Fatalf("expected tcp, got: %q", net) 508 } 509 if addr := ln.Addr().String(); addr != "127.0.0.1:24000" { 510 t.Fatalf("expected 127.0.0.1:4646, got: %q", addr) 511 } 512 513 // Falls back to default bind address if non provided 514 config.BindAddr = "0.0.0.0" 515 ln, err = config.Listener("tcp4", "", 24000) 516 if err != nil { 517 t.Fatalf("err: %s", err) 518 } 519 ln.Close() 520 521 if addr := ln.Addr().String(); addr != "0.0.0.0:24000" { 522 t.Fatalf("expected 0.0.0.0:24000, got: %q", addr) 523 } 524 } 525 526 func TestResources_ParseReserved(t *testing.T) { 527 cases := []struct { 528 Input string 529 Parsed []int 530 Err bool 531 }{ 532 { 533 "1,2,3", 534 []int{1, 2, 3}, 535 false, 536 }, 537 { 538 "3,1,2,1,2,3,1-3", 539 []int{1, 2, 3}, 540 false, 541 }, 542 { 543 "3-1", 544 nil, 545 true, 546 }, 547 { 548 "1-3,2-4", 549 []int{1, 2, 3, 4}, 550 false, 551 }, 552 { 553 "1-3,4,5-5,6,7,8-10", 554 []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 555 false, 556 }, 557 } 558 559 for i, tc := range cases { 560 r := &Resources{ReservedPorts: tc.Input} 561 err := r.ParseReserved() 562 if (err != nil) != tc.Err { 563 t.Fatalf("test case %d: %v", i, err) 564 continue 565 } 566 567 if !reflect.DeepEqual(r.ParsedReservedPorts, tc.Parsed) { 568 t.Fatalf("test case %d: \n\n%#v\n\n%#v", i, r.ParsedReservedPorts, tc.Parsed) 569 } 570 571 } 572 } 573 574 func TestIsMissingPort(t *testing.T) { 575 _, _, err := net.SplitHostPort("localhost") 576 if missing := isMissingPort(err); !missing { 577 t.Errorf("expected missing port error, but got %v", err) 578 } 579 _, _, err = net.SplitHostPort("localhost:9000") 580 if missing := isMissingPort(err); missing { 581 t.Errorf("expected no error, but got %v", err) 582 } 583 }