mirror of https://github.com/hashicorp/consul
agent: allow the /v1/connect/intentions/match endpoint to use the agent cache (#8875)
This is the recommended proxy integration API for listing intentions which should not require an active connection to the servers to resolve after the initial cache filling.pull/8890/head
parent
de99c78f14
commit
46a4ef7248
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
agent: allow the /v1/connect/intentions/match endpoint to use the agent cache
|
||||
```
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
cachetype "github.com/hashicorp/consul/agent/cache-types"
|
||||
"github.com/hashicorp/consul/agent/consul"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
)
|
||||
|
@ -135,19 +136,44 @@ func (s *HTTPHandlers) IntentionMatch(resp http.ResponseWriter, req *http.Reques
|
|||
}
|
||||
}
|
||||
|
||||
var reply structs.IndexedIntentionMatches
|
||||
if err := s.agent.RPC("Intention.Match", args, &reply); err != nil {
|
||||
return nil, err
|
||||
// Make the RPC request
|
||||
var out structs.IndexedIntentionMatches
|
||||
defer setMeta(resp, &out.QueryMeta)
|
||||
|
||||
if s.agent.config.HTTPUseCache && args.QueryOptions.UseCache {
|
||||
raw, m, err := s.agent.cache.Get(req.Context(), cachetype.IntentionMatchName, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer setCacheMeta(resp, &m)
|
||||
|
||||
reply, ok := raw.(*structs.IndexedIntentionMatches)
|
||||
if !ok {
|
||||
// This should never happen, but we want to protect against panics
|
||||
return nil, fmt.Errorf("internal error: response type not correct")
|
||||
}
|
||||
out = *reply
|
||||
} else {
|
||||
RETRY_ONCE:
|
||||
if err := s.agent.RPC("Intention.Match", args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact {
|
||||
args.AllowStale = false
|
||||
args.MaxStaleDuration = 0
|
||||
goto RETRY_ONCE
|
||||
}
|
||||
}
|
||||
out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
|
||||
|
||||
// We must have an identical count of matches
|
||||
if len(reply.Matches) != len(names) {
|
||||
if len(out.Matches) != len(names) {
|
||||
return nil, fmt.Errorf("internal error: match response count didn't match input count")
|
||||
}
|
||||
|
||||
// Use empty list instead of nil.
|
||||
response := make(map[string]structs.Intentions)
|
||||
for i, ixns := range reply.Matches {
|
||||
for i, ixns := range out.Matches {
|
||||
response[names[i]] = ixns
|
||||
}
|
||||
|
||||
|
|
|
@ -178,6 +178,46 @@ func TestIntentionMatch(t *testing.T) {
|
|||
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
|
||||
t.Run("success with cache", func(t *testing.T) {
|
||||
// First request is a MISS, but it primes the cache for the second attempt
|
||||
for i := 0; i < 2; i++ {
|
||||
req, err := http.NewRequest("GET", "/v1/connect/intentions/match?by=destination&name=bar&cached", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.IntentionMatch(resp, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The GET request primes the cache so the POST is a hit.
|
||||
if i == 0 {
|
||||
// Should be a cache miss
|
||||
require.Equal(t, "MISS", resp.Header().Get("X-Cache"))
|
||||
} else {
|
||||
// Should be a cache HIT now!
|
||||
require.Equal(t, "HIT", resp.Header().Get("X-Cache"))
|
||||
}
|
||||
|
||||
value := obj.(map[string]structs.Intentions)
|
||||
require.Len(t, value, 1)
|
||||
|
||||
var actual [][]string
|
||||
expected := [][]string{
|
||||
{"default", "*", "default", "bar"},
|
||||
{"default", "*", "default", "*"},
|
||||
}
|
||||
for _, ixn := range value["bar"] {
|
||||
actual = append(actual, []string{
|
||||
ixn.SourceNS,
|
||||
ixn.SourceName,
|
||||
ixn.DestinationNS,
|
||||
ixn.DestinationName,
|
||||
})
|
||||
}
|
||||
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntentionCheck(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue