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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package consul
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hashicorp/consul/api"
    12  	"github.com/hernad/nomad/ci"
    13  	"github.com/hernad/nomad/client/serviceregistration"
    14  	"github.com/hernad/nomad/helper/testlog"
    15  	"github.com/hernad/nomad/helper/uuid"
    16  	"github.com/hernad/nomad/nomad/structs"
    17  	"github.com/shoenig/test/must"
    18  	"github.com/stretchr/testify/require"
    19  	"golang.org/x/exp/maps"
    20  )
    21  
    22  func TestSyncLogic_maybeTweakTaggedAddresses(t *testing.T) {
    23  	ci.Parallel(t)
    24  
    25  	cases := []struct {
    26  		name     string
    27  		wanted   map[string]api.ServiceAddress
    28  		existing map[string]api.ServiceAddress
    29  		id       string
    30  		exp      []string
    31  	}{
    32  		{
    33  			name:   "not managed by nomad",
    34  			id:     "_nomad-other-hello",
    35  			wanted: map[string]api.ServiceAddress{
    36  				// empty
    37  			},
    38  			existing: map[string]api.ServiceAddress{
    39  				"lan_ipv4": {},
    40  				"wan_ipv4": {},
    41  				"custom":   {},
    42  			},
    43  			exp: []string{"lan_ipv4", "wan_ipv4", "custom"},
    44  		},
    45  		{
    46  			name: "remove defaults",
    47  			id:   "_nomad-task-hello",
    48  			wanted: map[string]api.ServiceAddress{
    49  				"lan_custom": {},
    50  				"wan_custom": {},
    51  			},
    52  			existing: map[string]api.ServiceAddress{
    53  				"lan_ipv4":   {},
    54  				"wan_ipv4":   {},
    55  				"lan_ipv6":   {},
    56  				"wan_ipv6":   {},
    57  				"lan_custom": {},
    58  				"wan_custom": {},
    59  			},
    60  			exp: []string{"lan_custom", "wan_custom"},
    61  		},
    62  		{
    63  			name: "overridden defaults",
    64  			id:   "_nomad-task-hello",
    65  			wanted: map[string]api.ServiceAddress{
    66  				"lan_ipv4": {},
    67  				"wan_ipv4": {},
    68  				"lan_ipv6": {},
    69  				"wan_ipv6": {},
    70  				"custom":   {},
    71  			},
    72  			existing: map[string]api.ServiceAddress{
    73  				"lan_ipv4": {},
    74  				"wan_ipv4": {},
    75  				"lan_ipv6": {},
    76  				"wan_ipv6": {},
    77  				"custom":   {},
    78  			},
    79  			exp: []string{"lan_ipv4", "wan_ipv4", "lan_ipv6", "wan_ipv6", "custom"},
    80  		},
    81  		{
    82  			name: "applies to nomad client",
    83  			id:   "_nomad-client-12345",
    84  			wanted: map[string]api.ServiceAddress{
    85  				"custom": {},
    86  			},
    87  			existing: map[string]api.ServiceAddress{
    88  				"lan_ipv4": {},
    89  				"wan_ipv4": {},
    90  				"lan_ipv6": {},
    91  				"wan_ipv6": {},
    92  				"custom":   {},
    93  			},
    94  			exp: []string{"custom"},
    95  		},
    96  		{
    97  			name: "applies to nomad server",
    98  			id:   "_nomad-server-12345",
    99  			wanted: map[string]api.ServiceAddress{
   100  				"custom": {},
   101  			},
   102  			existing: map[string]api.ServiceAddress{
   103  				"lan_ipv4": {},
   104  				"wan_ipv4": {},
   105  				"lan_ipv6": {},
   106  				"wan_ipv6": {},
   107  				"custom":   {},
   108  			},
   109  			exp: []string{"custom"},
   110  		},
   111  	}
   112  
   113  	for _, tc := range cases {
   114  		t.Run(tc.name, func(t *testing.T) {
   115  			asr := &api.AgentServiceRegistration{
   116  				ID:              tc.id,
   117  				TaggedAddresses: maps.Clone(tc.wanted),
   118  			}
   119  			as := &api.AgentService{
   120  				TaggedAddresses: maps.Clone(tc.existing),
   121  			}
   122  			maybeTweakTaggedAddresses(asr, as)
   123  			must.MapContainsKeys(t, as.TaggedAddresses, tc.exp)
   124  		})
   125  	}
   126  }
   127  
   128  func TestSyncLogic_agentServiceUpdateRequired(t *testing.T) {
   129  	ci.Parallel(t)
   130  
   131  	// the service as known by nomad
   132  	wanted := func() api.AgentServiceRegistration {
   133  		return api.AgentServiceRegistration{
   134  			Kind:              "",
   135  			ID:                "aca4c175-1778-5ef4-0220-2ab434147d35",
   136  			Name:              "myservice",
   137  			Tags:              []string{"a", "b"},
   138  			Port:              9000,
   139  			Address:           "1.1.1.1",
   140  			EnableTagOverride: true,
   141  			Meta:              map[string]string{"foo": "1"},
   142  			TaggedAddresses: map[string]api.ServiceAddress{
   143  				"public_wan": {Address: "1.2.3.4", Port: 8080},
   144  			},
   145  			Connect: &api.AgentServiceConnect{
   146  				Native: false,
   147  				SidecarService: &api.AgentServiceRegistration{
   148  					Kind: "connect-proxy",
   149  					ID:   "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy",
   150  					Name: "name-sidecar-proxy",
   151  					Tags: []string{"x", "y", "z"},
   152  					Proxy: &api.AgentServiceConnectProxyConfig{
   153  						Upstreams: []api.Upstream{{
   154  							Datacenter:      "dc1",
   155  							DestinationName: "dest1",
   156  						}},
   157  					},
   158  				},
   159  			},
   160  		}
   161  	}
   162  
   163  	// the service (and + connect proxy) as known by consul
   164  	existing := &api.AgentService{
   165  		Kind:              "",
   166  		ID:                "aca4c175-1778-5ef4-0220-2ab434147d35",
   167  		Service:           "myservice",
   168  		Tags:              []string{"a", "b"},
   169  		Port:              9000,
   170  		Address:           "1.1.1.1",
   171  		EnableTagOverride: true,
   172  		Meta:              map[string]string{"foo": "1"},
   173  		TaggedAddresses: map[string]api.ServiceAddress{
   174  			"public_wan": {Address: "1.2.3.4", Port: 8080},
   175  		},
   176  	}
   177  
   178  	sidecar := &api.AgentService{
   179  		Kind:    "connect-proxy",
   180  		ID:      "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy",
   181  		Service: "myservice-sidecar-proxy",
   182  		Tags:    []string{"x", "y", "z"},
   183  		Proxy: &api.AgentServiceConnectProxyConfig{
   184  			Upstreams: []api.Upstream{{
   185  				Datacenter:      "dc1",
   186  				DestinationName: "dest1",
   187  			}},
   188  		},
   189  	}
   190  
   191  	// By default wanted and existing match. Each test should modify wanted in
   192  	// 1 way, and / or configure the type of sync operation that is being
   193  	// considered, then evaluate the result of the update-required algebra.
   194  
   195  	type asr = api.AgentServiceRegistration
   196  	type tweaker func(w asr) *asr // create a conveniently modifiable copy
   197  
   198  	s := &ServiceClient{
   199  		logger: testlog.HCLogger(t),
   200  	}
   201  
   202  	try := func(
   203  		t *testing.T,
   204  		exp bool,
   205  		reason syncReason,
   206  		tweak tweaker) {
   207  		result := s.agentServiceUpdateRequired(reason, tweak(wanted()), existing, sidecar)
   208  		require.Equal(t, exp, result)
   209  	}
   210  
   211  	t.Run("matching", func(t *testing.T) {
   212  		try(t, false, syncNewOps, func(w asr) *asr {
   213  			return &w
   214  		})
   215  	})
   216  
   217  	t.Run("different kind", func(t *testing.T) {
   218  		try(t, true, syncNewOps, func(w asr) *asr {
   219  			w.Kind = "other"
   220  			return &w
   221  		})
   222  	})
   223  
   224  	t.Run("different id", func(t *testing.T) {
   225  		try(t, true, syncNewOps, func(w asr) *asr {
   226  			w.ID = "_other"
   227  			return &w
   228  		})
   229  	})
   230  
   231  	t.Run("different port", func(t *testing.T) {
   232  		try(t, true, syncNewOps, func(w asr) *asr {
   233  			w.Port = 9001
   234  			return &w
   235  		})
   236  	})
   237  
   238  	t.Run("different address", func(t *testing.T) {
   239  		try(t, true, syncNewOps, func(w asr) *asr {
   240  			w.Address = "2.2.2.2"
   241  			return &w
   242  		})
   243  	})
   244  
   245  	t.Run("different name", func(t *testing.T) {
   246  		try(t, true, syncNewOps, func(w asr) *asr {
   247  			w.Name = "bob"
   248  			return &w
   249  		})
   250  	})
   251  
   252  	t.Run("different enable_tag_override", func(t *testing.T) {
   253  		try(t, true, syncNewOps, func(w asr) *asr {
   254  			w.EnableTagOverride = false
   255  			return &w
   256  		})
   257  	})
   258  
   259  	t.Run("different meta", func(t *testing.T) {
   260  		try(t, true, syncNewOps, func(w asr) *asr {
   261  			w.Meta = map[string]string{"foo": "2"}
   262  			return &w
   263  		})
   264  	})
   265  
   266  	t.Run("different sidecar upstream", func(t *testing.T) {
   267  		try(t, true, syncNewOps, func(w asr) *asr {
   268  			w.Connect.SidecarService.Proxy.Upstreams[0].DestinationName = "dest2"
   269  			return &w
   270  		})
   271  	})
   272  
   273  	t.Run("remove sidecar upstream", func(t *testing.T) {
   274  		try(t, true, syncNewOps, func(w asr) *asr {
   275  			w.Connect.SidecarService.Proxy.Upstreams = nil
   276  			return &w
   277  		})
   278  	})
   279  
   280  	t.Run("additional sidecar upstream", func(t *testing.T) {
   281  		try(t, true, syncNewOps, func(w asr) *asr {
   282  			w.Connect.SidecarService.Proxy.Upstreams = append(
   283  				w.Connect.SidecarService.Proxy.Upstreams,
   284  				api.Upstream{
   285  					Datacenter:      "dc2",
   286  					DestinationName: "dest2",
   287  				},
   288  			)
   289  			return &w
   290  		})
   291  	})
   292  
   293  	t.Run("nil proxy block", func(t *testing.T) {
   294  		try(t, true, syncNewOps, func(w asr) *asr {
   295  			w.Connect.SidecarService.Proxy = nil
   296  			return &w
   297  		})
   298  	})
   299  
   300  	t.Run("different tags syncNewOps eto=true", func(t *testing.T) {
   301  		// sync is required even though eto=true, because NewOps indicates the
   302  		// service definition  in nomad has changed (e.g. job run a modified job)
   303  		try(t, true, syncNewOps, func(w asr) *asr {
   304  			w.Tags = []string{"other", "tags"}
   305  			return &w
   306  		})
   307  	})
   308  
   309  	t.Run("different tags syncPeriodic eto=true", func(t *testing.T) {
   310  		// sync is not required since eto=true and this is a periodic sync
   311  		// with consul - in which case we keep Consul's definition of the tags
   312  		try(t, false, syncPeriodic, func(w asr) *asr {
   313  			w.Tags = []string{"other", "tags"}
   314  			return &w
   315  		})
   316  	})
   317  
   318  	t.Run("different sidecar tags on syncPeriodic eto=true", func(t *testing.T) {
   319  		try(t, false, syncPeriodic, func(w asr) *asr {
   320  			// like the parent service, the sidecar's tags do not get enforced
   321  			// if ETO is true and this is a periodic sync
   322  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   323  			return &w
   324  		})
   325  	})
   326  
   327  	t.Run("different sidecar tags on syncNewOps eto=true", func(t *testing.T) {
   328  		try(t, true, syncNewOps, func(w asr) *asr {
   329  			// like the parent service, the sidecar's tags always get enforced
   330  			// regardless of ETO if this is a sync due to applied operations
   331  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   332  			return &w
   333  		})
   334  	})
   335  
   336  	t.Run("different tagged addresses", func(t *testing.T) {
   337  		try(t, true, syncNewOps, func(w asr) *asr {
   338  			w.TaggedAddresses = map[string]api.ServiceAddress{
   339  				"public_wan": {Address: "5.6.7.8", Port: 8080},
   340  			}
   341  			return &w
   342  		})
   343  	})
   344  
   345  	// for remaining tests, EnableTagOverride = false
   346  	existing.EnableTagOverride = false
   347  
   348  	t.Run("different tags syncPeriodic eto=false", func(t *testing.T) {
   349  		// sync is required because eto=false and the tags do not match
   350  		try(t, true, syncPeriodic, func(w asr) *asr {
   351  			w.EnableTagOverride = false
   352  			w.Tags = []string{"other", "tags"}
   353  			return &w
   354  		})
   355  	})
   356  
   357  	t.Run("different tags syncNewOps eto=false", func(t *testing.T) {
   358  		// sync is required because eto=false and the tags do not match
   359  		try(t, true, syncNewOps, func(w asr) *asr {
   360  			w.EnableTagOverride = false
   361  			w.Tags = []string{"other", "tags"}
   362  			return &w
   363  		})
   364  	})
   365  
   366  	t.Run("different sidecar tags on syncPeriodic eto=false", func(t *testing.T) {
   367  		// like the parent service, sync is required because eto=false and the
   368  		// sidecar's tags do not match
   369  		try(t, true, syncPeriodic, func(w asr) *asr {
   370  			w.EnableTagOverride = false
   371  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   372  			return &w
   373  		})
   374  	})
   375  
   376  	t.Run("different sidecar tags syncNewOps eto=false", func(t *testing.T) {
   377  		// like the parent service, sync is required because eto=false and the
   378  		// sidecar's tags do not match
   379  		try(t, true, syncNewOps, func(w asr) *asr {
   380  			w.EnableTagOverride = false
   381  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   382  			return &w
   383  		})
   384  	})
   385  }
   386  
   387  func TestSyncLogic_sidecarTagsDifferent(t *testing.T) {
   388  	ci.Parallel(t)
   389  
   390  	type tc struct {
   391  		parent, wanted, sidecar []string
   392  		expect                  bool
   393  	}
   394  
   395  	try := func(t *testing.T, test tc) {
   396  		result := sidecarTagsDifferent(test.parent, test.wanted, test.sidecar)
   397  		require.Equal(t, test.expect, result)
   398  	}
   399  
   400  	try(t, tc{parent: nil, wanted: nil, sidecar: nil, expect: false})
   401  
   402  	// wanted is nil, compare sidecar to parent
   403  	try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: nil, expect: true})
   404  	try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: []string{"foo"}, expect: false})
   405  	try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: []string{"bar"}, expect: true})
   406  	try(t, tc{parent: nil, wanted: nil, sidecar: []string{"foo"}, expect: true})
   407  
   408  	// wanted is non-nil, compare sidecar to wanted
   409  	try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: nil, expect: true})
   410  	try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: []string{"foo"}, expect: false})
   411  	try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: []string{"bar"}, expect: true})
   412  	try(t, tc{parent: []string{"foo"}, wanted: []string{"foo"}, sidecar: []string{"bar"}, expect: true})
   413  }
   414  
   415  func TestSyncLogic_maybeTweakTags(t *testing.T) {
   416  	ci.Parallel(t)
   417  
   418  	differentPointers := func(a, b []string) bool {
   419  		return &(a) != &(b)
   420  	}
   421  
   422  	try := func(inConsul, inConsulSC []string, eto bool) {
   423  		wanted := &api.AgentServiceRegistration{
   424  			Tags: []string{"original"},
   425  			Connect: &api.AgentServiceConnect{
   426  				SidecarService: &api.AgentServiceRegistration{
   427  					Tags: []string{"original-sidecar"},
   428  				},
   429  			},
   430  			EnableTagOverride: eto,
   431  		}
   432  
   433  		existing := &api.AgentService{Tags: inConsul}
   434  		sidecar := &api.AgentService{Tags: inConsulSC}
   435  
   436  		maybeTweakTags(wanted, existing, sidecar)
   437  
   438  		switch eto {
   439  		case false:
   440  			require.Equal(t, []string{"original"}, wanted.Tags)
   441  			require.Equal(t, []string{"original-sidecar"}, wanted.Connect.SidecarService.Tags)
   442  			require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags))
   443  		case true:
   444  			require.Equal(t, inConsul, wanted.Tags)
   445  			require.Equal(t, inConsulSC, wanted.Connect.SidecarService.Tags)
   446  			require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags))
   447  		}
   448  	}
   449  
   450  	try([]string{"original"}, []string{"original-sidecar"}, true)
   451  	try([]string{"original"}, []string{"original-sidecar"}, false)
   452  	try([]string{"modified"}, []string{"original-sidecar"}, true)
   453  	try([]string{"modified"}, []string{"original-sidecar"}, false)
   454  	try([]string{"original"}, []string{"modified-sidecar"}, true)
   455  	try([]string{"original"}, []string{"modified-sidecar"}, false)
   456  	try([]string{"modified"}, []string{"modified-sidecar"}, true)
   457  	try([]string{"modified"}, []string{"modified-sidecar"}, false)
   458  }
   459  
   460  func TestSyncLogic_maybeTweakTags_emptySC(t *testing.T) {
   461  	ci.Parallel(t)
   462  
   463  	// Check the edge cases where the connect service is deleted on the nomad
   464  	// side (i.e. are we checking multiple nil pointers).
   465  
   466  	try := func(asr *api.AgentServiceRegistration) {
   467  		existing := &api.AgentService{Tags: []string{"a", "b"}}
   468  		sidecar := &api.AgentService{Tags: []string{"a", "b"}}
   469  		maybeTweakTags(asr, existing, sidecar)
   470  		must.NotEq(t, []string{"original"}, asr.Tags)
   471  	}
   472  
   473  	try(&api.AgentServiceRegistration{
   474  		Tags:              []string{"original"},
   475  		EnableTagOverride: true,
   476  		Connect:           nil, // ooh danger!
   477  	})
   478  
   479  	try(&api.AgentServiceRegistration{
   480  		Tags:              []string{"original"},
   481  		EnableTagOverride: true,
   482  		Connect: &api.AgentServiceConnect{
   483  			SidecarService: nil, // ooh danger!
   484  		},
   485  	})
   486  }
   487  
   488  // TestServiceRegistration_CheckOnUpdate tests that a ServiceRegistrations
   489  // CheckOnUpdate is populated and updated properly
   490  func TestServiceRegistration_CheckOnUpdate(t *testing.T) {
   491  	ci.Parallel(t)
   492  
   493  	mockAgent := NewMockAgent(ossFeatures)
   494  	namespacesClient := NewNamespacesClient(NewMockNamespaces(nil), mockAgent)
   495  	logger := testlog.HCLogger(t)
   496  	sc := NewServiceClient(mockAgent, namespacesClient, logger, true)
   497  
   498  	allocID := uuid.Generate()
   499  	ws := &serviceregistration.WorkloadServices{
   500  		AllocInfo: structs.AllocInfo{
   501  			AllocID: allocID,
   502  			Task:    "taskname",
   503  		},
   504  		Restarter: &restartRecorder{},
   505  		Services: []*structs.Service{
   506  			{
   507  				Name:      "taskname-service",
   508  				PortLabel: "x",
   509  				Tags:      []string{"tag1", "tag2"},
   510  				Meta:      map[string]string{"meta1": "foo"},
   511  				Checks: []*structs.ServiceCheck{
   512  					{
   513  
   514  						Name:      "c1",
   515  						Type:      "tcp",
   516  						Interval:  time.Second,
   517  						Timeout:   time.Second,
   518  						PortLabel: "x",
   519  						OnUpdate:  structs.OnUpdateIgnoreWarn,
   520  					},
   521  				},
   522  			},
   523  		},
   524  		Networks: []*structs.NetworkResource{
   525  			{
   526  				DynamicPorts: []structs.Port{
   527  					{Label: "x", Value: xPort},
   528  					{Label: "y", Value: yPort},
   529  				},
   530  			},
   531  		},
   532  	}
   533  
   534  	require.NoError(t, sc.RegisterWorkload(ws))
   535  
   536  	require.NotNil(t, sc.allocRegistrations[allocID])
   537  
   538  	allocReg := sc.allocRegistrations[allocID]
   539  	serviceReg := allocReg.Tasks["taskname"]
   540  	require.NotNil(t, serviceReg)
   541  
   542  	// Ensure that CheckOnUpdate was set correctly
   543  	require.Len(t, serviceReg.Services, 1)
   544  	for _, sreg := range serviceReg.Services {
   545  		require.NotEmpty(t, sreg.CheckOnUpdate)
   546  		for _, onupdate := range sreg.CheckOnUpdate {
   547  			require.Equal(t, structs.OnUpdateIgnoreWarn, onupdate)
   548  		}
   549  	}
   550  
   551  	// Update
   552  	wsUpdate := new(serviceregistration.WorkloadServices)
   553  	*wsUpdate = *ws
   554  	wsUpdate.Services[0].Checks[0].OnUpdate = structs.OnUpdateRequireHealthy
   555  
   556  	require.NoError(t, sc.UpdateWorkload(ws, wsUpdate))
   557  
   558  	require.NotNil(t, sc.allocRegistrations[allocID])
   559  
   560  	allocReg = sc.allocRegistrations[allocID]
   561  	serviceReg = allocReg.Tasks["taskname"]
   562  	require.NotNil(t, serviceReg)
   563  
   564  	// Ensure that CheckOnUpdate was updated correctly
   565  	require.Len(t, serviceReg.Services, 1)
   566  	for _, sreg := range serviceReg.Services {
   567  		require.NotEmpty(t, sreg.CheckOnUpdate)
   568  		for _, onupdate := range sreg.CheckOnUpdate {
   569  			require.Equal(t, structs.OnUpdateRequireHealthy, onupdate)
   570  		}
   571  	}
   572  }
   573  
   574  func TestSyncLogic_proxyUpstreamsDifferent(t *testing.T) {
   575  	ci.Parallel(t)
   576  
   577  	upstream1 := func() api.Upstream {
   578  		return api.Upstream{
   579  			Datacenter:       "sfo",
   580  			DestinationName:  "billing",
   581  			LocalBindAddress: "127.0.0.1",
   582  			LocalBindPort:    5050,
   583  			MeshGateway: api.MeshGatewayConfig{
   584  				Mode: "remote",
   585  			},
   586  			Config: map[string]interface{}{"foo": 1},
   587  		}
   588  	}
   589  
   590  	upstream2 := func() api.Upstream {
   591  		return api.Upstream{
   592  			Datacenter:       "ny",
   593  			DestinationName:  "metrics",
   594  			LocalBindAddress: "127.0.0.1",
   595  			LocalBindPort:    6060,
   596  			MeshGateway: api.MeshGatewayConfig{
   597  				Mode: "local",
   598  			},
   599  			Config: nil,
   600  		}
   601  	}
   602  
   603  	newASC := func() *api.AgentServiceConnect {
   604  		return &api.AgentServiceConnect{
   605  			SidecarService: &api.AgentServiceRegistration{
   606  				Proxy: &api.AgentServiceConnectProxyConfig{
   607  					Upstreams: []api.Upstream{
   608  						upstream1(),
   609  						upstream2(),
   610  					},
   611  				},
   612  			},
   613  		}
   614  	}
   615  
   616  	original := newASC()
   617  
   618  	t.Run("same", func(t *testing.T) {
   619  		require.False(t, proxyUpstreamsDifferent(original, newASC().SidecarService.Proxy))
   620  	})
   621  
   622  	type proxy = *api.AgentServiceConnectProxyConfig
   623  	type tweaker = func(proxy)
   624  
   625  	try := func(t *testing.T, desc string, tweak tweaker) {
   626  		t.Run(desc, func(t *testing.T) {
   627  			p := newASC().SidecarService.Proxy
   628  			tweak(p)
   629  			require.True(t, proxyUpstreamsDifferent(original, p))
   630  		})
   631  	}
   632  
   633  	try(t, "empty upstreams", func(p proxy) {
   634  		p.Upstreams = make([]api.Upstream, 0)
   635  	})
   636  
   637  	try(t, "missing upstream", func(p proxy) {
   638  		p.Upstreams = []api.Upstream{
   639  			upstream1(),
   640  		}
   641  	})
   642  
   643  	try(t, "extra upstream", func(p proxy) {
   644  		p.Upstreams = []api.Upstream{
   645  			upstream1(),
   646  			upstream2(),
   647  			{
   648  				Datacenter:      "north",
   649  				DestinationName: "dest3",
   650  			},
   651  		}
   652  	})
   653  
   654  	try(t, "different datacenter", func(p proxy) {
   655  		diff := upstream2()
   656  		diff.Datacenter = "south"
   657  		p.Upstreams = []api.Upstream{
   658  			upstream1(),
   659  			diff,
   660  		}
   661  	})
   662  
   663  	try(t, "different destination", func(p proxy) {
   664  		diff := upstream2()
   665  		diff.DestinationName = "sink"
   666  		p.Upstreams = []api.Upstream{
   667  			upstream1(),
   668  			diff,
   669  		}
   670  	})
   671  
   672  	try(t, "different local_bind_address", func(p proxy) {
   673  		diff := upstream2()
   674  		diff.LocalBindAddress = "10.0.0.1"
   675  		p.Upstreams = []api.Upstream{
   676  			upstream1(),
   677  			diff,
   678  		}
   679  	})
   680  
   681  	try(t, "different local_bind_port", func(p proxy) {
   682  		diff := upstream2()
   683  		diff.LocalBindPort = 9999
   684  		p.Upstreams = []api.Upstream{
   685  			upstream1(),
   686  			diff,
   687  		}
   688  	})
   689  
   690  	try(t, "different mesh gateway mode", func(p proxy) {
   691  		diff := upstream2()
   692  		diff.MeshGateway.Mode = "none"
   693  		p.Upstreams = []api.Upstream{
   694  			upstream1(),
   695  			diff,
   696  		}
   697  	})
   698  
   699  	try(t, "different config", func(p proxy) {
   700  		diff := upstream1()
   701  		diff.Config = map[string]interface{}{"foo": 2}
   702  		p.Upstreams = []api.Upstream{
   703  			diff,
   704  			upstream2(),
   705  		}
   706  	})
   707  }
   708  
   709  func TestSyncReason_String(t *testing.T) {
   710  	ci.Parallel(t)
   711  
   712  	require.Equal(t, "periodic", fmt.Sprintf("%s", syncPeriodic))
   713  	require.Equal(t, "shutdown", fmt.Sprintf("%s", syncShutdown))
   714  	require.Equal(t, "operations", fmt.Sprintf("%s", syncNewOps))
   715  	require.Equal(t, "unexpected", fmt.Sprintf("%s", syncReason(128)))
   716  }
   717  
   718  func TestSyncOps_empty(t *testing.T) {
   719  	ci.Parallel(t)
   720  
   721  	try := func(ops *operations, exp bool) {
   722  		require.Equal(t, exp, ops.empty())
   723  	}
   724  
   725  	try(&operations{regServices: make([]*api.AgentServiceRegistration, 1)}, false)
   726  	try(&operations{regChecks: make([]*api.AgentCheckRegistration, 1)}, false)
   727  	try(&operations{deregServices: make([]string, 1)}, false)
   728  	try(&operations{deregChecks: make([]string, 1)}, false)
   729  	try(&operations{}, true)
   730  	try(nil, true)
   731  }
   732  
   733  func TestSyncLogic_maybeSidecarProxyCheck(t *testing.T) {
   734  	ci.Parallel(t)
   735  
   736  	try := func(input string, exp bool) {
   737  		result := maybeSidecarProxyCheck(input)
   738  		require.Equal(t, exp, result)
   739  	}
   740  
   741  	try("service:_nomad-task-2f5fb517-57d4-44ee-7780-dc1cb6e103cd-group-api-count-api-9001-sidecar-proxy", true)
   742  	try("service:_nomad-task-2f5fb517-57d4-44ee-7780-dc1cb6e103cd-group-api-count-api-9001-sidecar-proxy:1", true)
   743  	try("service:_nomad-task-2f5fb517-57d4-44ee-7780-dc1cb6e103cd-group-api-count-api-9001-sidecar-proxy:2", true)
   744  	try("service:_nomad-task-2f5fb517-57d4-44ee-7780-dc1cb6e103cd-group-api-count-api-9001", false)
   745  	try("_nomad-task-2f5fb517-57d4-44ee-7780-dc1cb6e103cd-group-api-count-api-9001-sidecar-proxy:1", false)
   746  	try("service:_nomad-task-2f5fb517-57d4-44ee-7780-dc1cb6e103cd-group-api-count-api-9001-sidecar-proxy:X", false)
   747  	try("service:_nomad-task-2f5fb517-57d4-44ee-7780-dc1cb6e103cd-group-api-count-api-9001-sidecar-proxy: ", false)
   748  	try("service", false)
   749  }
   750  
   751  func TestSyncLogic_parseTaggedAddresses(t *testing.T) {
   752  	ci.Parallel(t)
   753  
   754  	t.Run("nil", func(t *testing.T) {
   755  		m, err := parseTaggedAddresses(nil, 0)
   756  		must.NoError(t, err)
   757  		must.MapEmpty(t, m)
   758  	})
   759  
   760  	t.Run("parse fail", func(t *testing.T) {
   761  		ta := map[string]string{
   762  			"public_wan": "not an address",
   763  		}
   764  		result, err := parseTaggedAddresses(ta, 8080)
   765  		must.Error(t, err)
   766  		must.MapEmpty(t, result)
   767  	})
   768  
   769  	t.Run("parse address", func(t *testing.T) {
   770  		ta := map[string]string{
   771  			"public_wan": "1.2.3.4",
   772  		}
   773  		result, err := parseTaggedAddresses(ta, 8080)
   774  		must.NoError(t, err)
   775  		must.MapEq(t, map[string]api.ServiceAddress{
   776  			"public_wan": {Address: "1.2.3.4", Port: 8080},
   777  		}, result)
   778  	})
   779  
   780  	t.Run("parse address and port", func(t *testing.T) {
   781  		ta := map[string]string{
   782  			"public_wan": "1.2.3.4:9999",
   783  		}
   784  		result, err := parseTaggedAddresses(ta, 8080)
   785  		must.NoError(t, err)
   786  		must.MapEq(t, map[string]api.ServiceAddress{
   787  			"public_wan": {Address: "1.2.3.4", Port: 9999},
   788  		}, result)
   789  	})
   790  }