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