github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/consul_template_test.go (about) 1 package client 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 "time" 12 13 ctestutil "github.com/hashicorp/consul/testutil" 14 "github.com/hashicorp/nomad/client/config" 15 "github.com/hashicorp/nomad/client/driver/env" 16 "github.com/hashicorp/nomad/nomad/mock" 17 "github.com/hashicorp/nomad/nomad/structs" 18 sconfig "github.com/hashicorp/nomad/nomad/structs/config" 19 "github.com/hashicorp/nomad/testutil" 20 ) 21 22 // MockTaskHooks is a mock of the TaskHooks interface useful for testing 23 type MockTaskHooks struct { 24 Restarts int 25 RestartCh chan struct{} 26 27 Signals []os.Signal 28 SignalCh chan struct{} 29 30 // SignalError is returned when Signal is called on the mock hook 31 SignalError error 32 33 UnblockCh chan struct{} 34 Unblocked bool 35 36 KillReason string 37 KillCh chan struct{} 38 } 39 40 func NewMockTaskHooks() *MockTaskHooks { 41 return &MockTaskHooks{ 42 UnblockCh: make(chan struct{}, 1), 43 RestartCh: make(chan struct{}, 1), 44 SignalCh: make(chan struct{}, 1), 45 KillCh: make(chan struct{}, 1), 46 } 47 } 48 func (m *MockTaskHooks) Restart(source, reason string) { 49 m.Restarts++ 50 select { 51 case m.RestartCh <- struct{}{}: 52 default: 53 } 54 } 55 56 func (m *MockTaskHooks) Signal(source, reason string, s os.Signal) error { 57 m.Signals = append(m.Signals, s) 58 select { 59 case m.SignalCh <- struct{}{}: 60 default: 61 } 62 63 return m.SignalError 64 } 65 66 func (m *MockTaskHooks) Kill(source, reason string, fail bool) { 67 m.KillReason = reason 68 select { 69 case m.KillCh <- struct{}{}: 70 default: 71 } 72 } 73 74 func (m *MockTaskHooks) UnblockStart(source string) { 75 if !m.Unblocked { 76 close(m.UnblockCh) 77 } 78 79 m.Unblocked = true 80 } 81 82 // testHarness is used to test the TaskTemplateManager by spinning up 83 // Consul/Vault as needed 84 type testHarness struct { 85 manager *TaskTemplateManager 86 mockHooks *MockTaskHooks 87 templates []*structs.Template 88 taskEnv *env.TaskEnvironment 89 node *structs.Node 90 config *config.Config 91 vaultToken string 92 taskDir string 93 vault *testutil.TestVault 94 consul *ctestutil.TestServer 95 } 96 97 // newTestHarness returns a harness starting a dev consul and vault server, 98 // building the appropriate config and creating a TaskTemplateManager 99 func newTestHarness(t *testing.T, templates []*structs.Template, consul, vault bool) *testHarness { 100 harness := &testHarness{ 101 mockHooks: NewMockTaskHooks(), 102 templates: templates, 103 node: mock.Node(), 104 config: &config.Config{}, 105 } 106 107 // Build the task environment 108 harness.taskEnv = env.NewTaskEnvironment(harness.node) 109 110 // Make a tempdir 111 d, err := ioutil.TempDir("", "") 112 if err != nil { 113 t.Fatalf("Failed to make tmpdir: %v", err) 114 } 115 harness.taskDir = d 116 117 if consul { 118 harness.consul = ctestutil.NewTestServer(t) 119 harness.config.ConsulConfig = &sconfig.ConsulConfig{ 120 Addr: harness.consul.HTTPAddr, 121 } 122 } 123 124 if vault { 125 harness.vault = testutil.NewTestVault(t).Start() 126 harness.config.VaultConfig = harness.vault.Config 127 harness.vaultToken = harness.vault.RootToken 128 } 129 130 return harness 131 } 132 133 func (h *testHarness) start(t *testing.T) { 134 manager, err := NewTaskTemplateManager(h.mockHooks, h.templates, 135 h.config, h.vaultToken, h.taskDir, h.taskEnv) 136 if err != nil { 137 t.Fatalf("failed to build task template manager: %v", err) 138 } 139 140 h.manager = manager 141 } 142 143 func (h *testHarness) startWithErr() error { 144 manager, err := NewTaskTemplateManager(h.mockHooks, h.templates, 145 h.config, h.vaultToken, h.taskDir, h.taskEnv) 146 h.manager = manager 147 return err 148 } 149 150 // stop is used to stop any running Vault or Consul server plus the task manager 151 func (h *testHarness) stop() { 152 if h.vault != nil { 153 h.vault.Stop() 154 } 155 if h.consul != nil { 156 h.consul.Stop() 157 } 158 if h.manager != nil { 159 h.manager.Stop() 160 } 161 if h.taskDir != "" { 162 os.RemoveAll(h.taskDir) 163 } 164 } 165 166 func TestTaskTemplateManager_Invalid(t *testing.T) { 167 hooks := NewMockTaskHooks() 168 var tmpls []*structs.Template 169 config := &config.Config{} 170 taskDir := "foo" 171 vaultToken := "" 172 taskEnv := env.NewTaskEnvironment(mock.Node()) 173 174 _, err := NewTaskTemplateManager(nil, nil, nil, "", "", nil) 175 if err == nil { 176 t.Fatalf("Expected error") 177 } 178 179 _, err = NewTaskTemplateManager(nil, tmpls, config, vaultToken, taskDir, taskEnv) 180 if err == nil || !strings.Contains(err.Error(), "task hook") { 181 t.Fatalf("Expected invalid task hook error: %v", err) 182 } 183 184 _, err = NewTaskTemplateManager(hooks, tmpls, nil, vaultToken, taskDir, taskEnv) 185 if err == nil || !strings.Contains(err.Error(), "config") { 186 t.Fatalf("Expected invalid config error: %v", err) 187 } 188 189 _, err = NewTaskTemplateManager(hooks, tmpls, config, vaultToken, "", taskEnv) 190 if err == nil || !strings.Contains(err.Error(), "task directory") { 191 t.Fatalf("Expected invalid task dir error: %v", err) 192 } 193 194 _, err = NewTaskTemplateManager(hooks, tmpls, config, vaultToken, taskDir, nil) 195 if err == nil || !strings.Contains(err.Error(), "task environment") { 196 t.Fatalf("Expected invalid task environment error: %v", err) 197 } 198 199 tm, err := NewTaskTemplateManager(hooks, tmpls, config, vaultToken, taskDir, taskEnv) 200 if err != nil { 201 t.Fatalf("Unexpected error: %v", err) 202 } else if tm == nil { 203 t.Fatalf("Bad %v", tm) 204 } 205 206 // Build a template with a bad signal 207 tmpl := &structs.Template{ 208 DestPath: "foo", 209 EmbeddedTmpl: "hello, world", 210 ChangeMode: structs.TemplateChangeModeSignal, 211 ChangeSignal: "foobarbaz", 212 } 213 214 tmpls = append(tmpls, tmpl) 215 tm, err = NewTaskTemplateManager(hooks, tmpls, config, vaultToken, taskDir, taskEnv) 216 if err == nil || !strings.Contains(err.Error(), "Failed to parse signal") { 217 t.Fatalf("Expected signal parsing error: %v", err) 218 } 219 } 220 221 func TestTaskTemplateManager_HostPath(t *testing.T) { 222 // Make a template that will render immediately and write it to a tmp file 223 f, err := ioutil.TempFile("", "") 224 if err != nil { 225 t.Fatalf("Bad: %v", err) 226 } 227 defer f.Close() 228 defer os.Remove(f.Name()) 229 230 content := "hello, world!" 231 if _, err := io.WriteString(f, content); err != nil { 232 t.Fatalf("Bad: %v", err) 233 } 234 235 file := "my.tmpl" 236 template := &structs.Template{ 237 SourcePath: f.Name(), 238 DestPath: file, 239 ChangeMode: structs.TemplateChangeModeNoop, 240 } 241 242 harness := newTestHarness(t, []*structs.Template{template}, false, false) 243 harness.start(t) 244 defer harness.stop() 245 246 // Wait for the unblock 247 select { 248 case <-harness.mockHooks.UnblockCh: 249 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 250 t.Fatalf("Task unblock should have been called") 251 } 252 253 // Check the file is there 254 path := filepath.Join(harness.taskDir, file) 255 raw, err := ioutil.ReadFile(path) 256 if err != nil { 257 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 258 } 259 260 if s := string(raw); s != content { 261 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 262 } 263 264 // Change the config to disallow host sources 265 harness = newTestHarness(t, []*structs.Template{template}, false, false) 266 harness.config.Options = map[string]string{ 267 hostSrcOption: "false", 268 } 269 if err := harness.startWithErr(); err == nil || !strings.Contains(err.Error(), "absolute") { 270 t.Fatalf("Expected absolute template path disallowed: %v", err) 271 } 272 } 273 274 func TestTaskTemplateManager_Unblock_Static(t *testing.T) { 275 // Make a template that will render immediately 276 content := "hello, world!" 277 file := "my.tmpl" 278 template := &structs.Template{ 279 EmbeddedTmpl: content, 280 DestPath: file, 281 ChangeMode: structs.TemplateChangeModeNoop, 282 } 283 284 harness := newTestHarness(t, []*structs.Template{template}, false, false) 285 harness.start(t) 286 defer harness.stop() 287 288 // Wait for the unblock 289 select { 290 case <-harness.mockHooks.UnblockCh: 291 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 292 t.Fatalf("Task unblock should have been called") 293 } 294 295 // Check the file is there 296 path := filepath.Join(harness.taskDir, file) 297 raw, err := ioutil.ReadFile(path) 298 if err != nil { 299 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 300 } 301 302 if s := string(raw); s != content { 303 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 304 } 305 } 306 307 func TestTaskTemplateManager_Unblock_Static_AlreadyRendered(t *testing.T) { 308 // Make a template that will render immediately 309 content := "hello, world!" 310 file := "my.tmpl" 311 template := &structs.Template{ 312 EmbeddedTmpl: content, 313 DestPath: file, 314 ChangeMode: structs.TemplateChangeModeNoop, 315 } 316 317 harness := newTestHarness(t, []*structs.Template{template}, false, false) 318 319 // Write the contents 320 path := filepath.Join(harness.taskDir, file) 321 if err := ioutil.WriteFile(path, []byte(content), 0777); err != nil { 322 t.Fatalf("Failed to write data: %v", err) 323 } 324 325 harness.start(t) 326 defer harness.stop() 327 328 // Wait for the unblock 329 select { 330 case <-harness.mockHooks.UnblockCh: 331 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 332 t.Fatalf("Task unblock should have been called") 333 } 334 335 // Check the file is there 336 path = filepath.Join(harness.taskDir, file) 337 raw, err := ioutil.ReadFile(path) 338 if err != nil { 339 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 340 } 341 342 if s := string(raw); s != content { 343 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 344 } 345 } 346 347 func TestTaskTemplateManager_Unblock_Consul(t *testing.T) { 348 // Make a template that will render based on a key in Consul 349 key := "foo" 350 content := "barbaz" 351 embedded := fmt.Sprintf(`{{key "%s"}}`, key) 352 file := "my.tmpl" 353 template := &structs.Template{ 354 EmbeddedTmpl: embedded, 355 DestPath: file, 356 ChangeMode: structs.TemplateChangeModeNoop, 357 } 358 359 // Drop the retry rate 360 testRetryRate = 10 * time.Millisecond 361 362 harness := newTestHarness(t, []*structs.Template{template}, true, false) 363 harness.start(t) 364 defer harness.stop() 365 366 // Ensure no unblock 367 select { 368 case <-harness.mockHooks.UnblockCh: 369 t.Fatalf("Task unblock should have not have been called") 370 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 371 } 372 373 // Write the key to Consul 374 harness.consul.SetKV(key, []byte(content)) 375 376 // Wait for the unblock 377 select { 378 case <-harness.mockHooks.UnblockCh: 379 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 380 t.Fatalf("Task unblock should have been called") 381 } 382 383 // Check the file is there 384 path := filepath.Join(harness.taskDir, file) 385 raw, err := ioutil.ReadFile(path) 386 if err != nil { 387 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 388 } 389 390 if s := string(raw); s != content { 391 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 392 } 393 } 394 395 func TestTaskTemplateManager_Unblock_Vault(t *testing.T) { 396 // Make a template that will render based on a key in Vault 397 vaultPath := "secret/password" 398 key := "password" 399 content := "barbaz" 400 embedded := fmt.Sprintf(`{{with secret "%s"}}{{.Data.%s}}{{end}}`, vaultPath, key) 401 file := "my.tmpl" 402 template := &structs.Template{ 403 EmbeddedTmpl: embedded, 404 DestPath: file, 405 ChangeMode: structs.TemplateChangeModeNoop, 406 } 407 408 // Drop the retry rate 409 testRetryRate = 10 * time.Millisecond 410 411 harness := newTestHarness(t, []*structs.Template{template}, false, true) 412 harness.start(t) 413 defer harness.stop() 414 415 // Ensure no unblock 416 select { 417 case <-harness.mockHooks.UnblockCh: 418 t.Fatalf("Task unblock should not have been called") 419 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 420 } 421 422 // Write the secret to Vault 423 logical := harness.vault.Client.Logical() 424 logical.Write(vaultPath, map[string]interface{}{key: content}) 425 426 // Wait for the unblock 427 select { 428 case <-harness.mockHooks.UnblockCh: 429 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 430 t.Fatalf("Task unblock should have been called") 431 } 432 433 // Check the file is there 434 path := filepath.Join(harness.taskDir, file) 435 raw, err := ioutil.ReadFile(path) 436 if err != nil { 437 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 438 } 439 440 if s := string(raw); s != content { 441 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 442 } 443 } 444 445 func TestTaskTemplateManager_Unblock_Multi_Template(t *testing.T) { 446 // Make a template that will render immediately 447 staticContent := "hello, world!" 448 staticFile := "my.tmpl" 449 template := &structs.Template{ 450 EmbeddedTmpl: staticContent, 451 DestPath: staticFile, 452 ChangeMode: structs.TemplateChangeModeNoop, 453 } 454 455 // Make a template that will render based on a key in Consul 456 consulKey := "foo" 457 consulContent := "barbaz" 458 consulEmbedded := fmt.Sprintf(`{{key "%s"}}`, consulKey) 459 consulFile := "consul.tmpl" 460 template2 := &structs.Template{ 461 EmbeddedTmpl: consulEmbedded, 462 DestPath: consulFile, 463 ChangeMode: structs.TemplateChangeModeNoop, 464 } 465 466 // Drop the retry rate 467 testRetryRate = 10 * time.Millisecond 468 469 harness := newTestHarness(t, []*structs.Template{template, template2}, true, false) 470 harness.start(t) 471 defer harness.stop() 472 473 // Ensure no unblock 474 select { 475 case <-harness.mockHooks.UnblockCh: 476 t.Fatalf("Task unblock should have not have been called") 477 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 478 } 479 480 // Check that the static file has been rendered 481 path := filepath.Join(harness.taskDir, staticFile) 482 raw, err := ioutil.ReadFile(path) 483 if err != nil { 484 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 485 } 486 487 if s := string(raw); s != staticContent { 488 t.Fatalf("Unexpected template data; got %q, want %q", s, staticContent) 489 } 490 491 // Write the key to Consul 492 harness.consul.SetKV(consulKey, []byte(consulContent)) 493 494 // Wait for the unblock 495 select { 496 case <-harness.mockHooks.UnblockCh: 497 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 498 t.Fatalf("Task unblock should have been called") 499 } 500 501 // Check the consul file is there 502 path = filepath.Join(harness.taskDir, consulFile) 503 raw, err = ioutil.ReadFile(path) 504 if err != nil { 505 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 506 } 507 508 if s := string(raw); s != consulContent { 509 t.Fatalf("Unexpected template data; got %q, want %q", s, consulContent) 510 } 511 } 512 513 func TestTaskTemplateManager_Rerender_Noop(t *testing.T) { 514 // Make a template that will render based on a key in Consul 515 key := "foo" 516 content1 := "bar" 517 content2 := "baz" 518 embedded := fmt.Sprintf(`{{key "%s"}}`, key) 519 file := "my.tmpl" 520 template := &structs.Template{ 521 EmbeddedTmpl: embedded, 522 DestPath: file, 523 ChangeMode: structs.TemplateChangeModeNoop, 524 } 525 526 // Drop the retry rate 527 testRetryRate = 10 * time.Millisecond 528 529 harness := newTestHarness(t, []*structs.Template{template}, true, false) 530 harness.start(t) 531 defer harness.stop() 532 533 // Ensure no unblock 534 select { 535 case <-harness.mockHooks.UnblockCh: 536 t.Fatalf("Task unblock should have not have been called") 537 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 538 } 539 540 // Write the key to Consul 541 harness.consul.SetKV(key, []byte(content1)) 542 543 // Wait for the unblock 544 select { 545 case <-harness.mockHooks.UnblockCh: 546 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 547 t.Fatalf("Task unblock should have been called") 548 } 549 550 // Check the file is there 551 path := filepath.Join(harness.taskDir, file) 552 raw, err := ioutil.ReadFile(path) 553 if err != nil { 554 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 555 } 556 557 if s := string(raw); s != content1 { 558 t.Fatalf("Unexpected template data; got %q, want %q", s, content1) 559 } 560 561 // Update the key in Consul 562 harness.consul.SetKV(key, []byte(content2)) 563 564 select { 565 case <-harness.mockHooks.RestartCh: 566 t.Fatalf("Noop ignored: %+v", harness.mockHooks) 567 case <-harness.mockHooks.SignalCh: 568 t.Fatalf("Noop ignored: %+v", harness.mockHooks) 569 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 570 } 571 572 // Check the file has been updated 573 path = filepath.Join(harness.taskDir, file) 574 raw, err = ioutil.ReadFile(path) 575 if err != nil { 576 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 577 } 578 579 if s := string(raw); s != content2 { 580 t.Fatalf("Unexpected template data; got %q, want %q", s, content2) 581 } 582 } 583 584 func TestTaskTemplateManager_Rerender_Signal(t *testing.T) { 585 // Make a template that renders based on a key in Consul and sends SIGALRM 586 key1 := "foo" 587 content1_1 := "bar" 588 content1_2 := "baz" 589 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 590 file1 := "my.tmpl" 591 template := &structs.Template{ 592 EmbeddedTmpl: embedded1, 593 DestPath: file1, 594 ChangeMode: structs.TemplateChangeModeSignal, 595 ChangeSignal: "SIGALRM", 596 } 597 598 // Make a template that renders based on a key in Consul and sends SIGBUS 599 key2 := "bam" 600 content2_1 := "cat" 601 content2_2 := "dog" 602 embedded2 := fmt.Sprintf(`{{key "%s"}}`, key2) 603 file2 := "my-second.tmpl" 604 template2 := &structs.Template{ 605 EmbeddedTmpl: embedded2, 606 DestPath: file2, 607 ChangeMode: structs.TemplateChangeModeSignal, 608 ChangeSignal: "SIGBUS", 609 } 610 611 // Drop the retry rate 612 testRetryRate = 10 * time.Millisecond 613 614 harness := newTestHarness(t, []*structs.Template{template, template2}, true, false) 615 harness.start(t) 616 defer harness.stop() 617 618 // Ensure no unblock 619 select { 620 case <-harness.mockHooks.UnblockCh: 621 t.Fatalf("Task unblock should have not have been called") 622 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 623 } 624 625 // Write the key to Consul 626 harness.consul.SetKV(key1, []byte(content1_1)) 627 harness.consul.SetKV(key2, []byte(content2_1)) 628 629 // Wait for the unblock 630 select { 631 case <-harness.mockHooks.UnblockCh: 632 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 633 t.Fatalf("Task unblock should have been called") 634 } 635 636 if len(harness.mockHooks.Signals) != 0 { 637 t.Fatalf("Should not have received any signals: %+v", harness.mockHooks) 638 } 639 640 // Update the keys in Consul 641 harness.consul.SetKV(key1, []byte(content1_2)) 642 harness.consul.SetKV(key2, []byte(content2_2)) 643 644 // Wait for signals 645 timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second) 646 OUTER: 647 for { 648 select { 649 case <-harness.mockHooks.RestartCh: 650 t.Fatalf("Restart with signal policy: %+v", harness.mockHooks) 651 case <-harness.mockHooks.SignalCh: 652 if len(harness.mockHooks.Signals) != 2 { 653 continue 654 } 655 break OUTER 656 case <-timeout: 657 t.Fatalf("Should have received two signals: %+v", harness.mockHooks) 658 } 659 } 660 661 // Check the files have been updated 662 path := filepath.Join(harness.taskDir, file1) 663 raw, err := ioutil.ReadFile(path) 664 if err != nil { 665 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 666 } 667 668 if s := string(raw); s != content1_2 { 669 t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2) 670 } 671 672 path = filepath.Join(harness.taskDir, file2) 673 raw, err = ioutil.ReadFile(path) 674 if err != nil { 675 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 676 } 677 678 if s := string(raw); s != content2_2 { 679 t.Fatalf("Unexpected template data; got %q, want %q", s, content2_2) 680 } 681 } 682 683 func TestTaskTemplateManager_Rerender_Restart(t *testing.T) { 684 // Make a template that renders based on a key in Consul and sends restart 685 key1 := "bam" 686 content1_1 := "cat" 687 content1_2 := "dog" 688 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 689 file1 := "my.tmpl" 690 template := &structs.Template{ 691 EmbeddedTmpl: embedded1, 692 DestPath: file1, 693 ChangeMode: structs.TemplateChangeModeRestart, 694 } 695 696 // Drop the retry rate 697 testRetryRate = 10 * time.Millisecond 698 699 harness := newTestHarness(t, []*structs.Template{template}, true, false) 700 harness.start(t) 701 defer harness.stop() 702 703 // Ensure no unblock 704 select { 705 case <-harness.mockHooks.UnblockCh: 706 t.Fatalf("Task unblock should have not have been called") 707 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 708 } 709 710 // Write the key to Consul 711 harness.consul.SetKV(key1, []byte(content1_1)) 712 713 // Wait for the unblock 714 select { 715 case <-harness.mockHooks.UnblockCh: 716 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 717 t.Fatalf("Task unblock should have been called") 718 } 719 720 // Update the keys in Consul 721 harness.consul.SetKV(key1, []byte(content1_2)) 722 723 // Wait for restart 724 timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second) 725 OUTER: 726 for { 727 select { 728 case <-harness.mockHooks.RestartCh: 729 break OUTER 730 case <-harness.mockHooks.SignalCh: 731 t.Fatalf("Signal with restart policy: %+v", harness.mockHooks) 732 case <-timeout: 733 t.Fatalf("Should have received a restart: %+v", harness.mockHooks) 734 } 735 } 736 737 // Check the files have been updated 738 path := filepath.Join(harness.taskDir, file1) 739 raw, err := ioutil.ReadFile(path) 740 if err != nil { 741 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 742 } 743 744 if s := string(raw); s != content1_2 { 745 t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2) 746 } 747 } 748 749 func TestTaskTemplateManager_Interpolate_Destination(t *testing.T) { 750 // Make a template that will have its destination interpolated 751 content := "hello, world!" 752 file := "${node.unique.id}.tmpl" 753 template := &structs.Template{ 754 EmbeddedTmpl: content, 755 DestPath: file, 756 ChangeMode: structs.TemplateChangeModeNoop, 757 } 758 759 harness := newTestHarness(t, []*structs.Template{template}, false, false) 760 harness.start(t) 761 defer harness.stop() 762 763 // Ensure unblock 764 select { 765 case <-harness.mockHooks.UnblockCh: 766 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 767 t.Fatalf("Task unblock should have been called") 768 } 769 770 // Check the file is there 771 actual := fmt.Sprintf("%s.tmpl", harness.node.ID) 772 path := filepath.Join(harness.taskDir, actual) 773 raw, err := ioutil.ReadFile(path) 774 if err != nil { 775 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 776 } 777 778 if s := string(raw); s != content { 779 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 780 } 781 } 782 783 func TestTaskTemplateManager_Signal_Error(t *testing.T) { 784 // Make a template that renders based on a key in Consul and sends SIGALRM 785 key1 := "foo" 786 content1 := "bar" 787 content2 := "baz" 788 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 789 file1 := "my.tmpl" 790 template := &structs.Template{ 791 EmbeddedTmpl: embedded1, 792 DestPath: file1, 793 ChangeMode: structs.TemplateChangeModeSignal, 794 ChangeSignal: "SIGALRM", 795 } 796 797 // Drop the retry rate 798 testRetryRate = 10 * time.Millisecond 799 800 harness := newTestHarness(t, []*structs.Template{template}, true, false) 801 harness.start(t) 802 defer harness.stop() 803 804 harness.mockHooks.SignalError = fmt.Errorf("test error") 805 806 // Write the key to Consul 807 harness.consul.SetKV(key1, []byte(content1)) 808 809 // Wait a little 810 select { 811 case <-harness.mockHooks.UnblockCh: 812 case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second): 813 t.Fatalf("Should have received unblock: %+v", harness.mockHooks) 814 } 815 816 // Write the key to Consul 817 harness.consul.SetKV(key1, []byte(content2)) 818 819 // Wait for kill channel 820 select { 821 case <-harness.mockHooks.KillCh: 822 break 823 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 824 t.Fatalf("Should have received a signals: %+v", harness.mockHooks) 825 } 826 827 if !strings.Contains(harness.mockHooks.KillReason, "Sending signals") { 828 t.Fatalf("Unexpected error", harness.mockHooks.KillReason) 829 } 830 }