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