agent: check cache hit count to verify CA root caching, background update

This commit is contained in:
Mitchell Hashimoto 2018-04-11 10:18:24 +01:00
parent 6902d721d6
commit 917a9e63d5
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
2 changed files with 78 additions and 7 deletions

View File

@ -2121,32 +2121,77 @@ func TestAgentConnectCARoots_empty(t *testing.T) {
func TestAgentConnectCARoots_list(t *testing.T) { func TestAgentConnectCARoots_list(t *testing.T) {
t.Parallel() t.Parallel()
assert := assert.New(t) require := require.New(t)
a := NewTestAgent(t.Name(), "") a := NewTestAgent(t.Name(), "")
defer a.Shutdown() defer a.Shutdown()
// Grab the initial cache hit count
cacheHits := a.cache.Hits()
// Set some CAs // Set some CAs
var reply interface{} var reply interface{}
ca1 := connect.TestCA(t, nil) ca1 := connect.TestCA(t, nil)
ca1.Active = false ca1.Active = false
ca2 := connect.TestCA(t, nil) ca2 := connect.TestCA(t, nil)
assert.Nil(a.RPC("Test.ConnectCASetRoots", require.Nil(a.RPC("Test.ConnectCASetRoots",
[]*structs.CARoot{ca1, ca2}, &reply)) []*structs.CARoot{ca1, ca2}, &reply))
// List // List
req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/roots", nil) req, _ := http.NewRequest("GET", "/v1/agent/connect/ca/roots", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
obj, err := a.srv.AgentConnectCARoots(resp, req) obj, err := a.srv.AgentConnectCARoots(resp, req)
assert.Nil(err) require.Nil(err)
value := obj.(structs.IndexedCARoots) value := obj.(structs.IndexedCARoots)
assert.Equal(value.ActiveRootID, ca2.ID) require.Equal(value.ActiveRootID, ca2.ID)
assert.Len(value.Roots, 2) require.Len(value.Roots, 2)
// We should never have the secret information // We should never have the secret information
for _, r := range value.Roots { for _, r := range value.Roots {
assert.Equal("", r.SigningCert) require.Equal("", r.SigningCert)
assert.Equal("", r.SigningKey) require.Equal("", r.SigningKey)
}
// That should've been a cache miss, so not hit change
require.Equal(cacheHits, a.cache.Hits())
// Test caching
{
// List it again
obj2, err := a.srv.AgentConnectCARoots(httptest.NewRecorder(), req)
require.Nil(err)
require.Equal(obj, obj2)
// Should cache hit this time and not make request
require.Equal(cacheHits+1, a.cache.Hits())
cacheHits++
}
// Test that caching is updated in the background
{
// Set some new CAs
var reply interface{}
ca := connect.TestCA(t, nil)
require.Nil(a.RPC("Test.ConnectCASetRoots",
[]*structs.CARoot{ca}, &reply))
// Sleep a bit to wait for the cache to update
time.Sleep(100 * time.Millisecond)
// List it again
obj, err := a.srv.AgentConnectCARoots(httptest.NewRecorder(), req)
require.Nil(err)
require.Equal(obj, obj)
value := obj.(structs.IndexedCARoots)
require.Equal(value.ActiveRootID, ca.ID)
require.Len(value.Roots, 1)
// Should be a cache hit! The data should've updated in the cache
// in the background so this should've been fetched directly from
// the cache.
require.Equal(cacheHits+1, a.cache.Hits())
cacheHits++
} }
} }

26
agent/cache/cache.go vendored
View File

@ -15,6 +15,7 @@ package cache
import ( import (
"fmt" "fmt"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
@ -22,6 +23,11 @@ import (
// Cache is a agent-local cache of Consul data. // Cache is a agent-local cache of Consul data.
type Cache struct { type Cache struct {
// Keeps track of the cache hits and misses in total. This is used by
// tests currently to verify cache behavior and is not meant for general
// analytics; for that, go-metrics emitted values are better.
hits, misses uint64
// types stores the list of data types that the cache knows how to service. // types stores the list of data types that the cache knows how to service.
// These can be dynamically registered with RegisterType. // These can be dynamically registered with RegisterType.
typesLock sync.RWMutex typesLock sync.RWMutex
@ -127,6 +133,9 @@ func (c *Cache) Get(t string, r Request) (interface{}, error) {
// Get the actual key for our entry // Get the actual key for our entry
key := c.entryKey(&info) key := c.entryKey(&info)
// First time through
first := true
RETRY_GET: RETRY_GET:
// Get the current value // Get the current value
c.entriesLock.RLock() c.entriesLock.RLock()
@ -139,10 +148,22 @@ RETRY_GET:
// we have. // we have.
if ok && entry.Valid { if ok && entry.Valid {
if info.MinIndex == 0 || info.MinIndex < entry.Index { if info.MinIndex == 0 || info.MinIndex < entry.Index {
if first {
atomic.AddUint64(&c.hits, 1)
}
return entry.Value, nil return entry.Value, nil
} }
} }
if first {
// Record the miss if its our first time through
atomic.AddUint64(&c.misses, 1)
}
// No longer our first time through
first = false
// At this point, we know we either don't have a value at all or the // At this point, we know we either don't have a value at all or the
// value we have is too old. We need to wait for new data. // value we have is too old. We need to wait for new data.
waiter, err := c.fetch(t, key, r) waiter, err := c.fetch(t, key, r)
@ -263,3 +284,8 @@ func (c *Cache) refresh(opts *RegisterOptions, t string, key string, r Request)
// Trigger // Trigger
c.fetch(t, key, r) c.fetch(t, key, r)
} }
// Returns the number of cache hits. Safe to call concurrently.
func (c *Cache) Hits() uint64 {
return atomic.LoadUint64(&c.hits)
}