From 0f3f3d13cacf9da01164d5af6e9913a6be7bcfb9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 17 Apr 2018 18:18:16 -0500 Subject: [PATCH] agent/cache-types: support intention match queries --- agent/cache-types/intention_match.go | 41 ++++++++++++++++ agent/cache-types/intention_match_test.go | 57 +++++++++++++++++++++++ agent/structs/intention.go | 33 +++++++++++++ 3 files changed, 131 insertions(+) create mode 100644 agent/cache-types/intention_match.go create mode 100644 agent/cache-types/intention_match_test.go diff --git a/agent/cache-types/intention_match.go b/agent/cache-types/intention_match.go new file mode 100644 index 0000000000..4c42725a1a --- /dev/null +++ b/agent/cache-types/intention_match.go @@ -0,0 +1,41 @@ +package cachetype + +import ( + "fmt" + + "github.com/hashicorp/consul/agent/cache" + "github.com/hashicorp/consul/agent/structs" +) + +// Recommended name for registration. +const IntentionMatchName = "intention-match" + +// IntentionMatch supports fetching the intentions via match queries. +type IntentionMatch struct { + RPC RPC +} + +func (c *IntentionMatch) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) { + var result cache.FetchResult + + // The request should be an IntentionQueryRequest. + reqReal, ok := req.(*structs.IntentionQueryRequest) + if !ok { + return result, fmt.Errorf( + "Internal cache failure: request wrong type: %T", req) + } + + // Set the minimum query index to our current index so we block + reqReal.MinQueryIndex = opts.MinIndex + reqReal.MaxQueryTime = opts.Timeout + + // Fetch + var reply structs.IndexedIntentionMatches + if err := c.RPC.RPC("Intention.Match", reqReal, &reply); err != nil { + return result, err + } + + result.Value = &reply + result.Index = reply.Index + return result, nil +} diff --git a/agent/cache-types/intention_match_test.go b/agent/cache-types/intention_match_test.go new file mode 100644 index 0000000000..97b2951b36 --- /dev/null +++ b/agent/cache-types/intention_match_test.go @@ -0,0 +1,57 @@ +package cachetype + +import ( + "testing" + "time" + + "github.com/hashicorp/consul/agent/cache" + "github.com/hashicorp/consul/agent/structs" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestIntentionMatch(t *testing.T) { + require := require.New(t) + rpc := TestRPC(t) + defer rpc.AssertExpectations(t) + typ := &IntentionMatch{RPC: rpc} + + // Expect the proper RPC call. This also sets the expected value + // since that is return-by-pointer in the arguments. + var resp *structs.IndexedIntentionMatches + rpc.On("RPC", "Intention.Match", mock.Anything, mock.Anything).Return(nil). + Run(func(args mock.Arguments) { + req := args.Get(1).(*structs.IntentionQueryRequest) + require.Equal(uint64(24), req.MinQueryIndex) + require.Equal(1*time.Second, req.MaxQueryTime) + + reply := args.Get(2).(*structs.IndexedIntentionMatches) + reply.Index = 48 + resp = reply + }) + + // Fetch + result, err := typ.Fetch(cache.FetchOptions{ + MinIndex: 24, + Timeout: 1 * time.Second, + }, &structs.IntentionQueryRequest{Datacenter: "dc1"}) + require.Nil(err) + require.Equal(cache.FetchResult{ + Value: resp, + Index: 48, + }, result) +} + +func TestIntentionMatch_badReqType(t *testing.T) { + require := require.New(t) + rpc := TestRPC(t) + defer rpc.AssertExpectations(t) + typ := &IntentionMatch{RPC: rpc} + + // Fetch + _, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest( + t, cache.RequestInfo{Key: "foo", MinIndex: 64})) + require.NotNil(err) + require.Contains(err.Error(), "wrong type") + +} diff --git a/agent/structs/intention.go b/agent/structs/intention.go index 316c9632b0..6ad1a98359 100644 --- a/agent/structs/intention.go +++ b/agent/structs/intention.go @@ -2,10 +2,13 @@ package structs import ( "fmt" + "strconv" "strings" "time" + "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/go-multierror" + "github.com/mitchellh/hashstructure" ) const ( @@ -267,6 +270,36 @@ func (q *IntentionQueryRequest) RequestDatacenter() string { return q.Datacenter } +// cache.Request impl. +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{ + Token: q.Token, + Datacenter: q.Datacenter, + MinIndex: q.MinQueryIndex, + Timeout: q.MaxQueryTime, + } + + // Calculate the cache key via just hashing the Match struct. This + // has been configured so things like ordering of entries has no + // effect (via struct tags). + v, err := hashstructure.Hash(q.Match, nil) + if err == nil { + // 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 + // to the server. + info.Key = strconv.FormatUint(v, 10) + } + + return info +} + // IntentionQueryMatch are the parameters for performing a match request // against the state store. type IntentionQueryMatch struct {