github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/command/agent/consul/unit_test.go (about)

     1  package consul
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  	"sync/atomic"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/consul/api"
    13  	"github.com/hashicorp/nomad/helper/testlog"
    14  	"github.com/hashicorp/nomad/helper/uuid"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/hashicorp/nomad/plugins/drivers"
    17  	"github.com/hashicorp/nomad/testutil"
    18  	"github.com/kr/pretty"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  const (
    24  	// Ports used in testTask
    25  	xPort = 1234
    26  	yPort = 1235
    27  )
    28  
    29  func testTask() *TaskServices {
    30  	return &TaskServices{
    31  		AllocID:   uuid.Generate(),
    32  		Name:      "taskname",
    33  		Restarter: &restartRecorder{},
    34  		Services: []*structs.Service{
    35  			{
    36  				Name:      "taskname-service",
    37  				PortLabel: "x",
    38  				Tags:      []string{"tag1", "tag2"},
    39  			},
    40  		},
    41  		Networks: []*structs.NetworkResource{
    42  			{
    43  				DynamicPorts: []structs.Port{
    44  					{Label: "x", Value: xPort},
    45  					{Label: "y", Value: yPort},
    46  				},
    47  			},
    48  		},
    49  		DriverExec: newMockExec(),
    50  	}
    51  }
    52  
    53  // mockExec implements the ScriptExecutor interface and will use an alternate
    54  // implementation t.ExecFunc if non-nil.
    55  type mockExec struct {
    56  	// Ticked whenever a script is called
    57  	execs chan int
    58  
    59  	// If non-nil will be called by script checks
    60  	ExecFunc func(ctx context.Context, cmd string, args []string) ([]byte, int, error)
    61  }
    62  
    63  func newMockExec() *mockExec {
    64  	return &mockExec{
    65  		execs: make(chan int, 100),
    66  	}
    67  }
    68  
    69  func (m *mockExec) Exec(dur time.Duration, cmd string, args []string) ([]byte, int, error) {
    70  	select {
    71  	case m.execs <- 1:
    72  	default:
    73  	}
    74  	if m.ExecFunc == nil {
    75  		// Default impl is just "ok"
    76  		return []byte("ok"), 0, nil
    77  	}
    78  	ctx, cancel := context.WithTimeout(context.Background(), dur)
    79  	defer cancel()
    80  	return m.ExecFunc(ctx, cmd, args)
    81  }
    82  
    83  // restartRecorder is a minimal TaskRestarter implementation that simply
    84  // counts how many restarts were triggered.
    85  type restartRecorder struct {
    86  	restarts int64
    87  }
    88  
    89  func (r *restartRecorder) Restart(ctx context.Context, event *structs.TaskEvent, failure bool) error {
    90  	atomic.AddInt64(&r.restarts, 1)
    91  	return nil
    92  }
    93  
    94  // testFakeCtx contains a fake Consul AgentAPI
    95  type testFakeCtx struct {
    96  	ServiceClient *ServiceClient
    97  	FakeConsul    *MockAgent
    98  	Task          *TaskServices
    99  	MockExec      *mockExec
   100  }
   101  
   102  var errNoOps = fmt.Errorf("testing error: no pending operations")
   103  
   104  // syncOps simulates one iteration of the ServiceClient.Run loop and returns
   105  // any errors returned by sync() or errNoOps if no pending operations.
   106  func (t *testFakeCtx) syncOnce() error {
   107  	select {
   108  	case ops := <-t.ServiceClient.opCh:
   109  		t.ServiceClient.merge(ops)
   110  		return t.ServiceClient.sync()
   111  	default:
   112  		return errNoOps
   113  	}
   114  }
   115  
   116  // setupFake creates a testFakeCtx with a ServiceClient backed by a fakeConsul.
   117  // A test Task is also provided.
   118  func setupFake(t *testing.T) *testFakeCtx {
   119  	fc := NewMockAgent()
   120  	tt := testTask()
   121  	return &testFakeCtx{
   122  		ServiceClient: NewServiceClient(fc, testlog.HCLogger(t), true),
   123  		FakeConsul:    fc,
   124  		Task:          tt,
   125  		MockExec:      tt.DriverExec.(*mockExec),
   126  	}
   127  }
   128  
   129  func TestConsul_ChangeTags(t *testing.T) {
   130  	ctx := setupFake(t)
   131  	require := require.New(t)
   132  
   133  	require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task))
   134  	require.NoError(ctx.syncOnce())
   135  	require.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul")
   136  
   137  	// Validate the alloc registration
   138  	reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Task.AllocID)
   139  	require.NoError(err)
   140  	require.NotNil(reg1, "Unexpected nil alloc registration")
   141  	require.Equal(1, reg1.NumServices())
   142  	require.Equal(0, reg1.NumChecks())
   143  
   144  	for _, v := range ctx.FakeConsul.services {
   145  		require.Equal(v.Name, ctx.Task.Services[0].Name)
   146  		require.Equal(v.Tags, ctx.Task.Services[0].Tags)
   147  	}
   148  
   149  	// Update the task definition
   150  	origTask := ctx.Task.Copy()
   151  	ctx.Task.Services[0].Tags[0] = "newtag"
   152  
   153  	// Register and sync the update
   154  	require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task))
   155  	require.NoError(ctx.syncOnce())
   156  	require.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul")
   157  
   158  	// Validate the metadata changed
   159  	for _, v := range ctx.FakeConsul.services {
   160  		require.Equal(v.Name, ctx.Task.Services[0].Name)
   161  		require.Equal(v.Tags, ctx.Task.Services[0].Tags)
   162  		require.Equal("newtag", v.Tags[0])
   163  	}
   164  }
   165  
   166  // TestConsul_ChangePorts asserts that changing the ports on a service updates
   167  // it in Consul. Pre-0.7.1 ports were not part of the service ID and this was a
   168  // slightly different code path than changing tags.
   169  func TestConsul_ChangePorts(t *testing.T) {
   170  	ctx := setupFake(t)
   171  	require := require.New(t)
   172  
   173  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   174  		{
   175  			Name:      "c1",
   176  			Type:      "tcp",
   177  			Interval:  time.Second,
   178  			Timeout:   time.Second,
   179  			PortLabel: "x",
   180  		},
   181  		{
   182  			Name:     "c2",
   183  			Type:     "script",
   184  			Interval: 9000 * time.Hour,
   185  			Timeout:  time.Second,
   186  		},
   187  		{
   188  			Name:      "c3",
   189  			Type:      "http",
   190  			Protocol:  "http",
   191  			Path:      "/",
   192  			Interval:  time.Second,
   193  			Timeout:   time.Second,
   194  			PortLabel: "y",
   195  		},
   196  	}
   197  
   198  	require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task))
   199  	require.NoError(ctx.syncOnce())
   200  	require.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul")
   201  
   202  	for _, v := range ctx.FakeConsul.services {
   203  		require.Equal(ctx.Task.Services[0].Name, v.Name)
   204  		require.Equal(ctx.Task.Services[0].Tags, v.Tags)
   205  		require.Equal(xPort, v.Port)
   206  	}
   207  
   208  	require.Equal(3, len(ctx.FakeConsul.checks))
   209  
   210  	origTCPKey := ""
   211  	origScriptKey := ""
   212  	origHTTPKey := ""
   213  	for k, v := range ctx.FakeConsul.checks {
   214  		switch v.Name {
   215  		case "c1":
   216  			origTCPKey = k
   217  			require.Equal(fmt.Sprintf(":%d", xPort), v.TCP)
   218  		case "c2":
   219  			origScriptKey = k
   220  			select {
   221  			case <-ctx.MockExec.execs:
   222  				// Here we validate there is nothing left on the channel
   223  				require.Equal(0, len(ctx.MockExec.execs))
   224  			case <-time.After(3 * time.Second):
   225  				t.Fatalf("script not called in time")
   226  			}
   227  		case "c3":
   228  			origHTTPKey = k
   229  			require.Equal(fmt.Sprintf("http://:%d/", yPort), v.HTTP)
   230  		default:
   231  			t.Fatalf("unexpected check: %q", v.Name)
   232  		}
   233  	}
   234  
   235  	require.NotEmpty(origTCPKey)
   236  	require.NotEmpty(origScriptKey)
   237  	require.NotEmpty(origHTTPKey)
   238  
   239  	// Now update the PortLabel on the Service and Check c3
   240  	origTask := ctx.Task.Copy()
   241  	ctx.Task.Services[0].PortLabel = "y"
   242  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   243  		{
   244  			Name:      "c1",
   245  			Type:      "tcp",
   246  			Interval:  time.Second,
   247  			Timeout:   time.Second,
   248  			PortLabel: "x",
   249  		},
   250  		{
   251  			Name:     "c2",
   252  			Type:     "script",
   253  			Interval: 9000 * time.Hour,
   254  			Timeout:  time.Second,
   255  		},
   256  		{
   257  			Name:     "c3",
   258  			Type:     "http",
   259  			Protocol: "http",
   260  			Path:     "/",
   261  			Interval: time.Second,
   262  			Timeout:  time.Second,
   263  			// Removed PortLabel; should default to service's (y)
   264  		},
   265  	}
   266  
   267  	require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task))
   268  	require.NoError(ctx.syncOnce())
   269  	require.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul")
   270  
   271  	for _, v := range ctx.FakeConsul.services {
   272  		require.Equal(ctx.Task.Services[0].Name, v.Name)
   273  		require.Equal(ctx.Task.Services[0].Tags, v.Tags)
   274  		require.Equal(yPort, v.Port)
   275  	}
   276  
   277  	require.Equal(3, len(ctx.FakeConsul.checks))
   278  
   279  	for k, v := range ctx.FakeConsul.checks {
   280  		switch v.Name {
   281  		case "c1":
   282  			// C1 is not changed
   283  			require.Equal(origTCPKey, k)
   284  			require.Equal(fmt.Sprintf(":%d", xPort), v.TCP)
   285  		case "c2":
   286  			// C2 is not changed and should not have been re-registered
   287  			require.Equal(origScriptKey, k)
   288  		case "c3":
   289  			require.NotEqual(origHTTPKey, k)
   290  			require.Equal(fmt.Sprintf("http://:%d/", yPort), v.HTTP)
   291  		default:
   292  			t.Errorf("Unknown check: %q", k)
   293  		}
   294  	}
   295  }
   296  
   297  // TestConsul_ChangeChecks asserts that updating only the checks on a service
   298  // properly syncs with Consul.
   299  func TestConsul_ChangeChecks(t *testing.T) {
   300  	ctx := setupFake(t)
   301  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   302  		{
   303  			Name:      "c1",
   304  			Type:      "tcp",
   305  			Interval:  time.Second,
   306  			Timeout:   time.Second,
   307  			PortLabel: "x",
   308  			CheckRestart: &structs.CheckRestart{
   309  				Limit: 3,
   310  			},
   311  		},
   312  	}
   313  
   314  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
   315  		t.Fatalf("unexpected error registering task: %v", err)
   316  	}
   317  
   318  	if err := ctx.syncOnce(); err != nil {
   319  		t.Fatalf("unexpected error syncing task: %v", err)
   320  	}
   321  
   322  	if n := len(ctx.FakeConsul.services); n != 1 {
   323  		t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services)
   324  	}
   325  
   326  	// Assert a check restart watch update was enqueued and clear it
   327  	if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 {
   328  		t.Fatalf("expected 1 check restart update but found %d", n)
   329  	}
   330  	upd := <-ctx.ServiceClient.checkWatcher.checkUpdateCh
   331  	c1ID := upd.checkID
   332  
   333  	// Query the allocs registrations and then again when we update. The IDs
   334  	// should change
   335  	reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Task.AllocID)
   336  	if err != nil {
   337  		t.Fatalf("Looking up alloc registration failed: %v", err)
   338  	}
   339  	if reg1 == nil {
   340  		t.Fatalf("Nil alloc registrations: %v", err)
   341  	}
   342  	if num := reg1.NumServices(); num != 1 {
   343  		t.Fatalf("Wrong number of services: got %d; want 1", num)
   344  	}
   345  	if num := reg1.NumChecks(); num != 1 {
   346  		t.Fatalf("Wrong number of checks: got %d; want 1", num)
   347  	}
   348  
   349  	origServiceKey := ""
   350  	for k, v := range ctx.FakeConsul.services {
   351  		origServiceKey = k
   352  		if v.Name != ctx.Task.Services[0].Name {
   353  			t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name)
   354  		}
   355  		if v.Port != xPort {
   356  			t.Errorf("expected Port x=%v but found: %v", xPort, v.Port)
   357  		}
   358  	}
   359  
   360  	if n := len(ctx.FakeConsul.checks); n != 1 {
   361  		t.Fatalf("expected 1 check but found %d:\n%#v", n, ctx.FakeConsul.checks)
   362  	}
   363  	for _, v := range ctx.FakeConsul.checks {
   364  		if v.Name != "c1" {
   365  			t.Fatalf("expected check c1 but found %q", v.Name)
   366  		}
   367  	}
   368  
   369  	// Now add a check and modify the original
   370  	origTask := ctx.Task.Copy()
   371  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   372  		{
   373  			Name:      "c1",
   374  			Type:      "tcp",
   375  			Interval:  2 * time.Second,
   376  			Timeout:   time.Second,
   377  			PortLabel: "x",
   378  			CheckRestart: &structs.CheckRestart{
   379  				Limit: 3,
   380  			},
   381  		},
   382  		{
   383  			Name:      "c2",
   384  			Type:      "http",
   385  			Path:      "/",
   386  			Interval:  time.Second,
   387  			Timeout:   time.Second,
   388  			PortLabel: "x",
   389  		},
   390  	}
   391  	if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil {
   392  		t.Fatalf("unexpected error registering task: %v", err)
   393  	}
   394  
   395  	// Assert 2 check restart watch updates was enqueued
   396  	if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 2 {
   397  		t.Fatalf("expected 2 check restart updates but found %d", n)
   398  	}
   399  
   400  	// First the new watch
   401  	upd = <-ctx.ServiceClient.checkWatcher.checkUpdateCh
   402  	if upd.checkID == c1ID || upd.remove {
   403  		t.Fatalf("expected check watch update to be an add of checkID=%q but found remove=%t checkID=%q",
   404  			c1ID, upd.remove, upd.checkID)
   405  	}
   406  
   407  	// Then remove the old watch
   408  	upd = <-ctx.ServiceClient.checkWatcher.checkUpdateCh
   409  	if upd.checkID != c1ID || !upd.remove {
   410  		t.Fatalf("expected check watch update to be a removal of checkID=%q but found remove=%t checkID=%q",
   411  			c1ID, upd.remove, upd.checkID)
   412  	}
   413  
   414  	if err := ctx.syncOnce(); err != nil {
   415  		t.Fatalf("unexpected error syncing task: %v", err)
   416  	}
   417  
   418  	if n := len(ctx.FakeConsul.services); n != 1 {
   419  		t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services)
   420  	}
   421  
   422  	if _, ok := ctx.FakeConsul.services[origServiceKey]; !ok {
   423  		t.Errorf("unexpected key change; was: %q -- but found %#v", origServiceKey, ctx.FakeConsul.services)
   424  	}
   425  
   426  	if n := len(ctx.FakeConsul.checks); n != 2 {
   427  		t.Fatalf("expected 2 check but found %d:\n%#v", n, ctx.FakeConsul.checks)
   428  	}
   429  
   430  	for k, v := range ctx.FakeConsul.checks {
   431  		switch v.Name {
   432  		case "c1":
   433  			if expected := fmt.Sprintf(":%d", xPort); v.TCP != expected {
   434  				t.Errorf("expected Port x=%v but found: %v", expected, v.TCP)
   435  			}
   436  
   437  			// update id
   438  			c1ID = k
   439  		case "c2":
   440  			if expected := fmt.Sprintf("http://:%d/", xPort); v.HTTP != expected {
   441  				t.Errorf("expected Port x=%v but found: %v", expected, v.HTTP)
   442  			}
   443  		default:
   444  			t.Errorf("Unknown check: %q", k)
   445  		}
   446  	}
   447  
   448  	// Check again and ensure the IDs changed
   449  	reg2, err := ctx.ServiceClient.AllocRegistrations(ctx.Task.AllocID)
   450  	if err != nil {
   451  		t.Fatalf("Looking up alloc registration failed: %v", err)
   452  	}
   453  	if reg2 == nil {
   454  		t.Fatalf("Nil alloc registrations: %v", err)
   455  	}
   456  	if num := reg2.NumServices(); num != 1 {
   457  		t.Fatalf("Wrong number of services: got %d; want 1", num)
   458  	}
   459  	if num := reg2.NumChecks(); num != 2 {
   460  		t.Fatalf("Wrong number of checks: got %d; want 2", num)
   461  	}
   462  
   463  	for task, treg := range reg1.Tasks {
   464  		otherTaskReg, ok := reg2.Tasks[task]
   465  		if !ok {
   466  			t.Fatalf("Task %q not in second reg", task)
   467  		}
   468  
   469  		for sID, sreg := range treg.Services {
   470  			otherServiceReg, ok := otherTaskReg.Services[sID]
   471  			if !ok {
   472  				t.Fatalf("service ID changed")
   473  			}
   474  
   475  			for newID := range sreg.checkIDs {
   476  				if _, ok := otherServiceReg.checkIDs[newID]; ok {
   477  					t.Fatalf("check IDs should change")
   478  				}
   479  			}
   480  		}
   481  	}
   482  
   483  	// Alter a CheckRestart and make sure the watcher is updated but nothing else
   484  	origTask = ctx.Task.Copy()
   485  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   486  		{
   487  			Name:      "c1",
   488  			Type:      "tcp",
   489  			Interval:  2 * time.Second,
   490  			Timeout:   time.Second,
   491  			PortLabel: "x",
   492  			CheckRestart: &structs.CheckRestart{
   493  				Limit: 11,
   494  			},
   495  		},
   496  		{
   497  			Name:      "c2",
   498  			Type:      "http",
   499  			Path:      "/",
   500  			Interval:  time.Second,
   501  			Timeout:   time.Second,
   502  			PortLabel: "x",
   503  		},
   504  	}
   505  	if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil {
   506  		t.Fatalf("unexpected error registering task: %v", err)
   507  	}
   508  	if err := ctx.syncOnce(); err != nil {
   509  		t.Fatalf("unexpected error syncing task: %v", err)
   510  	}
   511  
   512  	if n := len(ctx.FakeConsul.checks); n != 2 {
   513  		t.Fatalf("expected 2 check but found %d:\n%#v", n, ctx.FakeConsul.checks)
   514  	}
   515  
   516  	for k, v := range ctx.FakeConsul.checks {
   517  		if v.Name == "c1" {
   518  			if k != c1ID {
   519  				t.Errorf("expected c1 to still have id %q but found %q", c1ID, k)
   520  			}
   521  			break
   522  		}
   523  	}
   524  
   525  	// Assert a check restart watch update was enqueued for a removal and an add
   526  	if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 {
   527  		t.Fatalf("expected 1 check restart update but found %d", n)
   528  	}
   529  	<-ctx.ServiceClient.checkWatcher.checkUpdateCh
   530  }
   531  
   532  // TestConsul_RegServices tests basic service registration.
   533  func TestConsul_RegServices(t *testing.T) {
   534  	ctx := setupFake(t)
   535  
   536  	// Add a check w/restarting
   537  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   538  		{
   539  			Name:     "testcheck",
   540  			Type:     "tcp",
   541  			Interval: 100 * time.Millisecond,
   542  			CheckRestart: &structs.CheckRestart{
   543  				Limit: 3,
   544  			},
   545  		},
   546  	}
   547  
   548  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
   549  		t.Fatalf("unexpected error registering task: %v", err)
   550  	}
   551  
   552  	if err := ctx.syncOnce(); err != nil {
   553  		t.Fatalf("unexpected error syncing task: %v", err)
   554  	}
   555  
   556  	if n := len(ctx.FakeConsul.services); n != 1 {
   557  		t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services)
   558  	}
   559  
   560  	for _, v := range ctx.FakeConsul.services {
   561  		if v.Name != ctx.Task.Services[0].Name {
   562  			t.Errorf("expected Name=%q != %q", ctx.Task.Services[0].Name, v.Name)
   563  		}
   564  		if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) {
   565  			t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags)
   566  		}
   567  		if v.Port != xPort {
   568  			t.Errorf("expected Port=%d != %d", xPort, v.Port)
   569  		}
   570  	}
   571  
   572  	// Assert the check update is pending
   573  	if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 {
   574  		t.Fatalf("expected 1 check restart update but found %d", n)
   575  	}
   576  
   577  	// Assert the check update is properly formed
   578  	checkUpd := <-ctx.ServiceClient.checkWatcher.checkUpdateCh
   579  	if checkUpd.checkRestart.allocID != ctx.Task.AllocID {
   580  		t.Fatalf("expected check's allocid to be %q but found %q", "allocid", checkUpd.checkRestart.allocID)
   581  	}
   582  	if expected := 200 * time.Millisecond; checkUpd.checkRestart.timeLimit != expected {
   583  		t.Fatalf("expected check's time limit to be %v but found %v", expected, checkUpd.checkRestart.timeLimit)
   584  	}
   585  
   586  	// Make a change which will register a new service
   587  	ctx.Task.Services[0].Name = "taskname-service2"
   588  	ctx.Task.Services[0].Tags[0] = "tag3"
   589  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
   590  		t.Fatalf("unexpected error registering task: %v", err)
   591  	}
   592  
   593  	// Assert check update is pending
   594  	if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 {
   595  		t.Fatalf("expected 1 check restart update but found %d", n)
   596  	}
   597  
   598  	// Assert the check update's id has changed
   599  	checkUpd2 := <-ctx.ServiceClient.checkWatcher.checkUpdateCh
   600  	if checkUpd.checkID == checkUpd2.checkID {
   601  		t.Fatalf("expected new check update to have a new ID both both have: %q", checkUpd.checkID)
   602  	}
   603  
   604  	// Make sure changes don't take affect until sync() is called (since
   605  	// Run() isn't running)
   606  	if n := len(ctx.FakeConsul.services); n != 1 {
   607  		t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services)
   608  	}
   609  	for _, v := range ctx.FakeConsul.services {
   610  		if reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) {
   611  			t.Errorf("expected Tags to differ, changes applied before sync()")
   612  		}
   613  	}
   614  
   615  	// Now sync() and re-check for the applied updates
   616  	if err := ctx.syncOnce(); err != nil {
   617  		t.Fatalf("unexpected error syncing task: %v", err)
   618  	}
   619  	if n := len(ctx.FakeConsul.services); n != 2 {
   620  		t.Fatalf("expected 2 services but found %d:\n%#v", n, ctx.FakeConsul.services)
   621  	}
   622  	found := false
   623  	for _, v := range ctx.FakeConsul.services {
   624  		if v.Name == ctx.Task.Services[0].Name {
   625  			if found {
   626  				t.Fatalf("found new service name %q twice", v.Name)
   627  			}
   628  			found = true
   629  			if !reflect.DeepEqual(v.Tags, ctx.Task.Services[0].Tags) {
   630  				t.Errorf("expected Tags=%v != %v", ctx.Task.Services[0].Tags, v.Tags)
   631  			}
   632  		}
   633  	}
   634  	if !found {
   635  		t.Fatalf("did not find new service %q", ctx.Task.Services[0].Name)
   636  	}
   637  
   638  	// Remove the new task
   639  	ctx.ServiceClient.RemoveTask(ctx.Task)
   640  	if err := ctx.syncOnce(); err != nil {
   641  		t.Fatalf("unexpected error syncing task: %v", err)
   642  	}
   643  	if n := len(ctx.FakeConsul.services); n != 1 {
   644  		t.Fatalf("expected 1 service but found %d:\n%#v", n, ctx.FakeConsul.services)
   645  	}
   646  	for _, v := range ctx.FakeConsul.services {
   647  		if v.Name != "taskname-service" {
   648  			t.Errorf("expected original task to survive not %q", v.Name)
   649  		}
   650  	}
   651  
   652  	// Assert check update is pending
   653  	if n := len(ctx.ServiceClient.checkWatcher.checkUpdateCh); n != 1 {
   654  		t.Fatalf("expected 1 check restart update but found %d", n)
   655  	}
   656  
   657  	// Assert the check update's id is correct and that it's a removal
   658  	checkUpd3 := <-ctx.ServiceClient.checkWatcher.checkUpdateCh
   659  	if checkUpd2.checkID != checkUpd3.checkID {
   660  		t.Fatalf("expected checkid %q but found %q", checkUpd2.checkID, checkUpd3.checkID)
   661  	}
   662  	if !checkUpd3.remove {
   663  		t.Fatalf("expected check watch removal update but found: %#v", checkUpd3)
   664  	}
   665  }
   666  
   667  // TestConsul_ShutdownOK tests the ok path for the shutdown logic in
   668  // ServiceClient.
   669  func TestConsul_ShutdownOK(t *testing.T) {
   670  	require := require.New(t)
   671  	ctx := setupFake(t)
   672  
   673  	// Add a script check to make sure its TTL gets updated
   674  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   675  		{
   676  			Name:    "scriptcheck",
   677  			Type:    "script",
   678  			Command: "true",
   679  			// Make check block until shutdown
   680  			Interval:      9000 * time.Hour,
   681  			Timeout:       10 * time.Second,
   682  			InitialStatus: "warning",
   683  		},
   684  	}
   685  
   686  	go ctx.ServiceClient.Run()
   687  
   688  	// Register a task and agent
   689  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
   690  		t.Fatalf("unexpected error registering task: %v", err)
   691  	}
   692  
   693  	agentServices := []*structs.Service{
   694  		{
   695  			Name:      "http",
   696  			Tags:      []string{"nomad"},
   697  			PortLabel: "localhost:2345",
   698  		},
   699  	}
   700  	if err := ctx.ServiceClient.RegisterAgent("client", agentServices); err != nil {
   701  		t.Fatalf("unexpected error registering agent: %v", err)
   702  	}
   703  
   704  	testutil.WaitForResult(func() (bool, error) {
   705  		return ctx.ServiceClient.hasSeen(), fmt.Errorf("error contacting Consul")
   706  	}, func(err error) {
   707  		t.Fatalf("err: %v", err)
   708  	})
   709  
   710  	// Shutdown should block until scripts finish
   711  	if err := ctx.ServiceClient.Shutdown(); err != nil {
   712  		t.Errorf("unexpected error shutting down client: %v", err)
   713  	}
   714  
   715  	// UpdateTTL should have been called once for the script check and once
   716  	// for shutdown
   717  	if n := len(ctx.FakeConsul.checkTTLs); n != 1 {
   718  		t.Fatalf("expected 1 checkTTL entry but found: %d", n)
   719  	}
   720  	for _, v := range ctx.FakeConsul.checkTTLs {
   721  		require.Equalf(2, v, "expected 2 updates but found %d", v)
   722  	}
   723  	for _, v := range ctx.FakeConsul.checks {
   724  		if v.Status != "passing" {
   725  			t.Fatalf("expected check to be passing but found %q", v.Status)
   726  		}
   727  	}
   728  }
   729  
   730  // TestConsul_ShutdownSlow tests the slow but ok path for the shutdown logic in
   731  // ServiceClient.
   732  func TestConsul_ShutdownSlow(t *testing.T) {
   733  	t.Parallel()
   734  	ctx := setupFake(t)
   735  
   736  	// Add a script check to make sure its TTL gets updated
   737  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   738  		{
   739  			Name:    "scriptcheck",
   740  			Type:    "script",
   741  			Command: "true",
   742  			// Make check block until shutdown
   743  			Interval:      9000 * time.Hour,
   744  			Timeout:       5 * time.Second,
   745  			InitialStatus: "warning",
   746  		},
   747  	}
   748  
   749  	// Make Exec slow, but not too slow
   750  	waiter := make(chan struct{})
   751  	ctx.MockExec.ExecFunc = func(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
   752  		select {
   753  		case <-waiter:
   754  		default:
   755  			close(waiter)
   756  		}
   757  		time.Sleep(time.Second)
   758  		return []byte{}, 0, nil
   759  	}
   760  
   761  	// Make shutdown wait time just a bit longer than ctx.Exec takes
   762  	ctx.ServiceClient.shutdownWait = 3 * time.Second
   763  
   764  	go ctx.ServiceClient.Run()
   765  
   766  	// Register a task and agent
   767  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
   768  		t.Fatalf("unexpected error registering task: %v", err)
   769  	}
   770  
   771  	// wait for Exec to get called before shutting down
   772  	<-waiter
   773  
   774  	// Shutdown should block until all enqueued operations finish.
   775  	preShutdown := time.Now()
   776  	if err := ctx.ServiceClient.Shutdown(); err != nil {
   777  		t.Errorf("unexpected error shutting down client: %v", err)
   778  	}
   779  
   780  	// Shutdown time should have taken: ~1s <= shutdown <= 3s
   781  	// actual timing might be less than 1s, to account for shutdown invocation overhead
   782  	shutdownTime := time.Now().Sub(preShutdown)
   783  	if shutdownTime < 900*time.Millisecond || shutdownTime > ctx.ServiceClient.shutdownWait {
   784  		t.Errorf("expected shutdown to take >1s and <%s but took: %s", ctx.ServiceClient.shutdownWait, shutdownTime)
   785  	}
   786  
   787  	// UpdateTTL should have been called once for the script check
   788  	if n := len(ctx.FakeConsul.checkTTLs); n != 1 {
   789  		t.Fatalf("expected 1 checkTTL entry but found: %d", n)
   790  	}
   791  	for _, v := range ctx.FakeConsul.checkTTLs {
   792  		if v != 1 {
   793  			t.Fatalf("expected script check to be updated once but found %d", v)
   794  		}
   795  	}
   796  	for _, v := range ctx.FakeConsul.checks {
   797  		if v.Status != "passing" {
   798  			t.Fatalf("expected check to be passing but found %q", v.Status)
   799  		}
   800  	}
   801  }
   802  
   803  // TestConsul_ShutdownBlocked tests the blocked past deadline path for the
   804  // shutdown logic in ServiceClient.
   805  func TestConsul_ShutdownBlocked(t *testing.T) {
   806  	t.Parallel()
   807  	ctx := setupFake(t)
   808  
   809  	// Add a script check to make sure its TTL gets updated
   810  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   811  		{
   812  			Name:    "scriptcheck",
   813  			Type:    "script",
   814  			Command: "true",
   815  			// Make check block until shutdown
   816  			Interval:      9000 * time.Hour,
   817  			Timeout:       9000 * time.Hour,
   818  			InitialStatus: "warning",
   819  		},
   820  	}
   821  
   822  	block := make(chan struct{})
   823  	defer close(block) // cleanup after test
   824  
   825  	// Make Exec block forever
   826  	waiter := make(chan struct{})
   827  	ctx.MockExec.ExecFunc = func(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
   828  		close(waiter)
   829  		<-block
   830  		return []byte{}, 0, nil
   831  	}
   832  
   833  	// Use a short shutdown deadline since we're intentionally blocking forever
   834  	ctx.ServiceClient.shutdownWait = time.Second
   835  
   836  	go ctx.ServiceClient.Run()
   837  
   838  	// Register a task and agent
   839  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
   840  		t.Fatalf("unexpected error registering task: %v", err)
   841  	}
   842  
   843  	// Wait for exec to be called
   844  	<-waiter
   845  
   846  	// Shutdown should block until all enqueued operations finish.
   847  	preShutdown := time.Now()
   848  	err := ctx.ServiceClient.Shutdown()
   849  	if err == nil {
   850  		t.Errorf("expected a timed out error from shutdown")
   851  	}
   852  
   853  	// Shutdown time should have taken shutdownWait; to avoid timing
   854  	// related errors simply test for wait <= shutdown <= wait+3s
   855  	shutdownTime := time.Now().Sub(preShutdown)
   856  	maxWait := ctx.ServiceClient.shutdownWait + (3 * time.Second)
   857  	if shutdownTime < ctx.ServiceClient.shutdownWait || shutdownTime > maxWait {
   858  		t.Errorf("expected shutdown to take >%s and <%s but took: %s", ctx.ServiceClient.shutdownWait, maxWait, shutdownTime)
   859  	}
   860  
   861  	// UpdateTTL should not have been called for the script check
   862  	if n := len(ctx.FakeConsul.checkTTLs); n != 0 {
   863  		t.Fatalf("expected 0 checkTTL entry but found: %d", n)
   864  	}
   865  	for _, v := range ctx.FakeConsul.checks {
   866  		if expected := "warning"; v.Status != expected {
   867  			t.Fatalf("expected check to be %q but found %q", expected, v.Status)
   868  		}
   869  	}
   870  }
   871  
   872  // TestConsul_RemoveScript assert removing a script check removes all objects
   873  // related to that check.
   874  func TestConsul_CancelScript(t *testing.T) {
   875  	ctx := setupFake(t)
   876  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   877  		{
   878  			Name:     "scriptcheckDel",
   879  			Type:     "script",
   880  			Interval: 9000 * time.Hour,
   881  			Timeout:  9000 * time.Hour,
   882  		},
   883  		{
   884  			Name:     "scriptcheckKeep",
   885  			Type:     "script",
   886  			Interval: 9000 * time.Hour,
   887  			Timeout:  9000 * time.Hour,
   888  		},
   889  	}
   890  
   891  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
   892  		t.Fatalf("unexpected error registering task: %v", err)
   893  	}
   894  
   895  	if err := ctx.syncOnce(); err != nil {
   896  		t.Fatalf("unexpected error syncing task: %v", err)
   897  	}
   898  
   899  	if len(ctx.FakeConsul.checks) != 2 {
   900  		t.Errorf("expected 2 checks but found %d", len(ctx.FakeConsul.checks))
   901  	}
   902  
   903  	if len(ctx.ServiceClient.scripts) != 2 && len(ctx.ServiceClient.runningScripts) != 2 {
   904  		t.Errorf("expected 2 running script but found scripts=%d runningScripts=%d",
   905  			len(ctx.ServiceClient.scripts), len(ctx.ServiceClient.runningScripts))
   906  	}
   907  
   908  	for i := 0; i < 2; i++ {
   909  		select {
   910  		case <-ctx.MockExec.execs:
   911  			// Script ran as expected!
   912  		case <-time.After(3 * time.Second):
   913  			t.Fatalf("timed out waiting for script check to run")
   914  		}
   915  	}
   916  
   917  	// Remove a check and update the task
   918  	origTask := ctx.Task.Copy()
   919  	ctx.Task.Services[0].Checks = []*structs.ServiceCheck{
   920  		{
   921  			Name:     "scriptcheckKeep",
   922  			Type:     "script",
   923  			Interval: 9000 * time.Hour,
   924  			Timeout:  9000 * time.Hour,
   925  		},
   926  	}
   927  
   928  	if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil {
   929  		t.Fatalf("unexpected error registering task: %v", err)
   930  	}
   931  
   932  	if err := ctx.syncOnce(); err != nil {
   933  		t.Fatalf("unexpected error syncing task: %v", err)
   934  	}
   935  
   936  	if len(ctx.FakeConsul.checks) != 1 {
   937  		t.Errorf("expected 1 check but found %d", len(ctx.FakeConsul.checks))
   938  	}
   939  
   940  	if len(ctx.ServiceClient.scripts) != 1 && len(ctx.ServiceClient.runningScripts) != 1 {
   941  		t.Errorf("expected 1 running script but found scripts=%d runningScripts=%d",
   942  			len(ctx.ServiceClient.scripts), len(ctx.ServiceClient.runningScripts))
   943  	}
   944  
   945  	// Make sure exec wasn't called again
   946  	select {
   947  	case <-ctx.MockExec.execs:
   948  		t.Errorf("unexpected execution of script; was goroutine not cancelled?")
   949  	case <-time.After(100 * time.Millisecond):
   950  		// No unexpected script execs
   951  	}
   952  
   953  	// Don't leak goroutines
   954  	for _, scriptHandle := range ctx.ServiceClient.runningScripts {
   955  		scriptHandle.cancel()
   956  	}
   957  }
   958  
   959  // TestConsul_DriverNetwork_AutoUse asserts that if a driver network has
   960  // auto-use set then services should advertise it unless explicitly set to
   961  // host. Checks should always use host.
   962  func TestConsul_DriverNetwork_AutoUse(t *testing.T) {
   963  	t.Parallel()
   964  	ctx := setupFake(t)
   965  
   966  	ctx.Task.Services = []*structs.Service{
   967  		{
   968  			Name:        "auto-advertise-x",
   969  			PortLabel:   "x",
   970  			AddressMode: structs.AddressModeAuto,
   971  			Checks: []*structs.ServiceCheck{
   972  				{
   973  					Name:     "default-check-x",
   974  					Type:     "tcp",
   975  					Interval: time.Second,
   976  					Timeout:  time.Second,
   977  				},
   978  				{
   979  					Name:      "weird-y-check",
   980  					Type:      "http",
   981  					Interval:  time.Second,
   982  					Timeout:   time.Second,
   983  					PortLabel: "y",
   984  				},
   985  			},
   986  		},
   987  		{
   988  			Name:        "driver-advertise-y",
   989  			PortLabel:   "y",
   990  			AddressMode: structs.AddressModeDriver,
   991  			Checks: []*structs.ServiceCheck{
   992  				{
   993  					Name:     "default-check-y",
   994  					Type:     "tcp",
   995  					Interval: time.Second,
   996  					Timeout:  time.Second,
   997  				},
   998  			},
   999  		},
  1000  		{
  1001  			Name:        "host-advertise-y",
  1002  			PortLabel:   "y",
  1003  			AddressMode: structs.AddressModeHost,
  1004  		},
  1005  	}
  1006  
  1007  	ctx.Task.DriverNetwork = &drivers.DriverNetwork{
  1008  		PortMap: map[string]int{
  1009  			"x": 8888,
  1010  			"y": 9999,
  1011  		},
  1012  		IP:            "172.18.0.2",
  1013  		AutoAdvertise: true,
  1014  	}
  1015  
  1016  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
  1017  		t.Fatalf("unexpected error registering task: %v", err)
  1018  	}
  1019  
  1020  	if err := ctx.syncOnce(); err != nil {
  1021  		t.Fatalf("unexpected error syncing task: %v", err)
  1022  	}
  1023  
  1024  	if n := len(ctx.FakeConsul.services); n != 3 {
  1025  		t.Fatalf("expected 2 services but found: %d", n)
  1026  	}
  1027  
  1028  	for _, v := range ctx.FakeConsul.services {
  1029  		switch v.Name {
  1030  		case ctx.Task.Services[0].Name: // x
  1031  			// Since DriverNetwork.AutoAdvertise=true, driver ports should be used
  1032  			if v.Port != ctx.Task.DriverNetwork.PortMap["x"] {
  1033  				t.Errorf("expected service %s's port to be %d but found %d",
  1034  					v.Name, ctx.Task.DriverNetwork.PortMap["x"], v.Port)
  1035  			}
  1036  			// The order of checks in Consul is not guaranteed to
  1037  			// be the same as their order in the Task definition,
  1038  			// so check in a loop
  1039  			if expected := 2; len(v.Checks) != expected {
  1040  				t.Errorf("expected %d checks but found %d", expected, len(v.Checks))
  1041  			}
  1042  			for _, c := range v.Checks {
  1043  				// No name on AgentServiceChecks, use type
  1044  				switch {
  1045  				case c.TCP != "":
  1046  					// Checks should always use host port though
  1047  					if c.TCP != ":1234" { // xPort
  1048  						t.Errorf("expected service %s check 1's port to be %d but found %q",
  1049  							v.Name, xPort, c.TCP)
  1050  					}
  1051  				case c.HTTP != "":
  1052  					if c.HTTP != "http://:1235" { // yPort
  1053  						t.Errorf("expected service %s check 2's port to be %d but found %q",
  1054  							v.Name, yPort, c.HTTP)
  1055  					}
  1056  				default:
  1057  					t.Errorf("unexpected check %#v on service %q", c, v.Name)
  1058  				}
  1059  			}
  1060  		case ctx.Task.Services[1].Name: // y
  1061  			// Service should be container ip:port
  1062  			if v.Address != ctx.Task.DriverNetwork.IP {
  1063  				t.Errorf("expected service %s's address to be %s but found %s",
  1064  					v.Name, ctx.Task.DriverNetwork.IP, v.Address)
  1065  			}
  1066  			if v.Port != ctx.Task.DriverNetwork.PortMap["y"] {
  1067  				t.Errorf("expected service %s's port to be %d but found %d",
  1068  					v.Name, ctx.Task.DriverNetwork.PortMap["x"], v.Port)
  1069  			}
  1070  			// Check should be host ip:port
  1071  			if v.Checks[0].TCP != ":1235" { // yPort
  1072  				t.Errorf("expected service %s check's port to be %d but found %s",
  1073  					v.Name, yPort, v.Checks[0].TCP)
  1074  			}
  1075  		case ctx.Task.Services[2].Name: // y + host mode
  1076  			if v.Port != yPort {
  1077  				t.Errorf("expected service %s's port to be %d but found %d",
  1078  					v.Name, yPort, v.Port)
  1079  			}
  1080  		default:
  1081  			t.Errorf("unexpected service name: %q", v.Name)
  1082  		}
  1083  	}
  1084  }
  1085  
  1086  // TestConsul_DriverNetwork_NoAutoUse asserts that if a driver network doesn't
  1087  // set auto-use only services which request the driver's network should
  1088  // advertise it.
  1089  func TestConsul_DriverNetwork_NoAutoUse(t *testing.T) {
  1090  	t.Parallel()
  1091  	ctx := setupFake(t)
  1092  
  1093  	ctx.Task.Services = []*structs.Service{
  1094  		{
  1095  			Name:        "auto-advertise-x",
  1096  			PortLabel:   "x",
  1097  			AddressMode: structs.AddressModeAuto,
  1098  		},
  1099  		{
  1100  			Name:        "driver-advertise-y",
  1101  			PortLabel:   "y",
  1102  			AddressMode: structs.AddressModeDriver,
  1103  		},
  1104  		{
  1105  			Name:        "host-advertise-y",
  1106  			PortLabel:   "y",
  1107  			AddressMode: structs.AddressModeHost,
  1108  		},
  1109  	}
  1110  
  1111  	ctx.Task.DriverNetwork = &drivers.DriverNetwork{
  1112  		PortMap: map[string]int{
  1113  			"x": 8888,
  1114  			"y": 9999,
  1115  		},
  1116  		IP:            "172.18.0.2",
  1117  		AutoAdvertise: false,
  1118  	}
  1119  
  1120  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
  1121  		t.Fatalf("unexpected error registering task: %v", err)
  1122  	}
  1123  
  1124  	if err := ctx.syncOnce(); err != nil {
  1125  		t.Fatalf("unexpected error syncing task: %v", err)
  1126  	}
  1127  
  1128  	if n := len(ctx.FakeConsul.services); n != 3 {
  1129  		t.Fatalf("expected 3 services but found: %d", n)
  1130  	}
  1131  
  1132  	for _, v := range ctx.FakeConsul.services {
  1133  		switch v.Name {
  1134  		case ctx.Task.Services[0].Name: // x + auto
  1135  			// Since DriverNetwork.AutoAdvertise=false, host ports should be used
  1136  			if v.Port != xPort {
  1137  				t.Errorf("expected service %s's port to be %d but found %d",
  1138  					v.Name, xPort, v.Port)
  1139  			}
  1140  		case ctx.Task.Services[1].Name: // y + driver mode
  1141  			// Service should be container ip:port
  1142  			if v.Address != ctx.Task.DriverNetwork.IP {
  1143  				t.Errorf("expected service %s's address to be %s but found %s",
  1144  					v.Name, ctx.Task.DriverNetwork.IP, v.Address)
  1145  			}
  1146  			if v.Port != ctx.Task.DriverNetwork.PortMap["y"] {
  1147  				t.Errorf("expected service %s's port to be %d but found %d",
  1148  					v.Name, ctx.Task.DriverNetwork.PortMap["x"], v.Port)
  1149  			}
  1150  		case ctx.Task.Services[2].Name: // y + host mode
  1151  			if v.Port != yPort {
  1152  				t.Errorf("expected service %s's port to be %d but found %d",
  1153  					v.Name, yPort, v.Port)
  1154  			}
  1155  		default:
  1156  			t.Errorf("unexpected service name: %q", v.Name)
  1157  		}
  1158  	}
  1159  }
  1160  
  1161  // TestConsul_DriverNetwork_Change asserts that if a driver network is
  1162  // specified and a service updates its use its properly updated in Consul.
  1163  func TestConsul_DriverNetwork_Change(t *testing.T) {
  1164  	t.Parallel()
  1165  	ctx := setupFake(t)
  1166  
  1167  	ctx.Task.Services = []*structs.Service{
  1168  		{
  1169  			Name:        "service-foo",
  1170  			PortLabel:   "x",
  1171  			AddressMode: structs.AddressModeAuto,
  1172  		},
  1173  	}
  1174  
  1175  	ctx.Task.DriverNetwork = &drivers.DriverNetwork{
  1176  		PortMap: map[string]int{
  1177  			"x": 8888,
  1178  			"y": 9999,
  1179  		},
  1180  		IP:            "172.18.0.2",
  1181  		AutoAdvertise: false,
  1182  	}
  1183  
  1184  	syncAndAssertPort := func(port int) {
  1185  		if err := ctx.syncOnce(); err != nil {
  1186  			t.Fatalf("unexpected error syncing task: %v", err)
  1187  		}
  1188  
  1189  		if n := len(ctx.FakeConsul.services); n != 1 {
  1190  			t.Fatalf("expected 1 service but found: %d", n)
  1191  		}
  1192  
  1193  		for _, v := range ctx.FakeConsul.services {
  1194  			switch v.Name {
  1195  			case ctx.Task.Services[0].Name:
  1196  				if v.Port != port {
  1197  					t.Errorf("expected service %s's port to be %d but found %d",
  1198  						v.Name, port, v.Port)
  1199  				}
  1200  			default:
  1201  				t.Errorf("unexpected service name: %q", v.Name)
  1202  			}
  1203  		}
  1204  	}
  1205  
  1206  	// Initial service should advertise host port x
  1207  	if err := ctx.ServiceClient.RegisterTask(ctx.Task); err != nil {
  1208  		t.Fatalf("unexpected error registering task: %v", err)
  1209  	}
  1210  
  1211  	syncAndAssertPort(xPort)
  1212  
  1213  	// UpdateTask to use Host (shouldn't change anything)
  1214  	origTask := ctx.Task.Copy()
  1215  	ctx.Task.Services[0].AddressMode = structs.AddressModeHost
  1216  
  1217  	if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil {
  1218  		t.Fatalf("unexpected error updating task: %v", err)
  1219  	}
  1220  
  1221  	syncAndAssertPort(xPort)
  1222  
  1223  	// UpdateTask to use Driver (*should* change IP and port)
  1224  	origTask = ctx.Task.Copy()
  1225  	ctx.Task.Services[0].AddressMode = structs.AddressModeDriver
  1226  
  1227  	if err := ctx.ServiceClient.UpdateTask(origTask, ctx.Task); err != nil {
  1228  		t.Fatalf("unexpected error updating task: %v", err)
  1229  	}
  1230  
  1231  	syncAndAssertPort(ctx.Task.DriverNetwork.PortMap["x"])
  1232  }
  1233  
  1234  // TestConsul_CanaryTags asserts CanaryTags are used when Canary=true
  1235  func TestConsul_CanaryTags(t *testing.T) {
  1236  	t.Parallel()
  1237  	require := require.New(t)
  1238  	ctx := setupFake(t)
  1239  
  1240  	canaryTags := []string{"tag1", "canary"}
  1241  	ctx.Task.Canary = true
  1242  	ctx.Task.Services[0].CanaryTags = canaryTags
  1243  
  1244  	require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task))
  1245  	require.NoError(ctx.syncOnce())
  1246  	require.Len(ctx.FakeConsul.services, 1)
  1247  	for _, service := range ctx.FakeConsul.services {
  1248  		require.Equal(canaryTags, service.Tags)
  1249  	}
  1250  
  1251  	// Disable canary and assert tags are not the canary tags
  1252  	origTask := ctx.Task.Copy()
  1253  	ctx.Task.Canary = false
  1254  	require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task))
  1255  	require.NoError(ctx.syncOnce())
  1256  	require.Len(ctx.FakeConsul.services, 1)
  1257  	for _, service := range ctx.FakeConsul.services {
  1258  		require.NotEqual(canaryTags, service.Tags)
  1259  	}
  1260  
  1261  	ctx.ServiceClient.RemoveTask(ctx.Task)
  1262  	require.NoError(ctx.syncOnce())
  1263  	require.Len(ctx.FakeConsul.services, 0)
  1264  }
  1265  
  1266  // TestConsul_CanaryTags_NoTags asserts Tags are used when Canary=true and there
  1267  // are no specified canary tags
  1268  func TestConsul_CanaryTags_NoTags(t *testing.T) {
  1269  	t.Parallel()
  1270  	require := require.New(t)
  1271  	ctx := setupFake(t)
  1272  
  1273  	tags := []string{"tag1", "foo"}
  1274  	ctx.Task.Canary = true
  1275  	ctx.Task.Services[0].Tags = tags
  1276  
  1277  	require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task))
  1278  	require.NoError(ctx.syncOnce())
  1279  	require.Len(ctx.FakeConsul.services, 1)
  1280  	for _, service := range ctx.FakeConsul.services {
  1281  		require.Equal(tags, service.Tags)
  1282  	}
  1283  
  1284  	// Disable canary and assert tags dont change
  1285  	origTask := ctx.Task.Copy()
  1286  	ctx.Task.Canary = false
  1287  	require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task))
  1288  	require.NoError(ctx.syncOnce())
  1289  	require.Len(ctx.FakeConsul.services, 1)
  1290  	for _, service := range ctx.FakeConsul.services {
  1291  		require.Equal(tags, service.Tags)
  1292  	}
  1293  
  1294  	ctx.ServiceClient.RemoveTask(ctx.Task)
  1295  	require.NoError(ctx.syncOnce())
  1296  	require.Len(ctx.FakeConsul.services, 0)
  1297  }
  1298  
  1299  // TestConsul_PeriodicSync asserts that Nomad periodically reconciles with
  1300  // Consul.
  1301  func TestConsul_PeriodicSync(t *testing.T) {
  1302  	t.Parallel()
  1303  
  1304  	ctx := setupFake(t)
  1305  	defer ctx.ServiceClient.Shutdown()
  1306  
  1307  	// Lower periodic sync interval to speed up test
  1308  	ctx.ServiceClient.periodicInterval = 2 * time.Millisecond
  1309  
  1310  	// Run for 10ms and assert hits >= 5 because each sync() calls multiple
  1311  	// Consul APIs
  1312  	go ctx.ServiceClient.Run()
  1313  
  1314  	select {
  1315  	case <-ctx.ServiceClient.exitCh:
  1316  		t.Fatalf("exited unexpectedly")
  1317  	case <-time.After(10 * time.Millisecond):
  1318  	}
  1319  
  1320  	minHits := 5
  1321  	if hits := ctx.FakeConsul.getHits(); hits < minHits {
  1322  		t.Fatalf("expected at least %d hits but found %d", minHits, hits)
  1323  	}
  1324  }
  1325  
  1326  // TestIsNomadService asserts the isNomadService helper returns true for Nomad
  1327  // task IDs and false for unknown IDs and Nomad agent IDs (see #2827).
  1328  func TestIsNomadService(t *testing.T) {
  1329  	t.Parallel()
  1330  
  1331  	tests := []struct {
  1332  		id     string
  1333  		result bool
  1334  	}{
  1335  		{"_nomad-client-nomad-client-http", false},
  1336  		{"_nomad-server-nomad-serf", false},
  1337  
  1338  		// Pre-0.7.1 style IDs still match
  1339  		{"_nomad-executor-abc", true},
  1340  		{"_nomad-executor", true},
  1341  
  1342  		// Post-0.7.1 style IDs match
  1343  		{"_nomad-task-FBBK265QN4TMT25ND4EP42TJVMYJ3HR4", true},
  1344  
  1345  		{"not-nomad", false},
  1346  		{"_nomad", false},
  1347  	}
  1348  
  1349  	for _, test := range tests {
  1350  		t.Run(test.id, func(t *testing.T) {
  1351  			actual := isNomadService(test.id)
  1352  			if actual != test.result {
  1353  				t.Errorf("%q should be %t but found %t", test.id, test.result, actual)
  1354  			}
  1355  		})
  1356  	}
  1357  }
  1358  
  1359  // TestCreateCheckReg_HTTP asserts Nomad ServiceCheck structs are properly
  1360  // converted to Consul API AgentCheckRegistrations for HTTP checks.
  1361  func TestCreateCheckReg_HTTP(t *testing.T) {
  1362  	t.Parallel()
  1363  	check := &structs.ServiceCheck{
  1364  		Name:      "name",
  1365  		Type:      "http",
  1366  		Path:      "/path",
  1367  		PortLabel: "label",
  1368  		Method:    "POST",
  1369  		Header: map[string][]string{
  1370  			"Foo": {"bar"},
  1371  		},
  1372  	}
  1373  
  1374  	serviceID := "testService"
  1375  	checkID := check.Hash(serviceID)
  1376  	host := "localhost"
  1377  	port := 41111
  1378  
  1379  	expected := &api.AgentCheckRegistration{
  1380  		ID:        checkID,
  1381  		Name:      "name",
  1382  		ServiceID: serviceID,
  1383  		AgentServiceCheck: api.AgentServiceCheck{
  1384  			Timeout:  "0s",
  1385  			Interval: "0s",
  1386  			HTTP:     fmt.Sprintf("http://%s:%d/path", host, port),
  1387  			Method:   "POST",
  1388  			Header: map[string][]string{
  1389  				"Foo": {"bar"},
  1390  			},
  1391  		},
  1392  	}
  1393  
  1394  	actual, err := createCheckReg(serviceID, checkID, check, host, port)
  1395  	if err != nil {
  1396  		t.Fatalf("err: %v", err)
  1397  	}
  1398  
  1399  	if diff := pretty.Diff(actual, expected); len(diff) > 0 {
  1400  		t.Fatalf("diff:\n%s\n", strings.Join(diff, "\n"))
  1401  	}
  1402  }
  1403  
  1404  // TestCreateCheckReg_GRPC asserts Nomad ServiceCheck structs are properly
  1405  // converted to Consul API AgentCheckRegistrations for GRPC checks.
  1406  func TestCreateCheckReg_GRPC(t *testing.T) {
  1407  	t.Parallel()
  1408  	check := &structs.ServiceCheck{
  1409  		Name:          "name",
  1410  		Type:          "grpc",
  1411  		PortLabel:     "label",
  1412  		GRPCService:   "foo.Bar",
  1413  		GRPCUseTLS:    true,
  1414  		TLSSkipVerify: true,
  1415  		Timeout:       time.Second,
  1416  		Interval:      time.Minute,
  1417  	}
  1418  
  1419  	serviceID := "testService"
  1420  	checkID := check.Hash(serviceID)
  1421  
  1422  	expected := &api.AgentCheckRegistration{
  1423  		ID:        checkID,
  1424  		Name:      "name",
  1425  		ServiceID: serviceID,
  1426  		AgentServiceCheck: api.AgentServiceCheck{
  1427  			Timeout:       "1s",
  1428  			Interval:      "1m0s",
  1429  			GRPC:          "localhost:8080/foo.Bar",
  1430  			GRPCUseTLS:    true,
  1431  			TLSSkipVerify: true,
  1432  		},
  1433  	}
  1434  
  1435  	actual, err := createCheckReg(serviceID, checkID, check, "localhost", 8080)
  1436  	require.NoError(t, err)
  1437  	require.Equal(t, expected, actual)
  1438  }
  1439  
  1440  // TestGetAddress asserts Nomad uses the correct ip and port for services and
  1441  // checks depending on port labels, driver networks, and address mode.
  1442  func TestGetAddress(t *testing.T) {
  1443  	const HostIP = "127.0.0.1"
  1444  
  1445  	cases := []struct {
  1446  		Name string
  1447  
  1448  		// Parameters
  1449  		Mode      string
  1450  		PortLabel string
  1451  		Host      map[string]int // will be converted to structs.Networks
  1452  		Driver    *drivers.DriverNetwork
  1453  
  1454  		// Results
  1455  		ExpectedIP   string
  1456  		ExpectedPort int
  1457  		ExpectedErr  string
  1458  	}{
  1459  		// Valid Configurations
  1460  		{
  1461  			Name:      "ExampleService",
  1462  			Mode:      structs.AddressModeAuto,
  1463  			PortLabel: "db",
  1464  			Host:      map[string]int{"db": 12435},
  1465  			Driver: &drivers.DriverNetwork{
  1466  				PortMap: map[string]int{"db": 6379},
  1467  				IP:      "10.1.2.3",
  1468  			},
  1469  			ExpectedIP:   HostIP,
  1470  			ExpectedPort: 12435,
  1471  		},
  1472  		{
  1473  			Name:      "Host",
  1474  			Mode:      structs.AddressModeHost,
  1475  			PortLabel: "db",
  1476  			Host:      map[string]int{"db": 12345},
  1477  			Driver: &drivers.DriverNetwork{
  1478  				PortMap: map[string]int{"db": 6379},
  1479  				IP:      "10.1.2.3",
  1480  			},
  1481  			ExpectedIP:   HostIP,
  1482  			ExpectedPort: 12345,
  1483  		},
  1484  		{
  1485  			Name:      "Driver",
  1486  			Mode:      structs.AddressModeDriver,
  1487  			PortLabel: "db",
  1488  			Host:      map[string]int{"db": 12345},
  1489  			Driver: &drivers.DriverNetwork{
  1490  				PortMap: map[string]int{"db": 6379},
  1491  				IP:      "10.1.2.3",
  1492  			},
  1493  			ExpectedIP:   "10.1.2.3",
  1494  			ExpectedPort: 6379,
  1495  		},
  1496  		{
  1497  			Name:      "AutoDriver",
  1498  			Mode:      structs.AddressModeAuto,
  1499  			PortLabel: "db",
  1500  			Host:      map[string]int{"db": 12345},
  1501  			Driver: &drivers.DriverNetwork{
  1502  				PortMap:       map[string]int{"db": 6379},
  1503  				IP:            "10.1.2.3",
  1504  				AutoAdvertise: true,
  1505  			},
  1506  			ExpectedIP:   "10.1.2.3",
  1507  			ExpectedPort: 6379,
  1508  		},
  1509  		{
  1510  			Name:      "DriverCustomPort",
  1511  			Mode:      structs.AddressModeDriver,
  1512  			PortLabel: "7890",
  1513  			Host:      map[string]int{"db": 12345},
  1514  			Driver: &drivers.DriverNetwork{
  1515  				PortMap: map[string]int{"db": 6379},
  1516  				IP:      "10.1.2.3",
  1517  			},
  1518  			ExpectedIP:   "10.1.2.3",
  1519  			ExpectedPort: 7890,
  1520  		},
  1521  
  1522  		// Invalid Configurations
  1523  		{
  1524  			Name:        "DriverWithoutNetwork",
  1525  			Mode:        structs.AddressModeDriver,
  1526  			PortLabel:   "db",
  1527  			Host:        map[string]int{"db": 12345},
  1528  			Driver:      nil,
  1529  			ExpectedErr: "no driver network exists",
  1530  		},
  1531  		{
  1532  			Name:      "DriverBadPort",
  1533  			Mode:      structs.AddressModeDriver,
  1534  			PortLabel: "bad-port-label",
  1535  			Host:      map[string]int{"db": 12345},
  1536  			Driver: &drivers.DriverNetwork{
  1537  				PortMap: map[string]int{"db": 6379},
  1538  				IP:      "10.1.2.3",
  1539  			},
  1540  			ExpectedErr: "invalid port",
  1541  		},
  1542  		{
  1543  			Name:      "DriverZeroPort",
  1544  			Mode:      structs.AddressModeDriver,
  1545  			PortLabel: "0",
  1546  			Driver: &drivers.DriverNetwork{
  1547  				IP: "10.1.2.3",
  1548  			},
  1549  			ExpectedErr: "invalid port",
  1550  		},
  1551  		{
  1552  			Name:        "HostBadPort",
  1553  			Mode:        structs.AddressModeHost,
  1554  			PortLabel:   "bad-port-label",
  1555  			ExpectedErr: "invalid port",
  1556  		},
  1557  		{
  1558  			Name:        "InvalidMode",
  1559  			Mode:        "invalid-mode",
  1560  			PortLabel:   "80",
  1561  			ExpectedErr: "invalid address mode",
  1562  		},
  1563  		{
  1564  			Name:       "NoPort_AutoMode",
  1565  			Mode:       structs.AddressModeAuto,
  1566  			ExpectedIP: HostIP,
  1567  		},
  1568  		{
  1569  			Name:       "NoPort_HostMode",
  1570  			Mode:       structs.AddressModeHost,
  1571  			ExpectedIP: HostIP,
  1572  		},
  1573  		{
  1574  			Name: "NoPort_DriverMode",
  1575  			Mode: structs.AddressModeDriver,
  1576  			Driver: &drivers.DriverNetwork{
  1577  				IP: "10.1.2.3",
  1578  			},
  1579  			ExpectedIP: "10.1.2.3",
  1580  		},
  1581  	}
  1582  
  1583  	for _, tc := range cases {
  1584  		t.Run(tc.Name, func(t *testing.T) {
  1585  			// convert host port map into a structs.Networks
  1586  			networks := []*structs.NetworkResource{
  1587  				{
  1588  					IP:            HostIP,
  1589  					ReservedPorts: make([]structs.Port, len(tc.Host)),
  1590  				},
  1591  			}
  1592  
  1593  			i := 0
  1594  			for label, port := range tc.Host {
  1595  				networks[0].ReservedPorts[i].Label = label
  1596  				networks[0].ReservedPorts[i].Value = port
  1597  				i++
  1598  			}
  1599  
  1600  			// Run getAddress
  1601  			ip, port, err := getAddress(tc.Mode, tc.PortLabel, networks, tc.Driver)
  1602  
  1603  			// Assert the results
  1604  			assert.Equal(t, tc.ExpectedIP, ip, "IP mismatch")
  1605  			assert.Equal(t, tc.ExpectedPort, port, "Port mismatch")
  1606  			if tc.ExpectedErr == "" {
  1607  				assert.Nil(t, err)
  1608  			} else {
  1609  				if err == nil {
  1610  					t.Fatalf("expected error containing %q but err=nil", tc.ExpectedErr)
  1611  				} else {
  1612  					assert.Contains(t, err.Error(), tc.ExpectedErr)
  1613  				}
  1614  			}
  1615  		})
  1616  	}
  1617  }