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