mirror of https://github.com/status-im/consul.git
Merge pull request #9556 from hashicorp/dnephin/add-more-cache-key-completness-tests
structs: Add more cache key completeness tests
This commit is contained in:
commit
d3ca202a32
|
@ -639,15 +639,21 @@ func (r *ServiceConfigRequest) CacheInfo() cache.RequestInfo {
|
||||||
// the slice would affect cache keys if we ever persist between agent restarts
|
// the slice would affect cache keys if we ever persist between agent restarts
|
||||||
// and change it.
|
// and change it.
|
||||||
v, err := hashstructure.Hash(struct {
|
v, err := hashstructure.Hash(struct {
|
||||||
Name string
|
Name string
|
||||||
EnterpriseMeta EnterpriseMeta
|
EnterpriseMeta EnterpriseMeta
|
||||||
Upstreams []string `hash:"set"`
|
Upstreams []string `hash:"set"`
|
||||||
UpstreamIDs []ServiceID `hash:"set"`
|
UpstreamIDs []ServiceID `hash:"set"`
|
||||||
|
MeshGatewayConfig MeshGatewayConfig
|
||||||
|
ProxyMode ProxyMode
|
||||||
|
Filter string
|
||||||
}{
|
}{
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
EnterpriseMeta: r.EnterpriseMeta,
|
EnterpriseMeta: r.EnterpriseMeta,
|
||||||
Upstreams: r.Upstreams,
|
Upstreams: r.Upstreams,
|
||||||
UpstreamIDs: r.UpstreamIDs,
|
UpstreamIDs: r.UpstreamIDs,
|
||||||
|
ProxyMode: r.Mode,
|
||||||
|
MeshGatewayConfig: r.MeshGateway,
|
||||||
|
Filter: r.QueryOptions.Filter,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// If there is an error, we don't set the key. A blank key forces
|
// If there is an error, we don't set the key. A blank key forces
|
||||||
|
|
|
@ -11,10 +11,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/hashstructure"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
"github.com/hashicorp/consul/lib"
|
"github.com/hashicorp/consul/lib"
|
||||||
"github.com/mitchellh/hashstructure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1378,6 +1379,7 @@ func (r *DiscoveryChainRequest) CacheInfo() cache.RequestInfo {
|
||||||
OverrideMeshGateway MeshGatewayConfig
|
OverrideMeshGateway MeshGatewayConfig
|
||||||
OverrideProtocol string
|
OverrideProtocol string
|
||||||
OverrideConnectTimeout time.Duration
|
OverrideConnectTimeout time.Duration
|
||||||
|
Filter string
|
||||||
}{
|
}{
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
EvaluateInDatacenter: r.EvaluateInDatacenter,
|
EvaluateInDatacenter: r.EvaluateInDatacenter,
|
||||||
|
@ -1385,6 +1387,7 @@ func (r *DiscoveryChainRequest) CacheInfo() cache.RequestInfo {
|
||||||
OverrideMeshGateway: r.OverrideMeshGateway,
|
OverrideMeshGateway: r.OverrideMeshGateway,
|
||||||
OverrideProtocol: r.OverrideProtocol,
|
OverrideProtocol: r.OverrideProtocol,
|
||||||
OverrideConnectTimeout: r.OverrideConnectTimeout,
|
OverrideConnectTimeout: r.OverrideConnectTimeout,
|
||||||
|
Filter: r.QueryOptions.Filter,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// If there is an error, we don't set the key. A blank key forces
|
// If there is an error, we don't set the key. A blank key forces
|
||||||
|
|
|
@ -2216,3 +2216,15 @@ func requireContainsLower(t *testing.T, haystack, needle string) {
|
||||||
func intPointer(i int) *int {
|
func intPointer(i int) *int {
|
||||||
return &i
|
return &i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigEntryQuery_CacheInfoKey(t *testing.T) {
|
||||||
|
assertCacheInfoKeyIsComplete(t, &ConfigEntryQuery{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceConfigRequest_CacheInfoKey(t *testing.T) {
|
||||||
|
assertCacheInfoKeyIsComplete(t, &ServiceConfigRequest{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscoveryChainRequest_CacheInfoKey(t *testing.T) {
|
||||||
|
assertCacheInfoKeyIsComplete(t, &DiscoveryChainRequest{})
|
||||||
|
}
|
||||||
|
|
|
@ -10,11 +10,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/mitchellh/hashstructure"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
"github.com/hashicorp/consul/lib"
|
"github.com/hashicorp/consul/lib"
|
||||||
"github.com/hashicorp/go-multierror"
|
|
||||||
"github.com/mitchellh/hashstructure"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/blake2b"
|
"golang.org/x/crypto/blake2b"
|
||||||
)
|
)
|
||||||
|
@ -596,13 +597,6 @@ func (q *IntentionQueryRequest) RequestDatacenter() string {
|
||||||
|
|
||||||
// CacheInfo implements cache.Request
|
// CacheInfo implements cache.Request
|
||||||
func (q *IntentionQueryRequest) CacheInfo() cache.RequestInfo {
|
func (q *IntentionQueryRequest) CacheInfo() cache.RequestInfo {
|
||||||
// We only support caching Match queries, so if Match isn't set,
|
|
||||||
// then return an empty info object which will cause a pass-through
|
|
||||||
// (and likely fail).
|
|
||||||
if q.Match == nil {
|
|
||||||
return cache.RequestInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
info := cache.RequestInfo{
|
info := cache.RequestInfo{
|
||||||
Token: q.Token,
|
Token: q.Token,
|
||||||
Datacenter: q.Datacenter,
|
Datacenter: q.Datacenter,
|
||||||
|
@ -610,10 +604,19 @@ func (q *IntentionQueryRequest) CacheInfo() cache.RequestInfo {
|
||||||
Timeout: q.MaxQueryTime,
|
Timeout: q.MaxQueryTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the cache key via just hashing the Match struct. This
|
v, err := hashstructure.Hash(struct {
|
||||||
// has been configured so things like ordering of entries has no
|
IntentionID string
|
||||||
// effect (via struct tags).
|
Match *IntentionQueryMatch
|
||||||
v, err := hashstructure.Hash(q.Match, nil)
|
Check *IntentionQueryCheck
|
||||||
|
Exact *IntentionQueryExact
|
||||||
|
Filter string
|
||||||
|
}{
|
||||||
|
IntentionID: q.IntentionID,
|
||||||
|
Check: q.Check,
|
||||||
|
Match: q.Match,
|
||||||
|
Exact: q.Exact,
|
||||||
|
Filter: q.QueryOptions.Filter,
|
||||||
|
}, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// If there is an error, we don't set the key. A blank key forces
|
// If there is an error, we don't set the key. A blank key forces
|
||||||
// no cache for this request so the request is forwarded directly
|
// no cache for this request so the request is forwarded directly
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIntention_ACLs(t *testing.T) {
|
func TestIntention_ACLs(t *testing.T) {
|
||||||
|
@ -453,3 +454,7 @@ func TestIntention_String(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntentionQueryRequest_CacheInfoKey(t *testing.T) {
|
||||||
|
assertCacheInfoKeyIsComplete(t, &IntentionQueryRequest{})
|
||||||
|
}
|
||||||
|
|
|
@ -27,3 +27,9 @@ func TestStructs_PreparedQuery_GetACLPrefix(t *testing.T) {
|
||||||
t.Fatalf("bad: ok=%v prefix=%#v", ok, prefix)
|
t.Fatalf("bad: ok=%v prefix=%#v", ok, prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreparedQueryExecuteRequest_CacheInfoKey(t *testing.T) {
|
||||||
|
// TODO: should these fields be included in the key?
|
||||||
|
ignored := []string{"Agent", "QueryOptions"}
|
||||||
|
assertCacheInfoKeyIsComplete(t, &PreparedQueryExecuteRequest{}, ignored...)
|
||||||
|
}
|
||||||
|
|
|
@ -642,6 +642,7 @@ func (r *ServiceSpecificRequest) CacheInfo() cache.RequestInfo {
|
||||||
r.Filter,
|
r.Filter,
|
||||||
r.EnterpriseMeta,
|
r.EnterpriseMeta,
|
||||||
r.Ingress,
|
r.Ingress,
|
||||||
|
r.ServiceKind,
|
||||||
}, nil)
|
}, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// If there is an error, we don't set the key. A blank key forces
|
// If there is an error, we don't set the key. A blank key forces
|
||||||
|
|
|
@ -1588,30 +1588,21 @@ func TestStructs_validateMetaPair(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDCSpecificRequestCacheInfoKey(t *testing.T) {
|
func TestDCSpecificRequest_CacheInfoKey(t *testing.T) {
|
||||||
assertCacheInfoKeyIsComplete(t, &DCSpecificRequest{}, nil)
|
assertCacheInfoKeyIsComplete(t, &DCSpecificRequest{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNodeSpecificRequestCacheInfoKey(t *testing.T) {
|
func TestNodeSpecificRequest_CacheInfoKey(t *testing.T) {
|
||||||
assertCacheInfoKeyIsComplete(t, &NodeSpecificRequest{}, nil)
|
assertCacheInfoKeyIsComplete(t, &NodeSpecificRequest{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceSpecificRequestCacheInfoKey(t *testing.T) {
|
func TestServiceSpecificRequest_CacheInfoKey(t *testing.T) {
|
||||||
ignoredFields := map[string]bool{
|
assertCacheInfoKeyIsComplete(t, &ServiceSpecificRequest{})
|
||||||
// TODO: should this filed be included?
|
|
||||||
"ServiceKind": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
assertCacheInfoKeyIsComplete(t, &ServiceSpecificRequest{}, ignoredFields)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceDumpRequestCacheInfoKey(t *testing.T) {
|
func TestServiceDumpRequest_CacheInfoKey(t *testing.T) {
|
||||||
ignoredFields := map[string]bool{
|
// ServiceKind is only included when UseServiceKind=true
|
||||||
// ServiceKind is only included when UseServiceKind=true
|
assertCacheInfoKeyIsComplete(t, &ServiceDumpRequest{}, "ServiceKind")
|
||||||
"ServiceKind": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
assertCacheInfoKeyIsComplete(t, &ServiceDumpRequest{}, ignoredFields)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cacheInfoIgnoredFields are fields that can be ignored in all cache.Request types
|
// cacheInfoIgnoredFields are fields that can be ignored in all cache.Request types
|
||||||
|
@ -1621,14 +1612,26 @@ func TestServiceDumpRequestCacheInfoKey(t *testing.T) {
|
||||||
var cacheInfoIgnoredFields = map[string]bool{
|
var cacheInfoIgnoredFields = map[string]bool{
|
||||||
// Datacenter is part of the cache key added by the cache itself.
|
// Datacenter is part of the cache key added by the cache itself.
|
||||||
"Datacenter": true,
|
"Datacenter": true,
|
||||||
// QuerySource is always the same for every request a single agent, so it
|
// QuerySource is always the same for every request from a single agent, so it
|
||||||
// is excluded from the key.
|
// is excluded from the key.
|
||||||
"Source": true,
|
"Source": true,
|
||||||
// EnterpriseMeta is an empty struct, so can not be included.
|
// EnterpriseMeta is an empty struct, so can not be included.
|
||||||
enterpriseMetaField: true,
|
enterpriseMetaField: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertCacheInfoKeyIsComplete(t *testing.T, request cache.Request, ignoredFields map[string]bool) {
|
// assertCacheInfoKeyIsComplete is an assertion to verify that all fields on a request
|
||||||
|
// struct are considered as part of the cache key. It is used to prevent regressions
|
||||||
|
// when new fields are added to the struct. If a field is not included in the cache
|
||||||
|
// key it can lead to API requests or DNS requests returning the wrong value
|
||||||
|
// because a request matches the wrong entry in the agent/cache.Cache.
|
||||||
|
func assertCacheInfoKeyIsComplete(t *testing.T, request cache.Request, ignoredFields ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ignored := make(map[string]bool, len(ignoredFields))
|
||||||
|
for _, f := range ignoredFields {
|
||||||
|
ignored[f] = true
|
||||||
|
}
|
||||||
|
|
||||||
fuzzer := fuzz.NewWithSeed(time.Now().UnixNano())
|
fuzzer := fuzz.NewWithSeed(time.Now().UnixNano())
|
||||||
fuzzer.Funcs(randQueryOptions)
|
fuzzer.Funcs(randQueryOptions)
|
||||||
fuzzer.Fuzz(request)
|
fuzzer.Fuzz(request)
|
||||||
|
@ -1640,7 +1643,7 @@ func assertCacheInfoKeyIsComplete(t *testing.T, request cache.Request, ignoredFi
|
||||||
fieldName := requestValue.Type().Field(i).Name
|
fieldName := requestValue.Type().Field(i).Name
|
||||||
originalValue := field.Interface()
|
originalValue := field.Interface()
|
||||||
|
|
||||||
if cacheInfoIgnoredFields[fieldName] || ignoredFields[fieldName] {
|
if cacheInfoIgnoredFields[fieldName] || ignored[fieldName] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1650,10 +1653,11 @@ func assertCacheInfoKeyIsComplete(t *testing.T, request cache.Request, ignoredFi
|
||||||
|
|
||||||
key := request.CacheInfo().Key
|
key := request.CacheInfo().Key
|
||||||
if originalKey == key {
|
if originalKey == key {
|
||||||
t.Fatalf("expected field %v to be represented in the CacheInfo.Key, %v change to %v",
|
t.Fatalf("expected field %v to be represented in the CacheInfo.Key, %v change to %v (key: %v)",
|
||||||
fieldName,
|
fieldName,
|
||||||
originalValue,
|
originalValue,
|
||||||
field.Interface())
|
field.Interface(),
|
||||||
|
key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue