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  }