github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent/consul/unit_test.go (about)

     1  package consul
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"sync/atomic"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/consul/api"
    13  	"github.com/hashicorp/nomad/ci"
    14  	"github.com/hashicorp/nomad/client/serviceregistration"
    15  	"github.com/hashicorp/nomad/helper/testlog"
    16  	"github.com/hashicorp/nomad/helper/uuid"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/hashicorp/nomad/plugins/drivers"
    19  	"github.com/kr/pretty"
    20  	"github.com/shoenig/test/must"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  const (
    25  	// Ports used in testWorkload
    26  	xPort = 1234
    27  	yPort = 1235
    28  )
    29  
    30  func testWorkload() *serviceregistration.WorkloadServices {
    31  	return &serviceregistration.WorkloadServices{
    32  		AllocInfo: structs.AllocInfo{
    33  			AllocID: uuid.Generate(),
    34  			Task:    "taskname",
    35  		},
    36  		Restarter: &restartRecorder{},
    37  		Services: []*structs.Service{
    38  			{
    39  				Name:      "taskname-service",
    40  				PortLabel: "x",
    41  				Tags:      []string{"tag1", "tag2"},
    42  				Meta:      map[string]string{"meta1": "foo"},
    43  			},
    44  		},
    45  		Networks: []*structs.NetworkResource{
    46  			{
    47  				DynamicPorts: []structs.Port{
    48  					{Label: "x", Value: xPort},
    49  					{Label: "y", Value: yPort},
    50  				},
    51  			},
    52  		},
    53  	}
    54  }
    55  
    56  // restartRecorder is a minimal WorkloadRestarter implementation that simply
    57  // counts how many restarts were triggered.
    58  type restartRecorder struct {
    59  	restarts int64
    60  }
    61  
    62  func (r *restartRecorder) Restart(ctx context.Context, event *structs.TaskEvent, failure bool) error {
    63  	atomic.AddInt64(&r.restarts, 1)
    64  	return nil
    65  }
    66  
    67  // testFakeCtx contains a fake Consul AgentAPI
    68  type testFakeCtx struct {
    69  	ServiceClient *ServiceClient
    70  	FakeConsul    *MockAgent
    71  	Workload      *serviceregistration.WorkloadServices
    72  }
    73  
    74  var errNoOps = fmt.Errorf("testing error: no pending operations")
    75  
    76  // syncOps simulates one iteration of the ServiceClient.Run loop and returns
    77  // any errors returned by sync() or errNoOps if no pending operations.
    78  func (t *testFakeCtx) syncOnce(reason syncReason) error {
    79  	switch reason {
    80  
    81  	case syncPeriodic:
    82  		err := t.ServiceClient.sync(syncPeriodic)
    83  		if err == nil {
    84  			t.ServiceClient.clearExplicitlyDeregistered()
    85  		}
    86  		return err
    87  
    88  	case syncNewOps:
    89  		select {
    90  		case ops := <-t.ServiceClient.opCh:
    91  			t.ServiceClient.merge(ops)
    92  			err := t.ServiceClient.sync(syncNewOps)
    93  			if err == nil {
    94  				t.ServiceClient.clearExplicitlyDeregistered()
    95  			}
    96  			return err
    97  		default:
    98  			return errNoOps
    99  		}
   100  
   101  	case syncShutdown:
   102  		return errors.New("no test for sync due to shutdown")
   103  	}
   104  
   105  	return errors.New("bad sync reason")
   106  }
   107  
   108  // setupFake creates a testFakeCtx with a ServiceClient backed by a fakeConsul.
   109  // A test Workload is also provided.
   110  func setupFake(t *testing.T) *testFakeCtx {
   111  	agentClient := NewMockAgent(ossFeatures)
   112  	nsClient := NewNamespacesClient(NewMockNamespaces(nil), agentClient)
   113  	workload := testWorkload()
   114  
   115  	// by default start fake client being out of probation
   116  	serviceClient := NewServiceClient(agentClient, nsClient, testlog.HCLogger(t), true)
   117  	serviceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Minute)
   118  
   119  	return &testFakeCtx{
   120  		ServiceClient: serviceClient,
   121  		FakeConsul:    agentClient,
   122  		Workload:      workload,
   123  	}
   124  }
   125  
   126  func TestConsul_ChangeTags(t *testing.T) {
   127  	ci.Parallel(t)
   128  
   129  	ctx := setupFake(t)
   130  	r := require.New(t)
   131  
   132  	r.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   133  	r.NoError(ctx.syncOnce(syncNewOps))
   134  	r.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul")
   135  
   136  	// Validate the alloc registration
   137  	reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Workload.AllocInfo.AllocID)
   138  	r.NoError(err)
   139  	r.NotNil(reg1, "Unexpected nil alloc registration")
   140  	r.Equal(1, reg1.NumServices())
   141  	r.Equal(0, reg1.NumChecks())
   142  
   143  	serviceBefore := ctx.FakeConsul.lookupService("default", "taskname-service")[0]
   144  	r.Equal(serviceBefore.Name, ctx.Workload.Services[0].Name)
   145  	r.Equal(serviceBefore.Tags, ctx.Workload.Services[0].Tags)
   146  
   147  	// Update the task definition
   148  	origWorkload := ctx.Workload.Copy()
   149  	ctx.Workload.Services[0].Tags[0] = "new-tag"
   150  
   151  	// Register and sync the update
   152  	r.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
   153  	r.NoError(ctx.syncOnce(syncNewOps))
   154  	r.Equal(1, len(ctx.FakeConsul.services), "Expected 1 service to be registered with Consul")
   155  
   156  	// Validate the consul service definition changed
   157  	serviceAfter := ctx.FakeConsul.lookupService("default", "taskname-service")[0]
   158  	r.Equal(serviceAfter.Name, ctx.Workload.Services[0].Name)
   159  	r.Equal(serviceAfter.Tags, ctx.Workload.Services[0].Tags)
   160  	r.Equal("new-tag", serviceAfter.Tags[0])
   161  }
   162  
   163  func TestConsul_EnableTagOverride_Syncs(t *testing.T) {
   164  	ci.Parallel(t)
   165  
   166  	ctx := setupFake(t)
   167  	r := require.New(t)
   168  
   169  	// Configure our test service to set EnableTagOverride = true
   170  	ctx.Workload.Services[0].EnableTagOverride = true
   171  
   172  	r.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   173  	r.NoError(ctx.syncOnce(syncNewOps))
   174  	r.Equal(1, len(ctx.FakeConsul.services))
   175  
   176  	// Validate the alloc registration
   177  	reg1, err := ctx.ServiceClient.AllocRegistrations(ctx.Workload.AllocInfo.AllocID)
   178  	r.NoError(err)
   179  	r.NotNil(reg1)
   180  	r.Equal(1, reg1.NumServices())
   181  	r.Equal(0, reg1.NumChecks())
   182  
   183  	const service = "taskname-service"
   184  
   185  	// check things are what we expect
   186  	consulServiceDefBefore := ctx.FakeConsul.lookupService("default", service)[0]
   187  	r.Equal(ctx.Workload.Services[0].Name, consulServiceDefBefore.Name)
   188  	r.Equal([]string{"tag1", "tag2"}, consulServiceDefBefore.Tags)
   189  	r.True(consulServiceDefBefore.EnableTagOverride)
   190  
   191  	// manually set the tags in consul
   192  	ctx.FakeConsul.lookupService("default", service)[0].Tags = []string{"new", "tags"}
   193  
   194  	// do a periodic sync (which will respect EnableTagOverride)
   195  	r.NoError(ctx.syncOnce(syncPeriodic))
   196  	r.Equal(1, len(ctx.FakeConsul.services))
   197  	consulServiceDefAfter := ctx.FakeConsul.lookupService("default", service)[0]
   198  	r.Equal([]string{"new", "tags"}, consulServiceDefAfter.Tags) // manually set tags should still be there
   199  
   200  	// now do a new-ops sync (which will override EnableTagOverride)
   201  	r.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   202  	r.NoError(ctx.syncOnce(syncNewOps))
   203  	r.Equal(1, len(ctx.FakeConsul.services))
   204  	consulServiceDefUpdated := ctx.FakeConsul.lookupService("default", service)[0]
   205  	r.Equal([]string{"tag1", "tag2"}, consulServiceDefUpdated.Tags) // jobspec tags should be set now
   206  }
   207  
   208  // TestConsul_ChangePorts asserts that changing the ports on a service updates
   209  // it in Consul. Pre-0.7.1 ports were not part of the service ID and this was a
   210  // slightly different code path than changing tags.
   211  func TestConsul_ChangePorts(t *testing.T) {
   212  	ci.Parallel(t)
   213  
   214  	ctx := setupFake(t)
   215  
   216  	ctx.Workload.Services[0].Checks = []*structs.ServiceCheck{
   217  		{
   218  			Name:      "c1",
   219  			Type:      "tcp",
   220  			Interval:  time.Second,
   221  			Timeout:   time.Second,
   222  			PortLabel: "x",
   223  		},
   224  		{
   225  			Name:     "c2",
   226  			Type:     "script",
   227  			Interval: 9000 * time.Hour,
   228  			Timeout:  time.Second,
   229  		},
   230  		{
   231  			Name:      "c3",
   232  			Type:      "http",
   233  			Protocol:  "http",
   234  			Path:      "/",
   235  			Interval:  time.Second,
   236  			Timeout:   time.Second,
   237  			PortLabel: "y",
   238  		},
   239  	}
   240  
   241  	must.NoError(t, ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   242  	must.NoError(t, ctx.syncOnce(syncNewOps))
   243  	must.MapLen(t, 1, ctx.FakeConsul.services["default"])
   244  
   245  	for _, v := range ctx.FakeConsul.services["default"] {
   246  		must.Eq(t, ctx.Workload.Services[0].Name, v.Name)
   247  		must.Eq(t, ctx.Workload.Services[0].Tags, v.Tags)
   248  		must.Eq(t, xPort, v.Port)
   249  	}
   250  
   251  	must.MapLen(t, 3, ctx.FakeConsul.checks["default"], must.Sprintf("checks %#v", ctx.FakeConsul.checks))
   252  
   253  	origTCPKey := ""
   254  	origScriptKey := ""
   255  	origHTTPKey := ""
   256  	for k, v := range ctx.FakeConsul.checks["default"] {
   257  		switch v.Name {
   258  		case "c1":
   259  			origTCPKey = k
   260  			must.Eq(t, fmt.Sprintf(":%d", xPort), v.TCP)
   261  		case "c2":
   262  			origScriptKey = k
   263  		case "c3":
   264  			origHTTPKey = k
   265  			must.Eq(t, fmt.Sprintf("http://:%d/", yPort), v.HTTP)
   266  		default:
   267  			t.Fatalf("unexpected check: %q", v.Name)
   268  		}
   269  	}
   270  
   271  	must.StrHasPrefix(t, "_nomad-check-", origTCPKey)
   272  	must.StrHasPrefix(t, "_nomad-check-", origScriptKey)
   273  	must.StrHasPrefix(t, "_nomad-check-", origHTTPKey)
   274  
   275  	// Now update the PortLabel on the Service and Check c3
   276  	origWorkload := ctx.Workload.Copy()
   277  	ctx.Workload.Services[0].PortLabel = "y"
   278  	ctx.Workload.Services[0].Checks = []*structs.ServiceCheck{
   279  		{
   280  			Name:      "c1",
   281  			Type:      "tcp",
   282  			Interval:  time.Second,
   283  			Timeout:   time.Second,
   284  			PortLabel: "x",
   285  		},
   286  		{
   287  			Name:     "c2",
   288  			Type:     "script",
   289  			Interval: 9000 * time.Hour,
   290  			Timeout:  time.Second,
   291  		},
   292  		{
   293  			Name:     "c3",
   294  			Type:     "http",
   295  			Protocol: "http",
   296  			Path:     "/",
   297  			Interval: time.Second,
   298  			Timeout:  time.Second,
   299  			// Removed PortLabel; should default to service's (y)
   300  		},
   301  	}
   302  
   303  	must.NoError(t, ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
   304  	must.NoError(t, ctx.syncOnce(syncNewOps))
   305  	must.MapLen(t, 1, ctx.FakeConsul.services["default"])
   306  
   307  	for _, v := range ctx.FakeConsul.services["default"] {
   308  		must.Eq(t, ctx.Workload.Services[0].Name, v.Name)
   309  		must.Eq(t, ctx.Workload.Services[0].Tags, v.Tags)
   310  		must.Eq(t, yPort, v.Port)
   311  	}
   312  	must.MapLen(t, 3, ctx.FakeConsul.checks["default"])
   313  
   314  	for k, v := range ctx.FakeConsul.checks["default"] {
   315  		switch v.Name {
   316  		case "c1":
   317  			// C1 is changed because the service was re-registered
   318  			must.NotEq(t, origTCPKey, k)
   319  			must.Eq(t, fmt.Sprintf(":%d", xPort), v.TCP)
   320  		case "c2":
   321  			// C2 is changed because the service was re-registered
   322  			must.NotEq(t, origScriptKey, k)
   323  		case "c3":
   324  			must.NotEq(t, origHTTPKey, k)
   325  			must.Eq(t, fmt.Sprintf("http://:%d/", yPort), v.HTTP)
   326  		default:
   327  			must.Unreachable(t, must.Sprintf("unknown check: %q", k))
   328  		}
   329  	}
   330  }
   331  
   332  // TestConsul_ShutdownOK tests the ok path for the shutdown logic in
   333  // ServiceClient.
   334  func TestConsul_ShutdownOK(t *testing.T) {
   335  	ci.Parallel(t)
   336  
   337  	require := require.New(t)
   338  	ctx := setupFake(t)
   339  	go ctx.ServiceClient.Run()
   340  
   341  	// register the Nomad agent service and check
   342  	agentServices := []*structs.Service{
   343  		{
   344  			Name:      "http",
   345  			Tags:      []string{"nomad"},
   346  			PortLabel: "localhost:2345",
   347  			Checks: []*structs.ServiceCheck{
   348  				{
   349  					Name:          "nomad-tcp",
   350  					Type:          "tcp",
   351  					Interval:      9000 * time.Hour, // make check block
   352  					Timeout:       10 * time.Second,
   353  					InitialStatus: "warning",
   354  				},
   355  			},
   356  		},
   357  	}
   358  	require.NoError(ctx.ServiceClient.RegisterAgent("client", agentServices))
   359  	require.Eventually(ctx.ServiceClient.hasSeen, time.Second, 10*time.Millisecond)
   360  
   361  	// assert successful registration
   362  	require.Len(ctx.FakeConsul.services["default"], 1, "expected agent service to be registered")
   363  	require.Len(ctx.FakeConsul.checks["default"], 1, "expected agent check to be registered")
   364  	require.Contains(ctx.FakeConsul.services["default"], makeAgentServiceID("client", agentServices[0]))
   365  
   366  	// Shutdown() should block until Nomad agent service/check is deregistered
   367  	require.NoError(ctx.ServiceClient.Shutdown())
   368  	require.Len(ctx.FakeConsul.services["default"], 0, "expected agent service to be deregistered")
   369  	require.Len(ctx.FakeConsul.checks["default"], 0, "expected agent check to be deregistered")
   370  }
   371  
   372  // TestConsul_ShutdownBlocked tests the blocked past deadline path for the
   373  // shutdown logic in ServiceClient.
   374  func TestConsul_ShutdownBlocked(t *testing.T) {
   375  	ci.Parallel(t)
   376  
   377  	require := require.New(t)
   378  	ctx := setupFake(t)
   379  	// can be short because we're intentionally blocking, but needs to
   380  	// be longer than the time we'll block Consul so we can be sure
   381  	// we're not delayed either.
   382  	ctx.ServiceClient.shutdownWait = time.Second
   383  	go ctx.ServiceClient.Run()
   384  
   385  	// register the Nomad agent service and check
   386  	agentServices := []*structs.Service{
   387  		{
   388  			Name:      "http",
   389  			Tags:      []string{"nomad"},
   390  			PortLabel: "localhost:2345",
   391  			Checks: []*structs.ServiceCheck{
   392  				{
   393  					Name:          "nomad-tcp",
   394  					Type:          "tcp",
   395  					Interval:      9000 * time.Hour, // make check block
   396  					Timeout:       10 * time.Second,
   397  					InitialStatus: "warning",
   398  				},
   399  			},
   400  		},
   401  	}
   402  	require.NoError(ctx.ServiceClient.RegisterAgent("client", agentServices))
   403  	require.Eventually(ctx.ServiceClient.hasSeen, time.Second, 10*time.Millisecond)
   404  	require.Len(ctx.FakeConsul.services["default"], 1, "expected agent service to be registered")
   405  	require.Len(ctx.FakeConsul.checks["default"], 1, "expected agent check to be registered")
   406  
   407  	// prevent normal shutdown by blocking Consul. the shutdown should wait
   408  	// until agent deregistration has finished
   409  	waiter := make(chan struct{})
   410  	result := make(chan error)
   411  	go func() {
   412  		ctx.FakeConsul.mu.Lock()
   413  		close(waiter)
   414  		result <- ctx.ServiceClient.Shutdown()
   415  	}()
   416  
   417  	<-waiter // wait for lock to be hit
   418  
   419  	// Shutdown should block until all enqueued operations finish.
   420  	preShutdown := time.Now()
   421  	select {
   422  	case <-time.After(200 * time.Millisecond):
   423  		ctx.FakeConsul.mu.Unlock()
   424  		require.NoError(<-result)
   425  	case <-result:
   426  		t.Fatal("should not have received result until Consul unblocked")
   427  	}
   428  	shutdownTime := time.Now().Sub(preShutdown).Seconds()
   429  	require.Less(shutdownTime, time.Second.Seconds(),
   430  		"expected shutdown to take >200ms and <1s")
   431  	require.Greater(shutdownTime, 200*time.Millisecond.Seconds(),
   432  		"expected shutdown to take >200ms and <1s")
   433  	require.Len(ctx.FakeConsul.services["default"], 0,
   434  		"expected agent service to be deregistered")
   435  	require.Len(ctx.FakeConsul.checks["default"], 0,
   436  		"expected agent check to be deregistered")
   437  }
   438  
   439  // TestConsul_DriverNetwork_AutoUse asserts that if a driver network has
   440  // auto-use set then services should advertise it unless explicitly set to
   441  // host. Checks should always use host.
   442  func TestConsul_DriverNetwork_AutoUse(t *testing.T) {
   443  	ci.Parallel(t)
   444  
   445  	ctx := setupFake(t)
   446  
   447  	ctx.Workload.Services = []*structs.Service{
   448  		{
   449  			Name:        "auto-advertise-x",
   450  			PortLabel:   "x",
   451  			AddressMode: structs.AddressModeAuto,
   452  			Checks: []*structs.ServiceCheck{
   453  				{
   454  					Name:     "default-check-x",
   455  					Type:     "tcp",
   456  					Interval: time.Second,
   457  					Timeout:  time.Second,
   458  				},
   459  				{
   460  					Name:      "weird-y-check",
   461  					Type:      "http",
   462  					Interval:  time.Second,
   463  					Timeout:   time.Second,
   464  					PortLabel: "y",
   465  				},
   466  			},
   467  		},
   468  		{
   469  			Name:        "driver-advertise-y",
   470  			PortLabel:   "y",
   471  			AddressMode: structs.AddressModeDriver,
   472  			Checks: []*structs.ServiceCheck{
   473  				{
   474  					Name:     "default-check-y",
   475  					Type:     "tcp",
   476  					Interval: time.Second,
   477  					Timeout:  time.Second,
   478  				},
   479  			},
   480  		},
   481  		{
   482  			Name:        "host-advertise-y",
   483  			PortLabel:   "y",
   484  			AddressMode: structs.AddressModeHost,
   485  		},
   486  	}
   487  
   488  	ctx.Workload.DriverNetwork = &drivers.DriverNetwork{
   489  		PortMap: map[string]int{
   490  			"x": 8888,
   491  			"y": 9999,
   492  		},
   493  		IP:            "172.18.0.2",
   494  		AutoAdvertise: true,
   495  	}
   496  
   497  	if err := ctx.ServiceClient.RegisterWorkload(ctx.Workload); err != nil {
   498  		t.Fatalf("unexpected error registering task: %v", err)
   499  	}
   500  
   501  	if err := ctx.syncOnce(syncNewOps); err != nil {
   502  		t.Fatalf("unexpected error syncing task: %v", err)
   503  	}
   504  
   505  	if n := len(ctx.FakeConsul.services["default"]); n != 3 {
   506  		t.Fatalf("expected 2 services but found: %d", n)
   507  	}
   508  
   509  	for _, v := range ctx.FakeConsul.services["default"] {
   510  		switch v.Name {
   511  		case ctx.Workload.Services[0].Name: // x
   512  			// Since DriverNetwork.AutoAdvertise=true, driver ports should be used
   513  			if v.Port != ctx.Workload.DriverNetwork.PortMap["x"] {
   514  				t.Errorf("expected service %s's port to be %d but found %d",
   515  					v.Name, ctx.Workload.DriverNetwork.PortMap["x"], v.Port)
   516  			}
   517  			// The order of checks in Consul is not guaranteed to
   518  			// be the same as their order in the Workload definition,
   519  			// so check in a loop
   520  			if expected := 2; len(v.Checks) != expected {
   521  				t.Errorf("expected %d checks but found %d", expected, len(v.Checks))
   522  			}
   523  			for _, c := range v.Checks {
   524  				// No name on AgentServiceChecks, use type
   525  				switch {
   526  				case c.TCP != "":
   527  					// Checks should always use host port though
   528  					if c.TCP != ":1234" { // xPort
   529  						t.Errorf("expected service %s check 1's port to be %d but found %q",
   530  							v.Name, xPort, c.TCP)
   531  					}
   532  				case c.HTTP != "":
   533  					if c.HTTP != "http://:1235" { // yPort
   534  						t.Errorf("expected service %s check 2's port to be %d but found %q",
   535  							v.Name, yPort, c.HTTP)
   536  					}
   537  				default:
   538  					t.Errorf("unexpected check %#v on service %q", c, v.Name)
   539  				}
   540  			}
   541  		case ctx.Workload.Services[1].Name: // y
   542  			// Service should be container ip:port
   543  			if v.Address != ctx.Workload.DriverNetwork.IP {
   544  				t.Errorf("expected service %s's address to be %s but found %s",
   545  					v.Name, ctx.Workload.DriverNetwork.IP, v.Address)
   546  			}
   547  			if v.Port != ctx.Workload.DriverNetwork.PortMap["y"] {
   548  				t.Errorf("expected service %s's port to be %d but found %d",
   549  					v.Name, ctx.Workload.DriverNetwork.PortMap["x"], v.Port)
   550  			}
   551  			// Check should be host ip:port
   552  			if v.Checks[0].TCP != ":1235" { // yPort
   553  				t.Errorf("expected service %s check's port to be %d but found %s",
   554  					v.Name, yPort, v.Checks[0].TCP)
   555  			}
   556  		case ctx.Workload.Services[2].Name: // y + host mode
   557  			if v.Port != yPort {
   558  				t.Errorf("expected service %s's port to be %d but found %d",
   559  					v.Name, yPort, v.Port)
   560  			}
   561  		default:
   562  			t.Errorf("unexpected service name: %q", v.Name)
   563  		}
   564  	}
   565  }
   566  
   567  // TestConsul_DriverNetwork_NoAutoUse asserts that if a driver network doesn't
   568  // set auto-use only services which request the driver's network should
   569  // advertise it.
   570  func TestConsul_DriverNetwork_NoAutoUse(t *testing.T) {
   571  	ci.Parallel(t)
   572  
   573  	ctx := setupFake(t)
   574  
   575  	ctx.Workload.Services = []*structs.Service{
   576  		{
   577  			Name:        "auto-advertise-x",
   578  			PortLabel:   "x",
   579  			AddressMode: structs.AddressModeAuto,
   580  		},
   581  		{
   582  			Name:        "driver-advertise-y",
   583  			PortLabel:   "y",
   584  			AddressMode: structs.AddressModeDriver,
   585  		},
   586  		{
   587  			Name:        "host-advertise-y",
   588  			PortLabel:   "y",
   589  			AddressMode: structs.AddressModeHost,
   590  		},
   591  	}
   592  
   593  	ctx.Workload.DriverNetwork = &drivers.DriverNetwork{
   594  		PortMap: map[string]int{
   595  			"x": 8888,
   596  			"y": 9999,
   597  		},
   598  		IP:            "172.18.0.2",
   599  		AutoAdvertise: false,
   600  	}
   601  
   602  	if err := ctx.ServiceClient.RegisterWorkload(ctx.Workload); err != nil {
   603  		t.Fatalf("unexpected error registering task: %v", err)
   604  	}
   605  
   606  	if err := ctx.syncOnce(syncNewOps); err != nil {
   607  		t.Fatalf("unexpected error syncing task: %v", err)
   608  	}
   609  
   610  	if n := len(ctx.FakeConsul.services["default"]); n != 3 {
   611  		t.Fatalf("expected 3 services but found: %d", n)
   612  	}
   613  
   614  	for _, v := range ctx.FakeConsul.services["default"] {
   615  		switch v.Name {
   616  		case ctx.Workload.Services[0].Name: // x + auto
   617  			// Since DriverNetwork.AutoAdvertise=false, host ports should be used
   618  			if v.Port != xPort {
   619  				t.Errorf("expected service %s's port to be %d but found %d",
   620  					v.Name, xPort, v.Port)
   621  			}
   622  		case ctx.Workload.Services[1].Name: // y + driver mode
   623  			// Service should be container ip:port
   624  			if v.Address != ctx.Workload.DriverNetwork.IP {
   625  				t.Errorf("expected service %s's address to be %s but found %s",
   626  					v.Name, ctx.Workload.DriverNetwork.IP, v.Address)
   627  			}
   628  			if v.Port != ctx.Workload.DriverNetwork.PortMap["y"] {
   629  				t.Errorf("expected service %s's port to be %d but found %d",
   630  					v.Name, ctx.Workload.DriverNetwork.PortMap["x"], v.Port)
   631  			}
   632  		case ctx.Workload.Services[2].Name: // y + host mode
   633  			if v.Port != yPort {
   634  				t.Errorf("expected service %s's port to be %d but found %d",
   635  					v.Name, yPort, v.Port)
   636  			}
   637  		default:
   638  			t.Errorf("unexpected service name: %q", v.Name)
   639  		}
   640  	}
   641  }
   642  
   643  // TestConsul_DriverNetwork_Change asserts that if a driver network is
   644  // specified and a service updates its use its properly updated in Consul.
   645  func TestConsul_DriverNetwork_Change(t *testing.T) {
   646  	ci.Parallel(t)
   647  
   648  	ctx := setupFake(t)
   649  
   650  	ctx.Workload.Services = []*structs.Service{
   651  		{
   652  			Name:        "service-foo",
   653  			PortLabel:   "x",
   654  			AddressMode: structs.AddressModeAuto,
   655  		},
   656  	}
   657  
   658  	ctx.Workload.DriverNetwork = &drivers.DriverNetwork{
   659  		PortMap: map[string]int{
   660  			"x": 8888,
   661  			"y": 9999,
   662  		},
   663  		IP:            "172.18.0.2",
   664  		AutoAdvertise: false,
   665  	}
   666  
   667  	syncAndAssertPort := func(port int) {
   668  		if err := ctx.syncOnce(syncNewOps); err != nil {
   669  			t.Fatalf("unexpected error syncing task: %v", err)
   670  		}
   671  
   672  		if n := len(ctx.FakeConsul.services["default"]); n != 1 {
   673  			t.Fatalf("expected 1 service but found: %d", n)
   674  		}
   675  
   676  		for _, v := range ctx.FakeConsul.services["default"] {
   677  			switch v.Name {
   678  			case ctx.Workload.Services[0].Name:
   679  				if v.Port != port {
   680  					t.Errorf("expected service %s's port to be %d but found %d",
   681  						v.Name, port, v.Port)
   682  				}
   683  			default:
   684  				t.Errorf("unexpected service name: %q", v.Name)
   685  			}
   686  		}
   687  	}
   688  
   689  	// Initial service should advertise host port x
   690  	if err := ctx.ServiceClient.RegisterWorkload(ctx.Workload); err != nil {
   691  		t.Fatalf("unexpected error registering task: %v", err)
   692  	}
   693  
   694  	syncAndAssertPort(xPort)
   695  
   696  	// UpdateWorkload to use Host (shouldn't change anything)
   697  	origWorkload := ctx.Workload.Copy()
   698  	ctx.Workload.Services[0].AddressMode = structs.AddressModeHost
   699  
   700  	if err := ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload); err != nil {
   701  		t.Fatalf("unexpected error updating task: %v", err)
   702  	}
   703  
   704  	syncAndAssertPort(xPort)
   705  
   706  	// UpdateWorkload to use Driver (*should* change IP and port)
   707  	origWorkload = ctx.Workload.Copy()
   708  	ctx.Workload.Services[0].AddressMode = structs.AddressModeDriver
   709  
   710  	if err := ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload); err != nil {
   711  		t.Fatalf("unexpected error updating task: %v", err)
   712  	}
   713  
   714  	syncAndAssertPort(ctx.Workload.DriverNetwork.PortMap["x"])
   715  }
   716  
   717  // TestConsul_CanaryTags asserts CanaryTags are used when Canary=true
   718  func TestConsul_CanaryTags(t *testing.T) {
   719  	ci.Parallel(t)
   720  
   721  	require := require.New(t)
   722  	ctx := setupFake(t)
   723  
   724  	canaryTags := []string{"tag1", "canary"}
   725  	ctx.Workload.Canary = true
   726  	ctx.Workload.Services[0].CanaryTags = canaryTags
   727  
   728  	require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   729  	require.NoError(ctx.syncOnce(syncNewOps))
   730  	require.Len(ctx.FakeConsul.services["default"], 1)
   731  	for _, service := range ctx.FakeConsul.services["default"] {
   732  		require.Equal(canaryTags, service.Tags)
   733  	}
   734  
   735  	// Disable canary and assert tags are not the canary tags
   736  	origWorkload := ctx.Workload.Copy()
   737  	ctx.Workload.Canary = false
   738  	require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
   739  	require.NoError(ctx.syncOnce(syncNewOps))
   740  	require.Len(ctx.FakeConsul.services["default"], 1)
   741  	for _, service := range ctx.FakeConsul.services["default"] {
   742  		require.NotEqual(canaryTags, service.Tags)
   743  	}
   744  
   745  	ctx.ServiceClient.RemoveWorkload(ctx.Workload)
   746  	require.NoError(ctx.syncOnce(syncNewOps))
   747  	require.Len(ctx.FakeConsul.services["default"], 0)
   748  }
   749  
   750  // TestConsul_CanaryTags_NoTags asserts Tags are used when Canary=true and there
   751  // are no specified canary tags
   752  func TestConsul_CanaryTags_NoTags(t *testing.T) {
   753  	ci.Parallel(t)
   754  
   755  	require := require.New(t)
   756  	ctx := setupFake(t)
   757  
   758  	tags := []string{"tag1", "foo"}
   759  	ctx.Workload.Canary = true
   760  	ctx.Workload.Services[0].Tags = tags
   761  
   762  	require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   763  	require.NoError(ctx.syncOnce(syncNewOps))
   764  	require.Len(ctx.FakeConsul.services["default"], 1)
   765  	for _, service := range ctx.FakeConsul.services["default"] {
   766  		require.Equal(tags, service.Tags)
   767  	}
   768  
   769  	// Disable canary and assert tags dont change
   770  	origWorkload := ctx.Workload.Copy()
   771  	ctx.Workload.Canary = false
   772  	require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
   773  	require.NoError(ctx.syncOnce(syncNewOps))
   774  	require.Len(ctx.FakeConsul.services["default"], 1)
   775  	for _, service := range ctx.FakeConsul.services["default"] {
   776  		require.Equal(tags, service.Tags)
   777  	}
   778  
   779  	ctx.ServiceClient.RemoveWorkload(ctx.Workload)
   780  	require.NoError(ctx.syncOnce(syncNewOps))
   781  	require.Len(ctx.FakeConsul.services["default"], 0)
   782  }
   783  
   784  // TestConsul_CanaryMeta asserts CanaryMeta are used when Canary=true
   785  func TestConsul_CanaryMeta(t *testing.T) {
   786  	ci.Parallel(t)
   787  
   788  	require := require.New(t)
   789  	ctx := setupFake(t)
   790  
   791  	canaryMeta := map[string]string{"meta1": "canary"}
   792  	canaryMeta["external-source"] = "nomad"
   793  	ctx.Workload.Canary = true
   794  	ctx.Workload.Services[0].CanaryMeta = canaryMeta
   795  
   796  	require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   797  	require.NoError(ctx.syncOnce(syncNewOps))
   798  	require.Len(ctx.FakeConsul.services["default"], 1)
   799  	for _, service := range ctx.FakeConsul.services["default"] {
   800  		require.Equal(canaryMeta, service.Meta)
   801  	}
   802  
   803  	// Disable canary and assert meta are not the canary meta
   804  	origWorkload := ctx.Workload.Copy()
   805  	ctx.Workload.Canary = false
   806  	require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
   807  	require.NoError(ctx.syncOnce(syncNewOps))
   808  	require.Len(ctx.FakeConsul.services["default"], 1)
   809  	for _, service := range ctx.FakeConsul.services["default"] {
   810  		require.NotEqual(canaryMeta, service.Meta)
   811  	}
   812  
   813  	ctx.ServiceClient.RemoveWorkload(ctx.Workload)
   814  	require.NoError(ctx.syncOnce(syncNewOps))
   815  	require.Len(ctx.FakeConsul.services["default"], 0)
   816  }
   817  
   818  // TestConsul_CanaryMeta_NoMeta asserts Meta are used when Canary=true and there
   819  // are no specified canary meta
   820  func TestConsul_CanaryMeta_NoMeta(t *testing.T) {
   821  	ci.Parallel(t)
   822  
   823  	require := require.New(t)
   824  	ctx := setupFake(t)
   825  
   826  	meta := map[string]string{"meta1": "foo"}
   827  	meta["external-source"] = "nomad"
   828  	ctx.Workload.Canary = true
   829  	ctx.Workload.Services[0].Meta = meta
   830  
   831  	require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
   832  	require.NoError(ctx.syncOnce(syncNewOps))
   833  	require.Len(ctx.FakeConsul.services["default"], 1)
   834  	for _, service := range ctx.FakeConsul.services["default"] {
   835  		require.Equal(meta, service.Meta)
   836  	}
   837  
   838  	// Disable canary and assert meta dont change
   839  	origWorkload := ctx.Workload.Copy()
   840  	ctx.Workload.Canary = false
   841  	require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
   842  	require.NoError(ctx.syncOnce(syncNewOps))
   843  	require.Len(ctx.FakeConsul.services["default"], 1)
   844  	for _, service := range ctx.FakeConsul.services["default"] {
   845  		require.Equal(meta, service.Meta)
   846  	}
   847  
   848  	ctx.ServiceClient.RemoveWorkload(ctx.Workload)
   849  	require.NoError(ctx.syncOnce(syncNewOps))
   850  	require.Len(ctx.FakeConsul.services["default"], 0)
   851  }
   852  
   853  // TestConsul_PeriodicSync asserts that Nomad periodically reconciles with
   854  // Consul.
   855  func TestConsul_PeriodicSync(t *testing.T) {
   856  	ci.Parallel(t)
   857  
   858  	ctx := setupFake(t)
   859  	defer ctx.ServiceClient.Shutdown()
   860  
   861  	// Lower periodic sync interval to speed up test
   862  	ctx.ServiceClient.periodicInterval = 1 * time.Millisecond
   863  
   864  	// Run for 20ms and assert hits >= 5 because each sync() calls multiple
   865  	// Consul APIs
   866  	go ctx.ServiceClient.Run()
   867  
   868  	select {
   869  	case <-ctx.ServiceClient.exitCh:
   870  		t.Fatalf("exited unexpectedly")
   871  	case <-time.After(20 * time.Millisecond):
   872  	}
   873  
   874  	minHits := 5
   875  	if hits := ctx.FakeConsul.getHits(); hits < minHits {
   876  		t.Fatalf("expected at least %d hits but found %d", minHits, hits)
   877  	}
   878  }
   879  
   880  // TestIsNomadService asserts the isNomadService helper returns true for Nomad
   881  // task IDs and false for unknown IDs and Nomad agent IDs (see #2827).
   882  func TestIsNomadService(t *testing.T) {
   883  	ci.Parallel(t)
   884  
   885  	tests := []struct {
   886  		id     string
   887  		result bool
   888  	}{
   889  		{"_nomad-client-nomad-client-http", false},
   890  		{"_nomad-server-nomad-serf", false},
   891  
   892  		// Pre-0.7.1 style IDs still match
   893  		{"_nomad-executor-abc", true},
   894  		{"_nomad-executor", true},
   895  
   896  		// Post-0.7.1 style IDs match
   897  		{"_nomad-task-FBBK265QN4TMT25ND4EP42TJVMYJ3HR4", true},
   898  
   899  		{"not-nomad", false},
   900  		{"_nomad", false},
   901  	}
   902  
   903  	for _, test := range tests {
   904  		t.Run(test.id, func(t *testing.T) {
   905  			actual := isNomadService(test.id)
   906  			if actual != test.result {
   907  				t.Errorf("%q should be %t but found %t", test.id, test.result, actual)
   908  			}
   909  		})
   910  	}
   911  }
   912  
   913  // TestCreateCheckReg_HTTP asserts Nomad ServiceCheck structs are properly
   914  // converted to Consul API AgentCheckRegistrations for HTTP checks.
   915  func TestCreateCheckReg_HTTP(t *testing.T) {
   916  	ci.Parallel(t)
   917  
   918  	check := &structs.ServiceCheck{
   919  		Name:      "name",
   920  		Type:      "http",
   921  		Path:      "/path",
   922  		PortLabel: "label",
   923  		Method:    "POST",
   924  		Header: map[string][]string{
   925  			"Foo": {"bar"},
   926  		},
   927  	}
   928  
   929  	serviceID := "testService"
   930  	checkID := check.Hash(serviceID)
   931  	host := "localhost"
   932  	port := 41111
   933  	namespace := ""
   934  
   935  	expected := &api.AgentCheckRegistration{
   936  		Namespace: namespace,
   937  		ID:        checkID,
   938  		Name:      "name",
   939  		ServiceID: serviceID,
   940  		AgentServiceCheck: api.AgentServiceCheck{
   941  			Timeout:  "0s",
   942  			Interval: "0s",
   943  			HTTP:     fmt.Sprintf("http://%s:%d/path", host, port),
   944  			Method:   "POST",
   945  			Header: map[string][]string{
   946  				"Foo": {"bar"},
   947  			},
   948  		},
   949  	}
   950  
   951  	actual, err := createCheckReg(serviceID, checkID, check, host, port, namespace)
   952  	if err != nil {
   953  		t.Fatalf("err: %v", err)
   954  	}
   955  
   956  	if diff := pretty.Diff(actual, expected); len(diff) > 0 {
   957  		t.Fatalf("diff:\n%s\n", strings.Join(diff, "\n"))
   958  	}
   959  }
   960  
   961  // TestCreateCheckReg_GRPC asserts Nomad ServiceCheck structs are properly
   962  // converted to Consul API AgentCheckRegistrations for GRPC checks.
   963  func TestCreateCheckReg_GRPC(t *testing.T) {
   964  	ci.Parallel(t)
   965  
   966  	check := &structs.ServiceCheck{
   967  		Name:          "name",
   968  		Type:          "grpc",
   969  		PortLabel:     "label",
   970  		GRPCService:   "foo.Bar",
   971  		GRPCUseTLS:    true,
   972  		TLSSkipVerify: true,
   973  		Timeout:       time.Second,
   974  		Interval:      time.Minute,
   975  	}
   976  
   977  	serviceID := "testService"
   978  	checkID := check.Hash(serviceID)
   979  
   980  	expected := &api.AgentCheckRegistration{
   981  		Namespace: "",
   982  		ID:        checkID,
   983  		Name:      check.Name,
   984  		ServiceID: serviceID,
   985  		AgentServiceCheck: api.AgentServiceCheck{
   986  			Timeout:       "1s",
   987  			Interval:      "1m0s",
   988  			GRPC:          "localhost:8080/foo.Bar",
   989  			GRPCUseTLS:    true,
   990  			TLSSkipVerify: true,
   991  		},
   992  	}
   993  
   994  	actual, err := createCheckReg(serviceID, checkID, check, "localhost", 8080, "default")
   995  	must.NoError(t, err)
   996  	must.Eq(t, expected, actual)
   997  }
   998  
   999  func TestConsul_ServiceName_Duplicates(t *testing.T) {
  1000  	ci.Parallel(t)
  1001  	ctx := setupFake(t)
  1002  
  1003  	ctx.Workload.Services = []*structs.Service{
  1004  		{
  1005  			Name:      "best-service",
  1006  			PortLabel: "x",
  1007  			Tags:      []string{"foo"},
  1008  			Checks: []*structs.ServiceCheck{
  1009  				{
  1010  					Name:     "check-a",
  1011  					Type:     "tcp",
  1012  					Interval: time.Second,
  1013  					Timeout:  time.Second,
  1014  				},
  1015  			},
  1016  		},
  1017  		{
  1018  			Name:      "best-service",
  1019  			PortLabel: "y",
  1020  			Tags:      []string{"bar"},
  1021  			Checks: []*structs.ServiceCheck{
  1022  				{
  1023  					Name:     "check-b",
  1024  					Type:     "tcp",
  1025  					Interval: time.Second,
  1026  					Timeout:  time.Second,
  1027  				},
  1028  			},
  1029  		},
  1030  		{
  1031  			Name:      "worst-service",
  1032  			PortLabel: "y",
  1033  		},
  1034  	}
  1035  
  1036  	must.NoError(t, ctx.ServiceClient.RegisterWorkload(ctx.Workload))
  1037  	must.NoError(t, ctx.syncOnce(syncNewOps))
  1038  	must.MapLen(t, 3, ctx.FakeConsul.services["default"])
  1039  
  1040  	for _, s := range ctx.FakeConsul.services["default"] {
  1041  		switch {
  1042  		case s.Name == "best-service" && s.Port == xPort:
  1043  			must.SliceContainsAll(t, s.Tags, ctx.Workload.Services[0].Tags)
  1044  			must.SliceLen(t, 1, s.Checks)
  1045  		case s.Name == "best-service" && s.Port == yPort:
  1046  			must.SliceContainsAll(t, s.Tags, ctx.Workload.Services[1].Tags)
  1047  			must.SliceLen(t, 1, s.Checks)
  1048  		case s.Name == "worst-service":
  1049  			must.SliceEmpty(t, s.Checks)
  1050  		}
  1051  	}
  1052  }
  1053  
  1054  // TestConsul_ServiceDeregistration_OutOfProbation asserts that during in steady
  1055  // state we remove any services we don't reconize locally
  1056  func TestConsul_ServiceDeregistration_OutProbation(t *testing.T) {
  1057  	ci.Parallel(t)
  1058  
  1059  	ctx := setupFake(t)
  1060  	require := require.New(t)
  1061  
  1062  	ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Hour)
  1063  
  1064  	remainingWorkload := testWorkload()
  1065  	remainingWorkload.Services = []*structs.Service{
  1066  		{
  1067  			Name:      "remaining-service",
  1068  			PortLabel: "x",
  1069  			Checks: []*structs.ServiceCheck{
  1070  				{
  1071  					Name:     "check",
  1072  					Type:     "tcp",
  1073  					Interval: time.Second,
  1074  					Timeout:  time.Second,
  1075  				},
  1076  			},
  1077  		},
  1078  	}
  1079  	remainingWorkloadServiceID := serviceregistration.MakeAllocServiceID(remainingWorkload.AllocInfo.AllocID,
  1080  		remainingWorkload.Name(), remainingWorkload.Services[0])
  1081  
  1082  	require.NoError(ctx.ServiceClient.RegisterWorkload(remainingWorkload))
  1083  	require.NoError(ctx.syncOnce(syncNewOps))
  1084  	require.Len(ctx.FakeConsul.services, 1)
  1085  	require.Len(ctx.FakeConsul.checks, 1)
  1086  
  1087  	explicitlyRemovedWorkload := testWorkload()
  1088  	explicitlyRemovedWorkload.Services = []*structs.Service{
  1089  		{
  1090  			Name:      "explicitly-removed-service",
  1091  			PortLabel: "y",
  1092  			Checks: []*structs.ServiceCheck{
  1093  				{
  1094  					Name:     "check",
  1095  					Type:     "tcp",
  1096  					Interval: time.Second,
  1097  					Timeout:  time.Second,
  1098  				},
  1099  			},
  1100  		},
  1101  	}
  1102  	explicitlyRemovedWorkloadServiceID := serviceregistration.MakeAllocServiceID(explicitlyRemovedWorkload.AllocInfo.AllocID,
  1103  		explicitlyRemovedWorkload.Name(), explicitlyRemovedWorkload.Services[0])
  1104  
  1105  	require.NoError(ctx.ServiceClient.RegisterWorkload(explicitlyRemovedWorkload))
  1106  
  1107  	require.NoError(ctx.syncOnce(syncNewOps))
  1108  	require.Len(ctx.FakeConsul.services["default"], 2)
  1109  	require.Len(ctx.FakeConsul.checks["default"], 2)
  1110  
  1111  	// we register a task through nomad API then remove it out of band
  1112  	outofbandWorkload := testWorkload()
  1113  	outofbandWorkload.Services = []*structs.Service{
  1114  		{
  1115  			Name:      "unknown-service",
  1116  			PortLabel: "x",
  1117  			Checks: []*structs.ServiceCheck{
  1118  				{
  1119  					Name:     "check",
  1120  					Type:     "tcp",
  1121  					Interval: time.Second,
  1122  					Timeout:  time.Second,
  1123  				},
  1124  			},
  1125  		},
  1126  	}
  1127  	outofbandWorkloadServiceID := serviceregistration.MakeAllocServiceID(outofbandWorkload.AllocInfo.AllocID,
  1128  		outofbandWorkload.Name(), outofbandWorkload.Services[0])
  1129  
  1130  	require.NoError(ctx.ServiceClient.RegisterWorkload(outofbandWorkload))
  1131  	require.NoError(ctx.syncOnce(syncNewOps))
  1132  
  1133  	require.Len(ctx.FakeConsul.services["default"], 3)
  1134  
  1135  	// remove outofbandWorkload from local services so it appears unknown to client
  1136  	require.Len(ctx.ServiceClient.services, 3)
  1137  	require.Len(ctx.ServiceClient.checks, 3)
  1138  
  1139  	delete(ctx.ServiceClient.services, outofbandWorkloadServiceID)
  1140  	delete(ctx.ServiceClient.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1141  
  1142  	require.Len(ctx.ServiceClient.services, 2)
  1143  	require.Len(ctx.ServiceClient.checks, 2)
  1144  
  1145  	// Sync and ensure that explicitly removed service as well as outofbandWorkload were removed
  1146  
  1147  	ctx.ServiceClient.RemoveWorkload(explicitlyRemovedWorkload)
  1148  	require.NoError(ctx.syncOnce(syncNewOps))
  1149  	require.NoError(ctx.ServiceClient.sync(syncNewOps))
  1150  	require.Len(ctx.FakeConsul.services["default"], 1)
  1151  	require.Len(ctx.FakeConsul.checks["default"], 1)
  1152  
  1153  	require.Contains(ctx.FakeConsul.services["default"], remainingWorkloadServiceID)
  1154  	require.NotContains(ctx.FakeConsul.services["default"], outofbandWorkloadServiceID)
  1155  	require.NotContains(ctx.FakeConsul.services["default"], explicitlyRemovedWorkloadServiceID)
  1156  
  1157  	require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0]))
  1158  	require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1159  	require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0]))
  1160  }
  1161  
  1162  // TestConsul_ServiceDeregistration_InProbation asserts that during initialization
  1163  // we only deregister services that were explicitly removed and leave unknown
  1164  // services untouched.  This adds a grace period for restoring recovered tasks
  1165  // before deregistering them
  1166  func TestConsul_ServiceDeregistration_InProbation(t *testing.T) {
  1167  	ci.Parallel(t)
  1168  
  1169  	ctx := setupFake(t)
  1170  	require := require.New(t)
  1171  
  1172  	ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(1 * time.Hour)
  1173  
  1174  	remainingWorkload := testWorkload()
  1175  	remainingWorkload.Services = []*structs.Service{
  1176  		{
  1177  			Name:      "remaining-service",
  1178  			PortLabel: "x",
  1179  			Checks: []*structs.ServiceCheck{
  1180  				{
  1181  					Name:     "check",
  1182  					Type:     "tcp",
  1183  					Interval: time.Second,
  1184  					Timeout:  time.Second,
  1185  				},
  1186  			},
  1187  		},
  1188  	}
  1189  	remainingWorkloadServiceID := serviceregistration.MakeAllocServiceID(remainingWorkload.AllocInfo.AllocID,
  1190  		remainingWorkload.Name(), remainingWorkload.Services[0])
  1191  
  1192  	require.NoError(ctx.ServiceClient.RegisterWorkload(remainingWorkload))
  1193  	require.NoError(ctx.syncOnce(syncNewOps))
  1194  	require.Len(ctx.FakeConsul.services, 1)
  1195  	require.Len(ctx.FakeConsul.checks, 1)
  1196  
  1197  	explicitlyRemovedWorkload := testWorkload()
  1198  	explicitlyRemovedWorkload.Services = []*structs.Service{
  1199  		{
  1200  			Name:      "explicitly-removed-service",
  1201  			PortLabel: "y",
  1202  			Checks: []*structs.ServiceCheck{
  1203  				{
  1204  					Name:     "check",
  1205  					Type:     "tcp",
  1206  					Interval: time.Second,
  1207  					Timeout:  time.Second,
  1208  				},
  1209  			},
  1210  		},
  1211  	}
  1212  	explicitlyRemovedWorkloadServiceID := serviceregistration.MakeAllocServiceID(explicitlyRemovedWorkload.AllocInfo.AllocID,
  1213  		explicitlyRemovedWorkload.Name(), explicitlyRemovedWorkload.Services[0])
  1214  
  1215  	require.NoError(ctx.ServiceClient.RegisterWorkload(explicitlyRemovedWorkload))
  1216  
  1217  	require.NoError(ctx.syncOnce(syncNewOps))
  1218  	require.Len(ctx.FakeConsul.services["default"], 2)
  1219  	require.Len(ctx.FakeConsul.checks["default"], 2)
  1220  
  1221  	// we register a task through nomad API then remove it out of band
  1222  	outofbandWorkload := testWorkload()
  1223  	outofbandWorkload.Services = []*structs.Service{
  1224  		{
  1225  			Name:      "unknown-service",
  1226  			PortLabel: "x",
  1227  			Checks: []*structs.ServiceCheck{
  1228  				{
  1229  					Name:     "check",
  1230  					Type:     "tcp",
  1231  					Interval: time.Second,
  1232  					Timeout:  time.Second,
  1233  				},
  1234  			},
  1235  		},
  1236  	}
  1237  	outofbandWorkloadServiceID := serviceregistration.MakeAllocServiceID(outofbandWorkload.AllocInfo.AllocID,
  1238  		outofbandWorkload.Name(), outofbandWorkload.Services[0])
  1239  
  1240  	require.NoError(ctx.ServiceClient.RegisterWorkload(outofbandWorkload))
  1241  	require.NoError(ctx.syncOnce(syncNewOps))
  1242  
  1243  	require.Len(ctx.FakeConsul.services["default"], 3)
  1244  
  1245  	// remove outofbandWorkload from local services so it appears unknown to client
  1246  	require.Len(ctx.ServiceClient.services, 3)
  1247  	require.Len(ctx.ServiceClient.checks, 3)
  1248  
  1249  	delete(ctx.ServiceClient.services, outofbandWorkloadServiceID)
  1250  	delete(ctx.ServiceClient.checks, MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1251  
  1252  	require.Len(ctx.ServiceClient.services, 2)
  1253  	require.Len(ctx.ServiceClient.checks, 2)
  1254  
  1255  	// Sync and ensure that explicitly removed service was removed, but outofbandWorkload remains
  1256  
  1257  	ctx.ServiceClient.RemoveWorkload(explicitlyRemovedWorkload)
  1258  	require.NoError(ctx.syncOnce(syncNewOps))
  1259  	require.NoError(ctx.ServiceClient.sync(syncNewOps))
  1260  	require.Len(ctx.FakeConsul.services["default"], 2)
  1261  	require.Len(ctx.FakeConsul.checks["default"], 2)
  1262  
  1263  	require.Contains(ctx.FakeConsul.services["default"], remainingWorkloadServiceID)
  1264  	require.Contains(ctx.FakeConsul.services["default"], outofbandWorkloadServiceID)
  1265  	require.NotContains(ctx.FakeConsul.services["default"], explicitlyRemovedWorkloadServiceID)
  1266  
  1267  	require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0]))
  1268  	require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1269  	require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0]))
  1270  
  1271  	// after probation, outofband services and checks are removed
  1272  	ctx.ServiceClient.deregisterProbationExpiry = time.Now().Add(-1 * time.Hour)
  1273  
  1274  	require.NoError(ctx.ServiceClient.sync(syncNewOps))
  1275  	require.Len(ctx.FakeConsul.services["default"], 1)
  1276  	require.Len(ctx.FakeConsul.checks["default"], 1)
  1277  
  1278  	require.Contains(ctx.FakeConsul.services["default"], remainingWorkloadServiceID)
  1279  	require.NotContains(ctx.FakeConsul.services["default"], outofbandWorkloadServiceID)
  1280  	require.NotContains(ctx.FakeConsul.services["default"], explicitlyRemovedWorkloadServiceID)
  1281  
  1282  	require.Contains(ctx.FakeConsul.checks["default"], MakeCheckID(remainingWorkloadServiceID, remainingWorkload.Services[0].Checks[0]))
  1283  	require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(outofbandWorkloadServiceID, outofbandWorkload.Services[0].Checks[0]))
  1284  	require.NotContains(ctx.FakeConsul.checks["default"], MakeCheckID(explicitlyRemovedWorkloadServiceID, explicitlyRemovedWorkload.Services[0].Checks[0]))
  1285  }