1package agent 2 3import ( 4 "bytes" 5 "encoding/base64" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "reflect" 10 "testing" 11 "time" 12) 13 14func TestConfigBindAddrParts(t *testing.T) { 15 testCases := []struct { 16 Value string 17 IP string 18 Port int 19 Error bool 20 }{ 21 {"0.0.0.0", "0.0.0.0", DefaultBindPort, false}, 22 {"0.0.0.0:1234", "0.0.0.0", 1234, false}, 23 } 24 25 for _, tc := range testCases { 26 c := &Config{BindAddr: tc.Value} 27 ip, port, err := c.AddrParts(c.BindAddr) 28 if tc.Error != (err != nil) { 29 t.Errorf("Bad error: %s", err) 30 continue 31 } 32 33 if tc.IP != ip { 34 t.Errorf("%s: Got IP %#v", tc.Value, ip) 35 continue 36 } 37 38 if tc.Port != port { 39 t.Errorf("%s: Got port %d", tc.Value, port) 40 continue 41 } 42 } 43} 44 45func TestConfigEncryptBytes(t *testing.T) { 46 // Test with some input 47 src := []byte("abc") 48 c := &Config{ 49 EncryptKey: base64.StdEncoding.EncodeToString(src), 50 } 51 52 result, err := c.EncryptBytes() 53 if err != nil { 54 t.Fatalf("err: %v", err) 55 } 56 57 if !bytes.Equal(src, result) { 58 t.Fatalf("bad: %#v", result) 59 } 60 61 // Test with no input 62 c = &Config{} 63 result, err = c.EncryptBytes() 64 if err != nil { 65 t.Fatalf("err: %v", err) 66 } 67 68 if len(result) > 0 { 69 t.Fatalf("bad: %#v", result) 70 } 71} 72 73func TestConfigEventScripts(t *testing.T) { 74 c := &Config{ 75 EventHandlers: []string{ 76 "foo.sh", 77 "bar=blah.sh", 78 }, 79 } 80 81 result := c.EventScripts() 82 if len(result) != 2 { 83 t.Fatalf("bad: %#v", result) 84 } 85 86 expected := []EventScript{ 87 {EventFilter{"*", ""}, "foo.sh"}, 88 {EventFilter{"bar", ""}, "blah.sh"}, 89 } 90 91 if !reflect.DeepEqual(result, expected) { 92 t.Fatalf("bad: %#v", result) 93 } 94} 95 96func TestDecodeConfig(t *testing.T) { 97 // Without a protocol 98 input := `{"node_name": "foo"}` 99 config, err := DecodeConfig(bytes.NewReader([]byte(input))) 100 if err != nil { 101 t.Fatalf("err: %v", err) 102 } 103 104 if config.NodeName != "foo" { 105 t.Fatalf("bad: %#v", config) 106 } 107 108 if config.Protocol != 0 { 109 t.Fatalf("bad: %#v", config) 110 } 111 112 if config.SkipLeaveOnInt != DefaultConfig().SkipLeaveOnInt { 113 t.Fatalf("bad: %#v", config) 114 } 115 116 if config.LeaveOnTerm != DefaultConfig().LeaveOnTerm { 117 t.Fatalf("bad: %#v", config) 118 } 119 120 // With a protocol 121 input = `{"node_name": "foo", "protocol": 7}` 122 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 123 if err != nil { 124 t.Fatalf("err: %v", err) 125 } 126 127 if config.NodeName != "foo" { 128 t.Fatalf("bad: %#v", config) 129 } 130 131 if config.Protocol != 7 { 132 t.Fatalf("bad: %#v", config) 133 } 134 135 // A bind addr 136 input = `{"bind": "127.0.0.2"}` 137 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 138 if err != nil { 139 t.Fatalf("err: %v", err) 140 } 141 142 if config.BindAddr != "127.0.0.2" { 143 t.Fatalf("bad: %#v", config) 144 } 145 146 // replayOnJoin 147 input = `{"replay_on_join": true}` 148 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 149 if err != nil { 150 t.Fatalf("err: %v", err) 151 } 152 153 if config.ReplayOnJoin != true { 154 t.Fatalf("bad: %#v", config) 155 } 156 157 // leave_on_terminate 158 input = `{"leave_on_terminate": true}` 159 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 160 if err != nil { 161 t.Fatalf("err: %v", err) 162 } 163 164 if config.LeaveOnTerm != true { 165 t.Fatalf("bad: %#v", config) 166 } 167 168 // skip_leave_on_interrupt 169 input = `{"skip_leave_on_interrupt": true}` 170 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 171 if err != nil { 172 t.Fatalf("err: %v", err) 173 } 174 175 if config.SkipLeaveOnInt != true { 176 t.Fatalf("bad: %#v", config) 177 } 178 179 // tags 180 input = `{"tags": {"foo": "bar", "role": "test"}}` 181 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 182 if err != nil { 183 t.Fatalf("err: %v", err) 184 } 185 186 if config.Tags["foo"] != "bar" { 187 t.Fatalf("bad: %#v", config) 188 } 189 if config.Tags["role"] != "test" { 190 t.Fatalf("bad: %#v", config) 191 } 192 193 // tags file 194 input = `{"tags_file": "/some/path"}` 195 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 196 if err != nil { 197 t.Fatalf("err: %v", err) 198 } 199 200 if config.TagsFile != "/some/path" { 201 t.Fatalf("bad: %#v", config) 202 } 203 204 // Discover 205 input = `{"discover": "foobar"}` 206 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 207 if err != nil { 208 t.Fatalf("err: %v", err) 209 } 210 211 if config.Discover != "foobar" { 212 t.Fatalf("bad: %#v", config) 213 } 214 215 // Interface 216 input = `{"interface": "eth0"}` 217 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 218 if err != nil { 219 t.Fatalf("err: %v", err) 220 } 221 222 if config.Interface != "eth0" { 223 t.Fatalf("bad: %#v", config) 224 } 225 226 // Reconnect intervals 227 input = `{"reconnect_interval": "15s", "reconnect_timeout": "48h"}` 228 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 229 if err != nil { 230 t.Fatalf("err: %v", err) 231 } 232 233 if config.ReconnectInterval != 15*time.Second { 234 t.Fatalf("bad: %#v", config) 235 } 236 237 if config.ReconnectTimeout != 48*time.Hour { 238 t.Fatalf("bad: %#v", config) 239 } 240 241 // RPC Auth 242 input = `{"rpc_auth": "foobar"}` 243 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 244 if err != nil { 245 t.Fatalf("err: %v", err) 246 } 247 248 if config.RPCAuthKey != "foobar" { 249 t.Fatalf("bad: %#v", config) 250 } 251 252 // DisableNameResolution 253 input = `{"disable_name_resolution": true}` 254 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 255 if err != nil { 256 t.Fatalf("err: %v", err) 257 } 258 259 if !config.DisableNameResolution { 260 t.Fatalf("bad: %#v", config) 261 } 262 263 // Tombstone intervals 264 input = `{"tombstone_timeout": "48h"}` 265 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 266 if err != nil { 267 t.Fatalf("err: %v", err) 268 } 269 270 if config.TombstoneTimeout != 48*time.Hour { 271 t.Fatalf("bad: %#v", config) 272 } 273 274 // Syslog 275 input = `{"enable_syslog": true, "syslog_facility": "LOCAL4"}` 276 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 277 if err != nil { 278 t.Fatalf("err: %v", err) 279 } 280 281 if !config.EnableSyslog { 282 t.Fatalf("bad: %#v", config) 283 } 284 if config.SyslogFacility != "LOCAL4" { 285 t.Fatalf("bad: %#v", config) 286 } 287 288 // Retry configs 289 input = `{"retry_max_attempts": 5, "retry_interval": "60s"}` 290 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 291 if err != nil { 292 t.Fatalf("err: %v", err) 293 } 294 295 if config.RetryMaxAttempts != 5 { 296 t.Fatalf("bad: %#v", config) 297 } 298 299 if config.RetryInterval != 60*time.Second { 300 t.Fatalf("bad: %#v", config) 301 } 302 303 // Broadcast timeout 304 input = `{"broadcast_timeout": "10s"}` 305 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 306 if err != nil { 307 t.Fatalf("err: %v", err) 308 } 309 310 if config.BroadcastTimeout != 10*time.Second { 311 t.Fatalf("bad: %#v", config) 312 } 313 314 // Retry configs 315 input = `{"retry_join": ["127.0.0.1", "127.0.0.2"]}` 316 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 317 if err != nil { 318 t.Fatalf("err: %v", err) 319 } 320 321 if len(config.RetryJoin) != 2 { 322 t.Fatalf("bad: %#v", config) 323 } 324 325 if config.RetryJoin[0] != "127.0.0.1" { 326 t.Fatalf("bad: %#v", config) 327 } 328 329 if config.RetryJoin[1] != "127.0.0.2" { 330 t.Fatalf("bad: %#v", config) 331 } 332 333 // Rejoin configs 334 input = `{"rejoin_after_leave": true}` 335 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 336 if err != nil { 337 t.Fatalf("err: %v", err) 338 } 339 340 if !config.RejoinAfterLeave { 341 t.Fatalf("bad: %#v", config) 342 } 343 344 // Stats configs 345 input = `{"statsite_addr": "127.0.0.1:8123", "statsd_addr": "127.0.0.1:8125"}` 346 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 347 if err != nil { 348 t.Fatalf("err: %v", err) 349 } 350 351 if config.StatsiteAddr != "127.0.0.1:8123" { 352 t.Fatalf("bad: %#v", config) 353 } 354 355 if config.StatsdAddr != "127.0.0.1:8125" { 356 t.Fatalf("bad: %#v", config) 357 } 358 359 // Query sizes 360 input = `{"query_response_size_limit": 123, "query_size_limit": 456}` 361 config, err = DecodeConfig(bytes.NewReader([]byte(input))) 362 if err != nil { 363 t.Fatalf("err: %v", err) 364 } 365 366 if config.QueryResponseSizeLimit != 123 || config.QuerySizeLimit != 456 { 367 t.Fatalf("bad: %#v", config) 368 } 369} 370 371func TestDecodeConfig_unknownDirective(t *testing.T) { 372 input := `{"unknown_directive": "titi"}` 373 _, err := DecodeConfig(bytes.NewReader([]byte(input))) 374 if err == nil { 375 t.Fatal("should have err") 376 } 377} 378 379func TestMergeConfig(t *testing.T) { 380 a := &Config{ 381 NodeName: "foo", 382 Role: "bar", 383 Protocol: 7, 384 EventHandlers: []string{"foo"}, 385 StartJoin: []string{"foo"}, 386 ReplayOnJoin: true, 387 RetryJoin: []string{"zab"}, 388 } 389 390 b := &Config{ 391 NodeName: "bname", 392 DisableCoordinates: true, 393 Protocol: -1, 394 EncryptKey: "foo", 395 EventHandlers: []string{"bar"}, 396 StartJoin: []string{"bar"}, 397 LeaveOnTerm: true, 398 SkipLeaveOnInt: true, 399 Discover: "tubez", 400 Interface: "eth0", 401 ReconnectInterval: 15 * time.Second, 402 ReconnectTimeout: 48 * time.Hour, 403 RPCAuthKey: "foobar", 404 DisableNameResolution: true, 405 TombstoneTimeout: 36 * time.Hour, 406 EnableSyslog: true, 407 RetryJoin: []string{"zip"}, 408 RetryMaxAttempts: 10, 409 RetryInterval: 120 * time.Second, 410 RejoinAfterLeave: true, 411 StatsiteAddr: "127.0.0.1:8125", 412 QueryResponseSizeLimit: 123, 413 QuerySizeLimit: 456, 414 BroadcastTimeout: 20 * time.Second, 415 EnableCompression: true, 416 } 417 418 c := MergeConfig(a, b) 419 420 if c.NodeName != "bname" { 421 t.Fatalf("bad: %#v", c) 422 } 423 424 if c.Role != "bar" { 425 t.Fatalf("bad: %#v", c) 426 } 427 428 if c.DisableCoordinates != true { 429 t.Fatalf("bad: %#v", c) 430 } 431 432 if c.Protocol != 7 { 433 t.Fatalf("bad: %#v", c) 434 } 435 436 if c.EncryptKey != "foo" { 437 t.Fatalf("bad: %#v", c.EncryptKey) 438 } 439 440 if c.ReplayOnJoin != true { 441 t.Fatalf("bad: %#v", c.ReplayOnJoin) 442 } 443 444 if !c.LeaveOnTerm { 445 t.Fatalf("bad: %#v", c.LeaveOnTerm) 446 } 447 448 if !c.SkipLeaveOnInt { 449 t.Fatalf("bad: %#v", c.SkipLeaveOnInt) 450 } 451 452 if c.Discover != "tubez" { 453 t.Fatalf("Bad: %v", c.Discover) 454 } 455 456 if c.Interface != "eth0" { 457 t.Fatalf("Bad: %v", c.Interface) 458 } 459 460 if c.ReconnectInterval != 15*time.Second { 461 t.Fatalf("bad: %#v", c) 462 } 463 464 if c.ReconnectTimeout != 48*time.Hour { 465 t.Fatalf("bad: %#v", c) 466 } 467 468 if c.TombstoneTimeout != 36*time.Hour { 469 t.Fatalf("bad: %#v", c) 470 } 471 472 if c.RPCAuthKey != "foobar" { 473 t.Fatalf("bad: %#v", c) 474 } 475 476 if !c.DisableNameResolution { 477 t.Fatalf("bad: %#v", c) 478 } 479 480 if !c.EnableSyslog { 481 t.Fatalf("bad: %#v", c) 482 } 483 484 if c.RetryMaxAttempts != 10 { 485 t.Fatalf("bad: %#v", c) 486 } 487 488 if c.RetryInterval != 120*time.Second { 489 t.Fatalf("bad: %#v", c) 490 } 491 492 if !c.RejoinAfterLeave { 493 t.Fatalf("bad: %#v", c) 494 } 495 496 if c.StatsiteAddr != "127.0.0.1:8125" { 497 t.Fatalf("bad: %#v", c) 498 } 499 500 expected := []string{"foo", "bar"} 501 if !reflect.DeepEqual(c.EventHandlers, expected) { 502 t.Fatalf("bad: %#v", c) 503 } 504 505 if !reflect.DeepEqual(c.StartJoin, expected) { 506 t.Fatalf("bad: %#v", c) 507 } 508 509 expected = []string{"zab", "zip"} 510 if !reflect.DeepEqual(c.RetryJoin, expected) { 511 t.Fatalf("bad: %#v", c) 512 } 513 514 if c.QueryResponseSizeLimit != 123 || c.QuerySizeLimit != 456 { 515 t.Fatalf("bad: %#v", c) 516 } 517 518 if c.BroadcastTimeout != 20*time.Second { 519 t.Fatalf("bad: %#v", c) 520 } 521 522 if !c.EnableCompression { 523 t.Fatalf("bad: %#v", c) 524 } 525} 526 527func TestReadConfigPaths_badPath(t *testing.T) { 528 _, err := ReadConfigPaths([]string{"/i/shouldnt/exist/ever/rainbows"}) 529 if err == nil { 530 t.Fatal("should have err") 531 } 532} 533 534func TestReadConfigPaths_file(t *testing.T) { 535 tf, err := ioutil.TempFile("", "serf") 536 if err != nil { 537 t.Fatalf("err: %v", err) 538 } 539 tf.Write([]byte(`{"node_name":"bar"}`)) 540 tf.Close() 541 defer os.Remove(tf.Name()) 542 543 config, err := ReadConfigPaths([]string{tf.Name()}) 544 if err != nil { 545 t.Fatalf("err: %v", err) 546 } 547 548 if config.NodeName != "bar" { 549 t.Fatalf("bad: %#v", config) 550 } 551} 552 553func TestReadConfigPaths_dir(t *testing.T) { 554 td, err := ioutil.TempDir("", "serf") 555 if err != nil { 556 t.Fatalf("err: %v", err) 557 } 558 defer os.RemoveAll(td) 559 560 err = ioutil.WriteFile(filepath.Join(td, "a.json"), 561 []byte(`{"node_name": "bar"}`), 0644) 562 if err != nil { 563 t.Fatalf("err: %v", err) 564 } 565 566 err = ioutil.WriteFile(filepath.Join(td, "b.json"), 567 []byte(`{"node_name": "baz"}`), 0644) 568 if err != nil { 569 t.Fatalf("err: %v", err) 570 } 571 572 // A non-json file, shouldn't be read 573 err = ioutil.WriteFile(filepath.Join(td, "c"), 574 []byte(`{"node_name": "bad"}`), 0644) 575 if err != nil { 576 t.Fatalf("err: %v", err) 577 } 578 579 config, err := ReadConfigPaths([]string{td}) 580 if err != nil { 581 t.Fatalf("err: %v", err) 582 } 583 584 if config.NodeName != "baz" { 585 t.Fatalf("bad: %#v", config) 586 } 587} 588