b19bc6be2b
Previously, connect sidecars would be re-registered with consul every cycle of Nomad's reconciliation loop around Consul service registrations. This is because part of the comparison used `reflect.DeepEqual` on []string objects, which returns false when one object is `[]string{}` and the other is `[]string{}(nil)`. Unforunately, this was always the case, and every Connect sidecar service would be re-registered on every iteration, which happens every 30 seconds.
303 lines
8.6 KiB
Go
303 lines
8.6 KiB
Go
package consul
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/consul/api"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
// the service as known by nomad
|
|
wanted = api.AgentServiceRegistration{
|
|
Kind: "",
|
|
ID: "aca4c175-1778-5ef4-0220-2ab434147d35",
|
|
Name: "myservice",
|
|
Tags: []string{"a", "b"},
|
|
Port: 9000,
|
|
Address: "1.1.1.1",
|
|
EnableTagOverride: true,
|
|
Meta: map[string]string{"foo": "1"},
|
|
Connect: &api.AgentServiceConnect{
|
|
Native: false,
|
|
SidecarService: &api.AgentServiceRegistration{
|
|
Kind: "connect-proxy",
|
|
ID: "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy",
|
|
Name: "name-sidecar-proxy",
|
|
Tags: []string{"x", "y", "z"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// the service (and + connect proxy) as known by consul
|
|
existing = &api.AgentService{
|
|
Kind: "",
|
|
ID: "aca4c175-1778-5ef4-0220-2ab434147d35",
|
|
Service: "myservice",
|
|
Tags: []string{"a", "b"},
|
|
Port: 9000,
|
|
Address: "1.1.1.1",
|
|
EnableTagOverride: true,
|
|
Meta: map[string]string{"foo": "1"},
|
|
}
|
|
|
|
sidecar = &api.AgentService{
|
|
Kind: "connect-proxy",
|
|
ID: "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy",
|
|
Service: "myservice-sidecar-proxy",
|
|
Tags: []string{"x", "y", "z"},
|
|
}
|
|
)
|
|
|
|
func TestSyncLogic_agentServiceUpdateRequired(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// By default wanted and existing match. Each test should modify wanted in
|
|
// 1 way, and / or configure the type of sync operation that is being
|
|
// considered, then evaluate the result of the update-required algebra.
|
|
|
|
type asr = api.AgentServiceRegistration
|
|
type tweaker func(w asr) *asr // create a conveniently modifiable copy
|
|
|
|
try := func(
|
|
t *testing.T,
|
|
exp bool,
|
|
reason syncReason,
|
|
tweak tweaker) {
|
|
result := agentServiceUpdateRequired(reason, tweak(wanted), existing, sidecar)
|
|
require.Equal(t, exp, result)
|
|
}
|
|
|
|
t.Run("matching", func(t *testing.T) {
|
|
try(t, false, syncNewOps, func(w asr) *asr {
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different kind", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Kind = "other"
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different id", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.ID = "_other"
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different port", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Port = 9001
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different address", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Address = "2.2.2.2"
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different name", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Name = "bob"
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different enable_tag_override", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.EnableTagOverride = false
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different meta", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Meta = map[string]string{"foo": "2"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different tags syncNewOps eto=true", func(t *testing.T) {
|
|
// sync is required even though eto=true, because NewOps indicates the
|
|
// service definition in nomad has changed (e.g. job run a modified job)
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different tags syncPeriodic eto=true", func(t *testing.T) {
|
|
// sync is not required since eto=true and this is a periodic sync
|
|
// with consul - in which case we keep Consul's definition of the tags
|
|
try(t, false, syncPeriodic, func(w asr) *asr {
|
|
w.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different sidecar tags on syncPeriodic eto=true", func(t *testing.T) {
|
|
try(t, false, syncPeriodic, func(w asr) *asr {
|
|
// like the parent service, the sidecar's tags do not get enforced
|
|
// if ETO is true and this is a periodic sync
|
|
w.Connect.SidecarService.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different sidecar tags on syncNewOps eto=true", func(t *testing.T) {
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
// like the parent service, the sidecar's tags always get enforced
|
|
// regardless of ETO if this is a sync due to applied operations
|
|
w.Connect.SidecarService.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
// for remaining tests, EnableTagOverride = false
|
|
wanted.EnableTagOverride = false
|
|
existing.EnableTagOverride = false
|
|
|
|
t.Run("different tags syncPeriodic eto=false", func(t *testing.T) {
|
|
// sync is required because eto=false and the tags do not match
|
|
try(t, true, syncPeriodic, func(w asr) *asr {
|
|
w.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different tags syncNewOps eto=false", func(t *testing.T) {
|
|
// sync is required because eto=false and the tags do not match
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different sidecar tags on syncPeriodic eto=false", func(t *testing.T) {
|
|
// like the parent service, sync is required because eto=false and the
|
|
// sidecar's tags do not match
|
|
try(t, true, syncPeriodic, func(w asr) *asr {
|
|
w.Connect.SidecarService.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
|
|
t.Run("different sidecar tags syncNewOps eto=false", func(t *testing.T) {
|
|
// like the parent service, sync is required because eto=false and the
|
|
// sidecar's tags do not match
|
|
try(t, true, syncNewOps, func(w asr) *asr {
|
|
w.Connect.SidecarService.Tags = []string{"other", "tags"}
|
|
return &w
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestSyncLogic_tagsDifferent(t *testing.T) {
|
|
t.Run("nil nil", func(t *testing.T) {
|
|
require.False(t, tagsDifferent(nil, nil))
|
|
})
|
|
|
|
t.Run("empty nil", func(t *testing.T) {
|
|
// where reflect.DeepEqual does not work
|
|
require.False(t, tagsDifferent([]string{}, nil))
|
|
})
|
|
|
|
t.Run("empty empty", func(t *testing.T) {
|
|
require.False(t, tagsDifferent([]string{}, []string{}))
|
|
})
|
|
|
|
t.Run("set empty", func(t *testing.T) {
|
|
require.True(t, tagsDifferent([]string{"A"}, []string{}))
|
|
})
|
|
|
|
t.Run("set nil", func(t *testing.T) {
|
|
require.True(t, tagsDifferent([]string{"A"}, nil))
|
|
})
|
|
|
|
t.Run("different content", func(t *testing.T) {
|
|
require.True(t, tagsDifferent([]string{"A"}, []string{"B"}))
|
|
})
|
|
|
|
t.Run("different lengths", func(t *testing.T) {
|
|
require.True(t, tagsDifferent([]string{"A"}, []string{"A", "B"}))
|
|
})
|
|
}
|
|
|
|
func TestSyncLogic_maybeTweakTags(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
differentPointers := func(a, b []string) bool {
|
|
return &(a) != &(b)
|
|
}
|
|
|
|
try := func(inConsul, inConsulSC []string, eto bool) {
|
|
wanted := &api.AgentServiceRegistration{
|
|
Tags: []string{"original"},
|
|
Connect: &api.AgentServiceConnect{
|
|
SidecarService: &api.AgentServiceRegistration{
|
|
Tags: []string{"original-sidecar"},
|
|
},
|
|
},
|
|
EnableTagOverride: eto,
|
|
}
|
|
|
|
existing := &api.AgentService{Tags: inConsul}
|
|
sidecar := &api.AgentService{Tags: inConsulSC}
|
|
|
|
maybeTweakTags(wanted, existing, sidecar)
|
|
|
|
switch eto {
|
|
case false:
|
|
require.Equal(t, []string{"original"}, wanted.Tags)
|
|
require.Equal(t, []string{"original-sidecar"}, wanted.Connect.SidecarService.Tags)
|
|
require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags))
|
|
case true:
|
|
require.Equal(t, inConsul, wanted.Tags)
|
|
require.Equal(t, inConsulSC, wanted.Connect.SidecarService.Tags)
|
|
require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags))
|
|
}
|
|
}
|
|
|
|
try([]string{"original"}, []string{"original-sidecar"}, true)
|
|
try([]string{"original"}, []string{"original-sidecar"}, false)
|
|
try([]string{"modified"}, []string{"original-sidecar"}, true)
|
|
try([]string{"modified"}, []string{"original-sidecar"}, false)
|
|
try([]string{"original"}, []string{"modified-sidecar"}, true)
|
|
try([]string{"original"}, []string{"modified-sidecar"}, false)
|
|
try([]string{"modified"}, []string{"modified-sidecar"}, true)
|
|
try([]string{"modified"}, []string{"modified-sidecar"}, false)
|
|
}
|
|
|
|
func TestSyncLogic_maybeTweakTags_emptySC(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Check the edge cases where the connect service is deleted on the nomad
|
|
// side (i.e. are we checking multiple nil pointers).
|
|
|
|
try := func(asr *api.AgentServiceRegistration) {
|
|
maybeTweakTags(asr, existing, sidecar)
|
|
require.False(t, reflect.DeepEqual([]string{"original"}, asr.Tags))
|
|
}
|
|
|
|
try(&api.AgentServiceRegistration{
|
|
Tags: []string{"original"},
|
|
EnableTagOverride: true,
|
|
Connect: nil, // ooh danger!
|
|
})
|
|
|
|
try(&api.AgentServiceRegistration{
|
|
Tags: []string{"original"},
|
|
EnableTagOverride: true,
|
|
Connect: &api.AgentServiceConnect{
|
|
SidecarService: nil, // ooh danger!
|
|
},
|
|
})
|
|
}
|