github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/command/agent/consul/service_client_test.go (about)

     1  package consul
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/hashicorp/consul/api"
     9  	"github.com/hashicorp/nomad/helper/testlog"
    10  	"github.com/hashicorp/nomad/helper/uuid"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  var (
    16  	// the service as known by nomad
    17  	wanted = api.AgentServiceRegistration{
    18  		Kind:              "",
    19  		ID:                "aca4c175-1778-5ef4-0220-2ab434147d35",
    20  		Name:              "myservice",
    21  		Tags:              []string{"a", "b"},
    22  		Port:              9000,
    23  		Address:           "1.1.1.1",
    24  		EnableTagOverride: true,
    25  		Meta:              map[string]string{"foo": "1"},
    26  		Connect: &api.AgentServiceConnect{
    27  			Native: false,
    28  			SidecarService: &api.AgentServiceRegistration{
    29  				Kind: "connect-proxy",
    30  				ID:   "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy",
    31  				Name: "name-sidecar-proxy",
    32  				Tags: []string{"x", "y", "z"},
    33  			},
    34  		},
    35  	}
    36  
    37  	// the service (and + connect proxy) as known by consul
    38  	existing = &api.AgentService{
    39  		Kind:              "",
    40  		ID:                "aca4c175-1778-5ef4-0220-2ab434147d35",
    41  		Service:           "myservice",
    42  		Tags:              []string{"a", "b"},
    43  		Port:              9000,
    44  		Address:           "1.1.1.1",
    45  		EnableTagOverride: true,
    46  		Meta:              map[string]string{"foo": "1"},
    47  	}
    48  
    49  	sidecar = &api.AgentService{
    50  		Kind:    "connect-proxy",
    51  		ID:      "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy",
    52  		Service: "myservice-sidecar-proxy",
    53  		Tags:    []string{"x", "y", "z"},
    54  	}
    55  )
    56  
    57  func TestSyncLogic_agentServiceUpdateRequired(t *testing.T) {
    58  	t.Parallel()
    59  
    60  	// By default wanted and existing match. Each test should modify wanted in
    61  	// 1 way, and / or configure the type of sync operation that is being
    62  	// considered, then evaluate the result of the update-required algebra.
    63  
    64  	type asr = api.AgentServiceRegistration
    65  	type tweaker func(w asr) *asr // create a conveniently modifiable copy
    66  
    67  	try := func(
    68  		t *testing.T,
    69  		exp bool,
    70  		reason syncReason,
    71  		tweak tweaker) {
    72  		result := agentServiceUpdateRequired(reason, tweak(wanted), existing, sidecar)
    73  		require.Equal(t, exp, result)
    74  	}
    75  
    76  	t.Run("matching", func(t *testing.T) {
    77  		try(t, false, syncNewOps, func(w asr) *asr {
    78  			return &w
    79  		})
    80  	})
    81  
    82  	t.Run("different kind", func(t *testing.T) {
    83  		try(t, true, syncNewOps, func(w asr) *asr {
    84  			w.Kind = "other"
    85  			return &w
    86  		})
    87  	})
    88  
    89  	t.Run("different id", func(t *testing.T) {
    90  		try(t, true, syncNewOps, func(w asr) *asr {
    91  			w.ID = "_other"
    92  			return &w
    93  		})
    94  	})
    95  
    96  	t.Run("different port", func(t *testing.T) {
    97  		try(t, true, syncNewOps, func(w asr) *asr {
    98  			w.Port = 9001
    99  			return &w
   100  		})
   101  	})
   102  
   103  	t.Run("different address", func(t *testing.T) {
   104  		try(t, true, syncNewOps, func(w asr) *asr {
   105  			w.Address = "2.2.2.2"
   106  			return &w
   107  		})
   108  	})
   109  
   110  	t.Run("different name", func(t *testing.T) {
   111  		try(t, true, syncNewOps, func(w asr) *asr {
   112  			w.Name = "bob"
   113  			return &w
   114  		})
   115  	})
   116  
   117  	t.Run("different enable_tag_override", func(t *testing.T) {
   118  		try(t, true, syncNewOps, func(w asr) *asr {
   119  			w.EnableTagOverride = false
   120  			return &w
   121  		})
   122  	})
   123  
   124  	t.Run("different meta", func(t *testing.T) {
   125  		try(t, true, syncNewOps, func(w asr) *asr {
   126  			w.Meta = map[string]string{"foo": "2"}
   127  			return &w
   128  		})
   129  	})
   130  
   131  	t.Run("different tags syncNewOps eto=true", func(t *testing.T) {
   132  		// sync is required even though eto=true, because NewOps indicates the
   133  		// service definition  in nomad has changed (e.g. job run a modified job)
   134  		try(t, true, syncNewOps, func(w asr) *asr {
   135  			w.Tags = []string{"other", "tags"}
   136  			return &w
   137  		})
   138  	})
   139  
   140  	t.Run("different tags syncPeriodic eto=true", func(t *testing.T) {
   141  		// sync is not required since eto=true and this is a periodic sync
   142  		// with consul - in which case we keep Consul's definition of the tags
   143  		try(t, false, syncPeriodic, func(w asr) *asr {
   144  			w.Tags = []string{"other", "tags"}
   145  			return &w
   146  		})
   147  	})
   148  
   149  	t.Run("different sidecar tags on syncPeriodic eto=true", func(t *testing.T) {
   150  		try(t, false, syncPeriodic, func(w asr) *asr {
   151  			// like the parent service, the sidecar's tags do not get enforced
   152  			// if ETO is true and this is a periodic sync
   153  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   154  			return &w
   155  		})
   156  	})
   157  
   158  	t.Run("different sidecar tags on syncNewOps eto=true", func(t *testing.T) {
   159  		try(t, true, syncNewOps, func(w asr) *asr {
   160  			// like the parent service, the sidecar's tags always get enforced
   161  			// regardless of ETO if this is a sync due to applied operations
   162  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   163  			return &w
   164  		})
   165  	})
   166  
   167  	// for remaining tests, EnableTagOverride = false
   168  	wanted.EnableTagOverride = false
   169  	existing.EnableTagOverride = false
   170  
   171  	t.Run("different tags syncPeriodic eto=false", func(t *testing.T) {
   172  		// sync is required because eto=false and the tags do not match
   173  		try(t, true, syncPeriodic, func(w asr) *asr {
   174  			w.Tags = []string{"other", "tags"}
   175  			return &w
   176  		})
   177  	})
   178  
   179  	t.Run("different tags syncNewOps eto=false", func(t *testing.T) {
   180  		// sync is required because eto=false and the tags do not match
   181  		try(t, true, syncNewOps, func(w asr) *asr {
   182  			w.Tags = []string{"other", "tags"}
   183  			return &w
   184  		})
   185  	})
   186  
   187  	t.Run("different sidecar tags on syncPeriodic eto=false", func(t *testing.T) {
   188  		// like the parent service, sync is required because eto=false and the
   189  		// sidecar's tags do not match
   190  		try(t, true, syncPeriodic, func(w asr) *asr {
   191  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   192  			return &w
   193  		})
   194  	})
   195  
   196  	t.Run("different sidecar tags syncNewOps eto=false", func(t *testing.T) {
   197  		// like the parent service, sync is required because eto=false and the
   198  		// sidecar's tags do not match
   199  		try(t, true, syncNewOps, func(w asr) *asr {
   200  			w.Connect.SidecarService.Tags = []string{"other", "tags"}
   201  			return &w
   202  		})
   203  	})
   204  }
   205  
   206  func TestSyncLogic_tagsDifferent(t *testing.T) {
   207  	t.Run("nil nil", func(t *testing.T) {
   208  		require.False(t, tagsDifferent(nil, nil))
   209  	})
   210  
   211  	t.Run("empty nil", func(t *testing.T) {
   212  		// where reflect.DeepEqual does not work
   213  		require.False(t, tagsDifferent([]string{}, nil))
   214  	})
   215  
   216  	t.Run("empty empty", func(t *testing.T) {
   217  		require.False(t, tagsDifferent([]string{}, []string{}))
   218  	})
   219  
   220  	t.Run("set empty", func(t *testing.T) {
   221  		require.True(t, tagsDifferent([]string{"A"}, []string{}))
   222  	})
   223  
   224  	t.Run("set nil", func(t *testing.T) {
   225  		require.True(t, tagsDifferent([]string{"A"}, nil))
   226  	})
   227  
   228  	t.Run("different content", func(t *testing.T) {
   229  		require.True(t, tagsDifferent([]string{"A"}, []string{"B"}))
   230  	})
   231  
   232  	t.Run("different lengths", func(t *testing.T) {
   233  		require.True(t, tagsDifferent([]string{"A"}, []string{"A", "B"}))
   234  	})
   235  }
   236  
   237  func TestSyncLogic_sidecarTagsDifferent(t *testing.T) {
   238  	type tc struct {
   239  		parent, wanted, sidecar []string
   240  		expect                  bool
   241  	}
   242  
   243  	try := func(t *testing.T, test tc) {
   244  		result := sidecarTagsDifferent(test.parent, test.wanted, test.sidecar)
   245  		require.Equal(t, test.expect, result)
   246  	}
   247  
   248  	try(t, tc{parent: nil, wanted: nil, sidecar: nil, expect: false})
   249  
   250  	// wanted is nil, compare sidecar to parent
   251  	try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: nil, expect: true})
   252  	try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: []string{"foo"}, expect: false})
   253  	try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: []string{"bar"}, expect: true})
   254  	try(t, tc{parent: nil, wanted: nil, sidecar: []string{"foo"}, expect: true})
   255  
   256  	// wanted is non-nil, compare sidecar to wanted
   257  	try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: nil, expect: true})
   258  	try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: []string{"foo"}, expect: false})
   259  	try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: []string{"bar"}, expect: true})
   260  	try(t, tc{parent: []string{"foo"}, wanted: []string{"foo"}, sidecar: []string{"bar"}, expect: true})
   261  }
   262  
   263  func TestSyncLogic_maybeTweakTags(t *testing.T) {
   264  	t.Parallel()
   265  
   266  	differentPointers := func(a, b []string) bool {
   267  		return &(a) != &(b)
   268  	}
   269  
   270  	try := func(inConsul, inConsulSC []string, eto bool) {
   271  		wanted := &api.AgentServiceRegistration{
   272  			Tags: []string{"original"},
   273  			Connect: &api.AgentServiceConnect{
   274  				SidecarService: &api.AgentServiceRegistration{
   275  					Tags: []string{"original-sidecar"},
   276  				},
   277  			},
   278  			EnableTagOverride: eto,
   279  		}
   280  
   281  		existing := &api.AgentService{Tags: inConsul}
   282  		sidecar := &api.AgentService{Tags: inConsulSC}
   283  
   284  		maybeTweakTags(wanted, existing, sidecar)
   285  
   286  		switch eto {
   287  		case false:
   288  			require.Equal(t, []string{"original"}, wanted.Tags)
   289  			require.Equal(t, []string{"original-sidecar"}, wanted.Connect.SidecarService.Tags)
   290  			require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags))
   291  		case true:
   292  			require.Equal(t, inConsul, wanted.Tags)
   293  			require.Equal(t, inConsulSC, wanted.Connect.SidecarService.Tags)
   294  			require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags))
   295  		}
   296  	}
   297  
   298  	try([]string{"original"}, []string{"original-sidecar"}, true)
   299  	try([]string{"original"}, []string{"original-sidecar"}, false)
   300  	try([]string{"modified"}, []string{"original-sidecar"}, true)
   301  	try([]string{"modified"}, []string{"original-sidecar"}, false)
   302  	try([]string{"original"}, []string{"modified-sidecar"}, true)
   303  	try([]string{"original"}, []string{"modified-sidecar"}, false)
   304  	try([]string{"modified"}, []string{"modified-sidecar"}, true)
   305  	try([]string{"modified"}, []string{"modified-sidecar"}, false)
   306  }
   307  
   308  func TestSyncLogic_maybeTweakTags_emptySC(t *testing.T) {
   309  	t.Parallel()
   310  
   311  	// Check the edge cases where the connect service is deleted on the nomad
   312  	// side (i.e. are we checking multiple nil pointers).
   313  
   314  	try := func(asr *api.AgentServiceRegistration) {
   315  		maybeTweakTags(asr, existing, sidecar)
   316  		require.False(t, reflect.DeepEqual([]string{"original"}, asr.Tags))
   317  	}
   318  
   319  	try(&api.AgentServiceRegistration{
   320  		Tags:              []string{"original"},
   321  		EnableTagOverride: true,
   322  		Connect:           nil, // ooh danger!
   323  	})
   324  
   325  	try(&api.AgentServiceRegistration{
   326  		Tags:              []string{"original"},
   327  		EnableTagOverride: true,
   328  		Connect: &api.AgentServiceConnect{
   329  			SidecarService: nil, // ooh danger!
   330  		},
   331  	})
   332  }
   333  
   334  // TestServiceRegistration_CheckOnUpdate tests that a ServiceRegistrations
   335  // CheckOnUpdate is populated and updated properly
   336  func TestServiceRegistration_CheckOnUpdate(t *testing.T) {
   337  	t.Parallel()
   338  
   339  	mock := NewMockAgent()
   340  	logger := testlog.HCLogger(t)
   341  	sc := NewServiceClient(mock, logger, true)
   342  
   343  	allocID := uuid.Generate()
   344  	ws := &WorkloadServices{
   345  		AllocID:   allocID,
   346  		Task:      "taskname",
   347  		Restarter: &restartRecorder{},
   348  		Services: []*structs.Service{
   349  			{
   350  				Name:      "taskname-service",
   351  				PortLabel: "x",
   352  				Tags:      []string{"tag1", "tag2"},
   353  				Meta:      map[string]string{"meta1": "foo"},
   354  				Checks: []*structs.ServiceCheck{
   355  					{
   356  
   357  						Name:      "c1",
   358  						Type:      "tcp",
   359  						Interval:  time.Second,
   360  						Timeout:   time.Second,
   361  						PortLabel: "x",
   362  						OnUpdate:  structs.OnUpdateIgnoreWarn,
   363  					},
   364  				},
   365  			},
   366  		},
   367  		Networks: []*structs.NetworkResource{
   368  			{
   369  				DynamicPorts: []structs.Port{
   370  					{Label: "x", Value: xPort},
   371  					{Label: "y", Value: yPort},
   372  				},
   373  			},
   374  		},
   375  	}
   376  
   377  	require.NoError(t, sc.RegisterWorkload(ws))
   378  
   379  	require.NotNil(t, sc.allocRegistrations[allocID])
   380  
   381  	allocReg := sc.allocRegistrations[allocID]
   382  	serviceReg := allocReg.Tasks["taskname"]
   383  	require.NotNil(t, serviceReg)
   384  
   385  	// Ensure that CheckOnUpdate was set correctly
   386  	require.Len(t, serviceReg.Services, 1)
   387  	for _, sreg := range serviceReg.Services {
   388  		require.NotEmpty(t, sreg.CheckOnUpdate)
   389  		for _, onupdate := range sreg.CheckOnUpdate {
   390  			require.Equal(t, structs.OnUpdateIgnoreWarn, onupdate)
   391  		}
   392  	}
   393  
   394  	// Update
   395  	wsUpdate := new(WorkloadServices)
   396  	*wsUpdate = *ws
   397  	wsUpdate.Services[0].Checks[0].OnUpdate = structs.OnUpdateRequireHealthy
   398  
   399  	require.NoError(t, sc.UpdateWorkload(ws, wsUpdate))
   400  
   401  	require.NotNil(t, sc.allocRegistrations[allocID])
   402  
   403  	allocReg = sc.allocRegistrations[allocID]
   404  	serviceReg = allocReg.Tasks["taskname"]
   405  	require.NotNil(t, serviceReg)
   406  
   407  	// Ensure that CheckOnUpdate was updated correctly
   408  	require.Len(t, serviceReg.Services, 1)
   409  	for _, sreg := range serviceReg.Services {
   410  		require.NotEmpty(t, sreg.CheckOnUpdate)
   411  		for _, onupdate := range sreg.CheckOnUpdate {
   412  			require.Equal(t, structs.OnUpdateRequireHealthy, onupdate)
   413  		}
   414  	}
   415  }