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