deps: upgrade to hashicorp/golang-lru/v2 (#16085)

This commit is contained in:
Seth Hoenig 2023-02-08 15:20:33 -06:00 committed by GitHub
parent a4519c739d
commit 0e7bf87ee1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 116 additions and 125 deletions

3
.changelog/16085.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
acl: refactor ACL cache based on golang-lru/v2
```

View File

@ -3,8 +3,7 @@ package client
import ( import (
"time" "time"
metrics "github.com/armon/go-metrics" "github.com/armon/go-metrics"
lru "github.com/hashicorp/golang-lru"
"github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs"
) )
@ -32,54 +31,25 @@ const (
// of ACLs // of ACLs
type clientACLResolver struct { type clientACLResolver struct {
// aclCache is used to maintain the parsed ACL objects // aclCache is used to maintain the parsed ACL objects
aclCache *lru.TwoQueueCache aclCache *structs.ACLCache[*acl.ACL]
// policyCache is used to maintain the fetched policy objects // policyCache is used to maintain the fetched policy objects
policyCache *lru.TwoQueueCache policyCache *structs.ACLCache[*structs.ACLPolicy]
// tokenCache is used to maintain the fetched token objects // tokenCache is used to maintain the fetched token objects
tokenCache *lru.TwoQueueCache tokenCache *structs.ACLCache[*structs.ACLToken]
// roleCache is used to maintain a cache of the fetched ACL roles. Each // roleCache is used to maintain a cache of the fetched ACL roles. Each
// entry is keyed by the role ID. // entry is keyed by the role ID.
roleCache *lru.TwoQueueCache roleCache *structs.ACLCache[*structs.ACLRole]
} }
// init is used to setup the client resolver state // init is used to setup the client resolver state
func (c *clientACLResolver) init() error { func (c *clientACLResolver) init() {
// Create the ACL object cache c.aclCache = structs.NewACLCache[*acl.ACL](aclCacheSize)
var err error c.policyCache = structs.NewACLCache[*structs.ACLPolicy](policyCacheSize)
c.aclCache, err = lru.New2Q(aclCacheSize) c.tokenCache = structs.NewACLCache[*structs.ACLToken](tokenCacheSize)
if err != nil { c.roleCache = structs.NewACLCache[*structs.ACLRole](roleCacheSize)
return err
}
c.policyCache, err = lru.New2Q(policyCacheSize)
if err != nil {
return err
}
c.tokenCache, err = lru.New2Q(tokenCacheSize)
if err != nil {
return err
}
c.roleCache, err = lru.New2Q(roleCacheSize)
if err != nil {
return err
}
return nil
}
// cachedACLValue is used to manage ACL Token, Policy, or Role cache entries
// and their TTLs.
type cachedACLValue struct {
Token *structs.ACLToken
Policy *structs.ACLPolicy
Role *structs.ACLRole
CacheTime time.Time
}
// Age is the time since the token was cached
func (c *cachedACLValue) Age() time.Duration {
return time.Since(c.CacheTime)
} }
// ResolveToken is used to translate an ACL Token Secret ID into // ResolveToken is used to translate an ACL Token Secret ID into
@ -154,12 +124,11 @@ func (c *Client) resolveTokenValue(secretID string) (*structs.ACLToken, error) {
return structs.AnonymousACLToken, nil return structs.AnonymousACLToken, nil
} }
// Lookup the token in the cache // Lookup the token entry in the cache
raw, ok := c.tokenCache.Get(secretID) entry, ok := c.tokenCache.Get(secretID)
if ok { if ok {
cached := raw.(*cachedACLValue) if entry.Age() <= c.GetConfig().ACLTokenTTL {
if cached.Age() <= c.GetConfig().ACLTokenTTL { return entry.Get(), nil
return cached.Token, nil
} }
} }
@ -176,17 +145,13 @@ func (c *Client) resolveTokenValue(secretID string) (*structs.ACLToken, error) {
// If we encounter an error but have a cached value, mask the error and extend the cache // If we encounter an error but have a cached value, mask the error and extend the cache
if ok { if ok {
c.logger.Warn("failed to resolve token, using expired cached value", "error", err) c.logger.Warn("failed to resolve token, using expired cached value", "error", err)
cached := raw.(*cachedACLValue) return entry.Get(), nil
return cached.Token, nil
} }
return nil, err return nil, err
} }
// Cache the response (positive or negative) // Cache the response (positive or negative)
c.tokenCache.Add(secretID, &cachedACLValue{ c.tokenCache.Add(secretID, resp.Token)
Token: resp.Token,
CacheTime: time.Now(),
})
return resp.Token, nil return resp.Token, nil
} }
@ -202,18 +167,17 @@ func (c *Client) resolvePolicies(secretID string, policies []string) ([]*structs
// Scan the cache for each policy // Scan the cache for each policy
for _, policyName := range policies { for _, policyName := range policies {
// Lookup the policy in the cache // Lookup the policy in the cache
raw, ok := c.policyCache.Get(policyName) entry, ok := c.policyCache.Get(policyName)
if !ok { if !ok {
missing = append(missing, policyName) missing = append(missing, policyName)
continue continue
} }
// Check if the cached value is valid or expired // Check if the cached value is valid or expired
cached := raw.(*cachedACLValue) if entry.Age() <= c.GetConfig().ACLPolicyTTL {
if cached.Age() <= c.GetConfig().ACLPolicyTTL { out = append(out, entry.Get())
out = append(out, cached.Policy)
} else { } else {
expired = append(expired, cached.Policy) expired = append(expired, entry.Get())
} }
} }
@ -248,10 +212,7 @@ func (c *Client) resolvePolicies(secretID string, policies []string) ([]*structs
// Handle each output // Handle each output
for _, policy := range resp.Policies { for _, policy := range resp.Policies {
c.policyCache.Add(policy.Name, &cachedACLValue{ c.policyCache.Add(policy.Name, policy)
Policy: policy,
CacheTime: time.Now(),
})
out = append(out, policy) out = append(out, policy)
} }
@ -290,7 +251,7 @@ func (c *Client) resolveTokenACLRoles(secretID string, roleLinks []*structs.ACLT
// Look within the cache to see if the role is already present. If we // Look within the cache to see if the role is already present. If we
// do not find it, add the ID to our tracking, so we look this up via // do not find it, add the ID to our tracking, so we look this up via
// RPC. // RPC.
raw, ok := c.roleCache.Get(roleLink.ID) entry, ok := c.roleCache.Get(roleLink.ID)
if !ok { if !ok {
missingRoleIDs = append(missingRoleIDs, roleLink.ID) missingRoleIDs = append(missingRoleIDs, roleLink.ID)
continue continue
@ -299,13 +260,12 @@ func (c *Client) resolveTokenACLRoles(secretID string, roleLinks []*structs.ACLT
// If the cached value is expired, add the ID to our tracking, so we // If the cached value is expired, add the ID to our tracking, so we
// look this up via RPC. Otherwise, iterate the policy links and add // look this up via RPC. Otherwise, iterate the policy links and add
// each policy name to our return object tracking. // each policy name to our return object tracking.
cached := raw.(*cachedACLValue) if entry.Age() <= c.GetConfig().ACLRoleTTL {
if cached.Age() <= c.GetConfig().ACLRoleTTL { for _, policyLink := range entry.Get().Policies {
for _, policyLink := range cached.Role.Policies {
policyNames = append(policyNames, policyLink.Name) policyNames = append(policyNames, policyLink.Name)
} }
} else { } else {
expiredRoleIDs = append(expiredRoleIDs, cached.Role.ID) expiredRoleIDs = append(expiredRoleIDs, entry.Get().ID)
} }
} }
@ -354,13 +314,11 @@ func (c *Client) resolveTokenACLRoles(secretID string, roleLinks []*structs.ACLT
// Generate a timestamp for the cache entry. We do not need to use a // Generate a timestamp for the cache entry. We do not need to use a
// timestamp per ACL role response integration. // timestamp per ACL role response integration.
now := time.Now() now := time.Now()
for _, aclRole := range roleByIDResp.ACLRoles { for _, aclRole := range roleByIDResp.ACLRoles {
// Add an entry to the cache using the generated timestamp for future // Add an entry to the cache using the generated timestamp for future
// expiry calculations. Any existing, expired entry will be // expiry calculations. Any existing, expired entry will be
// overwritten. // overwritten.
c.roleCache.Add(aclRole.ID, &cachedACLValue{Role: aclRole, CacheTime: now}) c.roleCache.AddAtTime(aclRole.ID, aclRole, now)
// Iterate the role policy links, extracting the name and adding this // Iterate the role policy links, extracting the name and adding this
// to our return response tracking. // to our return response tracking.

View File

@ -17,8 +17,8 @@ import (
) )
func Test_clientACLResolver_init(t *testing.T) { func Test_clientACLResolver_init(t *testing.T) {
resolver := &clientACLResolver{} resolver := new(clientACLResolver)
must.NoError(t, resolver.init()) resolver.init()
must.NotNil(t, resolver.aclCache) must.NotNil(t, resolver.aclCache)
must.NotNil(t, resolver.policyCache) must.NotNil(t, resolver.policyCache)
must.NotNil(t, resolver.tokenCache) must.NotNil(t, resolver.tokenCache)

View File

@ -441,9 +441,7 @@ func NewClient(cfg *config.Config, consulCatalog consul.CatalogAPI, consulProxie
c.setupClientRpc(rpcs) c.setupClientRpc(rpcs)
// Initialize the ACL state // Initialize the ACL state
if err := c.clientACLResolver.init(); err != nil { c.clientACLResolver.init()
return nil, fmt.Errorf("failed to initialize ACL state: %v", err)
}
// Setup the node // Setup the node
if err := c.setupNode(); err != nil { if err := c.setupNode(); err != nil {

4
go.mod
View File

@ -69,7 +69,7 @@ require (
github.com/hashicorp/go-syslog v1.0.0 github.com/hashicorp/go-syslog v1.0.0
github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru/v2 v2.0.1
github.com/hashicorp/hcl v1.0.1-vault-3 github.com/hashicorp/hcl v1.0.1-vault-3
github.com/hashicorp/hcl/v2 v2.9.2-0.20220525143345-ab3cae0737bc github.com/hashicorp/hcl/v2 v2.9.2-0.20220525143345-ab3cae0737bc
github.com/hashicorp/hil v0.0.0-20210521165536-27a72121fd40 github.com/hashicorp/hil v0.0.0-20210521165536-27a72121fd40
@ -214,7 +214,7 @@ require (
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/mdns v1.0.4 // indirect github.com/hashicorp/mdns v1.0.4 // indirect
github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 // indirect github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 // indirect
github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect

4
go.sum
View File

@ -732,8 +732,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.0 h1:Lf+9eD8m5pncvHAOCQj49GSN6aQI8XGfI5OpXNkoWaA= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.0/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee h1:8B4HqvMUtYSjsGkYjiQGStc9pXffY2J+Z2SPQAj+wMY= github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee h1:8B4HqvMUtYSjsGkYjiQGStc9pXffY2J+Z2SPQAj+wMY=
github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee/go.mod h1:gwlu9+/P9MmKtYrMsHeFRZPXj2CTPm11TDnMeaRHS7g= github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee/go.mod h1:gwlu9+/P9MmKtYrMsHeFRZPXj2CTPm11TDnMeaRHS7g=
github.com/hashicorp/hcl/v2 v2.9.2-0.20220525143345-ab3cae0737bc h1:32lGaCPq5JPYNgFFTjl/cTIar9UWWxCbimCs5G2hMHg= github.com/hashicorp/hcl/v2 v2.9.2-0.20220525143345-ab3cae0737bc h1:32lGaCPq5JPYNgFFTjl/cTIar9UWWxCbimCs5G2hMHg=

2
lib/lang/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package lang provides some features that really 'ought to be part of the Go language
package lang

7
lib/lang/pair.go Normal file
View File

@ -0,0 +1,7 @@
package lang
// Pair associates two arbitrary types together.
type Pair[T, U any] struct {
First T
Second U
}

View File

@ -7,7 +7,6 @@ import (
"time" "time"
metrics "github.com/armon/go-metrics" metrics "github.com/armon/go-metrics"
lru "github.com/hashicorp/golang-lru"
"github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/state"
@ -283,7 +282,7 @@ func (s *Server) ResolveClaims(claims *structs.IdentityClaims) (*acl.ACL, error)
// resolveTokenFromSnapshotCache is used to resolve an ACL object from a // resolveTokenFromSnapshotCache is used to resolve an ACL object from a
// snapshot of state, using a cache to avoid parsing and ACL construction when // snapshot of state, using a cache to avoid parsing and ACL construction when
// possible. It is split from resolveToken to simplify testing. // possible. It is split from resolveToken to simplify testing.
func resolveTokenFromSnapshotCache(snap *state.StateSnapshot, cache *lru.TwoQueueCache, secretID string) (*acl.ACL, error) { func resolveTokenFromSnapshotCache(snap *state.StateSnapshot, cache *structs.ACLCache[*acl.ACL], secretID string) (*acl.ACL, error) {
// Lookup the ACL Token // Lookup the ACL Token
var token *structs.ACLToken var token *structs.ACLToken
var err error var err error
@ -308,7 +307,7 @@ func resolveTokenFromSnapshotCache(snap *state.StateSnapshot, cache *lru.TwoQueu
} }
func resolveACLFromToken(snap *state.StateSnapshot, cache *lru.TwoQueueCache, token *structs.ACLToken) (*acl.ACL, error) { func resolveACLFromToken(snap *state.StateSnapshot, cache *structs.ACLCache[*acl.ACL], token *structs.ACLToken) (*acl.ACL, error) {
// Check if this is a management token // Check if this is a management token
if token.Type == structs.ACLManagementToken { if token.Type == structs.ACLManagementToken {

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"time" "time"
metrics "github.com/armon/go-metrics" "github.com/armon/go-metrics"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru/v2"
"github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/helper"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
@ -36,7 +36,7 @@ func (n *NoopBadNodeTracker) Add(string) bool {
// frequency and recency. // frequency and recency.
type CachedBadNodeTracker struct { type CachedBadNodeTracker struct {
logger hclog.Logger logger hclog.Logger
cache *lru.TwoQueueCache cache *lru.TwoQueueCache[string, *badNodeStats]
limiter *rate.Limiter limiter *rate.Limiter
window time.Duration window time.Duration
threshold int threshold int
@ -72,7 +72,7 @@ func NewCachedBadNodeTracker(logger hclog.Logger, config CachedBadNodeTrackerCon
With("window", config.Window). With("window", config.Window).
With("threshold", config.Threshold) With("threshold", config.Threshold)
cache, err := lru.New2Q(config.CacheSize) cache, err := lru.New2Q[string, *badNodeStats](config.CacheSize)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create new bad node tracker: %v", err) return nil, fmt.Errorf("failed to create new bad node tracker: %v", err)
} }
@ -93,12 +93,11 @@ func NewCachedBadNodeTracker(logger hclog.Logger, config CachedBadNodeTrackerCon
// cache. If the cache is full the least recently updated or accessed node is // cache. If the cache is full the least recently updated or accessed node is
// evicted. // evicted.
func (c *CachedBadNodeTracker) Add(nodeID string) bool { func (c *CachedBadNodeTracker) Add(nodeID string) bool {
value, ok := c.cache.Get(nodeID) stats, ok := c.cache.Get(nodeID)
if !ok { if !ok {
value = newBadNodeStats(nodeID, c.window) stats = newBadNodeStats(nodeID, c.window)
c.cache.Add(nodeID, value) c.cache.Add(nodeID, stats)
} }
stats := value.(*badNodeStats)
now := time.Now() now := time.Now()
stats.record(now) stats.record(now)
@ -147,13 +146,12 @@ func (c *CachedBadNodeTracker) isBad(t time.Time, stats *badNodeStats) bool {
func (c *CachedBadNodeTracker) emitStats() { func (c *CachedBadNodeTracker) emitStats() {
now := time.Now() now := time.Now()
for _, k := range c.cache.Keys() { for _, nodeID := range c.cache.Keys() {
value, _ := c.cache.Get(k) stats, _ := c.cache.Get(nodeID)
stats := value.(*badNodeStats)
score := stats.score(now) score := stats.score(now)
labels := []metrics.Label{ labels := []metrics.Label{
{Name: "node_id", Value: k.(string)}, {Name: "node_id", Value: nodeID},
} }
metrics.SetGaugeWithLabels([]string{"nomad", "plan", "rejection_tracker", "node_score"}, float32(score), labels) metrics.SetGaugeWithLabels([]string{"nomad", "plan", "rejection_tracker", "node_score"}, float32(score), labels)
} }

View File

@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TesCachedtBadNodeTracker(t *testing.T) { func TestCachedtBadNodeTracker(t *testing.T) {
ci.Parallel(t) ci.Parallel(t)
config := DefaultCachedBadNodeTrackerConfig() config := DefaultCachedBadNodeTrackerConfig()
@ -74,11 +74,10 @@ func TestCachedBadNodeTracker_isBad(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// Read value from cached. // Read value from cached.
v, ok := tracker.cache.Get(tc.nodeID) stats, ok := tracker.cache.Get(tc.nodeID)
require.True(t, ok) require.True(t, ok)
// Check if it's bad. // Check if it's bad.
stats := v.(*badNodeStats)
got := tracker.isBad(now, stats) got := tracker.isBad(now, stats)
require.Equal(t, tc.bad, got) require.Equal(t, tc.bad, got)
}) })
@ -88,10 +87,9 @@ func TestCachedBadNodeTracker_isBad(t *testing.T) {
nodes := []string{"node-1", "node-2", "node-3"} nodes := []string{"node-1", "node-2", "node-3"}
for _, n := range nodes { for _, n := range nodes {
t.Run(fmt.Sprintf("%s cache expires", n), func(t *testing.T) { t.Run(fmt.Sprintf("%s cache expires", n), func(t *testing.T) {
v, ok := tracker.cache.Get(n) stats, ok := tracker.cache.Get(n)
require.True(t, ok) require.True(t, ok)
stats := v.(*badNodeStats)
bad := tracker.isBad(future, stats) bad := tracker.isBad(future, stats)
require.False(t, bad) require.False(t, bad)
}) })
@ -115,11 +113,9 @@ func TesCachedtBadNodeTracker_rateLimit(t *testing.T) {
tracker.Add("node-1") tracker.Add("node-1")
tracker.Add("node-1") tracker.Add("node-1")
v, ok := tracker.cache.Get("node-1") stats, ok := tracker.cache.Get("node-1")
require.True(t, ok) require.True(t, ok)
stats := v.(*badNodeStats)
// Burst allows for max 3 operations. // Burst allows for max 3 operations.
now := time.Now() now := time.Now()
require.True(t, tracker.isBad(now, stats)) require.True(t, tracker.isBad(now, stats))

View File

@ -23,7 +23,7 @@ import (
consulapi "github.com/hashicorp/consul/api" consulapi "github.com/hashicorp/consul/api"
log "github.com/hashicorp/go-hclog" log "github.com/hashicorp/go-hclog"
multierror "github.com/hashicorp/go-multierror" multierror "github.com/hashicorp/go-multierror"
lru "github.com/hashicorp/golang-lru" "github.com/hashicorp/nomad/acl"
"github.com/hashicorp/raft" "github.com/hashicorp/raft"
autopilot "github.com/hashicorp/raft-autopilot" autopilot "github.com/hashicorp/raft-autopilot"
raftboltdb "github.com/hashicorp/raft-boltdb/v2" raftboltdb "github.com/hashicorp/raft-boltdb/v2"
@ -254,7 +254,7 @@ type Server struct {
workersEventCh chan interface{} workersEventCh chan interface{}
// aclCache is used to maintain the parsed ACL objects // aclCache is used to maintain the parsed ACL objects
aclCache *lru.TwoQueueCache aclCache *structs.ACLCache[*acl.ACL]
// oidcProviderCache maintains a cache of OIDC providers. This is useful as // oidcProviderCache maintains a cache of OIDC providers. This is useful as
// the provider performs background HTTP requests. When the Nomad server is // the provider performs background HTTP requests. When the Nomad server is
@ -341,10 +341,7 @@ func NewServer(config *Config, consulCatalog consul.CatalogAPI, consulConfigEntr
} }
// Create the ACL object cache // Create the ACL object cache
aclCache, err := lru.New2Q(aclCacheSize) aclCache := structs.NewACLCache[*acl.ACL](aclCacheSize)
if err != nil {
return nil, err
}
// Create the logger // Create the logger
logger := config.Logger.ResetNamedIntercept("nomad") logger := config.Logger.ResetNamedIntercept("nomad")

View File

@ -10,7 +10,6 @@ import (
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
lru "github.com/hashicorp/golang-lru"
"github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs"
@ -42,7 +41,7 @@ type EventBroker struct {
publishCh chan *structs.Events publishCh chan *structs.Events
aclDelegate ACLDelegate aclDelegate ACLDelegate
aclCache *lru.TwoQueueCache aclCache *structs.ACLCache[*acl.ACL]
aclCh chan structs.Event aclCh chan structs.Event
@ -63,11 +62,6 @@ func NewEventBroker(ctx context.Context, aclDelegate ACLDelegate, cfg EventBroke
cfg.EventBufferSize = 100 cfg.EventBufferSize = 100
} }
aclCache, err := lru.New2Q(aclCacheSize)
if err != nil {
return nil, err
}
buffer := newEventBuffer(cfg.EventBufferSize) buffer := newEventBuffer(cfg.EventBufferSize)
e := &EventBroker{ e := &EventBroker{
logger: cfg.Logger.Named("event_broker"), logger: cfg.Logger.Named("event_broker"),
@ -75,7 +69,7 @@ func NewEventBroker(ctx context.Context, aclDelegate ACLDelegate, cfg EventBroke
publishCh: make(chan *structs.Events, 64), publishCh: make(chan *structs.Events, 64),
aclCh: make(chan structs.Event, 10), aclCh: make(chan structs.Event, 10),
aclDelegate: aclDelegate, aclDelegate: aclDelegate,
aclCache: aclCache, aclCache: structs.NewACLCache[*acl.ACL](aclCacheSize),
subscriptions: &subscriptions{ subscriptions: &subscriptions{
byToken: make(map[string]map[*SubscribeRequest]*Subscription), byToken: make(map[string]map[*SubscribeRequest]*Subscription),
}, },
@ -277,7 +271,7 @@ func (e *EventBroker) checkSubscriptionsAgainstACLChange() {
} }
func aclObjFromSnapshotForTokenSecretID( func aclObjFromSnapshotForTokenSecretID(
aclSnapshot ACLTokenProvider, aclCache *lru.TwoQueueCache, tokenSecretID string) ( aclSnapshot ACLTokenProvider, aclCache *structs.ACLCache[*acl.ACL], tokenSecretID string) (
*acl.ACL, *time.Time, error) { *acl.ACL, *time.Time, error) {
aclToken, err := aclSnapshot.ACLTokenBySecretID(nil, tokenSecretID) aclToken, err := aclSnapshot.ACLTokenBySecretID(nil, tokenSecretID)

View File

@ -11,11 +11,14 @@ import (
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-set" "github.com/hashicorp/go-set"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/lib/lang"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"oss.indeed.com/go/libtime"
) )
const ( const (
@ -187,10 +190,48 @@ var (
// ValidACLRoleName is used to validate an ACL role name. // ValidACLRoleName is used to validate an ACL role name.
ValidACLRoleName = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$") ValidACLRoleName = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$")
// validACLAuthMethodName is used to validate an ACL auth method name. // ValidACLAuthMethod is used to validate an ACL auth method name.
ValidACLAuthMethod = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$") ValidACLAuthMethod = regexp.MustCompile("^[a-zA-Z0-9-]{1,128}$")
) )
type ACLCacheEntry[T any] lang.Pair[T, time.Time]
func (e ACLCacheEntry[T]) Age() time.Duration {
return time.Since(e.Second)
}
func (e ACLCacheEntry[T]) Get() T {
return e.First
}
// An ACLCache caches ACL tokens by their policy content.
type ACLCache[T any] struct {
*lru.TwoQueueCache[string, ACLCacheEntry[T]]
clock libtime.Clock
}
func (c *ACLCache[T]) Add(key string, item T) {
c.AddAtTime(key, item, c.clock.Now())
}
func (c *ACLCache[T]) AddAtTime(key string, item T, now time.Time) {
c.TwoQueueCache.Add(key, ACLCacheEntry[T]{
First: item,
Second: now,
})
}
func NewACLCache[T any](size int) *ACLCache[T] {
c, err := lru.New2Q[string, ACLCacheEntry[T]](size)
if err != nil {
panic(err) // not possible
}
return &ACLCache[T]{
TwoQueueCache: c,
clock: libtime.SystemClock(),
}
}
// ACLTokenRoleLink is used to link an ACL token to an ACL role. The ACL token // ACLTokenRoleLink is used to link an ACL token to an ACL role. The ACL token
// can therefore inherit all the ACL policy permissions that the ACL role // can therefore inherit all the ACL policy permissions that the ACL role
// contains. // contains.

View File

@ -10,9 +10,8 @@ import (
"strconv" "strconv"
"strings" "strings"
multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-set" "github.com/hashicorp/go-set"
lru "github.com/hashicorp/golang-lru"
"github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/acl"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
) )
@ -436,7 +435,7 @@ func ACLPolicyListHash(policies []*ACLPolicy) string {
} }
// CompileACLObject compiles a set of ACL policies into an ACL object with a cache // CompileACLObject compiles a set of ACL policies into an ACL object with a cache
func CompileACLObject(cache *lru.TwoQueueCache, policies []*ACLPolicy) (*acl.ACL, error) { func CompileACLObject(cache *ACLCache[*acl.ACL], policies []*ACLPolicy) (*acl.ACL, error) {
// Sort the policies to ensure consistent ordering // Sort the policies to ensure consistent ordering
sort.Slice(policies, func(i, j int) bool { sort.Slice(policies, func(i, j int) bool {
return policies[i].Name < policies[j].Name return policies[i].Name < policies[j].Name
@ -444,9 +443,9 @@ func CompileACLObject(cache *lru.TwoQueueCache, policies []*ACLPolicy) (*acl.ACL
// Determine the cache key // Determine the cache key
cacheKey := ACLPolicyListHash(policies) cacheKey := ACLPolicyListHash(policies)
aclRaw, ok := cache.Get(cacheKey) entry, ok := cache.Get(cacheKey)
if ok { if ok {
return aclRaw.(*acl.ACL), nil return entry.Get(), nil
} }
// Parse the policies // Parse the policies

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"testing" "testing"
lru "github.com/hashicorp/golang-lru" "github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/helper/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -1007,8 +1007,7 @@ func TestCompileACLObject(t *testing.T) {
p2.Name = fmt.Sprintf("policy-%s", uuid.Generate()) p2.Name = fmt.Sprintf("policy-%s", uuid.Generate())
// Create a small cache // Create a small cache
cache, err := lru.New2Q(16) cache := NewACLCache[*acl.ACL](10)
assert.Nil(t, err)
// Test compilation // Test compilation
aclObj, err := CompileACLObject(cache, []*ACLPolicy{p1}) aclObj, err := CompileACLObject(cache, []*ACLPolicy{p1})

View File

@ -8,5 +8,5 @@ codecgen \
-d 100 \ -d 100 \
-t codegen_generated \ -t codegen_generated \
-o structs.generated.go \ -o structs.generated.go \
-nr="^IdentityClaims$" \ -nr="(^ACLCache$)|(^IdentityClaims$)" \
${FILES} ${FILES}