github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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  		Ports     structs.AllocatedPorts
  1363  		Status    *structs.AllocNetworkStatus
  1364  
  1365  		// Results
  1366  		ExpectedIP   string
  1367  		ExpectedPort int
  1368  		ExpectedErr  string
  1369  	}{
  1370  		// Valid Configurations
  1371  		{
  1372  			Name:      "ExampleService",
  1373  			Mode:      structs.AddressModeAuto,
  1374  			PortLabel: "db",
  1375  			Host:      map[string]int{"db": 12435},
  1376  			Driver: &drivers.DriverNetwork{
  1377  				PortMap: map[string]int{"db": 6379},
  1378  				IP:      "10.1.2.3",
  1379  			},
  1380  			ExpectedIP:   HostIP,
  1381  			ExpectedPort: 12435,
  1382  		},
  1383  		{
  1384  			Name:      "Host",
  1385  			Mode:      structs.AddressModeHost,
  1386  			PortLabel: "db",
  1387  			Host:      map[string]int{"db": 12345},
  1388  			Driver: &drivers.DriverNetwork{
  1389  				PortMap: map[string]int{"db": 6379},
  1390  				IP:      "10.1.2.3",
  1391  			},
  1392  			ExpectedIP:   HostIP,
  1393  			ExpectedPort: 12345,
  1394  		},
  1395  		{
  1396  			Name:      "Driver",
  1397  			Mode:      structs.AddressModeDriver,
  1398  			PortLabel: "db",
  1399  			Host:      map[string]int{"db": 12345},
  1400  			Driver: &drivers.DriverNetwork{
  1401  				PortMap: map[string]int{"db": 6379},
  1402  				IP:      "10.1.2.3",
  1403  			},
  1404  			ExpectedIP:   "10.1.2.3",
  1405  			ExpectedPort: 6379,
  1406  		},
  1407  		{
  1408  			Name:      "AutoDriver",
  1409  			Mode:      structs.AddressModeAuto,
  1410  			PortLabel: "db",
  1411  			Host:      map[string]int{"db": 12345},
  1412  			Driver: &drivers.DriverNetwork{
  1413  				PortMap:       map[string]int{"db": 6379},
  1414  				IP:            "10.1.2.3",
  1415  				AutoAdvertise: true,
  1416  			},
  1417  			ExpectedIP:   "10.1.2.3",
  1418  			ExpectedPort: 6379,
  1419  		},
  1420  		{
  1421  			Name:      "DriverCustomPort",
  1422  			Mode:      structs.AddressModeDriver,
  1423  			PortLabel: "7890",
  1424  			Host:      map[string]int{"db": 12345},
  1425  			Driver: &drivers.DriverNetwork{
  1426  				PortMap: map[string]int{"db": 6379},
  1427  				IP:      "10.1.2.3",
  1428  			},
  1429  			ExpectedIP:   "10.1.2.3",
  1430  			ExpectedPort: 7890,
  1431  		},
  1432  
  1433  		// Invalid Configurations
  1434  		{
  1435  			Name:        "DriverWithoutNetwork",
  1436  			Mode:        structs.AddressModeDriver,
  1437  			PortLabel:   "db",
  1438  			Host:        map[string]int{"db": 12345},
  1439  			Driver:      nil,
  1440  			ExpectedErr: "no driver network exists",
  1441  		},
  1442  		{
  1443  			Name:      "DriverBadPort",
  1444  			Mode:      structs.AddressModeDriver,
  1445  			PortLabel: "bad-port-label",
  1446  			Host:      map[string]int{"db": 12345},
  1447  			Driver: &drivers.DriverNetwork{
  1448  				PortMap: map[string]int{"db": 6379},
  1449  				IP:      "10.1.2.3",
  1450  			},
  1451  			ExpectedErr: "invalid port",
  1452  		},
  1453  		{
  1454  			Name:      "DriverZeroPort",
  1455  			Mode:      structs.AddressModeDriver,
  1456  			PortLabel: "0",
  1457  			Driver: &drivers.DriverNetwork{
  1458  				IP: "10.1.2.3",
  1459  			},
  1460  			ExpectedErr: "invalid port",
  1461  		},
  1462  		{
  1463  			Name:        "HostBadPort",
  1464  			Mode:        structs.AddressModeHost,
  1465  			PortLabel:   "bad-port-label",
  1466  			ExpectedErr: "invalid port",
  1467  		},
  1468  		{
  1469  			Name:        "InvalidMode",
  1470  			Mode:        "invalid-mode",
  1471  			PortLabel:   "80",
  1472  			ExpectedErr: "invalid address mode",
  1473  		},
  1474  		{
  1475  			Name:       "NoPort_AutoMode",
  1476  			Mode:       structs.AddressModeAuto,
  1477  			ExpectedIP: HostIP,
  1478  		},
  1479  		{
  1480  			Name:       "NoPort_HostMode",
  1481  			Mode:       structs.AddressModeHost,
  1482  			ExpectedIP: HostIP,
  1483  		},
  1484  		{
  1485  			Name: "NoPort_DriverMode",
  1486  			Mode: structs.AddressModeDriver,
  1487  			Driver: &drivers.DriverNetwork{
  1488  				IP: "10.1.2.3",
  1489  			},
  1490  			ExpectedIP: "10.1.2.3",
  1491  		},
  1492  
  1493  		// Scenarios using port 0.12 networking fields (NetworkStatus, AllocatedPortMapping)
  1494  		{
  1495  			Name:      "ExampleServer_withAllocatedPorts",
  1496  			Mode:      structs.AddressModeAuto,
  1497  			PortLabel: "db",
  1498  			Ports: []structs.AllocatedPortMapping{
  1499  				{
  1500  					Label:  "db",
  1501  					Value:  12435,
  1502  					To:     6379,
  1503  					HostIP: HostIP,
  1504  				},
  1505  			},
  1506  			Status: &structs.AllocNetworkStatus{
  1507  				InterfaceName: "eth0",
  1508  				Address:       "172.26.0.1",
  1509  			},
  1510  			ExpectedIP:   HostIP,
  1511  			ExpectedPort: 12435,
  1512  		},
  1513  		{
  1514  			Name:      "Host_withAllocatedPorts",
  1515  			Mode:      structs.AddressModeHost,
  1516  			PortLabel: "db",
  1517  			Ports: []structs.AllocatedPortMapping{
  1518  				{
  1519  					Label:  "db",
  1520  					Value:  12345,
  1521  					To:     6379,
  1522  					HostIP: HostIP,
  1523  				},
  1524  			},
  1525  			Status: &structs.AllocNetworkStatus{
  1526  				InterfaceName: "eth0",
  1527  				Address:       "172.26.0.1",
  1528  			},
  1529  			ExpectedIP:   HostIP,
  1530  			ExpectedPort: 12345,
  1531  		},
  1532  		{
  1533  			Name:      "Driver_withAllocatedPorts",
  1534  			Mode:      structs.AddressModeDriver,
  1535  			PortLabel: "db",
  1536  			Ports: []structs.AllocatedPortMapping{
  1537  				{
  1538  					Label:  "db",
  1539  					Value:  12345,
  1540  					To:     6379,
  1541  					HostIP: HostIP,
  1542  				},
  1543  			},
  1544  			Driver: &drivers.DriverNetwork{
  1545  				IP: "10.1.2.3",
  1546  			},
  1547  			Status: &structs.AllocNetworkStatus{
  1548  				InterfaceName: "eth0",
  1549  				Address:       "172.26.0.1",
  1550  			},
  1551  			ExpectedIP:   "10.1.2.3",
  1552  			ExpectedPort: 6379,
  1553  		},
  1554  		{
  1555  			Name:      "AutoDriver_withAllocatedPorts",
  1556  			Mode:      structs.AddressModeAuto,
  1557  			PortLabel: "db",
  1558  			Ports: []structs.AllocatedPortMapping{
  1559  				{
  1560  					Label:  "db",
  1561  					Value:  12345,
  1562  					To:     6379,
  1563  					HostIP: HostIP,
  1564  				},
  1565  			},
  1566  			Driver: &drivers.DriverNetwork{
  1567  				IP:            "10.1.2.3",
  1568  				AutoAdvertise: true,
  1569  			},
  1570  			Status: &structs.AllocNetworkStatus{
  1571  				InterfaceName: "eth0",
  1572  				Address:       "172.26.0.1",
  1573  			},
  1574  			ExpectedIP:   "10.1.2.3",
  1575  			ExpectedPort: 6379,
  1576  		},
  1577  		{
  1578  			Name:      "DriverCustomPort_withAllocatedPorts",
  1579  			Mode:      structs.AddressModeDriver,
  1580  			PortLabel: "7890",
  1581  			Ports: []structs.AllocatedPortMapping{
  1582  				{
  1583  					Label:  "db",
  1584  					Value:  12345,
  1585  					To:     6379,
  1586  					HostIP: HostIP,
  1587  				},
  1588  			},
  1589  			Driver: &drivers.DriverNetwork{
  1590  				IP: "10.1.2.3",
  1591  			},
  1592  			Status: &structs.AllocNetworkStatus{
  1593  				InterfaceName: "eth0",
  1594  				Address:       "172.26.0.1",
  1595  			},
  1596  			ExpectedIP:   "10.1.2.3",
  1597  			ExpectedPort: 7890,
  1598  		},
  1599  		{
  1600  			Name:      "Host_MultiHostInterface",
  1601  			Mode:      structs.AddressModeAuto,
  1602  			PortLabel: "db",
  1603  			Ports: []structs.AllocatedPortMapping{
  1604  				{
  1605  					Label:  "db",
  1606  					Value:  12345,
  1607  					To:     6379,
  1608  					HostIP: "127.0.0.100",
  1609  				},
  1610  			},
  1611  			Status: &structs.AllocNetworkStatus{
  1612  				InterfaceName: "eth0",
  1613  				Address:       "172.26.0.1",
  1614  			},
  1615  			ExpectedIP:   "127.0.0.100",
  1616  			ExpectedPort: 12345,
  1617  		},
  1618  		{
  1619  			Name:      "Alloc",
  1620  			Mode:      structs.AddressModeAlloc,
  1621  			PortLabel: "db",
  1622  			Ports: []structs.AllocatedPortMapping{
  1623  				{
  1624  					Label:  "db",
  1625  					Value:  12345,
  1626  					To:     6379,
  1627  					HostIP: HostIP,
  1628  				},
  1629  			},
  1630  			Status: &structs.AllocNetworkStatus{
  1631  				InterfaceName: "eth0",
  1632  				Address:       "172.26.0.1",
  1633  			},
  1634  			ExpectedIP:   "172.26.0.1",
  1635  			ExpectedPort: 6379,
  1636  		},
  1637  		{
  1638  			Name:      "Alloc no to value",
  1639  			Mode:      structs.AddressModeAlloc,
  1640  			PortLabel: "db",
  1641  			Ports: []structs.AllocatedPortMapping{
  1642  				{
  1643  					Label:  "db",
  1644  					Value:  12345,
  1645  					HostIP: HostIP,
  1646  				},
  1647  			},
  1648  			Status: &structs.AllocNetworkStatus{
  1649  				InterfaceName: "eth0",
  1650  				Address:       "172.26.0.1",
  1651  			},
  1652  			ExpectedIP:   "172.26.0.1",
  1653  			ExpectedPort: 12345,
  1654  		},
  1655  		{
  1656  			Name:      "AllocCustomPort",
  1657  			Mode:      structs.AddressModeAlloc,
  1658  			PortLabel: "6379",
  1659  			Status: &structs.AllocNetworkStatus{
  1660  				InterfaceName: "eth0",
  1661  				Address:       "172.26.0.1",
  1662  			},
  1663  			ExpectedIP:   "172.26.0.1",
  1664  			ExpectedPort: 6379,
  1665  		},
  1666  	}
  1667  
  1668  	for _, tc := range cases {
  1669  		t.Run(tc.Name, func(t *testing.T) {
  1670  			// convert host port map into a structs.Networks
  1671  			networks := []*structs.NetworkResource{
  1672  				{
  1673  					IP:            HostIP,
  1674  					ReservedPorts: make([]structs.Port, len(tc.Host)),
  1675  				},
  1676  			}
  1677  
  1678  			i := 0
  1679  			for label, port := range tc.Host {
  1680  				networks[0].ReservedPorts[i].Label = label
  1681  				networks[0].ReservedPorts[i].Value = port
  1682  				i++
  1683  			}
  1684  
  1685  			// Run getAddress
  1686  			ip, port, err := getAddress(tc.Mode, tc.PortLabel, networks, tc.Driver, tc.Ports, tc.Status)
  1687  
  1688  			// Assert the results
  1689  			assert.Equal(t, tc.ExpectedIP, ip, "IP mismatch")
  1690  			assert.Equal(t, tc.ExpectedPort, port, "Port mismatch")
  1691  			if tc.ExpectedErr == "" {
  1692  				assert.Nil(t, err)
  1693  			} else {
  1694  				if err == nil {
  1695  					t.Fatalf("expected error containing %q but err=nil", tc.ExpectedErr)
  1696  				} else {
  1697  					assert.Contains(t, err.Error(), tc.ExpectedErr)
  1698  				}
  1699  			}
  1700  		})
  1701  	}
  1702  }
  1703  
  1704  func TestConsul_ServiceName_Duplicates(t *testing.T) {
  1705  	t.Parallel()
  1706  	ctx := setupFake(t)
  1707  	require := require.New(t)
  1708  
  1709  	ctx.Workload.Services = []*structs.Service{
  1710  		{
  1711  			Name:      "best-service",
  1712  			PortLabel: "x",
  1713  			Tags: []string{
  1714  				"foo",
  1715  			},
  1716  			Checks: []*structs.ServiceCheck{
  1717  				{
  1718  					Name:     "check-a",
  1719  					Type:     "tcp",
  1720  					Interval: time.Second,
  1721  					Timeout:  time.Second,
  1722  				},
  1723  			},
  1724  		},
  1725  		{
  1726  			Name:      "best-service",
  1727  			PortLabel: "y",
  1728  			Tags: []string{
  1729  				"bar",
  1730  			},
  1731  			Checks: []*structs.ServiceCheck{
  1732  				{
  1733  					Name:     "checky-mccheckface",
  1734  					Type:     "tcp",
  1735  					Interval: time.Second,
  1736  					Timeout:  time.Second,
  1737  				},
  1738  			},
  1739  		},
  1740  		{
  1741  			Name:      "worst-service",
  1742  			PortLabel: "y",
  1743  		},
  1744  	}
  1745  
  1746  	require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
  1747  
  1748  	require.NoError(ctx.syncOnce(syncNewOps))
  1749  
  1750  	require.Len(ctx.FakeConsul.services, 3)
  1751  
  1752  	for _, v := range ctx.FakeConsul.services {
  1753  		if v.Name == ctx.Workload.Services[0].Name && v.Port == xPort {
  1754  			require.ElementsMatch(v.Tags, ctx.Workload.Services[0].Tags)
  1755  			require.Len(v.Checks, 1)
  1756  		} else if v.Name == ctx.Workload.Services[1].Name && v.Port == yPort {
  1757  			require.ElementsMatch(v.Tags, ctx.Workload.Services[1].Tags)
  1758  			require.Len(v.Checks, 1)
  1759  		} else if v.Name == ctx.Workload.Services[2].Name {
  1760  			require.Len(v.Checks, 0)
  1761  		}
  1762  	}
  1763  }
  1764  
  1765  // TestConsul_ServiceDeregistration_OutOfProbation asserts that during in steady
  1766  // state we remove any services we don't reconize locally
  1767  func TestConsul_ServiceDeregistration_OutProbation(t *testing.T) {
  1768  	t.Parallel()
  1769  	ctx := setupFake(t)
  1770  	require := require.New(t)
  1771  
  1772  	ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Hour)
  1773  
  1774  	remainingWorkload := testWorkload()
  1775  	remainingWorkload.Services = []*structs.Service{
  1776  		{
  1777  			Name:      "remaining-service",
  1778  			PortLabel: "x",
  1779  			Checks: []*structs.ServiceCheck{
  1780  				{
  1781  					Name:     "check",
  1782  					Type:     "tcp",
  1783  					Interval: time.Second,
  1784  					Timeout:  time.Second,
  1785  				},
  1786  			},
  1787  		},
  1788  	}
  1789  	remainingWorkloadServiceID := MakeAllocServiceID(remainingWorkload.AllocID,
  1790  		remainingWorkload.Name(), remainingWorkload.Services[0])
  1791  
  1792  	require.NoError(ctx.ServiceClient.RegisterWorkload(remainingWorkload))
  1793  	require.NoError(ctx.syncOnce(syncNewOps))
  1794  	require.Len(ctx.FakeConsul.services, 1)
  1795  	require.Len(ctx.FakeConsul.checks, 1)
  1796  
  1797  	explicitlyRemovedWorkload := testWorkload()
  1798  	explicitlyRemovedWorkload.Services = []*structs.Service{
  1799  		{
  1800  			Name:      "explicitly-removed-service",
  1801  			PortLabel: "y",
  1802  			Checks: []*structs.ServiceCheck{
  1803  				{
  1804  					Name:     "check",
  1805  					Type:     "tcp",
  1806  					Interval: time.Second,
  1807  					Timeout:  time.Second,
  1808  				},
  1809  			},
  1810  		},
  1811  	}
  1812  	explicitlyRemovedWorkloadServiceID := MakeAllocServiceID(explicitlyRemovedWorkload.AllocID,
  1813  		explicitlyRemovedWorkload.Name(), explicitlyRemovedWorkload.Services[0])
  1814  
  1815  	require.NoError(ctx.ServiceClient.RegisterWorkload(explicitlyRemovedWorkload))
  1816  
  1817  	require.NoError(ctx.syncOnce(syncNewOps))
  1818  	require.Len(ctx.FakeConsul.services, 2)
  1819  	require.Len(ctx.FakeConsul.checks, 2)
  1820  
  1821  	// we register a task through nomad API then remove it out of band
  1822  	outofbandWorkload := testWorkload()
  1823  	outofbandWorkload.Services = []*structs.Service{
  1824  		{
  1825  			Name:      "unknown-service",
  1826  			PortLabel: "x",
  1827  			Checks: []*structs.ServiceCheck{
  1828  				{
  1829  					Name:     "check",
  1830  					Type:     "tcp",
  1831  					Interval: time.Second,
  1832  					Timeout:  time.Second,
  1833  				},
  1834  			},
  1835  		},
  1836  	}
  1837  	outofbandWorkloadServiceID := MakeAllocServiceID(outofbandWorkload.AllocID,
  1838  		outofbandWorkload.Name(), outofbandWorkload.Services[0])
  1839  
  1840  	require.NoError(ctx.ServiceClient.RegisterWorkload(outofbandWorkload))
  1841  	require.NoError(ctx.syncOnce(syncNewOps))
  1842  
  1843  	require.Len(ctx.FakeConsul.services, 3)
  1844  
  1845  	// remove outofbandWorkload from local services so it appears unknown to client
  1846  	require.Len(ctx.ServiceClient.services, 3)
  1847  	require.Len(ctx.ServiceClient.checks, 3)
  1848  
  1849  	delete(ctx.ServiceClient.services, outofbandWorkloadServiceID)
  1850  	delete(ctx.ServiceClient.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1851  
  1852  	require.Len(ctx.ServiceClient.services, 2)
  1853  	require.Len(ctx.ServiceClient.checks, 2)
  1854  
  1855  	// Sync and ensure that explicitly removed service as well as outofbandWorkload were removed
  1856  
  1857  	ctx.ServiceClient.RemoveWorkload(explicitlyRemovedWorkload)
  1858  	require.NoError(ctx.syncOnce(syncNewOps))
  1859  	require.NoError(ctx.ServiceClient.sync(syncNewOps))
  1860  	require.Len(ctx.FakeConsul.services, 1)
  1861  	require.Len(ctx.FakeConsul.checks, 1)
  1862  
  1863  	require.Contains(ctx.FakeConsul.services, remainingWorkloadServiceID)
  1864  	require.NotContains(ctx.FakeConsul.services, outofbandWorkloadServiceID)
  1865  	require.NotContains(ctx.FakeConsul.services, explicitlyRemovedWorkloadServiceID)
  1866  
  1867  	require.Contains(ctx.FakeConsul.checks, MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0]))
  1868  	require.NotContains(ctx.FakeConsul.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1869  	require.NotContains(ctx.FakeConsul.checks, MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0]))
  1870  }
  1871  
  1872  // TestConsul_ServiceDeregistration_InProbation asserts that during initialization
  1873  // we only deregister services that were explicitly removed and leave unknown
  1874  // services untouched.  This adds a grace period for restoring recovered tasks
  1875  // before deregistering them
  1876  func TestConsul_ServiceDeregistration_InProbation(t *testing.T) {
  1877  	t.Parallel()
  1878  	ctx := setupFake(t)
  1879  	require := require.New(t)
  1880  
  1881  	ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(1 * time.Hour)
  1882  
  1883  	remainingWorkload := testWorkload()
  1884  	remainingWorkload.Services = []*structs.Service{
  1885  		{
  1886  			Name:      "remaining-service",
  1887  			PortLabel: "x",
  1888  			Checks: []*structs.ServiceCheck{
  1889  				{
  1890  					Name:     "check",
  1891  					Type:     "tcp",
  1892  					Interval: time.Second,
  1893  					Timeout:  time.Second,
  1894  				},
  1895  			},
  1896  		},
  1897  	}
  1898  	remainingWorkloadServiceID := MakeAllocServiceID(remainingWorkload.AllocID,
  1899  		remainingWorkload.Name(), remainingWorkload.Services[0])
  1900  
  1901  	require.NoError(ctx.ServiceClient.RegisterWorkload(remainingWorkload))
  1902  	require.NoError(ctx.syncOnce(syncNewOps))
  1903  	require.Len(ctx.FakeConsul.services, 1)
  1904  	require.Len(ctx.FakeConsul.checks, 1)
  1905  
  1906  	explicitlyRemovedWorkload := testWorkload()
  1907  	explicitlyRemovedWorkload.Services = []*structs.Service{
  1908  		{
  1909  			Name:      "explicitly-removed-service",
  1910  			PortLabel: "y",
  1911  			Checks: []*structs.ServiceCheck{
  1912  				{
  1913  					Name:     "check",
  1914  					Type:     "tcp",
  1915  					Interval: time.Second,
  1916  					Timeout:  time.Second,
  1917  				},
  1918  			},
  1919  		},
  1920  	}
  1921  	explicitlyRemovedWorkloadServiceID := MakeAllocServiceID(explicitlyRemovedWorkload.AllocID,
  1922  		explicitlyRemovedWorkload.Name(), explicitlyRemovedWorkload.Services[0])
  1923  
  1924  	require.NoError(ctx.ServiceClient.RegisterWorkload(explicitlyRemovedWorkload))
  1925  
  1926  	require.NoError(ctx.syncOnce(syncNewOps))
  1927  	require.Len(ctx.FakeConsul.services, 2)
  1928  	require.Len(ctx.FakeConsul.checks, 2)
  1929  
  1930  	// we register a task through nomad API then remove it out of band
  1931  	outofbandWorkload := testWorkload()
  1932  	outofbandWorkload.Services = []*structs.Service{
  1933  		{
  1934  			Name:      "unknown-service",
  1935  			PortLabel: "x",
  1936  			Checks: []*structs.ServiceCheck{
  1937  				{
  1938  					Name:     "check",
  1939  					Type:     "tcp",
  1940  					Interval: time.Second,
  1941  					Timeout:  time.Second,
  1942  				},
  1943  			},
  1944  		},
  1945  	}
  1946  	outofbandWorkloadServiceID := MakeAllocServiceID(outofbandWorkload.AllocID,
  1947  		outofbandWorkload.Name(), outofbandWorkload.Services[0])
  1948  
  1949  	require.NoError(ctx.ServiceClient.RegisterWorkload(outofbandWorkload))
  1950  	require.NoError(ctx.syncOnce(syncNewOps))
  1951  
  1952  	require.Len(ctx.FakeConsul.services, 3)
  1953  
  1954  	// remove outofbandWorkload from local services so it appears unknown to client
  1955  	require.Len(ctx.ServiceClient.services, 3)
  1956  	require.Len(ctx.ServiceClient.checks, 3)
  1957  
  1958  	delete(ctx.ServiceClient.services, outofbandWorkloadServiceID)
  1959  	delete(ctx.ServiceClient.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1960  
  1961  	require.Len(ctx.ServiceClient.services, 2)
  1962  	require.Len(ctx.ServiceClient.checks, 2)
  1963  
  1964  	// Sync and ensure that explicitly removed service was removed, but outofbandWorkload remains
  1965  
  1966  	ctx.ServiceClient.RemoveWorkload(explicitlyRemovedWorkload)
  1967  	require.NoError(ctx.syncOnce(syncNewOps))
  1968  	require.NoError(ctx.ServiceClient.sync(syncNewOps))
  1969  	require.Len(ctx.FakeConsul.services, 2)
  1970  	require.Len(ctx.FakeConsul.checks, 2)
  1971  
  1972  	require.Contains(ctx.FakeConsul.services, remainingWorkloadServiceID)
  1973  	require.Contains(ctx.FakeConsul.services, outofbandWorkloadServiceID)
  1974  	require.NotContains(ctx.FakeConsul.services, explicitlyRemovedWorkloadServiceID)
  1975  
  1976  	require.Contains(ctx.FakeConsul.checks, MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0]))
  1977  	require.Contains(ctx.FakeConsul.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1978  	require.NotContains(ctx.FakeConsul.checks, MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0]))
  1979  
  1980  	// after probation, outofband services and checks are removed
  1981  	ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Hour)
  1982  
  1983  	require.NoError(ctx.ServiceClient.sync(syncNewOps))
  1984  	require.Len(ctx.FakeConsul.services, 1)
  1985  	require.Len(ctx.FakeConsul.checks, 1)
  1986  
  1987  	require.Contains(ctx.FakeConsul.services, remainingWorkloadServiceID)
  1988  	require.NotContains(ctx.FakeConsul.services, outofbandWorkloadServiceID)
  1989  	require.NotContains(ctx.FakeConsul.services, explicitlyRemovedWorkloadServiceID)
  1990  
  1991  	require.Contains(ctx.FakeConsul.checks, MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0]))
  1992  	require.NotContains(ctx.FakeConsul.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1993  	require.NotContains(ctx.FakeConsul.checks, MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0]))
  1994  
  1995  }