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  }