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