github.com/ilhicas/nomad@v0.11.8/command/agent/consul/unit_test.go (about)

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