github.com/hernad/nomad@v1.6.112/command/agent/consul/unit_test.go (about)

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