From 208d1cfb0a71d4a805e634e778a4dd696add86de Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 17 Sep 2024 10:59:30 -0700 Subject: [PATCH 01/32] Intial work on backoff algorithm. Signed-off-by: Edwin Buck --- .../endpoints/authorized_entryfetcher.go | 10 +- ...rized_entryfetcher_registration_entries.go | 9 +- pkg/server/endpoints/eventTracker.go | 133 +++ pkg/server/endpoints/eventTracker_test.go | 951 ++++++++++++++++++ 4 files changed, 1095 insertions(+), 8 deletions(-) create mode 100644 pkg/server/endpoints/eventTracker.go create mode 100644 pkg/server/endpoints/eventTracker_test.go diff --git a/pkg/server/endpoints/authorized_entryfetcher.go b/pkg/server/endpoints/authorized_entryfetcher.go index 27de3f14f3..5e9cea8a7c 100644 --- a/pkg/server/endpoints/authorized_entryfetcher.go +++ b/pkg/server/endpoints/authorized_entryfetcher.go @@ -37,9 +37,9 @@ type eventsBasedCache interface { pruneMissedEvents() } -func NewAuthorizedEntryFetcherWithEventsBasedCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, clk clock.Clock, ds datastore.DataStore, cacheReloadInterval, pruneEventsOlderThan, sqlTransactionTimeout time.Duration) (*AuthorizedEntryFetcherWithEventsBasedCache, error) { +func NewAuthorizedEntryFetcherWithEventsBasedCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, clk clock.Clock, ds datastore.DataStore, cri cacheReloadInterval, pruneEventsOlderThan, sqlTransactionTimeout time.Duration) (*AuthorizedEntryFetcherWithEventsBasedCache, error) { log.Info("Building event-based in-memory entry cache") - cache, registrationEntries, attestedNodes, err := buildCache(ctx, log, metrics, ds, clk, sqlTransactionTimeout) + cache, registrationEntries, attestedNodes, err := buildCache(ctx, log, metrics, ds, clk, cri, sqlTransactionTimeout) if err != nil { return nil, err } @@ -112,10 +112,12 @@ func (a *AuthorizedEntryFetcherWithEventsBasedCache) updateCache(ctx context.Con return errors.Join(updateRegistrationEntriesCacheErr, updateAttestedNodesCacheErr) } -func buildCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, sqlTransactionTimeout time.Duration) (*authorizedentries.Cache, *registrationEntries, *attestedNodes, error) { +func buildCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cri cacheReloadInterval, pollPeriods uint, sqlTransactionTimeout time.Duration) (*authorizedentries.Cache, *registrationEntries, *attestedNodes, error) { cache := authorizedentries.NewCache(clk) - registrationEntries, err := buildRegistrationEntriesCache(ctx, log, metrics, ds, clk, cache, buildCachePageSize, sqlTransactionTimeout) + pollPeriods := PollPeriods(cri, sqlTransactionTimeout) + pollBoundaries := LinearPollBoundaryBuilder(pollPeriods, 0, sqlTransactionime.Duration(10) * time.MINUTE, time.Duration(30) * time.Second) + registrationEntries, err := buildRegistrationEntriesCache(ctx, log, metrics, ds, clk, cache, buildCachePageSize, pollPeriods, sqlTransactionTimeout) if err != nil { return nil, nil, nil, err } diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index 570cbad008..bab27dda89 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -28,13 +28,15 @@ type registrationEntries struct { firstEventID uint firstEventTime time.Time lastEventID uint - missedEvents map[uint]time.Time + eventTracker *eventTracker seenMissedStartupEvents map[uint]struct{} sqlTransactionTimeout time.Duration } // buildRegistrationEntriesCache Fetches all registration entries and adds them to the cache func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, pageSize int32, sqlTransactionTimeout time.Duration) (*registrationEntries, error) { +func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, pageSize int32, pollPeriods uint, sqlTransactionTimeout time.Duration) (*registrationEntries, error) { + eventTracker := NewEventTracker(pollPeriods, ) resp, err := ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) if err != nil { return nil, err @@ -44,7 +46,6 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, var firstEventID uint var firstEventTime time.Time var lastEventID uint - missedEvents := make(map[uint]time.Time) for _, event := range resp.Events { now := clk.Now() if firstEventTime.IsZero() { @@ -54,7 +55,7 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, // After getting the first event, search for any gaps in the event stream, from the first event to the last event. // During each cache refresh cycle, we will check if any of these missed events get populated. for i := lastEventID + 1; i < event.EventID; i++ { - missedEvents[i] = clk.Now() + eventTracker.StartTracking(i) } } lastEventID = event.EventID @@ -93,12 +94,12 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, cache: cache, clk: clk, ds: ds, + eventTracker: NewEventTracker(pollPeriods, ), firstEventID: firstEventID, firstEventTime: firstEventTime, log: log, metrics: metrics, lastEventID: lastEventID, - missedEvents: missedEvents, seenMissedStartupEvents: make(map[uint]struct{}), sqlTransactionTimeout: sqlTransactionTimeout, }, nil diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go new file mode 100644 index 0000000000..ffddfe8dd8 --- /dev/null +++ b/pkg/server/endpoints/eventTracker.go @@ -0,0 +1,133 @@ +package endpoints + +import ( +// "fmt" + "slices" + "time" +) + +const INITIAL_POLL_COUNT uint = 10 + +type eventTracker struct { + initialPolls uint + pollPeriods uint + events map[uint]*eventStats + boundaries []uint +} + +type eventStats struct { + hash uint + ticks uint + polls uint +} + +func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { + if pollTime < (time.Duration(1) * time.Minute) { + pollTime = time.Duration(1) * time.Minute + } + if trackTime < (time.Duration(1) * time.Minute) { + trackTime = time.Duration(1) * time.Minute + } + return uint(1 + (trackTime - 1) / pollTime) +} + +func NewEventTracker(pollPeriods uint, boundaries []uint) *eventTracker { + if pollPeriods < 1 { + pollPeriods = 1 + } + + // cleanup boundaries into incrasing slice of no duplicates + boundaryMap := make(map[uint]bool) + filteredBounds := []uint{} + for _, boundary := range boundaries { + // trim duplicates and boundaries outside of polling range + if _, found := boundaryMap[boundary]; !found && boundary < pollPeriods { + boundaryMap[boundary] = true + filteredBounds = append(filteredBounds, boundary) + } + } + slices.Sort(filteredBounds) + + initialPolls := uint(0) + switch { + case boundaries == nil, len(filteredBounds) == 0: + initialPolls = pollPeriods + default: + initialPolls = filteredBounds[0] + } + + return &eventTracker{ + initialPolls: initialPolls, + pollPeriods: pollPeriods, + boundaries: filteredBounds, + events: make(map[uint]*eventStats), + } +} + +func (et *eventTracker) PollPeriods() uint { + return et.pollPeriods +} + +func (et *eventTracker) InitialPolls() uint { + return et.initialPolls +} + +func (et *eventTracker) PollBoundaries() []uint { + return et.boundaries +} + +func (et *eventTracker) Polls() uint { + return et.initialPolls + uint(len(et.boundaries)) +} + +func (et *eventTracker) StartTracking(event uint) { + et.events[event] = &eventStats{ + hash: hash(event), + ticks: 0, + polls: 0, + } +} + +func (et *eventTracker) PollEvents() []uint { + // fmt.Print("polling events\n") + pollList := make([]uint, 0) + for event, _ := range et.events { + eventStats := et.events[event] + bucket := eventStats.polls - et.initialPolls + // fmt.Printf(" event %d: %+v, bucket %d\n", event, eventStats, bucket) + switch { + case eventStats.polls < et.initialPolls: + // fmt.Print(" initial poll range, adding\n") + pollList = append(pollList, event) + eventStats.polls++ + case bucket + 1 < uint(len(et.boundaries)): + // fmt.Print(" not last range\n") + bucketWidth := et.boundaries[1+bucket] - et.boundaries[bucket] + bucketPosition := eventStats.hash % bucketWidth + //fmt.Printf("event %d, hash %d, bucket %d\n", event, eventStats.hash, bucketPosition) + if eventStats.ticks == et.boundaries[bucket] + bucketPosition { + pollList = append(pollList, event) + } + case bucket < uint(len(et.boundaries)): + // fmt.Print(" last range\n") + bucketWidth := et.pollPeriods - et.boundaries[bucket] + bucketPosition := eventStats.hash % bucketWidth + //fmt.Printf("event %d, hash %d, bucket %d\n", event, eventStats.hash, bucketPosition) + if eventStats.ticks == et.boundaries[bucket] + bucketPosition { + pollList = append(pollList, event) + } + } + eventStats.ticks++ + } + return pollList +} + +func hash(event uint) uint { + h := event + h ^= h >> 16 + h *= 0x119de1f3 + h ^= h >> 15 + h *= 0x119de1f3 + h ^= h >> 16 + return h +} diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go new file mode 100644 index 0000000000..9e94091a94 --- /dev/null +++ b/pkg/server/endpoints/eventTracker_test.go @@ -0,0 +1,951 @@ +package endpoints_test + +import ( + "testing" + "time" + + "github.com/spiffe/spire/pkg/server/endpoints" + "github.com/stretchr/testify/require" +) + +func TestPollPeriods(t *testing.T) { + for _, tt := range []struct { + name string + pollInterval time.Duration + pollDuration time.Duration + + expectedPollPeriods uint + }{ + { + name: "polling always polls at least once, even for zero duration", + pollInterval: time.Duration(1) * time.Minute, + pollDuration: time.Duration(0) * time.Minute, + + expectedPollPeriods: 1, + }, + { + name: "polling always polls at least once, even for negative durations", + pollInterval: time.Duration(1) * time.Minute, + pollDuration: time.Duration(-10) * time.Minute, + + expectedPollPeriods: 1, + }, + { + name: "minimum poll interval of one minute", + pollInterval: time.Duration(20) * time.Second, + pollDuration: time.Duration(10) * time.Minute, + + expectedPollPeriods: 10, + }, + { + name: "minimum poll interval of one minute, even for negative intervals", + pollInterval: time.Duration(-1) * time.Minute, + pollDuration: time.Duration(10) * time.Minute, + + expectedPollPeriods: 10, + }, + { + name: "polling every minute in two mintues", + pollInterval: time.Minute * time.Duration(1), + pollDuration: time.Minute * time.Duration(2), + + expectedPollPeriods: 2, + }, + { + name: "polling every minute of an hours", + pollInterval: time.Minute * time.Duration(1), + pollDuration: time.Hour * time.Duration(1), + + expectedPollPeriods: 60, + }, + { + name: "polling rounds up", + pollInterval: time.Minute * time.Duration(3), + pollDuration: time.Minute * time.Duration(10), + + expectedPollPeriods: 4, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + pollPeriods := endpoints.PollPeriods(tt.pollInterval, tt.pollDuration) + + require.Equal(t, tt.expectedPollPeriods, pollPeriods, "interval %s, polled over %s yeilds %d poll periods, not %d poll periods", tt.pollInterval.String(), tt.pollDuration.String(), pollPeriods, tt.expectedPollPeriods) + }) + } +} + +func TestNewEventTracker(t *testing.T) { + for _, tt := range []struct { + name string + pollPeriods uint + boundaries []uint + + expectedInitialPolls uint + expectedPollPeriods uint + expectedPolls uint + expectedBoundaries []uint + }{ + { + name: "polling always polls at least once", + pollPeriods: 0, + boundaries: []uint{}, + + expectedInitialPolls: 1, + expectedPollPeriods: 1, + expectedPolls: 1, + expectedBoundaries: []uint{}, + }, + { + name: "polling once, pre-boundary", + pollPeriods: 1, + boundaries: []uint{}, + + expectedInitialPolls: 1, + expectedPollPeriods: 1, + expectedPolls: 1, + expectedBoundaries: []uint{}, + }, + { + name: "polling once, in one bucket boundary", + pollPeriods: 1, + boundaries: []uint{0}, + + expectedInitialPolls: 0, + expectedPollPeriods: 1, + expectedPolls: 1, + expectedBoundaries: []uint{0}, + }, + { + name: "polling twice, both initial", + pollPeriods: 2, + boundaries: []uint{}, + + expectedInitialPolls: 2, + expectedPollPeriods: 2, + expectedPolls: 2, + expectedBoundaries: []uint{}, + }, + { + name: "polling twice, once initial, once in one bucket boundary", + pollPeriods: 2, + boundaries: []uint{1}, + + expectedInitialPolls: 1, + expectedPollPeriods: 2, + expectedPolls: 2, + expectedBoundaries: []uint{1}, + }, + { + name: "polling once, in two bucket boundary", + pollPeriods: 2, + boundaries: []uint{0}, + + expectedInitialPolls: 0, + expectedPollPeriods: 2, + expectedPolls: 1, + expectedBoundaries: []uint{0}, + }, + { + name: "polling once, in three bucket boundary", + pollPeriods: 3, + boundaries: []uint{0}, + + expectedInitialPolls: 0, + expectedPollPeriods: 3, + expectedPolls: 1, + expectedBoundaries: []uint{0}, + }, + { + name: "polling six times in exponential backoff", + pollPeriods: 120, + boundaries: []uint{0, 2, 6, 14, 30, 62}, + + expectedInitialPolls: 0, + expectedPollPeriods: 120, + expectedPolls: 6, + expectedBoundaries: []uint{0, 2, 6, 14, 30, 62}, + }, + { + name: "distributed linear polling for a while, then exponential", + pollPeriods: 600, + boundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, + + expectedInitialPolls: 0, + expectedPollPeriods: 600, + expectedPolls: 10, + expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, + }, + { + name: "clip boundaries outside of poll periods", + pollPeriods: 600, + boundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480, 9600}, + + expectedInitialPolls: 0, + expectedPollPeriods: 600, + expectedPolls: 10, + expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, + }, + { + name: "order of boundaries doesn't matter", + pollPeriods: 600, + boundaries: []uint{240, 480, 9600, 0, 10, 50, 60, 120, 20, 30, 40}, + + expectedInitialPolls: 0, + expectedPollPeriods: 600, + expectedPolls: 10, + expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, + }, + { + name: "duplicate boundaries are collapsed", + pollPeriods: 600, + boundaries: []uint{0, 10, 10, 10, 20, 30, 40, 50, 60, 60, 120, 240, 480, 240, 9600}, + + expectedInitialPolls: 0, + expectedPollPeriods: 600, + expectedPolls: 10, + expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + eventTracker := endpoints.NewEventTracker(tt.pollPeriods, tt.boundaries) + + require.Equal(t, tt.expectedPollPeriods, eventTracker.PollPeriods(), "expcting %d poll periods; but, %d poll periods reported", eventTracker.PollPeriods(), tt.expectedPollPeriods) + + require.Equal(t, tt.expectedBoundaries, eventTracker.PollBoundaries(), "expected %v boundaries, not %v boundaries", eventTracker.PollBoundaries(), tt.expectedBoundaries) + require.Equal(t, tt.expectedInitialPolls, eventTracker.InitialPolls(), "inital polls of %d when requesting %d", eventTracker.InitialPolls(), tt.expectedInitialPolls) + require.Equal(t, tt.expectedPolls, eventTracker.Polls(), "polling each element %d times, when expecting %d times", tt.expectedPolls, eventTracker.Polls()) + }) + } +} + +/* + 0 1 2 3 4 5 (pollPeriods) + 0 3 (boundaries) + 0 0 0 1 1 1 (bucket number) + 3 3 3 3 3 3 (bucket width) + + 0 3 0 0 3 0 (timeslots to poll event 3) + 3 3 3 3 3 3 (every slot is polled) + + pollEvent(3) + hash := hash(3) # hash must distribute well + poll within the bucket = 34234 % (bucket width) + */ + +func TestEvenTrackerPolling(t *testing.T) { + for _, tt := range []struct { + name string + pollPeriods uint + boundaries []uint + + trackEvents [][]uint + expectedPolls uint + expectedEvents [][]uint + }{ + { + name: "every event is polled at least once, even when zero polling periods", + pollPeriods: 0, + boundaries: []uint{}, + trackEvents: [][]uint{ + {5, 11, 12, 15}, + {6, 7, 8, 9, 10}, + }, + + expectedPolls: 1, + expectedEvents: [][]uint{ + {5, 11, 12, 15}, + {6, 7, 8, 9, 10}, + {}, + }, + }, + { + name: "polling each event once, initial period", + pollPeriods: 1, + boundaries: []uint{}, + trackEvents: [][]uint{ + {5, 11, 12, 15}, + {6, 7, 8, 9, 10}, + }, + + expectedPolls: 1, + expectedEvents: [][]uint{ + {5, 11, 12, 15}, + {6, 7, 8, 9, 10}, + {}, + }, + }, + { + name: "polling each event twice, initial period", + pollPeriods: 2, + boundaries: []uint{}, + trackEvents: [][]uint{ + {5, 11, 12, 15}, + {6, 7, 8, 9, 10}, + }, + + expectedPolls: 2, + expectedEvents: [][]uint{ + {5, 11, 12, 15}, + {5, 6, 7, 8, 9, 10, 11, 12, 15}, + {6, 7, 8, 9, 10}, + {}, + }, + }, + { + name: "polling each event thrice, initial period", + pollPeriods: 3, + boundaries: []uint{}, + trackEvents: [][]uint{ + {5, 11, 12, 15}, + {6, 7, 8, 9, 10}, + {1, 2, 3, 4, 13}, + }, + + expectedPolls: 3, + expectedEvents: [][]uint{ + {5, 11, 12, 15}, + {5, 6, 7, 8, 9, 10, 11, 12, 15}, + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15}, + {1, 2, 3, 4, 6, 7, 8, 9, 10, 13}, + {1, 2, 3, 4, 13}, + {}, + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + eventTracker := endpoints.NewEventTracker(tt.pollPeriods, tt.boundaries) + require.Equal(t, tt.expectedPolls, eventTracker.Polls(), + "expecting %d polls per event, but event tracker reports %d polls per event", + tt.expectedPolls, eventTracker.Polls()) + + pollCount := make(map[uint]uint) + + // run the simulation over what we expect + for index, expectedEvents := range tt.expectedEvents { + // if there are new tracking requests, add them + if index < len(tt.trackEvents) { + for _, event := range tt.trackEvents[index] { + eventTracker.StartTracking(event) + } + } + // get the events we should poll + events := eventTracker.PollEvents() + // update count for each event + for _, event := range events { + pollCount[event]++ + } + // see if the results match the expecations + require.ElementsMatch(t, expectedEvents, events, + "At time step %d, expected set of Events %v, received %v", + index, expectedEvents, events) + } + for event, polls := range pollCount { + require.Equal(t, tt.expectedPolls, polls, + "expecting %d polls for event %d, but received %d polls", + tt.expectedPolls, polls, event) + } + }) + } +} + +func TestEvenDispersion(t *testing.T) { + for _, tt := range []struct { + name string + pollPeriods uint + + startEvent uint + eventIncrement uint + eventCount uint + + expectedSlotCount uint + permissibleCountError uint + }{ + { + name: "increment by 2 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 2 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 1, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 2) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 2, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 1, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 2) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 2, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 3) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 3, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 1, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 2) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 2, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 3) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 3, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 4) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 4, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 500, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 2 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 2 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 1, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 2) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 2, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 1, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 2) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 2, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 3) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 3, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 1, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 2) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 2, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 3) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 3, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 4) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 4, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 333, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 2 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 2 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 1, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 2) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 2, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 1, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 2) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 2, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 3) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 3, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 1, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 2) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 2, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 3) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 3, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 4) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 4, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 250, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 2 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 2 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, + eventIncrement: 2, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 1, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 3 (offset 2) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 2, + eventIncrement: 3, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 1, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 2) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 2, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 4 (offset 3) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 3, + eventIncrement: 4, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 1, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 2) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 2, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 3) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 3, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + { + name: "increment by 5 (offset 4) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 4, + eventIncrement: 5, + eventCount: 1000, + + expectedSlotCount: 200, + permissibleCountError: 50, // 5% of 1000 + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + eventTracker := endpoints.NewEventTracker(tt.pollPeriods, []uint{0}) + slotCount := make(map[uint]uint) + for item := range tt.eventCount { + event := tt.startEvent + tt.eventIncrement*item + eventTracker.StartTracking(event) + } + for pollPeriod := range tt.pollPeriods { + events := eventTracker.PollEvents() + slotCount[pollPeriod] = uint(len(events)) + t.Logf("pollPeriod %d, count = %d", pollPeriod, slotCount[pollPeriod]) + } + for slot, count := range slotCount { + require.LessOrEqual(t, tt.expectedSlotCount - tt.permissibleCountError, count, + "for slot %d, expecting at least %d polls, but received %d polls", + slot, tt.expectedSlotCount - tt.permissibleCountError, count) + require.GreaterOrEqual(t, tt.expectedSlotCount + tt.permissibleCountError, count, + "for slot %d, expecting no more than %d polls, but received %d polls", + slot, tt.expectedSlotCount + tt.permissibleCountError, count) + } + + }) + } +} From c6d23aa3b198b0e8e8346e915b768579c6df2796 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Wed, 18 Sep 2024 12:17:32 -0700 Subject: [PATCH 02/32] Push after refactoring registration entry event tracking. Signed-off-by: Edwin Buck --- .../endpoints/authorized_entryfetcher.go | 14 +- .../authorized_entryfetcher_attested_nodes.go | 14 +- ...rized_entryfetcher_registration_entries.go | 298 ++++++++---------- pkg/server/endpoints/eventTracker.go | 38 +++ 4 files changed, 176 insertions(+), 188 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher.go b/pkg/server/endpoints/authorized_entryfetcher.go index 5e9cea8a7c..b531bc0949 100644 --- a/pkg/server/endpoints/authorized_entryfetcher.go +++ b/pkg/server/endpoints/authorized_entryfetcher.go @@ -34,12 +34,11 @@ type AuthorizedEntryFetcherWithEventsBasedCache struct { type eventsBasedCache interface { updateCache(ctx context.Context) error - pruneMissedEvents() } -func NewAuthorizedEntryFetcherWithEventsBasedCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, clk clock.Clock, ds datastore.DataStore, cri cacheReloadInterval, pruneEventsOlderThan, sqlTransactionTimeout time.Duration) (*AuthorizedEntryFetcherWithEventsBasedCache, error) { +func NewAuthorizedEntryFetcherWithEventsBasedCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, clk clock.Clock, ds datastore.DataStore, cacheReloadInterval, pruneEventsOlderThan, sqlTransactionTimeout time.Duration) (*AuthorizedEntryFetcherWithEventsBasedCache, error) { log.Info("Building event-based in-memory entry cache") - cache, registrationEntries, attestedNodes, err := buildCache(ctx, log, metrics, ds, clk, cri, sqlTransactionTimeout) + cache, registrationEntries, attestedNodes, err := buildCache(ctx, log, metrics, ds, clk, cacheReloadInterval, sqlTransactionTimeout) if err != nil { return nil, err } @@ -99,9 +98,6 @@ func (a *AuthorizedEntryFetcherWithEventsBasedCache) pruneEvents(ctx context.Con pruneRegistrationEntriesEventsErr := a.ds.PruneRegistrationEntriesEvents(ctx, olderThan) pruneAttestedNodesEventsErr := a.ds.PruneAttestedNodesEvents(ctx, olderThan) - a.registrationEntries.pruneMissedEvents() - a.attestedNodes.pruneMissedEvents() - return errors.Join(pruneRegistrationEntriesEventsErr, pruneAttestedNodesEventsErr) } @@ -112,12 +108,10 @@ func (a *AuthorizedEntryFetcherWithEventsBasedCache) updateCache(ctx context.Con return errors.Join(updateRegistrationEntriesCacheErr, updateAttestedNodesCacheErr) } -func buildCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cri cacheReloadInterval, pollPeriods uint, sqlTransactionTimeout time.Duration) (*authorizedentries.Cache, *registrationEntries, *attestedNodes, error) { +func buildCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cacheReloadInterval, sqlTransactionTimeout time.Duration) (*authorizedentries.Cache, *registrationEntries, *attestedNodes, error) { cache := authorizedentries.NewCache(clk) - pollPeriods := PollPeriods(cri, sqlTransactionTimeout) - pollBoundaries := LinearPollBoundaryBuilder(pollPeriods, 0, sqlTransactionime.Duration(10) * time.MINUTE, time.Duration(30) * time.Second) - registrationEntries, err := buildRegistrationEntriesCache(ctx, log, metrics, ds, clk, cache, buildCachePageSize, pollPeriods, sqlTransactionTimeout) + registrationEntries, err := buildRegistrationEntriesCache(ctx, log, metrics, ds, clk, cache, buildCachePageSize, cacheReloadInterval, sqlTransactionTimeout) if err != nil { return nil, nil, nil, err } diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 6d692eeba7..2112d2d371 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -37,6 +37,9 @@ type attestedNodes struct { // buildAttestedNodesCache fetches all attested nodes and adds the unexpired ones to the cache. // It runs once at startup. func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, sqlTransactionTimeout time.Duration) (*attestedNodes, error) { + pollPeriods := PollPeriods(cacheReloadInterval, sqlTransactionTimeout) + pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) + resp, err := ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) if err != nil { return nil, err @@ -237,14 +240,3 @@ func (a *attestedNodes) updateCacheEntry(ctx context.Context, spiffeID string) e return nil } -// prunedMissedEvents delete missed events that are older than the configured SQL transaction timeout time. -func (a *attestedNodes) pruneMissedEvents() { - a.mu.Lock() - defer a.mu.Unlock() - - for eventID, eventTime := range a.missedEvents { - if a.clk.Now().Sub(eventTime) > a.sqlTransactionTimeout { - delete(a.missedEvents, eventID) - } - } -} diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index bab27dda89..b5e1706a6e 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -25,46 +25,101 @@ type registrationEntries struct { metrics telemetry.Metrics mu sync.RWMutex - firstEventID uint - firstEventTime time.Time - lastEventID uint - eventTracker *eventTracker - seenMissedStartupEvents map[uint]struct{} - sqlTransactionTimeout time.Duration + eventsBeforeFirst map[uint]struct{} + + firstEvent uint + firstEventTime time.Time + lastEvent uint + + eventTracker *eventTracker + sqlTransactionTimeout time.Duration + + fetchEntries map[string]struct{} } -// buildRegistrationEntriesCache Fetches all registration entries and adds them to the cache -func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, pageSize int32, sqlTransactionTimeout time.Duration) (*registrationEntries, error) { -func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, pageSize int32, pollPeriods uint, sqlTransactionTimeout time.Duration) (*registrationEntries, error) { - eventTracker := NewEventTracker(pollPeriods, ) - resp, err := ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) +func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { + // first reset what we might fetch. + a.fetchEntries = make(map[string]struct{}) + + // before first event logic. Typically an empty space, so we poll the range and filter out the already seen + if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { + resp, err := a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ + LessThanEventID: a.firstEvent, + }) + if err != nil { + return err + } + for _, event := range resp.Events { + if _, seen := a.eventsBeforeFirst[event.EventID]; !seen { + a.fetchEntries[event.EntryID] = struct{}{} + a.eventsBeforeFirst[event.EventID] = struct{}{} + } + } + } else { + // clear out the before first tracking + a.eventsBeforeFirst = make(map[uint]struct{}) + } + + // check if the polled events have appeared out-of-order + for _, eventID := range a.eventTracker.PollEvents() { + log := a.log.WithField(telemetry.EventID, eventID) + event, err := a.ds.FetchRegistrationEntryEvent(ctx, eventID) + + switch status.Code(err) { + case codes.OK: + case codes.NotFound: + continue + default: + log.WithError(err).Errorf("Failed to fetch info about skipped event %d", eventID) + continue + } + + a.fetchEntries[event.EntryID] = struct{}{} + a.eventTracker.StopTracking(uint(eventID)) + } + server_telemetry.SetSkippedEntryEventIDsCacheCountGauge(a.metrics, int(a.eventTracker.EventCount())) + + // If we haven't seen an event, scan for all events; otherwise, scan from the last event. + var resp *datastore.ListRegistrationEntriesEventsResponse + var err error + if a.firstEventTime.IsZero() { + resp, err = a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + } else { + resp, err = a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ + GreaterThanEventID: a.lastEvent, + }) + } if err != nil { - return nil, err + return err } - // Gather any events that may have been skipped during restart - var firstEventID uint - var firstEventTime time.Time - var lastEventID uint for _, event := range resp.Events { - now := clk.Now() - if firstEventTime.IsZero() { - firstEventID = event.EventID - firstEventTime = now - } else { - // After getting the first event, search for any gaps in the event stream, from the first event to the last event. - // During each cache refresh cycle, we will check if any of these missed events get populated. - for i := lastEventID + 1; i < event.EventID; i++ { - eventTracker.StartTracking(i) - } + // event time determines if we have seen the first event. + if a.firstEventTime.IsZero() { + a.firstEvent = event.EventID + a.lastEvent = event.EventID + a.fetchEntries[event.EntryID] = struct{}{} + a.firstEventTime = a.clk.Now() + continue } - lastEventID = event.EventID + + // track any skipped event ids, should the appear later. + for skipped := a.lastEvent + 1; skipped < event.EventID; skipped++ { + a.eventTracker.StartTracking(skipped) + } + + // every event adds its entry to the entry fetch list. + a.fetchEntries[event.EntryID] = struct{}{} + a.lastEvent = event.EventID } + return nil +} +func (a *registrationEntries) loadCache(ctx context.Context, pageSize int32) error { // Build the cache var token string for { - resp, err := ds.ListRegistrationEntries(ctx, &datastore.ListRegistrationEntriesRequest{ + resp, err := a.ds.ListRegistrationEntries(ctx, &datastore.ListRegistrationEntriesRequest{ DataConsistency: datastore.RequireCurrent, // preliminary loading should not be done via read-replicas Pagination: &datastore.Pagination{ Token: token, @@ -72,7 +127,7 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, }, }) if err != nil { - return nil, fmt.Errorf("failed to list registration entries: %w", err) + return fmt.Errorf("failed to list registration entries: %w", err) } token = resp.Pagination.Token @@ -82,80 +137,57 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, entries, err := api.RegistrationEntriesToProto(resp.Entries) if err != nil { - return nil, fmt.Errorf("failed to convert registration entries: %w", err) + return fmt.Errorf("failed to convert registration entries: %w", err) } for _, entry := range entries { - cache.UpdateEntry(entry) + a.cache.UpdateEntry(entry) } } + return nil +} - return ®istrationEntries{ +// buildRegistrationEntriesCache Fetches all registration entries and adds them to the cache +func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, pageSize int32, cacheReloadInterval, sqlTransactionTimeout time.Duration) (*registrationEntries, error) { + pollPeriods := PollPeriods(cacheReloadInterval, sqlTransactionTimeout) + pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) + + registrationEntries := ®istrationEntries{ cache: cache, clk: clk, ds: ds, - eventTracker: NewEventTracker(pollPeriods, ), - firstEventID: firstEventID, - firstEventTime: firstEventTime, log: log, metrics: metrics, - lastEventID: lastEventID, - seenMissedStartupEvents: make(map[uint]struct{}), sqlTransactionTimeout: sqlTransactionTimeout, - }, nil -} -// updateCache Fetches all the events since the last time this function was running and updates -// the cache with all the changes. -func (a *registrationEntries) updateCache(ctx context.Context) error { - // Process events skipped over previously - if err := a.missedStartupEvents(ctx); err != nil { - a.log.WithError(err).Error("Unable to process missed startup events") + eventsBeforeFirst: make(map[uint]struct{}), + fetchEntries: make(map[string]struct{}), + + eventTracker: NewEventTracker(pollPeriods, pollBoundaries), } - a.replayMissedEvents(ctx) - req := &datastore.ListRegistrationEntriesEventsRequest{ - GreaterThanEventID: a.lastEventID, + if err := registrationEntries.loadCache(ctx, pageSize); err != nil { + return nil, err } - resp, err := a.ds.ListRegistrationEntriesEvents(ctx, req) - if err != nil { - return err + if err := registrationEntries.captureChangedEntries(ctx); err != nil { + return nil, err + } + if err := registrationEntries.updateCacheEntries(ctx); err != nil { + return nil, err } - seenMap := map[string]struct{}{} - for _, event := range resp.Events { - // If there is a gap in the event stream, log the missed events for later processing. - // For example if the current event ID is 6 and the previous one was 3, events 4 and 5 - // were skipped over and need to be queued in case they show up later. - // This can happen when a long running transaction allocates an event ID but a shorter transaction - // comes in after, allocates and commits the ID first. If a read comes in at this moment, the event id for - // the longer running transaction will be skipped over. - if !a.firstEventTime.IsZero() { - for i := a.lastEventID + 1; i < event.EventID; i++ { - a.log.WithField(telemetry.EventID, i).Info("Detected skipped registration entry event") - a.mu.Lock() - a.missedEvents[i] = a.clk.Now() - a.mu.Unlock() - } - } - - // Skip fetching entries we've already fetched this call - if _, seen := seenMap[event.EntryID]; seen { - a.lastEventID = event.EventID - continue - } - seenMap[event.EntryID] = struct{}{} + return registrationEntries, nil +} - // Update the cache - if err := a.updateCacheEntry(ctx, event.EntryID); err != nil { - return err - } +// updateCache Fetches all the events since the last time this function was running and updates +// the cache with all the changes. +func (a *registrationEntries) updateCache(ctx context.Context) error { - if a.firstEventTime.IsZero() { - a.firstEventID = event.EventID - a.firstEventTime = a.clk.Now() - } - a.lastEventID = event.EventID + if err := a.captureChangedEntries(ctx); err != nil { + return err + } + if err := a.updateCacheEntries(ctx); err != nil { + return err } // These two should be the same value but it's valuable to have them both be emitted for incident triage. @@ -169,96 +201,28 @@ func (a *registrationEntries) updateCache(ctx context.Context) error { return nil } -// missedStartupEvents will check for any events come in with an ID less than the first event ID we receive. -// For example if the first event ID we receive is 3, this function will check for any IDs less than that. -// If event ID 2 comes in later on, due to a long running transaction, this function will update the cache -// with the information from this event. This function will run until time equal to sqlTransactionTimeout has elapsed after startup. -func (a *registrationEntries) missedStartupEvents(ctx context.Context) error { - if a.firstEventTime.IsZero() || a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { - return nil - } - - req := &datastore.ListRegistrationEntriesEventsRequest{ - LessThanEventID: a.firstEventID, - } - resp, err := a.ds.ListRegistrationEntriesEvents(ctx, req) - if err != nil { - return err - } - - for _, event := range resp.Events { - if _, seen := a.seenMissedStartupEvents[event.EventID]; !seen { - if err := a.updateCacheEntry(ctx, event.EntryID); err != nil { - a.log.WithError(err).Error("Failed to process missed startup event") - continue - } - a.seenMissedStartupEvents[event.EventID] = struct{}{} +// updateCacheEntry update/deletes/creates an individual registration entry in the cache. +func (a *registrationEntries) updateCacheEntries(ctx context.Context) error { + for entryId, _ := range a.fetchEntries { + commonEntry, err := a.ds.FetchRegistrationEntry(ctx, entryId) + if err != nil { + return err } - } - - return nil -} -// replayMissedEvents Processes events that have been skipped over. Events can come out of order from -// SQL. This function processes events that came in later than expected. -func (a *registrationEntries) replayMissedEvents(ctx context.Context) { - a.mu.Lock() - defer a.mu.Unlock() - - for eventID := range a.missedEvents { - log := a.log.WithField(telemetry.EventID, eventID) - - event, err := a.ds.FetchRegistrationEntryEvent(ctx, eventID) - switch status.Code(err) { - case codes.OK: - case codes.NotFound: - continue - default: - log.WithError(err).Error("Failed to fetch info about missed event") - continue + if commonEntry == nil { + a.cache.RemoveEntry(entryId) + return nil } - if err := a.updateCacheEntry(ctx, event.EntryID); err != nil { - log.WithError(err).Error("Failed to process missed event") - continue + entry, err := api.RegistrationEntryToProto(commonEntry) + if err != nil { + a.cache.RemoveEntry(entryId) + a.log.WithField(telemetry.RegistrationID, entryId).Warn("Removed malformed registration entry from cache") + return nil } - delete(a.missedEvents, eventID) + a.cache.UpdateEntry(entry) + delete(a.fetchEntries, entryId) } - server_telemetry.SetSkippedEntryEventIDsCacheCountGauge(a.metrics, len(a.missedEvents)) -} - -// updateCacheEntry update/deletes/creates an individual registration entry in the cache. -func (a *registrationEntries) updateCacheEntry(ctx context.Context, entryID string) error { - commonEntry, err := a.ds.FetchRegistrationEntry(ctx, entryID) - if err != nil { - return err - } - - if commonEntry == nil { - a.cache.RemoveEntry(entryID) - return nil - } - - entry, err := api.RegistrationEntryToProto(commonEntry) - if err != nil { - a.cache.RemoveEntry(entryID) - a.log.WithField(telemetry.RegistrationID, entryID).Warn("Removed malformed registration entry from cache") - return nil - } - - a.cache.UpdateEntry(entry) return nil } - -// prunedMissedEvents delete missed events that are older than the configured SQL transaction timeout time. -func (a *registrationEntries) pruneMissedEvents() { - a.mu.Lock() - defer a.mu.Unlock() - - for eventID, eventTime := range a.missedEvents { - if a.clk.Now().Sub(eventTime) > a.sqlTransactionTimeout { - delete(a.missedEvents, eventID) - } - } -} diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index ffddfe8dd8..ae68ab8009 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -31,6 +31,35 @@ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { return uint(1 + (trackTime - 1) / pollTime) } +func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { + pollPeriods := PollPeriods(pollTime, trackTime) + + pollBoundaries := make([]uint, 0) + // number of polls in a minute + pollsPerMinute := uint(time.Duration(1) * time.Minute / pollTime) + // number of polls in ten minutes + pollsPerTenMinutes := uint(time.Duration(10) * time.Minute / pollTime) + + // for first minute, poll at cacheReloadInterval + pollBoundaries = append(pollBoundaries, pollsPerMinute) + // next 9 minutes, poll at 1/2 minute + currentBoundary := pollsPerMinute + for currentBoundary < pollsPerTenMinutes { + pollBoundaries = append(pollBoundaries, currentBoundary + (pollsPerMinute / 2)) + pollBoundaries = append(pollBoundaries, currentBoundary + pollsPerMinute) + currentBoundary += pollsPerMinute + } + // rest of polling at 1 minute + for currentBoundary < pollPeriods { + pollBoundaries = append(pollBoundaries, currentBoundary + pollsPerMinute) + currentBoundary += pollsPerMinute + } + // always poll at end of transaction timeout + pollBoundaries = append(pollBoundaries, pollPeriods - 1) + + return pollBoundaries +} + func NewEventTracker(pollPeriods uint, boundaries []uint) *eventTracker { if pollPeriods < 1 { pollPeriods = 1 @@ -88,6 +117,11 @@ func (et *eventTracker) StartTracking(event uint) { } } +func (et *eventTracker) StopTracking(event uint) { + delete(et.events, event) +} + + func (et *eventTracker) PollEvents() []uint { // fmt.Print("polling events\n") pollList := make([]uint, 0) @@ -122,6 +156,10 @@ func (et *eventTracker) PollEvents() []uint { return pollList } +func (et *eventTracker) EventCount() uint { + return uint(len(et.events)) +} + func hash(event uint) uint { h := event h ^= h >> 16 From b4d2de995cb76b901308ed2846403ca45baf67d0 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Wed, 18 Sep 2024 15:16:04 -0700 Subject: [PATCH 03/32] Final refactoring done. Need to fix unit tests. Signed-off-by: Edwin Buck --- .../endpoints/authorized_entryfetcher.go | 2 +- .../authorized_entryfetcher_attested_nodes.go | 318 +++++++++--------- ...rized_entryfetcher_registration_entries.go | 40 ++- pkg/server/endpoints/eventTracker.go | 2 +- pkg/server/endpoints/eventTracker_test.go | 47 ++- 5 files changed, 230 insertions(+), 179 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher.go b/pkg/server/endpoints/authorized_entryfetcher.go index b531bc0949..50edc9426b 100644 --- a/pkg/server/endpoints/authorized_entryfetcher.go +++ b/pkg/server/endpoints/authorized_entryfetcher.go @@ -116,7 +116,7 @@ func buildCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.M return nil, nil, nil, err } - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, sqlTransactionTimeout) + attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, cacheReloadInterval, sqlTransactionTimeout) if err != nil { return nil, nil, nil, err } diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 2112d2d371..c8de524f13 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -26,217 +26,211 @@ type attestedNodes struct { metrics telemetry.Metrics mu sync.RWMutex - firstEventID uint + eventsBeforeFirst map[uint]struct{} + + firstEvent uint firstEventTime time.Time - lastEventID uint - missedEvents map[uint]time.Time - seenMissedStartupEvents map[uint]struct{} + lastEvent uint + + eventTracker *eventTracker sqlTransactionTimeout time.Duration + + fetchNodes map[string]struct{} } -// buildAttestedNodesCache fetches all attested nodes and adds the unexpired ones to the cache. -// It runs once at startup. -func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, sqlTransactionTimeout time.Duration) (*attestedNodes, error) { - pollPeriods := PollPeriods(cacheReloadInterval, sqlTransactionTimeout) - pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) +func (a *attestedNodes) captureChangedNodes(ctx context.Context) error { + // first, reset what we might fetch + a.fetchNodes = make(map[string]struct{}) - resp, err := ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) - if err != nil { - return nil, err + if err := a.searchBeforeFirstEvent(ctx); err != nil { + return err } - - // Gather any events that may have been skipped during restart - var firstEventID uint - var firstEventTime time.Time - var lastEventID uint - missedEvents := make(map[uint]time.Time) - for _, event := range resp.Events { - now := clk.Now() - if firstEventTime.IsZero() { - firstEventID = event.EventID - firstEventTime = now - } else { - // After getting the first event, search for any gaps in the event stream, from the first event to the last event. - // During each cache refresh cycle, we will check if any of these missed events get populated. - for i := lastEventID + 1; i < event.EventID; i++ { - missedEvents[i] = now - } - } - lastEventID = event.EventID + a.selectPolledEvents(ctx) + if err := a.scanForNewEvents(ctx); err != nil { + return err } - // Build the cache - nodesResp, err := ds.ListAttestedNodes(ctx, &datastore.ListAttestedNodesRequest{ + return nil +} + +func (a *attestedNodes) searchBeforeFirstEvent(ctx context.Context) error { + // First event detected, and startup was less than a transaction timout away. + if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { + resp, err := a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ + LessThanEventID: a.firstEvent, + }) + if err != nil { + return err + } + for _, event := range resp.Events { + // if we have seen it before, don't reload it. + if _, seen := a.eventsBeforeFirst[event.EventID]; !seen { + a.fetchNodes[event.SpiffeID] = struct{}{} + a.eventsBeforeFirst[event.EventID] = struct{}{} + } + } + return nil + } + + // zero out unused event tracker + if len(a.eventsBeforeFirst) != 0 { + a.eventsBeforeFirst = make(map[uint]struct{}) + } + + return nil +} + +func (a *attestedNodes) selectPolledEvents(ctx context.Context) { + // check if the polled events have appeared out-of-order + for _, eventID := range a.eventTracker.SelectEvents() { + log := a.log.WithField(telemetry.EventID, eventID) + event, err := a.ds.FetchAttestedNodeEvent(ctx, eventID) + + switch status.Code(err) { + case codes.OK: + case codes.NotFound: + continue + default: + log.WithError(err).Errorf("Failed to fetch info about skipped node event %d", eventID) + continue + } + + a.fetchNodes[event.SpiffeID] = struct{}{} + a.eventTracker.StopTracking(uint(eventID)) + } + server_telemetry.SetSkippedNodeEventIDsCacheCountGauge(a.metrics, int(a.eventTracker.EventCount())) +} + +func (a *attestedNodes) scanForNewEvents(ctx context.Context) error { + // If we haven't seen an event, scan for all events; otherwise, scan from the last event. + var resp *datastore.ListAttestedNodesEventsResponse + var err error + if a.firstEventTime.IsZero() { + resp, err = a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) + } else { + resp, err = a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ + GreaterThanEventID: a.lastEvent, + }) + } + if err != nil { + return err + } + + for _, event := range resp.Events { + // event time determines if we have seen the first event. + if a.firstEventTime.IsZero() { + a.firstEvent = event.EventID + a.lastEvent = event.EventID + a.fetchNodes[event.SpiffeID] = struct{}{} + a.firstEventTime = a.clk.Now() + continue + } + + // track any skipped event ids, should the appear later. + for skipped := a.lastEvent + 1; skipped < event.EventID; skipped++ { + a.eventTracker.StartTracking(skipped) + } + + // every event adds its entry to the entry fetch list. + a.fetchNodes[event.SpiffeID] = struct{}{} + a.lastEvent = event.EventID + } + return nil +} + +func (a *attestedNodes) loadCache(ctx context.Context) error { + // TODO: determine if this needs paging + nodesResp, err := a.ds.ListAttestedNodes(ctx, &datastore.ListAttestedNodesRequest{ FetchSelectors: true, }) if err != nil { - return nil, fmt.Errorf("failed to list attested nodes: %w", err) + return fmt.Errorf("failed to list attested nodes: %w", err) } for _, node := range nodesResp.Nodes { agentExpiresAt := time.Unix(node.CertNotAfter, 0) - if agentExpiresAt.Before(clk.Now()) { + if agentExpiresAt.Before(a.clk.Now()) { continue } - cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) + a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) } - return &attestedNodes{ + return nil +} + +// buildAttestedNodesCache fetches all attested nodes and adds the unexpired ones to the cache. +// It runs once at startup. +func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, cacheReloadInterval, sqlTransactionTimeout time.Duration) (*attestedNodes, error) { + pollPeriods := PollPeriods(cacheReloadInterval, sqlTransactionTimeout) + pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) + + attestedNodes := &attestedNodes{ cache: cache, clk: clk, ds: ds, - firstEventID: firstEventID, - firstEventTime: firstEventTime, log: log, metrics: metrics, - lastEventID: lastEventID, - missedEvents: missedEvents, - seenMissedStartupEvents: make(map[uint]struct{}), sqlTransactionTimeout: sqlTransactionTimeout, - }, nil -} -// updateCache Fetches all the events since the last time this function was running and updates -// the cache with all the changes. -func (a *attestedNodes) updateCache(ctx context.Context) error { - // Process events skipped over previously - if err := a.missedStartupEvents(ctx); err != nil { - a.log.WithError(err).Error("Unable to process missed startup events") + eventsBeforeFirst: make(map[uint]struct{}), + fetchNodes: make(map[string]struct{}), + + eventTracker: NewEventTracker(pollPeriods, pollBoundaries), } - a.replayMissedEvents(ctx) - req := &datastore.ListAttestedNodesEventsRequest{ - GreaterThanEventID: a.lastEventID, + if err := attestedNodes.loadCache(ctx); err != nil { + return nil, err } - resp, err := a.ds.ListAttestedNodesEvents(ctx, req) - if err != nil { - return err + if err := attestedNodes.captureChangedNodes(ctx); err != nil { + return nil, err } - - seenMap := map[string]struct{}{} - for _, event := range resp.Events { - // If there is a gap in the event stream, log the missed events for later processing. - // For example if the current event ID is 6 and the previous one was 3, events 4 and 5 - // were skipped over and need to be queued in case they show up later. - // This can happen when a long running transaction allocates an event ID but a shorter transaction - // comes in after, allocates and commits the ID first. If a read comes in at this moment, the event id for - // the longer running transaction will be skipped over. - if !a.firstEventTime.IsZero() { - for i := a.lastEventID + 1; i < event.EventID; i++ { - a.log.WithField(telemetry.EventID, i).Info("Detected skipped attested node event") - a.mu.Lock() - a.missedEvents[i] = a.clk.Now() - a.mu.Unlock() - } - } - - // Skip fetching entries we've already fetched this call - if _, seen := seenMap[event.SpiffeID]; seen { - a.lastEventID = event.EventID - continue - } - seenMap[event.SpiffeID] = struct{}{} - - // Update the cache - if err := a.updateCacheEntry(ctx, event.SpiffeID); err != nil { - return err - } - - if a.firstEventTime.IsZero() { - a.firstEventID = event.EventID - a.firstEventTime = a.clk.Now() - } - a.lastEventID = event.EventID + if err := attestedNodes.updateCachedNodes(ctx); err != nil { + return nil, err } - // These two should be the same value but it's valuable to have them both be emitted for incident triage. - server_telemetry.SetAgentsByExpiresAtCacheCountGauge(a.metrics, a.cache.Stats().AgentsByExpiresAt) - server_telemetry.SetAgentsByIDCacheCountGauge(a.metrics, a.cache.Stats().AgentsByID) - - return nil + return attestedNodes, nil } -// missedStartupEvents will check for any events that arrive with an ID less than the first event ID we receive. -// For example if the first event ID we receive is 3, this function will check for any IDs less than that. -// If event ID 2 comes in later on, due to a long running transaction, this function will update the cache -// with the information from this event. This function will run until time equal to sqlTransactionTimeout has elapsed after startup. -func (a *attestedNodes) missedStartupEvents(ctx context.Context) error { - if a.firstEventTime.IsZero() || a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { - return nil - } - - req := &datastore.ListAttestedNodesEventsRequest{ - LessThanEventID: a.firstEventID, +// updateCache Fetches all the events since the last time this function was running and updates +// the cache with all the changes. +func (a *attestedNodes) updateCache(ctx context.Context) error { + if err := a.captureChangedNodes(ctx); err != nil { + return err } - resp, err := a.ds.ListAttestedNodesEvents(ctx, req) - if err != nil { + if err := a.updateCachedNodes(ctx); err != nil { return err } - for _, event := range resp.Events { - if _, seen := a.seenMissedStartupEvents[event.EventID]; !seen { - if err := a.updateCacheEntry(ctx, event.SpiffeID); err != nil { - a.log.WithError(err).Error("Failed to process missed startup event") - continue - } - a.seenMissedStartupEvents[event.EventID] = struct{}{} - } - } + // These two should be the same value but it's valuable to have them both be emitted for incident triage. + server_telemetry.SetAgentsByExpiresAtCacheCountGauge(a.metrics, a.cache.Stats().AgentsByExpiresAt) + server_telemetry.SetAgentsByIDCacheCountGauge(a.metrics, a.cache.Stats().AgentsByID) return nil } -// replayMissedEvents Processes events that have been skipped over. Events can come out of order from -// SQL. This function processes events that came in later than expected. -func (a *attestedNodes) replayMissedEvents(ctx context.Context) { - a.mu.Lock() - defer a.mu.Unlock() - - for eventID := range a.missedEvents { - log := a.log.WithField(telemetry.EventID, eventID) - - event, err := a.ds.FetchAttestedNodeEvent(ctx, eventID) - switch status.Code(err) { - case codes.OK: - case codes.NotFound: - continue - default: - log.WithError(err).Error("Failed to fetch info about missed Attested Node event") - continue +func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { + for spiffeId, _ := range a.fetchNodes { + node, err := a.ds.FetchAttestedNode(ctx, spiffeId) + if err != nil { + return err } - if err := a.updateCacheEntry(ctx, event.SpiffeID); err != nil { - log.WithError(err).Error("Failed to process missed Attested Node event") - continue + // Node was deleted + if node == nil { + a.cache.RemoveAgent(spiffeId) + return nil } - delete(a.missedEvents, eventID) - } - server_telemetry.SetSkippedNodeEventIDsCacheCountGauge(a.metrics, len(a.missedEvents)) -} - -// updatedCacheEntry update/deletes/creates an individual attested node in the cache. -func (a *attestedNodes) updateCacheEntry(ctx context.Context, spiffeID string) error { - node, err := a.ds.FetchAttestedNode(ctx, spiffeID) - if err != nil { - return err - } + selectors, err := a.ds.GetNodeSelectors(ctx, spiffeId, datastore.RequireCurrent) + if err != nil { + return err + } + node.Selectors = selectors - // Node was deleted - if node == nil { - a.cache.RemoveAgent(spiffeID) - return nil - } + agentExpiresAt := time.Unix(node.CertNotAfter, 0) + a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) - selectors, err := a.ds.GetNodeSelectors(ctx, spiffeID, datastore.RequireCurrent) - if err != nil { - return err } - node.Selectors = selectors - - agentExpiresAt := time.Unix(node.CertNotAfter, 0) - a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) - return nil } diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index b5e1706a6e..e1b1d56b23 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -38,10 +38,22 @@ type registrationEntries struct { } func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { - // first reset what we might fetch. + // first, reset the entries we might fetch. a.fetchEntries = make(map[string]struct{}) - // before first event logic. Typically an empty space, so we poll the range and filter out the already seen + if err := a.searchBeforeFirstEvent(ctx); err != nil { + return err + } + a.selectPolledEvents(ctx); + if err := a.scanNewEvents(ctx); err != nil { + return err + } + + return nil +} + +func (a *registrationEntries) searchBeforeFirstEvent(ctx context.Context) error { + // First event detected, and startup was less than a transaction timout away. if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { resp, err := a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ LessThanEventID: a.firstEvent, @@ -50,18 +62,27 @@ func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { return err } for _, event := range resp.Events { + // if we have seen it before, don't reload it. if _, seen := a.eventsBeforeFirst[event.EventID]; !seen { a.fetchEntries[event.EntryID] = struct{}{} a.eventsBeforeFirst[event.EventID] = struct{}{} } } - } else { - // clear out the before first tracking + return nil + } + + // zero out unused event tracker + if len(a.eventsBeforeFirst) != 0 { a.eventsBeforeFirst = make(map[uint]struct{}) } + return nil +} + + +func (a *registrationEntries) selectPolledEvents(ctx context.Context) { // check if the polled events have appeared out-of-order - for _, eventID := range a.eventTracker.PollEvents() { + for _, eventID := range a.eventTracker.SelectEvents() { log := a.log.WithField(telemetry.EventID, eventID) event, err := a.ds.FetchRegistrationEntryEvent(ctx, eventID) @@ -78,7 +99,9 @@ func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { a.eventTracker.StopTracking(uint(eventID)) } server_telemetry.SetSkippedEntryEventIDsCacheCountGauge(a.metrics, int(a.eventTracker.EventCount())) +} +func (a *registrationEntries) scanNewEvents(ctx context.Context) error { // If we haven't seen an event, scan for all events; otherwise, scan from the last event. var resp *datastore.ListRegistrationEntriesEventsResponse var err error @@ -172,7 +195,7 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, if err := registrationEntries.captureChangedEntries(ctx); err != nil { return nil, err } - if err := registrationEntries.updateCacheEntries(ctx); err != nil { + if err := registrationEntries.updateCachedEntries(ctx); err != nil { return nil, err } @@ -182,11 +205,10 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, // updateCache Fetches all the events since the last time this function was running and updates // the cache with all the changes. func (a *registrationEntries) updateCache(ctx context.Context) error { - if err := a.captureChangedEntries(ctx); err != nil { return err } - if err := a.updateCacheEntries(ctx); err != nil { + if err := a.updateCachedEntries(ctx); err != nil { return err } @@ -202,7 +224,7 @@ func (a *registrationEntries) updateCache(ctx context.Context) error { } // updateCacheEntry update/deletes/creates an individual registration entry in the cache. -func (a *registrationEntries) updateCacheEntries(ctx context.Context) error { +func (a *registrationEntries) updateCachedEntries(ctx context.Context) error { for entryId, _ := range a.fetchEntries { commonEntry, err := a.ds.FetchRegistrationEntry(ctx, entryId) if err != nil { diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index ae68ab8009..9d657bb23e 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -122,7 +122,7 @@ func (et *eventTracker) StopTracking(event uint) { } -func (et *eventTracker) PollEvents() []uint { +func (et *eventTracker) SelectEvents() []uint { // fmt.Print("polling events\n") pollList := make([]uint, 0) for event, _ := range et.events { diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index 9e94091a94..ecf358f84b 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -332,7 +332,7 @@ func TestEvenTrackerPolling(t *testing.T) { } } // get the events we should poll - events := eventTracker.PollEvents() + events := eventTracker.SelectEvents() // update count for each event for _, event := range events { pollCount[event]++ @@ -364,159 +364,190 @@ func TestEvenDispersion(t *testing.T) { permissibleCountError uint }{ { + // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... name: "increment by 2 (offset 0) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 0, eventIncrement: 2, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 1, 3, 5, 7, 9, 11, 13, ... name: "increment by 2 (offset 1) events distribute fairly across 2 slots", pollPeriods: 2, - startEvent: 0, + startEvent: 1, eventIncrement: 2, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 0, 3, 6, 9, 12, 15, ... name: "increment by 3 (offset 0) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 0, eventIncrement: 3, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 1, 4, 7, 10, 13, 16, ... name: "increment by 3 (offset 1) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 1, eventIncrement: 3, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + expectedSlotCount: 500, expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 2, 5, 8, 11, 14, 17, ... name: "increment by 3 (offset 2) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 2, eventIncrement: 3, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 0, 4, 8, 12, 16, 20, ... name: "increment by 4 (offset 0) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 0, eventIncrement: 4, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 1, 5, 9, 13, 17, 21, ... name: "increment by 4 (offset 1) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 1, eventIncrement: 4, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 2, 6, 10, 14, 18, 22, ... name: "increment by 4 (offset 2) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 2, eventIncrement: 4, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 3, 7, 11, 15, 19, 23, ... name: "increment by 4 (offset 3) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 3, eventIncrement: 4, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 0, 5, 10, 15, 20, 25, ... name: "increment by 5 (offset 0) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 0, eventIncrement: 5, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 1, 6, 11, 16, 21, 26, ... name: "increment by 5 (offset 1) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 1, eventIncrement: 5, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 2, 7, 12, 17, 22, 27, ... name: "increment by 5 (offset 2) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 2, eventIncrement: 5, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 3, 8, 13, 18, 23, 28, ... name: "increment by 5 (offset 3) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 3, eventIncrement: 5, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 4, 9, 14, 19, 24, 29, ... name: "increment by 5 (offset 4) events distribute fairly across 2 slots", pollPeriods: 2, startEvent: 4, eventIncrement: 5, eventCount: 1000, + // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... name: "increment by 2 (offset 0) events distribute fairly across 3 slots", pollPeriods: 3, startEvent: 0, eventIncrement: 2, eventCount: 1000, + // should disperse into three slots, with an approxmiate count of [ 333, 333, 333 ] expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { name: "increment by 2 (offset 1) events distribute fairly across 3 slots", pollPeriods: 3, - startEvent: 0, + startEvent: 1, eventIncrement: 2, eventCount: 1000, @@ -644,19 +675,21 @@ func TestEvenDispersion(t *testing.T) { permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... name: "increment by 2 (offset 0) events distribute fairly across 4 slots", pollPeriods: 4, startEvent: 0, eventIncrement: 2, eventCount: 1000, + // should disperse into four slots, with an approxmiate count of [ 250, 250, 250, 250 ] expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { name: "increment by 2 (offset 1) events distribute fairly across 4 slots", pollPeriods: 4, - startEvent: 0, + startEvent: 1, eventIncrement: 2, eventCount: 1000, @@ -784,19 +817,21 @@ func TestEvenDispersion(t *testing.T) { permissibleCountError: 50, // 5% of 1000 }, { + // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... name: "increment by 2 (offset 0) events distribute fairly across 5 slots", pollPeriods: 5, startEvent: 0, eventIncrement: 2, eventCount: 1000, + // should disperse into five slots, with an approxmiate count of [ 200, 200, 200, 200, 200 ] expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { name: "increment by 2 (offset 1) events distribute fairly across 5 slots", pollPeriods: 5, - startEvent: 0, + startEvent: 1, eventIncrement: 2, eventCount: 1000, @@ -933,7 +968,7 @@ func TestEvenDispersion(t *testing.T) { eventTracker.StartTracking(event) } for pollPeriod := range tt.pollPeriods { - events := eventTracker.PollEvents() + events := eventTracker.SelectEvents() slotCount[pollPeriod] = uint(len(events)) t.Logf("pollPeriod %d, count = %d", pollPeriod, slotCount[pollPeriod]) } From bf6ad3cba63804ead14cb4df6cd1d1592372ce10 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 23 Sep 2024 21:19:20 -0700 Subject: [PATCH 04/32] Reworked some of the unit tests to the refactored algorithm. Signed-off-by: Edwin Buck --- pkg/server/authorizedentries/cache.go | 6 +- pkg/server/datastore/sqlstore/sqlstore.go | 2 + .../authorized_entryfetcher_attested_nodes.go | 195 +++---- ...orized_entryfetcher_attested_nodes_test.go | 484 ++++++++++++++---- ..._entryfetcher_registration_entries_test.go | 8 +- .../endpoints/authorized_entryfetcher_test.go | 4 +- pkg/server/endpoints/eventTracker_test.go | 1 - 7 files changed, 496 insertions(+), 204 deletions(-) diff --git a/pkg/server/authorizedentries/cache.go b/pkg/server/authorizedentries/cache.go index 77e2d7aaf5..bbf3c464cb 100644 --- a/pkg/server/authorizedentries/cache.go +++ b/pkg/server/authorizedentries/cache.go @@ -267,8 +267,8 @@ func (c *Cache) removeEntry(entryID string) { } } -func (c *Cache) Stats() cacheStats { - return cacheStats{ +func (c *Cache) Stats() CacheStats { + return CacheStats{ AgentsByID: c.agentsByID.Len(), AgentsByExpiresAt: c.agentsByExpiresAt.Len(), AliasesByEntryID: c.aliasesByEntryID.Len(), @@ -286,7 +286,7 @@ func isNodeAlias(e *types.Entry) bool { return e.ParentId.Path == idutil.ServerIDPath } -type cacheStats struct { +type CacheStats struct { AgentsByID int AgentsByExpiresAt int AliasesByEntryID int diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index f1645e7bd4..fefc18b9ad 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -295,6 +295,8 @@ func (ds *Plugin) CreateAttestedNode(ctx context.Context, node *common.AttestedN if err != nil { return err } + // TODO: this is at the wrong level of the software stack. + // It should be created in the caller of the datastore interface. return createAttestedNodeEvent(tx, &datastore.AttestedNodeEvent{ SpiffeID: node.SpiffeId, }) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index c8de524f13..615a928173 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -28,14 +28,15 @@ type attestedNodes struct { eventsBeforeFirst map[uint]struct{} - firstEvent uint - firstEventTime time.Time - lastEvent uint + firstEvent uint + firstEventTime time.Time + lastEvent uint - eventTracker *eventTracker - sqlTransactionTimeout time.Duration + eventTracker *eventTracker + sqlTransactionTimeout time.Duration - fetchNodes map[string]struct{} + fetchNodes map[string]struct{} + lastCacheStats authorizedentries.CacheStats } func (a *attestedNodes) captureChangedNodes(ctx context.Context) error { @@ -54,88 +55,88 @@ func (a *attestedNodes) captureChangedNodes(ctx context.Context) error { } func (a *attestedNodes) searchBeforeFirstEvent(ctx context.Context) error { - // First event detected, and startup was less than a transaction timout away. - if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { + // First event detected, and startup was less than a transaction timout away. + if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { resp, err := a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ LessThanEventID: a.firstEvent, }) - if err != nil { - return err - } - for _, event := range resp.Events { - // if we have seen it before, don't reload it. - if _, seen := a.eventsBeforeFirst[event.EventID]; !seen { - a.fetchNodes[event.SpiffeID] = struct{}{} - a.eventsBeforeFirst[event.EventID] = struct{}{} - } - } - return nil - } - - // zero out unused event tracker - if len(a.eventsBeforeFirst) != 0 { - a.eventsBeforeFirst = make(map[uint]struct{}) - } + if err != nil { + return err + } + for _, event := range resp.Events { + // if we have seen it before, don't reload it. + if _, seen := a.eventsBeforeFirst[event.EventID]; !seen { + a.fetchNodes[event.SpiffeID] = struct{}{} + a.eventsBeforeFirst[event.EventID] = struct{}{} + } + } + return nil + } + + // zero out unused event tracker + if len(a.eventsBeforeFirst) != 0 { + a.eventsBeforeFirst = make(map[uint]struct{}) + } return nil } func (a *attestedNodes) selectPolledEvents(ctx context.Context) { - // check if the polled events have appeared out-of-order - for _, eventID := range a.eventTracker.SelectEvents() { - log := a.log.WithField(telemetry.EventID, eventID) + // check if the polled events have appeared out-of-order + for _, eventID := range a.eventTracker.SelectEvents() { + log := a.log.WithField(telemetry.EventID, eventID) event, err := a.ds.FetchAttestedNodeEvent(ctx, eventID) - switch status.Code(err) { - case codes.OK: - case codes.NotFound: - continue - default: - log.WithError(err).Errorf("Failed to fetch info about skipped node event %d", eventID) - continue - } - - a.fetchNodes[event.SpiffeID] = struct{}{} - a.eventTracker.StopTracking(uint(eventID)) - } + switch status.Code(err) { + case codes.OK: + case codes.NotFound: + continue + default: + log.WithError(err).Errorf("Failed to fetch info about skipped node event %d", eventID) + continue + } + + a.fetchNodes[event.SpiffeID] = struct{}{} + a.eventTracker.StopTracking(uint(eventID)) + } server_telemetry.SetSkippedNodeEventIDsCacheCountGauge(a.metrics, int(a.eventTracker.EventCount())) } func (a *attestedNodes) scanForNewEvents(ctx context.Context) error { - // If we haven't seen an event, scan for all events; otherwise, scan from the last event. - var resp *datastore.ListAttestedNodesEventsResponse - var err error - if a.firstEventTime.IsZero() { + // If we haven't seen an event, scan for all events; otherwise, scan from the last event. + var resp *datastore.ListAttestedNodesEventsResponse + var err error + if a.firstEventTime.IsZero() { resp, err = a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) - } else { + } else { resp, err = a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ GreaterThanEventID: a.lastEvent, }) - } - if err != nil { - return err - } - - for _, event := range resp.Events { - // event time determines if we have seen the first event. - if a.firstEventTime.IsZero() { - a.firstEvent = event.EventID - a.lastEvent = event.EventID - a.fetchNodes[event.SpiffeID] = struct{}{} - a.firstEventTime = a.clk.Now() - continue - } - - // track any skipped event ids, should the appear later. - for skipped := a.lastEvent + 1; skipped < event.EventID; skipped++ { - a.eventTracker.StartTracking(skipped) - } - - // every event adds its entry to the entry fetch list. - a.fetchNodes[event.SpiffeID] = struct{}{} - a.lastEvent = event.EventID - } - return nil + } + if err != nil { + return err + } + + for _, event := range resp.Events { + // event time determines if we have seen the first event. + if a.firstEventTime.IsZero() { + a.firstEvent = event.EventID + a.lastEvent = event.EventID + a.fetchNodes[event.SpiffeID] = struct{}{} + a.firstEventTime = a.clk.Now() + continue + } + + // track any skipped event ids, should the appear later. + for skipped := a.lastEvent + 1; skipped < event.EventID; skipped++ { + a.eventTracker.StartTracking(skipped) + } + + // every event adds its entry to the entry fetch list. + a.fetchNodes[event.SpiffeID] = struct{}{} + a.lastEvent = event.EventID + } + return nil } func (a *attestedNodes) loadCache(ctx context.Context) error { @@ -154,6 +155,7 @@ func (a *attestedNodes) loadCache(ctx context.Context) error { } a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) } + a.emitCacheMetrics() return nil } @@ -165,26 +167,29 @@ func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metric pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) attestedNodes := &attestedNodes{ - cache: cache, - clk: clk, - ds: ds, - log: log, - metrics: metrics, - sqlTransactionTimeout: sqlTransactionTimeout, - - eventsBeforeFirst: make(map[uint]struct{}), - fetchNodes: make(map[string]struct{}), - - eventTracker: NewEventTracker(pollPeriods, pollBoundaries), + cache: cache, + clk: clk, + ds: ds, + log: log, + metrics: metrics, + sqlTransactionTimeout: sqlTransactionTimeout, + + eventsBeforeFirst: make(map[uint]struct{}), + fetchNodes: make(map[string]struct{}), + + eventTracker: NewEventTracker(pollPeriods, pollBoundaries), + lastCacheStats: authorizedentries.CacheStats{ + // nonsense counts to force a change, even to zero + AgentsByID: -1, + AgentsByExpiresAt: -1, + }, } if err := attestedNodes.loadCache(ctx); err != nil { return nil, err } - if err := attestedNodes.captureChangedNodes(ctx); err != nil { - return nil, err - } - if err := attestedNodes.updateCachedNodes(ctx); err != nil { + // TODO: is it really necessary to udpate on first load? + if err := attestedNodes.updateCache(ctx); err != nil { return nil, err } @@ -201,15 +206,11 @@ func (a *attestedNodes) updateCache(ctx context.Context) error { return err } - // These two should be the same value but it's valuable to have them both be emitted for incident triage. - server_telemetry.SetAgentsByExpiresAtCacheCountGauge(a.metrics, a.cache.Stats().AgentsByExpiresAt) - server_telemetry.SetAgentsByIDCacheCountGauge(a.metrics, a.cache.Stats().AgentsByID) - return nil } func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { - for spiffeId, _ := range a.fetchNodes { + for spiffeId, _ := range a.fetchNodes { node, err := a.ds.FetchAttestedNode(ctx, spiffeId) if err != nil { return err @@ -231,6 +232,20 @@ func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) } + a.emitCacheMetrics() return nil } +func (a *attestedNodes) emitCacheMetrics() { + cacheStats := a.cache.Stats() + + if a.lastCacheStats.AgentsByExpiresAt != cacheStats.AgentsByExpiresAt { + server_telemetry.SetAgentsByExpiresAtCacheCountGauge(a.metrics, cacheStats.AgentsByExpiresAt) + a.lastCacheStats.AgentsByExpiresAt = cacheStats.AgentsByExpiresAt + } + // Should be the same as AgentsByExpireAt. Not de-duplicated for incident triage. + if a.lastCacheStats.AgentsByID != cacheStats.AgentsByID { + server_telemetry.SetAgentsByIDCacheCountGauge(a.metrics, cacheStats.AgentsByID) + a.lastCacheStats.AgentsByID = cacheStats.AgentsByID + } +} diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index d02ebe60dd..20382bade4 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -3,12 +3,15 @@ package endpoints import ( "context" "errors" + "maps" + "slices" + "strings" "testing" "time" "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" - "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire/pkg/common/telemetry" "github.com/spiffe/spire/pkg/server/authorizedentries" "github.com/spiffe/spire/pkg/server/datastore" @@ -16,147 +19,418 @@ import ( "github.com/spiffe/spire/test/clock" "github.com/spiffe/spire/test/fakes/fakedatastore" "github.com/spiffe/spire/test/fakes/fakemetrics" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestUpdateAttestedNodesCache(t *testing.T) { +var ( + CachedAgentsByID = []string{telemetry.Node, telemetry.AgentsByIDCache, telemetry.Count} + CachedAgentsByExpiresAt = []string{telemetry.Node, telemetry.AgentsByExpiresAtCache, telemetry.Count} + SkippedNodeEventID = []string{telemetry.Node, telemetry.SkippedNodeEventIDs, telemetry.Count} +) + +type expectedGauge struct { + Key []string + Value int +} + +func TestLoadCache(t *testing.T) { for _, tt := range []struct { - name string - errs []error - expectedLastAttestedNodeEventID uint - expectMetrics []fakemetrics.MetricItem + name string + attestedNodes []*common.AttestedNode + errors []error + + expectedNodes int + expectedError error + expectedAuthorizedEntries []string + expectedGauges []expectedGauge }{ { - name: "Error Listing Attested Node Events", - errs: []error{errors.New("listing attested node events")}, - expectedLastAttestedNodeEventID: uint(0), - expectMetrics: nil, + name: "initial load returns an error", + errors: []error{ + errors.New("any error, doesn't matter"), + }, + expectedError: errors.New("any error, doesn't matter"), + }, + { + name: "initial load loads nothing", }, { - name: "Error Fetching Attested Node", - errs: []error{nil, errors.New("fetching attested node")}, - expectedLastAttestedNodeEventID: uint(0), - expectMetrics: nil, + name: "initial load loads one attested node", + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + }, + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + }, + expectedGauges: []expectedGauge{ + expectedGauge{Key: SkippedNodeEventID, Value: 0}, + expectedGauge{Key: CachedAgentsByID, Value: 1}, + expectedGauge{Key: CachedAgentsByExpiresAt, Value: 1}, + }, }, { - name: "Error Getting Node Selectors", - errs: []error{nil, nil, errors.New("getting node selectors")}, - expectedLastAttestedNodeEventID: uint(0), - expectMetrics: nil, + name: "initial load loads five attested nodes", + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + }, + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + }, + { + name: "initial load loads five attested nodes, one expired", + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + }, + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, }, { - name: "No Errors", - expectedLastAttestedNodeEventID: uint(1), - expectMetrics: []fakemetrics.MetricItem{ - { - Type: fakemetrics.SetGaugeType, - Key: []string{telemetry.Node, telemetry.AgentsByExpiresAtCache, telemetry.Count}, - Val: 1, - Labels: nil, - }, - { - Type: fakemetrics.SetGaugeType, - Key: []string{telemetry.Node, telemetry.AgentsByIDCache, telemetry.Count}, - Val: 1, - Labels: nil, + name: "initial load loads five attested nodes, all expired", + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), }, }, + expectedAuthorizedEntries: []string{}, }, } { tt := tt t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - log, _ := test.NewNullLogger() + log, hook := test.NewNullLogger() + log.SetLevel(logrus.DebugLevel) clk := clock.NewMock(t) - ds := fakedatastore.New(t) cache := authorizedentries.NewCache(clk) metrics := fakemetrics.New() - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultSQLTransactionTimeout) - require.NoError(t, err) - require.NotNil(t, attestedNodes) + ds := fakedatastore.New(t) + // initialize the database + for _, attestedNode := range tt.attestedNodes { + ds.CreateAttestedNode(ctx, attestedNode) + } + // prune attested node entires, to test the load independently of the events + // this can be removed once CreateAttestedNode no longer creates node events. + ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + for _, err := range tt.errors { + ds.AppendNextError(err) + } - agentID, err := spiffeid.FromString("spiffe://example.org/myagent") - require.NoError(t, err) + cacheStats := cache.Stats() + require.Equal(t, 0, cacheStats.AgentsByID, "cache must be empty to start") + + attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + if tt.expectedError != nil { + require.Error(t, err, tt.expectedError) + return + } - _, err = ds.CreateAttestedNode(ctx, &common.AttestedNode{ - SpiffeId: agentID.String(), - CertNotAfter: time.Now().Add(5 * time.Hour).Unix(), - }) require.NoError(t, err) - for _, err = range tt.errs { - ds.AppendNextError(err) + cacheStats = attestedNodes.cache.Stats() + require.Equal(t, len(tt.expectedAuthorizedEntries), cacheStats.AgentsByID, "wrong number of agents by ID") + + for _, expectedAuthorizedId := range tt.expectedAuthorizedEntries { + attestedNodes.cache.RemoveAgent(expectedAuthorizedId) } - err = attestedNodes.updateCache(ctx) - if len(tt.errs) > 0 { - assert.EqualError(t, err, tt.errs[len(tt.errs)-1].Error()) - } else { - assert.NoError(t, err) + cacheStats = attestedNodes.cache.Stats() + require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear ccache") + + var lastMetrics map[string]int = make(map[string]int) + for _, metricItem := range metrics.AllMetrics() { + if metricItem.Type == fakemetrics.SetGaugeType { + key := strings.Join(metricItem.Key, " ") + lastMetrics[key] = int(metricItem.Val) + } } - assert.Equal(t, tt.expectedLastAttestedNodeEventID, attestedNodes.lastEventID) + for _, expectedGauge := range tt.expectedGauges { + key := strings.Join(expectedGauge.Key, " ") + value, exists := lastMetrics[key] + require.True(t, exists, "No metric value for %q", key) + require.Equal(t, expectedGauge.Value, value, "unexpected final metric value for %q", key) + } + + require.Zero(t, hook.Entries) + }) + } +} + +func TestSearchBeforeFirstEvent(t *testing.T) { +} - if tt.expectMetrics != nil { - assert.Subset(t, metrics.AllMetrics(), tt.expectMetrics) +func TestSelectedPolledEvents(t *testing.T) { + for _, tt := range []struct { + name string + polling []uint + events []*datastore.AttestedNodeEvent + expectedFetches []string + }{ + // polling is based on the eventTracker, not on events in the database + { + name: "nothing to poll, no action taken, no events", + events: []*datastore.AttestedNodeEvent{}, + }, + { + name: "nothing to poll, no action taken, one event", + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 100, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + { + name: "nothing to poll, no action taken, five events", + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, + }, + }, + { + name: "polling one item, not found", + polling: []uint{103}, + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, + }, + }, + { + name: "polling five items, not found", + polling: []uint{102, 103, 104, 105, 106}, + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 107, + SpiffeID: "spiffe://example.org/test_node_7", + }, + }, + }, + { + name: "polling one item, found", + polling: []uint{102}, + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + }, + expectedFetches: []string{ + "spiffe://example.org/test_node_2", + }, + }, + { + name: "polling five items, two found", + polling: []uint{102, 103, 104, 105, 106}, + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 106, + SpiffeID: "spiffe://example.org/test_node_6", + }, + &datastore.AttestedNodeEvent{ + EventID: 107, + SpiffeID: "spiffe://example.org/test_node_7", + }, + }, + expectedFetches: []string{ + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_6", + }, + }, + { + name: "polling five items, five found", + polling: []uint{102, 103, 104, 105, 106}, + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, + &datastore.AttestedNodeEvent{ + EventID: 106, + SpiffeID: "spiffe://example.org/test_node_6", + }, + &datastore.AttestedNodeEvent{ + EventID: 107, + SpiffeID: "spiffe://example.org/test_node_7", + }, + }, + expectedFetches: []string{ + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + "spiffe://example.org/test_node_6", + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + log, hook := test.NewNullLogger() + log.SetLevel(logrus.DebugLevel) + clk := clock.NewMock(t) + ds := fakedatastore.New(t) + cache := authorizedentries.NewCache(clk) + metrics := fakemetrics.New() + + attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + require.NoError(t, err) + + // initialize the database + for _, event := range tt.events { + ds.CreateAttestedNodeEventForTesting(ctx, event) + } + // initialize the event tracker + for _, event := range tt.polling { + attestedNodes.eventTracker.StartTracking(event) } + + // poll the events + attestedNodes.selectPolledEvents(ctx) + + require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes))) + require.Zero(t, hook.Entries) }) } } -func TestAttestedNodesCacheMissedEventNotFound(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - ds := fakedatastore.New(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() - - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultSQLTransactionTimeout) - require.NoError(t, err) - require.NotNil(t, attestedNodes) - - attestedNodes.missedEvents[1] = clk.Now() - attestedNodes.replayMissedEvents(ctx) - require.Zero(t, hook.Entries) +func TestScanForNewEvents(t *testing.T) { } -func TestAttestedNodesSavesMissedStartupEvents(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - ds := fakedatastore.New(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() - - err := ds.CreateAttestedNodeEventForTesting(ctx, &datastore.AttestedNodeEvent{ - EventID: 3, - SpiffeID: "test", - }) - require.NoError(t, err) - - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultSQLTransactionTimeout) - require.NoError(t, err) - require.NotNil(t, attestedNodes) - require.Equal(t, uint(3), attestedNodes.firstEventID) - - err = ds.CreateAttestedNodeEventForTesting(ctx, &datastore.AttestedNodeEvent{ - EventID: 2, - SpiffeID: "test", - }) - require.NoError(t, err) - - err = attestedNodes.missedStartupEvents(ctx) - require.NoError(t, err) - - // Make sure no dupliate calls are made - ds.AppendNextError(nil) - ds.AppendNextError(errors.New("Duplicate call")) - err = attestedNodes.missedStartupEvents(ctx) - require.NoError(t, err) - require.Equal(t, 0, len(hook.AllEntries())) +func TestUpdateAttestedNodesCache(t *testing.T) { } diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 44b0b531ed..6431299e26 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -1,10 +1,7 @@ package endpoints +/* import ( - "context" - "errors" - "sort" - "strconv" "testing" "github.com/sirupsen/logrus" @@ -20,6 +17,8 @@ import ( "github.com/stretchr/testify/require" ) +*/ +/* func TestBuildRegistrationEntriesCache(t *testing.T) { ctx := context.Background() log, _ := test.NewNullLogger() @@ -154,3 +153,4 @@ func TestRegistrationEntriesSavesMissedStartupEvents(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, len(hook.AllEntries())) } +*/ diff --git a/pkg/server/endpoints/authorized_entryfetcher_test.go b/pkg/server/endpoints/authorized_entryfetcher_test.go index 761ce966ed..a8df6d5fe0 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_test.go @@ -133,6 +133,7 @@ func TestNewAuthorizedEntryFetcherWithEventsBasedCacheErrorBuildingCache(t *test assert.ElementsMatch(t, expectedMetrics, metrics.AllMetrics(), "should emit no metrics") } +/* func TestBuildCacheSavesMissedEvents(t *testing.T) { ctx := context.Background() log, _ := test.NewNullLogger() @@ -166,7 +167,7 @@ func TestBuildCacheSavesMissedEvents(t *testing.T) { }) require.NoError(t, err) - _, registrationEntries, attestedNodes, err := buildCache(ctx, log, metrics, ds, clk, defaultSQLTransactionTimeout) + _, registrationEntries, attestedNodes, err := buildCache(ctx, log, metrics, ds, clk, defaultCacheReloadInterval, defaultSQLTransactionTimeout) require.NoError(t, err) require.NotNil(t, registrationEntries) require.NotNil(t, attestedNodes) @@ -182,6 +183,7 @@ func TestBuildCacheSavesMissedEvents(t *testing.T) { expectedMetrics := []fakemetrics.MetricItem{} assert.ElementsMatch(t, expectedMetrics, metrics.AllMetrics(), "should emit no metrics") } +*/ func TestRunUpdateCacheTaskPrunesExpiredAgents(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index ecf358f84b..78bd9ac780 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -409,7 +409,6 @@ func TestEvenDispersion(t *testing.T) { // should disperse into two slots, with an approxmiate count of [ 500, 500 ] expectedSlotCount: 500, - expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { From f81942fc2524dbf343251b8f0bc8b10b0d3db7f7 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Wed, 25 Sep 2024 14:19:49 -0700 Subject: [PATCH 05/32] Saving work off so I can switch machines. Signed-off-by: Edwin Buck --- pkg/server/datastore/sqlstore/sqlstore.go | 4 + .../authorized_entryfetcher_attested_nodes.go | 3 +- ...orized_entryfetcher_attested_nodes_test.go | 517 +++++++++++++++++- pkg/server/endpoints/eventTracker.go | 135 ++++- 4 files changed, 620 insertions(+), 39 deletions(-) diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index fefc18b9ad..8253d1a89c 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -353,6 +353,8 @@ func (ds *Plugin) UpdateAttestedNode(ctx context.Context, n *common.AttestedNode if err != nil { return err } + // TODO: this is at the wrong level of the software stack. + // It should be created in the caller of the datastore interface. return createAttestedNodeEvent(tx, &datastore.AttestedNodeEvent{ SpiffeID: n.SpiffeId, }) @@ -369,6 +371,8 @@ func (ds *Plugin) DeleteAttestedNode(ctx context.Context, spiffeID string) (atte if err != nil { return err } + // TODO: this is at the wrong level of the software stack. + // It should be created in the caller of the datastore interface. return createAttestedNodeEvent(tx, &datastore.AttestedNodeEvent{ SpiffeID: spiffeID, }) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 615a928173..377b88f75a 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -56,7 +56,7 @@ func (a *attestedNodes) captureChangedNodes(ctx context.Context) error { func (a *attestedNodes) searchBeforeFirstEvent(ctx context.Context) error { // First event detected, and startup was less than a transaction timout away. - if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { + if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) <= a.sqlTransactionTimeout { resp, err := a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ LessThanEventID: a.firstEvent, }) @@ -230,6 +230,7 @@ func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { agentExpiresAt := time.Unix(node.CertNotAfter, 0) a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) + delete(a.fetchNodes, node.SpiffeId) } a.emitCacheMetrics() diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 20382bade4..2b8ed30b9c 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -40,7 +40,6 @@ func TestLoadCache(t *testing.T) { attestedNodes []*common.AttestedNode errors []error - expectedNodes int expectedError error expectedAuthorizedEntries []string expectedGauges []expectedGauge @@ -183,9 +182,6 @@ func TestLoadCache(t *testing.T) { ds.AppendNextError(err) } - cacheStats := cache.Stats() - require.Equal(t, 0, cacheStats.AgentsByID, "cache must be empty to start") - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) if tt.expectedError != nil { require.Error(t, err, tt.expectedError) @@ -194,16 +190,18 @@ func TestLoadCache(t *testing.T) { require.NoError(t, err) - cacheStats = attestedNodes.cache.Stats() + cacheStats := attestedNodes.cache.Stats() require.Equal(t, len(tt.expectedAuthorizedEntries), cacheStats.AgentsByID, "wrong number of agents by ID") + // for now, the only way to ensure the desired agent ids are prsent is + // to remove the desired ids and check the count it zero. for _, expectedAuthorizedId := range tt.expectedAuthorizedEntries { attestedNodes.cache.RemoveAgent(expectedAuthorizedId) } - cacheStats = attestedNodes.cache.Stats() require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear ccache") + // build up a map of the last metrics var lastMetrics map[string]int = make(map[string]int) for _, metricItem := range metrics.AllMetrics() { if metricItem.Type == fakemetrics.SetGaugeType { @@ -225,6 +223,404 @@ func TestLoadCache(t *testing.T) { } func TestSearchBeforeFirstEvent(t *testing.T) { + var ( + defaultFirstEvent = uint(60) + defaultLastEvent = uint(61) + defaultAttestedNodes = []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + } + defaultEventsStartingAt60 = []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 60, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 61, + SpiffeID: "spiffe://example.org/test_node_3", + }, + } + NoFetches = []string{} + ) + for _, tt := range []struct { + name string + attestedNodes []*common.AttestedNode + attestedNodeEvents []*datastore.AttestedNodeEvent + waitToPoll time.Duration + eventsBeforeFirst []uint + polledEvents []*datastore.AttestedNodeEvent + errors []error + + expectedError error + expectedEventsBeforeFirst []uint + expectedFetches []string + }{ + { + name: "first event not loaded", + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: []string{}, + }, + { + name: "before first event arrived, after transaction timeout", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + waitToPoll: time.Duration(2) * defaultSQLTransactionTimeout, + // even with new before first events, they shouldn't load + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 58, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: NoFetches, + }, + { + name: "no before first events", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + polledEvents: []*datastore.AttestedNodeEvent{}, + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: []string{}, + }, + { + name: "new before first event", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 58, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + + expectedEventsBeforeFirst: []uint{58}, + expectedFetches: []string{"spiffe://example.org/test_node_1"}, + }, + { + name: "new after last event", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 64, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: []string{}, + }, + { + name: "previously seen before first event", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + eventsBeforeFirst: []uint{58}, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 58, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + + expectedEventsBeforeFirst: []uint{58}, + expectedFetches: []string{}, + }, + { + name: "previously seen before first event and after last event", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + eventsBeforeFirst: []uint{58}, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: defaultFirstEvent - 2, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: defaultLastEvent + 2, + SpiffeID: "spiffe://example.org/test_node_4", + }, + }, + + expectedEventsBeforeFirst: []uint{defaultFirstEvent - 2}, + expectedFetches: []string{}, + }, + { + name: "five new before first events", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 48, + SpiffeID: "spiffe://example.org/test_node_10", + }, + &datastore.AttestedNodeEvent{ + EventID: 49, + SpiffeID: "spiffe://example.org/test_node_11", + }, + &datastore.AttestedNodeEvent{ + EventID: 53, + SpiffeID: "spiffe://example.org/test_node_12", + }, + &datastore.AttestedNodeEvent{ + EventID: 56, + SpiffeID: "spiffe://example.org/test_node_13", + }, + &datastore.AttestedNodeEvent{ + EventID: 57, + SpiffeID: "spiffe://example.org/test_node_14", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{ + "spiffe://example.org/test_node_10", + "spiffe://example.org/test_node_11", + "spiffe://example.org/test_node_12", + "spiffe://example.org/test_node_13", + "spiffe://example.org/test_node_14", + }, + }, + { + name: "five new before first events, one after last event", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 48, + SpiffeID: "spiffe://example.org/test_node_10", + }, + &datastore.AttestedNodeEvent{ + EventID: 49, + SpiffeID: "spiffe://example.org/test_node_11", + }, + &datastore.AttestedNodeEvent{ + EventID: 53, + SpiffeID: "spiffe://example.org/test_node_12", + }, + &datastore.AttestedNodeEvent{ + EventID: 56, + SpiffeID: "spiffe://example.org/test_node_13", + }, + &datastore.AttestedNodeEvent{ + EventID: defaultLastEvent + 1, + SpiffeID: "spiffe://example.org/test_node_14", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56}, + expectedFetches: []string{ + "spiffe://example.org/test_node_10", + "spiffe://example.org/test_node_11", + "spiffe://example.org/test_node_12", + "spiffe://example.org/test_node_13", + }, + }, + { + name: "five before first events, two previously seen", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + eventsBeforeFirst: []uint{48, 49}, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 48, + SpiffeID: "spiffe://example.org/test_node_10", + }, + &datastore.AttestedNodeEvent{ + EventID: 49, + SpiffeID: "spiffe://example.org/test_node_11", + }, + &datastore.AttestedNodeEvent{ + EventID: 53, + SpiffeID: "spiffe://example.org/test_node_12", + }, + &datastore.AttestedNodeEvent{ + EventID: 56, + SpiffeID: "spiffe://example.org/test_node_13", + }, + &datastore.AttestedNodeEvent{ + EventID: 57, + SpiffeID: "spiffe://example.org/test_node_14", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{ + "spiffe://example.org/test_node_12", + "spiffe://example.org/test_node_13", + "spiffe://example.org/test_node_14", + }, + }, + { + name: "five before first events, two previously seen, one after last event", + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + eventsBeforeFirst: []uint{48, 49}, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 48, + SpiffeID: "spiffe://example.org/test_node_10", + }, + &datastore.AttestedNodeEvent{ + EventID: 49, + SpiffeID: "spiffe://example.org/test_node_11", + }, + &datastore.AttestedNodeEvent{ + EventID: 53, + SpiffeID: "spiffe://example.org/test_node_12", + }, + &datastore.AttestedNodeEvent{ + EventID: 56, + SpiffeID: "spiffe://example.org/test_node_13", + }, + &datastore.AttestedNodeEvent{ + EventID: defaultLastEvent + 1, + SpiffeID: "spiffe://example.org/test_node_14", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56}, + expectedFetches: []string{ + "spiffe://example.org/test_node_12", + "spiffe://example.org/test_node_13", + }, + }, + { + name: "five before first events, five previously seen", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 48, + SpiffeID: "spiffe://example.org/test_node_10", + }, + &datastore.AttestedNodeEvent{ + EventID: 49, + SpiffeID: "spiffe://example.org/test_node_11", + }, + &datastore.AttestedNodeEvent{ + EventID: 53, + SpiffeID: "spiffe://example.org/test_node_12", + }, + &datastore.AttestedNodeEvent{ + EventID: 56, + SpiffeID: "spiffe://example.org/test_node_13", + }, + &datastore.AttestedNodeEvent{ + EventID: 57, + SpiffeID: "spiffe://example.org/test_node_14", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{ + }, + }, + { + name: "five before first events, five previously seen, with after last event", + + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + polledEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 48, + SpiffeID: "spiffe://example.org/test_node_10", + }, + &datastore.AttestedNodeEvent{ + EventID: 49, + SpiffeID: "spiffe://example.org/test_node_11", + }, + &datastore.AttestedNodeEvent{ + EventID: 53, + SpiffeID: "spiffe://example.org/test_node_12", + }, + &datastore.AttestedNodeEvent{ + EventID: 56, + SpiffeID: "spiffe://example.org/test_node_13", + }, + &datastore.AttestedNodeEvent{ + EventID: 57, + SpiffeID: "spiffe://example.org/test_node_14", + }, + &datastore.AttestedNodeEvent{ + EventID: defaultLastEvent + 1, + SpiffeID: "spiffe://example.org/test_node_28", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{ + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + log, hook := test.NewNullLogger() + log.SetLevel(logrus.DebugLevel) + clk := clock.NewMock(t) + cache := authorizedentries.NewCache(clk) + metrics := fakemetrics.New() + + ds := fakedatastore.New(t) + // initialize the database + for _, attestedNode := range tt.attestedNodes { + ds.CreateAttestedNode(ctx, attestedNode) + } + // prune attested node entires, to test the load independently of the events + // this can be removed once CreateAttestedNode no longer creates node events. + ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + // add them back so we can control their event id. + for _, event := range tt.attestedNodeEvents { + ds.CreateAttestedNodeEventForTesting(ctx, event) + } + + attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + require.NoError(t, err) + + if tt.waitToPoll == 0 { + clk.Add(time.Duration(1) * defaultCacheReloadInterval) + } else { + clk.Add(tt.waitToPoll) + } + + for _, event := range tt.eventsBeforeFirst { + attestedNodes.eventsBeforeFirst[event] = struct{}{} + } + + for _, event := range tt.polledEvents { + ds.CreateAttestedNodeEventForTesting(ctx, event) + } + + attestedNodes.searchBeforeFirstEvent(ctx) + + require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(attestedNodes.eventsBeforeFirst)), "expected events before tracking mismatch") + require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes)), "expected fetches mismatch") + + require.Zero(t, hook.Entries) + }) + } } func TestSelectedPolledEvents(t *testing.T) { @@ -236,11 +632,11 @@ func TestSelectedPolledEvents(t *testing.T) { }{ // polling is based on the eventTracker, not on events in the database { - name: "nothing to poll, no action taken, no events", + name: "nothing after to poll, no action taken, no events", events: []*datastore.AttestedNodeEvent{}, }, { - name: "nothing to poll, no action taken, one event", + name: "nothing to poll, no action take, one event", events: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 100, @@ -430,6 +826,111 @@ func TestSelectedPolledEvents(t *testing.T) { } func TestScanForNewEvents(t *testing.T) { + var ( + defaultLastEvent = uint(81) + ) + for _, tt := range []struct { + name string + polling []uint + events []*datastore.AttestedNodeEvent + expectedFetches []string + }{ + { + name: "nothing to poll, no action taken, no events", + events: []*datastore.AttestedNodeEvent{}, + }, + { + name: "nothing to poll, no action taken, one event", + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: defaultLastEvent + 1, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + { + name: "poll first event", + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, + }, + }, + { + name: "polling one item, not found", + polling: []uint{103}, + events: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + { + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + log, hook := test.NewNullLogger() + log.SetLevel(logrus.DebugLevel) + clk := clock.NewMock(t) + cache := authorizedentries.NewCache(clk) + metrics := fakemetrics.New() + + ds := fakedatastore.New(t) + // initialize the database + for _, attestedNode := range tt.attestedNodes { + ds.CreateAttestedNode(ctx, attestedNode) + } + // prune attested node entires, to test the load independently of the events + // this can be removed once CreateAttestedNode no longer creates node events. + ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + // add them back so we can control their event id. + for _, event := range tt.attestedNodeEvents { + ds.CreateAttestedNodeEventForTesting(ctx, event) + } + + attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + require.NoError(t, err) + + if tt.waitToPoll == 0 { + clk.Add(time.Duration(1) * defaultCacheReloadInterval) + } else { + clk.Add(tt.waitToPoll) + } + + for _, event := range tt.polledEvents { + ds.CreateAttestedNodeEventForTesting(ctx, event) + } + + attestedNodes.searchBeforeFirstEvent(ctx) + + require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(attestedNodes.eventsBeforeFirst)), "expected events before tracking mismatch") + require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes)), "expected fetches mismatch") + + require.Zero(t, hook.Entries) + }) + } } func TestUpdateAttestedNodesCache(t *testing.T) { diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 9d657bb23e..08a0f984d7 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -1,34 +1,58 @@ package endpoints import ( -// "fmt" + // "fmt" "slices" "time" ) -const INITIAL_POLL_COUNT uint = 10 - +/** + * Tracks events as they indivicually walk through a list of event boundaries. + * + * An event track is defined with a set of foundaries, which are indexes to + * virtual hash tables, with the event's hash determining the position within + * that hash table where the event will be selected to be polled. + * For eventTRackers that lack boundaries, or polls that exist prior to + * boundaries, the event is always polled. + */ type eventTracker struct { + /* Times the event is polled before entering a boundary */ initialPolls uint + /* Times the event is polled */ pollPeriods uint + /* Per event context of each event's walk across the boudaries */ events map[uint]*eventStats + /* The leading index of boundaries in which an event should only report once */ boundaries []uint } +/** + * Tracks event context in its walk of the event boundaries. + */ type eventStats struct { + /* The event's hash for hash table calcuations */ hash uint + /* The number of times the event was considered for polling */ ticks uint + /* The number of times the event was selected for polling */ polls uint } +/** + * A utility function to get the number of PollTimes (ticks) in an interval. + * + * Subsecond inputs are adjusted to a minimum value one second. + * + * @returns One tick, or the smallest number of ticks that just exceeds the trackTime.. + */ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { - if pollTime < (time.Duration(1) * time.Minute) { - pollTime = time.Duration(1) * time.Minute + if pollTime < (time.Duration(1) * time.Second) { + pollTime = time.Duration(1) * time.Second } - if trackTime < (time.Duration(1) * time.Minute) { - trackTime = time.Duration(1) * time.Minute + if trackTime < (time.Duration(1) * time.Second) { + trackTime = time.Duration(1) * time.Second } - return uint(1 + (trackTime - 1) / pollTime) + return uint(1 + (trackTime-1)/pollTime) } func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { @@ -45,17 +69,17 @@ func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { // next 9 minutes, poll at 1/2 minute currentBoundary := pollsPerMinute for currentBoundary < pollsPerTenMinutes { - pollBoundaries = append(pollBoundaries, currentBoundary + (pollsPerMinute / 2)) - pollBoundaries = append(pollBoundaries, currentBoundary + pollsPerMinute) + pollBoundaries = append(pollBoundaries, currentBoundary+(pollsPerMinute/2)) + pollBoundaries = append(pollBoundaries, currentBoundary+pollsPerMinute) currentBoundary += pollsPerMinute } // rest of polling at 1 minute for currentBoundary < pollPeriods { - pollBoundaries = append(pollBoundaries, currentBoundary + pollsPerMinute) + pollBoundaries = append(pollBoundaries, currentBoundary+pollsPerMinute) currentBoundary += pollsPerMinute } // always poll at end of transaction timeout - pollBoundaries = append(pollBoundaries, pollPeriods - 1) + pollBoundaries = append(pollBoundaries, pollPeriods-1) return pollBoundaries } @@ -109,45 +133,75 @@ func (et *eventTracker) Polls() uint { return et.initialPolls + uint(len(et.boundaries)) } +/** + * Starts tracking an event's walk through the boundaries. + */ func (et *eventTracker) StartTracking(event uint) { et.events[event] = &eventStats{ - hash: hash(event), + hash: hash(event), ticks: 0, polls: 0, } } +/** + * Remove an event from the tracker. + * + * Events not explicitly removed will remove themselves + * after SelectEvents has been called PollPeriods() number + * of times. + */ func (et *eventTracker) StopTracking(event uint) { delete(et.events, event) } - +/** + * Selects the events one should pool for the next poll cycle. + * + * This algorithm determines if the events should be polled, and + * increments each event's time, allowing every event to act on + * the time the event was inserted into eventTracker. + * + * The event's boundary Index is computed, and if it is below the + * number of initial polls, the event is polled without further + * analysis. + * + * If the boundary index is inside a defined boundary, the width + * of the boundary is computed and the event's position within + * the virtual hash table is computed from "hash(event) % width". + * As the hash and width are stable, an event will always remain + * in the same slot within a boundary. + * + * If the event's ticks (the events local sense of time) match + * the event's slot within the boundary, the event is added to + * the poll list. + */ func (et *eventTracker) SelectEvents() []uint { - // fmt.Print("polling events\n") pollList := make([]uint, 0) for event, _ := range et.events { + if event.ticks >= et.pollPeriods { + et.RemoveEvent(event) + continue + } eventStats := et.events[event] - bucket := eventStats.polls - et.initialPolls - // fmt.Printf(" event %d: %+v, bucket %d\n", event, eventStats, bucket) + boundaryIndex := eventStats.polls - et.initialPolls switch { + // before boundaries case eventStats.polls < et.initialPolls: - // fmt.Print(" initial poll range, adding\n") pollList = append(pollList, event) eventStats.polls++ - case bucket + 1 < uint(len(et.boundaries)): - // fmt.Print(" not last range\n") - bucketWidth := et.boundaries[1+bucket] - et.boundaries[bucket] - bucketPosition := eventStats.hash % bucketWidth - //fmt.Printf("event %d, hash %d, bucket %d\n", event, eventStats.hash, bucketPosition) - if eventStats.ticks == et.boundaries[bucket] + bucketPosition { + // between boundaries + case boundaryIndex+1 < uint(len(et.boundaries)): + boundaryWidth := et.boundaries[1+boundaryIndex] - et.boundaries[boundaryIndex] + boundaryPosition := eventStats.hash % boundaryWidth + if eventStats.ticks == et.boundaries[boundaryIndex]+boundaryPosition { pollList = append(pollList, event) } - case bucket < uint(len(et.boundaries)): - // fmt.Print(" last range\n") - bucketWidth := et.pollPeriods - et.boundaries[bucket] - bucketPosition := eventStats.hash % bucketWidth - //fmt.Printf("event %d, hash %d, bucket %d\n", event, eventStats.hash, bucketPosition) - if eventStats.ticks == et.boundaries[bucket] + bucketPosition { + // last boundary + case boundaryIndex < uint(len(et.boundaries)): + boundaryWidth := et.pollPeriods - et.boundaries[boundaryIndex] + boundaryPosition := eventStats.hash % boundaryWidth + if eventStats.ticks == et.boundaries[boundaryIndex]+boundaryPosition { pollList = append(pollList, event) } } @@ -156,10 +210,31 @@ func (et *eventTracker) SelectEvents() []uint { return pollList } +/** + * Returns the count of events being tracked. + * + * @return the events being tracked. + */ func (et *eventTracker) EventCount() uint { return uint(len(et.events)) } +/** + * A hash function for uint. + * + * The slots within a boundary are conceptually a hash table, even + * though the hash table doesn't exist as an struct. This means that + * each event being polled must distribute within the conceptual hash + * table evenly, or the conceptual hash table will only have entries in + * a subset of slots. + * + * This hashing algorithm is the modification of a number of algorithms + * previously found on the internet. It avoids the factorization problem + * (even events only go into even slots) by repeatedly mixing high order + * bits into the low order bits ( h^h >> (number)). The high order bits + * are primarly set by the low order bits repeatedly by multipliation with + * a number designed to mix bits deterministicly for better hash dispersion. + */ func hash(event uint) uint { h := event h ^= h >> 16 From 411867911f0873ed3d5a202f8a31037a1cbae4ae Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Thu, 26 Sep 2024 08:26:31 -0500 Subject: [PATCH 06/32] Fix compliation. Signed-off-by: Edwin Buck --- pkg/server/endpoints/eventTracker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 08a0f984d7..3007f456f6 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -179,8 +179,8 @@ func (et *eventTracker) StopTracking(event uint) { func (et *eventTracker) SelectEvents() []uint { pollList := make([]uint, 0) for event, _ := range et.events { - if event.ticks >= et.pollPeriods { - et.RemoveEvent(event) + if et.events[event].ticks >= et.pollPeriods { + et.StopTracking(event) continue } eventStats := et.events[event] From 791b503b1c36b5fa06d42b30c2911a34dd3597c0 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Thu, 26 Sep 2024 18:40:52 -0700 Subject: [PATCH 07/32] Unit test fixes and restoration of authorized_entryfetcher.go. Improved testing of authorized_entryfetcher_attested_nodes.go Minor fixes to authorized_entryfetcher_regisration_entries.go Fixed authorizedentries/cache_test.go Better documentation for eventTracker.go Fixed lack of sub-minute polling on eventTracker.go Fixed unit tests to match sub-minute polling abilities. Signed-off-by: Edwin Buck --- pkg/server/authorizedentries/cache_test.go | 16 +- .../authorized_entryfetcher_attested_nodes.go | 36 +- ...orized_entryfetcher_attested_nodes_test.go | 966 +++++++++++------- ...rized_entryfetcher_registration_entries.go | 80 +- .../endpoints/authorized_entryfetcher_test.go | 64 +- pkg/server/endpoints/eventTracker.go | 19 +- pkg/server/endpoints/eventTracker_test.go | 603 ++++++----- 7 files changed, 1027 insertions(+), 757 deletions(-) diff --git a/pkg/server/authorizedentries/cache_test.go b/pkg/server/authorizedentries/cache_test.go index 86315bece5..f16c9d8b08 100644 --- a/pkg/server/authorizedentries/cache_test.go +++ b/pkg/server/authorizedentries/cache_test.go @@ -186,19 +186,19 @@ func TestCacheInternalStats(t *testing.T) { cache := NewCache(clk) cache.UpdateEntry(entry1) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ EntriesByEntryID: 1, EntriesByParentID: 1, }, cache.Stats()) cache.UpdateEntry(entry2a) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ EntriesByEntryID: 2, EntriesByParentID: 2, }, cache.Stats()) cache.UpdateEntry(entry2b) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ EntriesByEntryID: 1, EntriesByParentID: 1, AliasesByEntryID: 2, // one for each selector @@ -206,7 +206,7 @@ func TestCacheInternalStats(t *testing.T) { }, cache.Stats()) cache.RemoveEntry(entry1.Id) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ AliasesByEntryID: 2, // one for each selector AliasesBySelector: 2, // one for each selector }, cache.Stats()) @@ -222,25 +222,25 @@ func TestCacheInternalStats(t *testing.T) { t.Run("agents", func(t *testing.T) { cache := NewCache(clk) cache.UpdateAgent(agent1.String(), now.Add(time.Hour), []*types.Selector{sel1}) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ AgentsByID: 1, AgentsByExpiresAt: 1, }, cache.Stats()) cache.UpdateAgent(agent2.String(), now.Add(time.Hour*2), []*types.Selector{sel2}) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ AgentsByID: 2, AgentsByExpiresAt: 2, }, cache.Stats()) cache.UpdateAgent(agent2.String(), now.Add(time.Hour*3), []*types.Selector{sel2}) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ AgentsByID: 2, AgentsByExpiresAt: 2, }, cache.Stats()) cache.RemoveAgent(agent1.String()) - require.Equal(t, cacheStats{ + require.Equal(t, CacheStats{ AgentsByID: 1, AgentsByExpiresAt: 1, }, cache.Stats()) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 377b88f75a..055cc90012 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -35,8 +35,11 @@ type attestedNodes struct { eventTracker *eventTracker sqlTransactionTimeout time.Duration - fetchNodes map[string]struct{} - lastCacheStats authorizedentries.CacheStats + fetchNodes map[string]struct{} + + // metrics change detection + skippedNodeEvents int + lastCacheStats authorizedentries.CacheStats } func (a *attestedNodes) captureChangedNodes(ctx context.Context) error { @@ -99,7 +102,6 @@ func (a *attestedNodes) selectPolledEvents(ctx context.Context) { a.fetchNodes[event.SpiffeID] = struct{}{} a.eventTracker.StopTracking(uint(eventID)) } - server_telemetry.SetSkippedNodeEventIDsCacheCountGauge(a.metrics, int(a.eventTracker.EventCount())) } func (a *attestedNodes) scanForNewEvents(ctx context.Context) error { @@ -155,7 +157,6 @@ func (a *attestedNodes) loadCache(ctx context.Context) error { } a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) } - a.emitCacheMetrics() return nil } @@ -178,8 +179,10 @@ func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metric fetchNodes: make(map[string]struct{}), eventTracker: NewEventTracker(pollPeriods, pollBoundaries), + + // initialize guages to nonsense values to force a change. + skippedNodeEvents: -1, lastCacheStats: authorizedentries.CacheStats{ - // nonsense counts to force a change, even to zero AgentsByID: -1, AgentsByExpiresAt: -1, }, @@ -188,7 +191,6 @@ func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metric if err := attestedNodes.loadCache(ctx); err != nil { return nil, err } - // TODO: is it really necessary to udpate on first load? if err := attestedNodes.updateCache(ctx); err != nil { return nil, err } @@ -205,6 +207,7 @@ func (a *attestedNodes) updateCache(ctx context.Context) error { if err := a.updateCachedNodes(ctx); err != nil { return err } + a.emitMetrics() return nil } @@ -233,20 +236,23 @@ func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { delete(a.fetchNodes, node.SpiffeId) } - a.emitCacheMetrics() return nil } -func (a *attestedNodes) emitCacheMetrics() { - cacheStats := a.cache.Stats() - - if a.lastCacheStats.AgentsByExpiresAt != cacheStats.AgentsByExpiresAt { - server_telemetry.SetAgentsByExpiresAtCacheCountGauge(a.metrics, cacheStats.AgentsByExpiresAt) - a.lastCacheStats.AgentsByExpiresAt = cacheStats.AgentsByExpiresAt +func (a *attestedNodes) emitMetrics() { + if a.skippedNodeEvents != int(a.eventTracker.EventCount()) { + a.skippedNodeEvents = int(a.eventTracker.EventCount()) + server_telemetry.SetSkippedNodeEventIDsCacheCountGauge(a.metrics, a.skippedNodeEvents) } - // Should be the same as AgentsByExpireAt. Not de-duplicated for incident triage. + + cacheStats := a.cache.Stats() + // AgentsByID and AgentsByExpiresAt should be the same. if a.lastCacheStats.AgentsByID != cacheStats.AgentsByID { - server_telemetry.SetAgentsByIDCacheCountGauge(a.metrics, cacheStats.AgentsByID) a.lastCacheStats.AgentsByID = cacheStats.AgentsByID + server_telemetry.SetAgentsByIDCacheCountGauge(a.metrics, a.lastCacheStats.AgentsByID) + } + if a.lastCacheStats.AgentsByExpiresAt != cacheStats.AgentsByExpiresAt { + a.lastCacheStats.AgentsByExpiresAt = cacheStats.AgentsByExpiresAt + server_telemetry.SetAgentsByExpiresAtCacheCountGauge(a.metrics, a.lastCacheStats.AgentsByExpiresAt) } } diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 2b8ed30b9c..1003710297 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -27,6 +27,32 @@ var ( CachedAgentsByID = []string{telemetry.Node, telemetry.AgentsByIDCache, telemetry.Count} CachedAgentsByExpiresAt = []string{telemetry.Node, telemetry.AgentsByExpiresAtCache, telemetry.Count} SkippedNodeEventID = []string{telemetry.Node, telemetry.SkippedNodeEventIDs, telemetry.Count} + + // defaults used to setup a small initial load of attested nodes and events. + defaultAttestedNodes = []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + } + defaultEventsStartingAt60 = []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 60, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 61, + SpiffeID: "spiffe://example.org/test_node_3", + }, + } + defaultFirstEvent = uint(60) + defaultLastEvent = uint(61) + + NoFetches = []string{} ) type expectedGauge struct { @@ -36,9 +62,8 @@ type expectedGauge struct { func TestLoadCache(t *testing.T) { for _, tt := range []struct { - name string - attestedNodes []*common.AttestedNode - errors []error + name string + setup *scenarioSetup expectedError error expectedAuthorizedEntries []string @@ -46,8 +71,8 @@ func TestLoadCache(t *testing.T) { }{ { name: "initial load returns an error", - errors: []error{ - errors.New("any error, doesn't matter"), + setup: &scenarioSetup{ + err: errors.New("any error, doesn't matter"), }, expectedError: errors.New("any error, doesn't matter"), }, @@ -56,10 +81,12 @@ func TestLoadCache(t *testing.T) { }, { name: "initial load loads one attested node", - attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_1", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, }, }, expectedAuthorizedEntries: []string{ @@ -73,26 +100,28 @@ func TestLoadCache(t *testing.T) { }, { name: "initial load loads five attested nodes", - attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_1", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_2", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_3", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_4", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_5", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, }, }, expectedAuthorizedEntries: []string{ @@ -105,26 +134,28 @@ func TestLoadCache(t *testing.T) { }, { name: "initial load loads five attested nodes, one expired", - attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_1", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_2", - CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_3", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_4", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_5", - CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), + }, }, }, expectedAuthorizedEntries: []string{ @@ -136,26 +167,28 @@ func TestLoadCache(t *testing.T) { }, { name: "initial load loads five attested nodes, all expired", - attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_1", - CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_2", - CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_3", - CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_4", - CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_5", - CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), + }, }, }, expectedAuthorizedEntries: []string{}, @@ -163,31 +196,13 @@ func TestLoadCache(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() - - ds := fakedatastore.New(t) - // initialize the database - for _, attestedNode := range tt.attestedNodes { - ds.CreateAttestedNode(ctx, attestedNode) - } - // prune attested node entires, to test the load independently of the events - // this can be removed once CreateAttestedNode no longer creates node events. - ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) - for _, err := range tt.errors { - ds.AppendNextError(err) - } + scenario := NewScenario(t, tt.setup) + attestedNodes, err := scenario.buildAttestedNodesCache() - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) if tt.expectedError != nil { require.Error(t, err, tt.expectedError) return } - require.NoError(t, err) cacheStats := attestedNodes.cache.Stats() @@ -201,9 +216,8 @@ func TestLoadCache(t *testing.T) { cacheStats = attestedNodes.cache.Stats() require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear ccache") - // build up a map of the last metrics var lastMetrics map[string]int = make(map[string]int) - for _, metricItem := range metrics.AllMetrics() { + for _, metricItem := range scenario.metrics.AllMetrics() { if metricItem.Type == fakemetrics.SetGaugeType { key := strings.Join(metricItem.Key, " ") lastMetrics[key] = int(metricItem.Val) @@ -217,45 +231,20 @@ func TestLoadCache(t *testing.T) { require.Equal(t, expectedGauge.Value, value, "unexpected final metric value for %q", key) } - require.Zero(t, hook.Entries) + require.Zero(t, scenario.hook.Entries) }) } } func TestSearchBeforeFirstEvent(t *testing.T) { - var ( - defaultFirstEvent = uint(60) - defaultLastEvent = uint(61) - defaultAttestedNodes = []*common.AttestedNode{ - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_2", - CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), - }, - &common.AttestedNode{ - SpiffeId: "spiffe://example.org/test_node_3", - CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), - }, - } - defaultEventsStartingAt60 = []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 60, - SpiffeID: "spiffe://example.org/test_node_2", - }, - &datastore.AttestedNodeEvent{ - EventID: 61, - SpiffeID: "spiffe://example.org/test_node_3", - }, - } - NoFetches = []string{} - ) for _, tt := range []struct { - name string - attestedNodes []*common.AttestedNode - attestedNodeEvents []*datastore.AttestedNodeEvent - waitToPoll time.Duration - eventsBeforeFirst []uint - polledEvents []*datastore.AttestedNodeEvent - errors []error + name string + setup *scenarioSetup + + waitToPoll time.Duration + eventsBeforeFirst []uint + polledEvents []*datastore.AttestedNodeEvent + errors []error expectedError error expectedEventsBeforeFirst []uint @@ -269,10 +258,12 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "before first event arrived, after transaction timeout", + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, - waitToPoll: time.Duration(2) * defaultSQLTransactionTimeout, + waitToPoll: time.Duration(2) * defaultSQLTransactionTimeout, // even with new before first events, they shouldn't load polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ @@ -287,9 +278,11 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "no before first events", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, - polledEvents: []*datastore.AttestedNodeEvent{}, + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, + polledEvents: []*datastore.AttestedNodeEvent{}, expectedEventsBeforeFirst: []uint{}, expectedFetches: []string{}, @@ -297,8 +290,10 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "new before first event", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 58, @@ -312,8 +307,10 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "new after last event", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 64, @@ -327,9 +324,11 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "previously seen before first event", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, - eventsBeforeFirst: []uint{58}, + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, + eventsBeforeFirst: []uint{58}, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 58, @@ -343,9 +342,11 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "previously seen before first event and after last event", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, - eventsBeforeFirst: []uint{58}, + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, + eventsBeforeFirst: []uint{58}, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: defaultFirstEvent - 2, @@ -363,8 +364,10 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "five new before first events", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 48, @@ -400,8 +403,10 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "five new before first events, one after last event", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 48, @@ -435,10 +440,12 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "five before first events, two previously seen", + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, - eventsBeforeFirst: []uint{48, 49}, + eventsBeforeFirst: []uint{48, 49}, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 48, @@ -470,10 +477,12 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, }, { - name: "five before first events, two previously seen, one after last event", - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, - eventsBeforeFirst: []uint{48, 49}, + name: "five before first events, two previously seen, one after last event", + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, + eventsBeforeFirst: []uint{48, 49}, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 48, @@ -505,9 +514,11 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "five before first events, five previously seen", + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ @@ -533,14 +544,15 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, - expectedFetches: []string{ - }, + expectedFetches: []string{}, }, { name: "five before first events, five previously seen, with after last event", + setup: &scenarioSetup{ + attestedNodes: defaultAttestedNodes, + attestedNodeEvents: defaultEventsStartingAt60, + }, - attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ @@ -570,39 +582,20 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, - expectedFetches: []string{ - }, + expectedFetches: []string{}, }, } { tt := tt t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() - - ds := fakedatastore.New(t) - // initialize the database - for _, attestedNode := range tt.attestedNodes { - ds.CreateAttestedNode(ctx, attestedNode) - } - // prune attested node entires, to test the load independently of the events - // this can be removed once CreateAttestedNode no longer creates node events. - ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) - // add them back so we can control their event id. - for _, event := range tt.attestedNodeEvents { - ds.CreateAttestedNodeEventForTesting(ctx, event) - } + scenario := NewScenario(t, tt.setup) + attestedNodes, err := scenario.buildAttestedNodesCache() - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) require.NoError(t, err) if tt.waitToPoll == 0 { - clk.Add(time.Duration(1) * defaultCacheReloadInterval) + scenario.clk.Add(time.Duration(1) * defaultCacheReloadInterval) } else { - clk.Add(tt.waitToPoll) + scenario.clk.Add(tt.waitToPoll) } for _, event := range tt.eventsBeforeFirst { @@ -610,22 +603,24 @@ func TestSearchBeforeFirstEvent(t *testing.T) { } for _, event := range tt.polledEvents { - ds.CreateAttestedNodeEventForTesting(ctx, event) + scenario.ds.CreateAttestedNodeEventForTesting(scenario.ctx, event) } - attestedNodes.searchBeforeFirstEvent(ctx) + attestedNodes.searchBeforeFirstEvent(scenario.ctx) require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(attestedNodes.eventsBeforeFirst)), "expected events before tracking mismatch") - require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes)), "expected fetches mismatch") + require.ElementsMatch(t, tt.expectedFetches, slices.Collect[string](maps.Keys(attestedNodes.fetchNodes)), "expected fetches mismatch") - require.Zero(t, hook.Entries) + require.Zero(t, scenario.hook.Entries) }) } } func TestSelectedPolledEvents(t *testing.T) { for _, tt := range []struct { - name string + name string + setup *scenarioSetup + polling []uint events []*datastore.AttestedNodeEvent expectedFetches []string @@ -637,154 +632,171 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "nothing to poll, no action take, one event", - events: []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 100, - SpiffeID: "spiffe://example.org/test_node_1", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 100, + SpiffeID: "spiffe://example.org/test_node_1", + }, }, }, }, { name: "nothing to poll, no action taken, five events", - events: []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 101, - SpiffeID: "spiffe://example.org/test_node_1", - }, - &datastore.AttestedNodeEvent{ - EventID: 102, - SpiffeID: "spiffe://example.org/test_node_2", - }, - &datastore.AttestedNodeEvent{ - EventID: 103, - SpiffeID: "spiffe://example.org/test_node_3", - }, - &datastore.AttestedNodeEvent{ - EventID: 104, - SpiffeID: "spiffe://example.org/test_node_4", - }, - &datastore.AttestedNodeEvent{ - EventID: 105, - SpiffeID: "spiffe://example.org/test_node_5", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, }, }, }, { - name: "polling one item, not found", - polling: []uint{103}, - events: []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 101, - SpiffeID: "spiffe://example.org/test_node_1", - }, - &datastore.AttestedNodeEvent{ - EventID: 102, - SpiffeID: "spiffe://example.org/test_node_2", - }, - &datastore.AttestedNodeEvent{ - EventID: 104, - SpiffeID: "spiffe://example.org/test_node_4", - }, - &datastore.AttestedNodeEvent{ - EventID: 105, - SpiffeID: "spiffe://example.org/test_node_5", + name: "polling one item, not found", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, }, }, + polling: []uint{103}, }, { - name: "polling five items, not found", - polling: []uint{102, 103, 104, 105, 106}, - events: []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 101, - SpiffeID: "spiffe://example.org/test_node_1", - }, - &datastore.AttestedNodeEvent{ - EventID: 107, - SpiffeID: "spiffe://example.org/test_node_7", + name: "polling five items, not found", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 107, + SpiffeID: "spiffe://example.org/test_node_7", + }, }, }, + polling: []uint{102, 103, 104, 105, 106}, }, { - name: "polling one item, found", - polling: []uint{102}, - events: []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 101, - SpiffeID: "spiffe://example.org/test_node_1", - }, - &datastore.AttestedNodeEvent{ - EventID: 102, - SpiffeID: "spiffe://example.org/test_node_2", - }, - &datastore.AttestedNodeEvent{ - EventID: 103, - SpiffeID: "spiffe://example.org/test_node_3", + name: "polling one item, found", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, }, }, + polling: []uint{102}, + expectedFetches: []string{ "spiffe://example.org/test_node_2", }, }, { - name: "polling five items, two found", - polling: []uint{102, 103, 104, 105, 106}, - events: []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 101, - SpiffeID: "spiffe://example.org/test_node_1", - }, - &datastore.AttestedNodeEvent{ - EventID: 103, - SpiffeID: "spiffe://example.org/test_node_3", - }, - &datastore.AttestedNodeEvent{ - EventID: 106, - SpiffeID: "spiffe://example.org/test_node_6", - }, - &datastore.AttestedNodeEvent{ - EventID: 107, - SpiffeID: "spiffe://example.org/test_node_7", + name: "polling five items, two found", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 106, + SpiffeID: "spiffe://example.org/test_node_6", + }, + &datastore.AttestedNodeEvent{ + EventID: 107, + SpiffeID: "spiffe://example.org/test_node_7", + }, }, }, + polling: []uint{102, 103, 104, 105, 106}, + expectedFetches: []string{ "spiffe://example.org/test_node_3", "spiffe://example.org/test_node_6", }, }, { - name: "polling five items, five found", - polling: []uint{102, 103, 104, 105, 106}, - events: []*datastore.AttestedNodeEvent{ - &datastore.AttestedNodeEvent{ - EventID: 101, - SpiffeID: "spiffe://example.org/test_node_1", - }, - &datastore.AttestedNodeEvent{ - EventID: 102, - SpiffeID: "spiffe://example.org/test_node_2", - }, - &datastore.AttestedNodeEvent{ - EventID: 103, - SpiffeID: "spiffe://example.org/test_node_3", - }, - &datastore.AttestedNodeEvent{ - EventID: 104, - SpiffeID: "spiffe://example.org/test_node_4", - }, - &datastore.AttestedNodeEvent{ - EventID: 105, - SpiffeID: "spiffe://example.org/test_node_5", - }, - &datastore.AttestedNodeEvent{ - EventID: 106, - SpiffeID: "spiffe://example.org/test_node_6", - }, - &datastore.AttestedNodeEvent{ - EventID: 107, - SpiffeID: "spiffe://example.org/test_node_7", + name: "polling five items, five found", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, + &datastore.AttestedNodeEvent{ + EventID: 106, + SpiffeID: "spiffe://example.org/test_node_6", + }, + &datastore.AttestedNodeEvent{ + EventID: 107, + SpiffeID: "spiffe://example.org/test_node_7", + }, }, }, + polling: []uint{102, 103, 104, 105, 106}, + expectedFetches: []string{ "spiffe://example.org/test_node_2", "spiffe://example.org/test_node_3", @@ -796,142 +808,346 @@ func TestSelectedPolledEvents(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - ds := fakedatastore.New(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() - - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + scenario := NewScenario(t, tt.setup) + attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) - // initialize the database - for _, event := range tt.events { - ds.CreateAttestedNodeEventForTesting(ctx, event) - } + t.Logf("startup expected fetches %v\n", slices.Collect(maps.Keys(attestedNodes.fetchNodes))) // initialize the event tracker for _, event := range tt.polling { + t.Logf("polling %d\n", event) attestedNodes.eventTracker.StartTracking(event) } - // poll the events - attestedNodes.selectPolledEvents(ctx) + attestedNodes.selectPolledEvents(scenario.ctx) require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes))) - require.Zero(t, hook.Entries) + require.Zero(t, scenario.hook.Entries) }) } } func TestScanForNewEvents(t *testing.T) { - var ( - defaultLastEvent = uint(81) - ) for _, tt := range []struct { - name string - polling []uint - events []*datastore.AttestedNodeEvent - expectedFetches []string + name string + setup *scenarioSetup + + newEvents []*datastore.AttestedNodeEvent + + expectedTrackedEvents []uint + expectedFetches []string }{ { - name: "nothing to poll, no action taken, no events", - events: []*datastore.AttestedNodeEvent{}, + name: "no new events, no first event", + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{}, }, { - name: "nothing to poll, no action taken, one event", - events: []*datastore.AttestedNodeEvent{ + name: "no new event, with first event", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{}, + }, + { + name: "one new event", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + newEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ - EventID: defaultLastEvent + 1, + EventID: 102, SpiffeID: "spiffe://example.org/test_node_1", }, }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{ + "spiffe://example.org/test_node_1", + }, }, { - name: "poll first event", - events: []*datastore.AttestedNodeEvent{ + name: "one new event, skipping an event", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + newEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ - EventID: 101, + EventID: 103, SpiffeID: "spiffe://example.org/test_node_1", }, + }, + + expectedTrackedEvents: []uint{102}, + expectedFetches: []string{ + "spiffe://example.org/test_node_1", + }, + }, + { + name: "two new events, same attested node", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + newEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 102, - SpiffeID: "spiffe://example.org/test_node_2", + SpiffeID: "spiffe://example.org/test_node_1", }, &datastore.AttestedNodeEvent{ EventID: 103, - SpiffeID: "spiffe://example.org/test_node_3", + SpiffeID: "spiffe://example.org/test_node_1", }, + }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{ + "spiffe://example.org/test_node_1", + }, + }, + { + name: "two new events, different attested nodes", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + newEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ - EventID: 104, - SpiffeID: "spiffe://example.org/test_node_4", + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_1", }, &datastore.AttestedNodeEvent{ - EventID: 105, - SpiffeID: "spiffe://example.org/test_node_5", + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_2", }, }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + }, }, { - name: "polling one item, not found", - polling: []uint{103}, - events: []*datastore.AttestedNodeEvent{ + name: "two new events, with a skipped event", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + newEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ - EventID: 101, + EventID: 102, SpiffeID: "spiffe://example.org/test_node_1", }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_2", + }, + }, + + expectedTrackedEvents: []uint{103}, + expectedFetches: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + }, + }, + { + name: "two new events, with three skipped events", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + }, + }, + newEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 102, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 106, SpiffeID: "spiffe://example.org/test_node_2", }, + }, + + expectedTrackedEvents: []uint{103, 104, 105}, + expectedFetches: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + }, + }, { + name: "five events, four new events, two skip regions", + setup: &scenarioSetup{ + attestedNodeEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 101, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 102, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 103, + SpiffeID: "spiffe://example.org/test_node_3", + }, + &datastore.AttestedNodeEvent{ + EventID: 104, + SpiffeID: "spiffe://example.org/test_node_4", + }, + &datastore.AttestedNodeEvent{ + EventID: 105, + SpiffeID: "spiffe://example.org/test_node_5", + }, + }, + }, + newEvents: []*datastore.AttestedNodeEvent{ + &datastore.AttestedNodeEvent{ + EventID: 108, + SpiffeID: "spiffe://example.org/test_node_1", + }, + &datastore.AttestedNodeEvent{ + EventID: 109, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 110, + SpiffeID: "spiffe://example.org/test_node_2", + }, + &datastore.AttestedNodeEvent{ + EventID: 112, + SpiffeID: "spiffe://example.org/test_node_11", + }, + }, + + expectedTrackedEvents: []uint{106, 107, 111}, + expectedFetches: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_11", + }, }, } { tt := tt t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() - - ds := fakedatastore.New(t) - // initialize the database - for _, attestedNode := range tt.attestedNodes { - ds.CreateAttestedNode(ctx, attestedNode) - } - // prune attested node entires, to test the load independently of the events - // this can be removed once CreateAttestedNode no longer creates node events. - ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) - // add them back so we can control their event id. - for _, event := range tt.attestedNodeEvents { - ds.CreateAttestedNodeEventForTesting(ctx, event) - } - - attestedNodes, err := buildAttestedNodesCache(ctx, log, metrics, ds, clk, cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + scenario := NewScenario(t, tt.setup) + attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) - if tt.waitToPoll == 0 { - clk.Add(time.Duration(1) * defaultCacheReloadInterval) - } else { - clk.Add(tt.waitToPoll) + for _, newEvent := range tt.newEvents { + scenario.ds.CreateAttestedNodeEventForTesting(scenario.ctx, newEvent) } + err = attestedNodes.scanForNewEvents(scenario.ctx) + require.NoError(t, err) - for _, event := range tt.polledEvents { - ds.CreateAttestedNodeEventForTesting(ctx, event) - } + require.ElementsMatch(t, tt.expectedTrackedEvents, slices.Collect(maps.Keys(attestedNodes.eventTracker.events))) + require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes))) + require.Zero(t, scenario.hook.Entries) + }) + } +} - attestedNodes.searchBeforeFirstEvent(ctx) +func TestUpdateAttestedNodesCache(t *testing.T) { +} - require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(attestedNodes.eventsBeforeFirst)), "expected events before tracking mismatch") - require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes)), "expected fetches mismatch") +// utility functions +type scenario struct { + ctx context.Context + log *logrus.Logger + hook *test.Hook + clk *clock.Mock + cache *authorizedentries.Cache + metrics *fakemetrics.FakeMetrics + ds *fakedatastore.DataStore +} - require.Zero(t, hook.Entries) - }) +type scenarioSetup struct { + attestedNodes []*common.AttestedNode + attestedNodeEvents []*datastore.AttestedNodeEvent + err error +} + +func NewScenario(t *testing.T, setup *scenarioSetup) *scenario { + t.Helper() + ctx := context.Background() + log, hook := test.NewNullLogger() + log.SetLevel(logrus.DebugLevel) + clk := clock.NewMock(t) + cache := authorizedentries.NewCache(clk) + metrics := fakemetrics.New() + ds := fakedatastore.New(t) + + if setup == nil { + setup = &scenarioSetup{} + } + + // initialize the database + for _, attestedNode := range setup.attestedNodes { + ds.CreateAttestedNode(ctx, attestedNode) + } + // prune autocreated node events, to test the event logic in more scenarios + // than possible with autocreated node events. + ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + // and then add back the specified node events + for _, event := range setup.attestedNodeEvents { + ds.CreateAttestedNodeEventForTesting(ctx, event) + } + // inject db error for buildAttestedNodesCache call + if setup.err != nil { + ds.AppendNextError(setup.err) + } + + return &scenario{ + ctx: ctx, + log: log, + hook: hook, + clk: clk, + cache: cache, + metrics: metrics, + ds: ds, } } -func TestUpdateAttestedNodesCache(t *testing.T) { +func (s *scenario) buildAttestedNodesCache() (*attestedNodes, error) { + attestedNodes, err := buildAttestedNodesCache(s.ctx, s.log, s.metrics, s.ds, s.clk, s.cache, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + if attestedNodes != nil { + // clear out the fetches + for node, _ := range attestedNodes.fetchNodes { + delete(attestedNodes.fetchNodes, node) + } + } + return attestedNodes, err } diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index e1b1d56b23..b18b17bd76 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -35,6 +35,10 @@ type registrationEntries struct { sqlTransactionTimeout time.Duration fetchEntries map[string]struct{} + + // metrics change detection + skippedEntryEvents int + lastCacheStats authorizedentries.CacheStats } func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { @@ -44,7 +48,7 @@ func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { if err := a.searchBeforeFirstEvent(ctx); err != nil { return err } - a.selectPolledEvents(ctx); + a.selectPolledEvents(ctx) if err := a.scanNewEvents(ctx); err != nil { return err } @@ -54,7 +58,7 @@ func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { func (a *registrationEntries) searchBeforeFirstEvent(ctx context.Context) error { // First event detected, and startup was less than a transaction timout away. - if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) > a.sqlTransactionTimeout { + if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) <= a.sqlTransactionTimeout { resp, err := a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ LessThanEventID: a.firstEvent, }) @@ -79,7 +83,6 @@ func (a *registrationEntries) searchBeforeFirstEvent(ctx context.Context) error return nil } - func (a *registrationEntries) selectPolledEvents(ctx context.Context) { // check if the polled events have appeared out-of-order for _, eventID := range a.eventTracker.SelectEvents() { @@ -98,7 +101,6 @@ func (a *registrationEntries) selectPolledEvents(ctx context.Context) { a.fetchEntries[event.EntryID] = struct{}{} a.eventTracker.StopTracking(uint(eventID)) } - server_telemetry.SetSkippedEntryEventIDsCacheCountGauge(a.metrics, int(a.eventTracker.EventCount())) } func (a *registrationEntries) scanNewEvents(ctx context.Context) error { @@ -176,26 +178,31 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) registrationEntries := ®istrationEntries{ - cache: cache, - clk: clk, - ds: ds, - log: log, - metrics: metrics, - sqlTransactionTimeout: sqlTransactionTimeout, - - eventsBeforeFirst: make(map[uint]struct{}), - fetchEntries: make(map[string]struct{}), - - eventTracker: NewEventTracker(pollPeriods, pollBoundaries), + cache: cache, + clk: clk, + ds: ds, + log: log, + metrics: metrics, + sqlTransactionTimeout: sqlTransactionTimeout, + + eventsBeforeFirst: make(map[uint]struct{}), + fetchEntries: make(map[string]struct{}), + + eventTracker: NewEventTracker(pollPeriods, pollBoundaries), + + skippedEntryEvents: -1, + lastCacheStats: authorizedentries.CacheStats{ + AliasesByEntryID: -1, + AliasesBySelector: -1, + EntriesByEntryID: -1, + EntriesByParentID: -1, + }, } if err := registrationEntries.loadCache(ctx, pageSize); err != nil { return nil, err } - if err := registrationEntries.captureChangedEntries(ctx); err != nil { - return nil, err - } - if err := registrationEntries.updateCachedEntries(ctx); err != nil { + if err := registrationEntries.updateCache(ctx); err != nil { return nil, err } @@ -211,14 +218,7 @@ func (a *registrationEntries) updateCache(ctx context.Context) error { if err := a.updateCachedEntries(ctx); err != nil { return err } - - // These two should be the same value but it's valuable to have them both be emitted for incident triage. - server_telemetry.SetNodeAliasesByEntryIDCacheCountGauge(a.metrics, a.cache.Stats().AliasesByEntryID) - server_telemetry.SetNodeAliasesBySelectorCacheCountGauge(a.metrics, a.cache.Stats().AliasesBySelector) - - // These two should be the same value but it's valuable to have them both be emitted for incident triage. - server_telemetry.SetEntriesByEntryIDCacheCountGauge(a.metrics, a.cache.Stats().EntriesByEntryID) - server_telemetry.SetEntriesByParentIDCacheCountGauge(a.metrics, a.cache.Stats().EntriesByParentID) + a.emitMetrics() return nil } @@ -248,3 +248,29 @@ func (a *registrationEntries) updateCachedEntries(ctx context.Context) error { } return nil } + +func (a *registrationEntries) emitMetrics() { + if a.skippedEntryEvents != int(a.eventTracker.EventCount()) { + a.skippedEntryEvents = int(a.eventTracker.EventCount()) + server_telemetry.SetSkippedEntryEventIDsCacheCountGauge(a.metrics, a.skippedEntryEvents) + } + + cacheStats := a.cache.Stats() + if a.lastCacheStats.AliasesByEntryID != cacheStats.AliasesByEntryID { + a.lastCacheStats.AliasesByEntryID = cacheStats.AliasesByEntryID + server_telemetry.SetNodeAliasesByEntryIDCacheCountGauge(a.metrics, a.lastCacheStats.AliasesByEntryID) + } + if a.lastCacheStats.AliasesBySelector != cacheStats.AliasesBySelector { + a.lastCacheStats.AliasesBySelector = cacheStats.AliasesBySelector + server_telemetry.SetNodeAliasesBySelectorCacheCountGauge(a.metrics, a.lastCacheStats.AliasesBySelector) + } + if a.lastCacheStats.EntriesByEntryID != cacheStats.EntriesByEntryID { + a.lastCacheStats.EntriesByEntryID = cacheStats.EntriesByEntryID + server_telemetry.SetEntriesByEntryIDCacheCountGauge(a.metrics, a.lastCacheStats.EntriesByEntryID) + } + if a.lastCacheStats.EntriesByParentID != cacheStats.EntriesByParentID { + a.lastCacheStats.EntriesByParentID = cacheStats.EntriesByParentID + server_telemetry.SetEntriesByParentIDCacheCountGauge(a.metrics, a.lastCacheStats.EntriesByParentID) + } + +} diff --git a/pkg/server/endpoints/authorized_entryfetcher_test.go b/pkg/server/endpoints/authorized_entryfetcher_test.go index a8df6d5fe0..e0dac53201 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_test.go @@ -31,6 +31,21 @@ func TestNewAuthorizedEntryFetcherWithEventsBasedCache(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, ef) + buildMetrics := []fakemetrics.MetricItem{ + agentsByIDMetric(0), + agentsByIDExpiresAtMetric(0), + nodeAliasesByEntryIDMetric(0), + nodeAliasesBySelectorMetric(0), + nodeSkippedEventMetric(0), + + entriesByEntryIDMetric(0), + entriesByParentIDMetric(0), + entriesSkippedEventMetric(0), + } + + assert.ElementsMatch(t, buildMetrics, metrics.AllMetrics(), "should emit metrics for node aliases, entries, and agents") + metrics.Reset() + agentID := spiffeid.RequireFromString("spiffe://example.org/myagent") _, err = ds.CreateAttestedNode(ctx, &common.AttestedNode{ @@ -106,9 +121,6 @@ func TestNewAuthorizedEntryFetcherWithEventsBasedCache(t *testing.T) { nodeAliasesBySelectorMetric(1), entriesByEntryIDMetric(2), entriesByParentIDMetric(2), - // Here we have 2 skipped events, one for nodes, one for entries - nodeSkippedEventMetric(0), - entriesSkippedEventMetric(0), } assert.ElementsMatch(t, expectedMetrics, metrics.AllMetrics(), "should emit metrics for node aliases, entries, and agents") @@ -133,8 +145,7 @@ func TestNewAuthorizedEntryFetcherWithEventsBasedCacheErrorBuildingCache(t *test assert.ElementsMatch(t, expectedMetrics, metrics.AllMetrics(), "should emit no metrics") } -/* -func TestBuildCacheSavesMissedEvents(t *testing.T) { +func TestBuildCacheSavesSkippedEvents(t *testing.T) { ctx := context.Background() log, _ := test.NewNullLogger() clk := clock.NewMock(t) @@ -172,18 +183,27 @@ func TestBuildCacheSavesMissedEvents(t *testing.T) { require.NotNil(t, registrationEntries) require.NotNil(t, attestedNodes) - assert.Contains(t, registrationEntries.missedEvents, uint(2)) - assert.Equal(t, uint(3), registrationEntries.lastEventID) + assert.Contains(t, registrationEntries.eventTracker.events, uint(2)) + assert.Equal(t, uint(3), registrationEntries.lastEvent) - assert.Contains(t, attestedNodes.missedEvents, uint(2)) - assert.Contains(t, attestedNodes.missedEvents, uint(3)) - assert.Equal(t, uint(4), attestedNodes.lastEventID) + assert.Contains(t, attestedNodes.eventTracker.events, uint(2)) + assert.Contains(t, attestedNodes.eventTracker.events, uint(3)) + assert.Equal(t, uint(4), attestedNodes.lastEvent) - // Assert metrics since the updateCache() method doesn't get called right at built time. - expectedMetrics := []fakemetrics.MetricItem{} + // Assert zero metrics since the updateCache() method doesn't get called right at built time. + expectedMetrics := []fakemetrics.MetricItem{ + agentsByIDMetric(0), + agentsByIDExpiresAtMetric(0), + nodeAliasesByEntryIDMetric(0), + nodeAliasesBySelectorMetric(0), + nodeSkippedEventMetric(2), + + entriesByEntryIDMetric(0), + entriesByParentIDMetric(0), + entriesSkippedEventMetric(1), + } assert.ElementsMatch(t, expectedMetrics, metrics.AllMetrics(), "should emit no metrics") } -*/ func TestRunUpdateCacheTaskPrunesExpiredAgents(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -252,7 +272,7 @@ func TestRunUpdateCacheTaskPrunesExpiredAgents(t *testing.T) { require.ErrorIs(t, err, context.Canceled) } -func TestUpdateRegistrationEntriesCacheMissedEvents(t *testing.T) { +func TestUpdateRegistrationEntriesCacheSkippedEvents(t *testing.T) { ctx := context.Background() log, _ := test.NewNullLogger() clk := clock.NewMock(t) @@ -336,7 +356,7 @@ func TestUpdateRegistrationEntriesCacheMissedEvents(t *testing.T) { require.Equal(t, 1, len(entries)) } -func TestUpdateRegistrationEntriesCacheMissedStartupEvents(t *testing.T) { +func TestUpdateRegistrationEntriesCacheSkippedStartupEvents(t *testing.T) { ctx := context.Background() log, _ := test.NewNullLogger() clk := clock.NewMock(t) @@ -358,12 +378,17 @@ func TestUpdateRegistrationEntriesCacheMissedStartupEvents(t *testing.T) { }) require.NoError(t, err) - // Delete the event and entry for now and then add it back later to simulate out of order events + // Delete the create event for the first entry err = ds.DeleteRegistrationEntryEventForTesting(ctx, 1) require.NoError(t, err) + _, err = ds.DeleteRegistrationEntry(ctx, entry1.EntryId) require.NoError(t, err) + // Delete the delete event for the first entry + err = ds.DeleteRegistrationEntryEventForTesting(ctx, 2) + require.NoError(t, err) + // Create Second entry entry2, err := ds.CreateRegistrationEntry(ctx, &common.RegistrationEntry{ SpiffeId: "spiffe://example.org/workload2", @@ -401,6 +426,7 @@ func TestUpdateRegistrationEntriesCacheMissedStartupEvents(t *testing.T) { }, }) require.NoError(t, err) + err = ds.DeleteRegistrationEntryEventForTesting(ctx, 4) require.NoError(t, err) @@ -408,7 +434,7 @@ func TestUpdateRegistrationEntriesCacheMissedStartupEvents(t *testing.T) { err = ef.updateCache(ctx) require.NoError(t, err) - // Still should be 1 entry + // Still should be 1 entry, no event tells us about spiffe://example.org/workload entries, err = ef.FetchAuthorizedEntries(ctx, agentID) require.NoError(t, err) require.Equal(t, 1, len(entries)) @@ -443,7 +469,7 @@ func TestUpdateRegistrationEntriesCacheMissedStartupEvents(t *testing.T) { require.Contains(t, spiffeIDs, entry2.SpiffeId) } -func TestUpdateAttestedNodesCacheMissedEvents(t *testing.T) { +func TestUpdateAttestedNodesCacheSkippedEvents(t *testing.T) { ctx := context.Background() log, _ := test.NewNullLogger() clk := clock.NewMock(t) @@ -560,7 +586,7 @@ func TestUpdateAttestedNodesCacheMissedEvents(t *testing.T) { require.Equal(t, entry.SpiffeId, idutil.RequireIDProtoString(entries[0].SpiffeId)) } -func TestUpdateAttestedNodesCacheMissedStartupEvents(t *testing.T) { +func TestUpdateAttestedNodesCacheSkippedStartupEvents(t *testing.T) { ctx := context.Background() log, _ := test.NewNullLogger() clk := clock.NewMock(t) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 3007f456f6..e0a42abc01 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -11,7 +11,7 @@ import ( * * An event track is defined with a set of foundaries, which are indexes to * virtual hash tables, with the event's hash determining the position within - * that hash table where the event will be selected to be polled. + * that hash table where the event will be selected to be polled. * For eventTRackers that lack boundaries, or polls that exist prior to * boundaries, the event is always polled. */ @@ -19,11 +19,11 @@ type eventTracker struct { /* Times the event is polled before entering a boundary */ initialPolls uint /* Times the event is polled */ - pollPeriods uint + pollPeriods uint /* Per event context of each event's walk across the boudaries */ - events map[uint]*eventStats + events map[uint]*eventStats /* The leading index of boundaries in which an event should only report once */ - boundaries []uint + boundaries []uint } /** @@ -31,7 +31,7 @@ type eventTracker struct { */ type eventStats struct { /* The event's hash for hash table calcuations */ - hash uint + hash uint /* The number of times the event was considered for polling */ ticks uint /* The number of times the event was selected for polling */ @@ -55,6 +55,13 @@ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { return uint(1 + (trackTime-1)/pollTime) } +/** + * The default boundary strategy. + * + * Poll everything at poll rate for at least one minute, then poll everything + * twice a minute for 9 minute, then onece a minute for rest of time, with a + * guaranteed poll just before no longer tracking. + */ func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { pollPeriods := PollPeriods(pollTime, trackTime) @@ -164,7 +171,7 @@ func (et *eventTracker) StopTracking(event uint) { * * The event's boundary Index is computed, and if it is below the * number of initial polls, the event is polled without further - * analysis. + * analysis. * * If the boundary index is inside a defined boundary, the width * of the boundary is computed and the event's position within diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index 78bd9ac780..5957eac164 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -31,16 +31,16 @@ func TestPollPeriods(t *testing.T) { expectedPollPeriods: 1, }, { - name: "minimum poll interval of one minute", - pollInterval: time.Duration(20) * time.Second, - pollDuration: time.Duration(10) * time.Minute, + name: "minimum poll interval of one second", + pollInterval: time.Duration(0) * time.Second, + pollDuration: time.Duration(10) * time.Second, expectedPollPeriods: 10, }, { - name: "minimum poll interval of one minute, even for negative intervals", - pollInterval: time.Duration(-1) * time.Minute, - pollDuration: time.Duration(10) * time.Minute, + name: "minimum poll interval of one second, even for negative intervals", + pollInterval: time.Duration(-100) * time.Second, + pollDuration: time.Duration(10) * time.Second, expectedPollPeriods: 10, }, @@ -220,20 +220,6 @@ func TestNewEventTracker(t *testing.T) { } } -/* - 0 1 2 3 4 5 (pollPeriods) - 0 3 (boundaries) - 0 0 0 1 1 1 (bucket number) - 3 3 3 3 3 3 (bucket width) - - 0 3 0 0 3 0 (timeslots to poll event 3) - 3 3 3 3 3 3 (every slot is polled) - - pollEvent(3) - hash := hash(3) # hash must distribute well - poll within the bucket = 34234 % (bucket width) - */ - func TestEvenTrackerPolling(t *testing.T) { for _, tt := range []struct { name string @@ -353,608 +339,608 @@ func TestEvenTrackerPolling(t *testing.T) { func TestEvenDispersion(t *testing.T) { for _, tt := range []struct { - name string - pollPeriods uint + name string + pollPeriods uint startEvent uint eventIncrement uint eventCount uint - expectedSlotCount uint + expectedSlotCount uint permissibleCountError uint }{ { // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, + name: "increment by 2 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 1, 3, 5, 7, 9, 11, 13, ... - name: "increment by 2 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, + name: "increment by 2 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 1, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 0, 3, 6, 9, 12, 15, ... - name: "increment by 3 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, + name: "increment by 3 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 1, 4, 7, 10, 13, 16, ... - name: "increment by 3 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, + name: "increment by 3 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 1, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 2, 5, 8, 11, 14, 17, ... - name: "increment by 3 (offset 2) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 2, + name: "increment by 3 (offset 2) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 2, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 0, 4, 8, 12, 16, 20, ... - name: "increment by 4 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, + name: "increment by 4 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 1, 5, 9, 13, 17, 21, ... - name: "increment by 4 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, + name: "increment by 4 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 1, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 2, 6, 10, 14, 18, 22, ... - name: "increment by 4 (offset 2) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 2, + name: "increment by 4 (offset 2) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 2, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 3, 7, 11, 15, 19, 23, ... - name: "increment by 4 (offset 3) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 3, + name: "increment by 4 (offset 3) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 3, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 0, 5, 10, 15, 20, 25, ... - name: "increment by 5 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, + name: "increment by 5 (offset 0) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 0, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 1, 6, 11, 16, 21, 26, ... - name: "increment by 5 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, + name: "increment by 5 (offset 1) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 1, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 2, 7, 12, 17, 22, 27, ... - name: "increment by 5 (offset 2) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 2, + name: "increment by 5 (offset 2) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 2, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 3, 8, 13, 18, 23, 28, ... - name: "increment by 5 (offset 3) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 3, + name: "increment by 5 (offset 3) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 3, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 4, 9, 14, 19, 24, 29, ... - name: "increment by 5 (offset 4) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 4, + name: "increment by 5 (offset 4) events distribute fairly across 2 slots", + pollPeriods: 2, + startEvent: 4, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, // should disperse into two slots, with an approxmiate count of [ 500, 500 ] - expectedSlotCount: 500, + expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, + name: "increment by 2 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, // should disperse into three slots, with an approxmiate count of [ 333, 333, 333 ] - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 2 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, + name: "increment by 2 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 1, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, + name: "increment by 3 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, + name: "increment by 3 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 1, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 2) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 2, + name: "increment by 3 (offset 2) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 2, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, + name: "increment by 4 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, + name: "increment by 4 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 1, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 2) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 2, + name: "increment by 4 (offset 2) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 2, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 3) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 3, + name: "increment by 4 (offset 3) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 3, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, + name: "increment by 5 (offset 0) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 0, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, + name: "increment by 5 (offset 1) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 1, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 2) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 2, + name: "increment by 5 (offset 2) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 2, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 3) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 3, + name: "increment by 5 (offset 3) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 3, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 4) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 4, + name: "increment by 5 (offset 4) events distribute fairly across 3 slots", + pollPeriods: 3, + startEvent: 4, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 333, + expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, + name: "increment by 2 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, // should disperse into four slots, with an approxmiate count of [ 250, 250, 250, 250 ] - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 2 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, + name: "increment by 2 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 1, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, + name: "increment by 3 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, + name: "increment by 3 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 1, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 2) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 2, + name: "increment by 3 (offset 2) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 2, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, + name: "increment by 4 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, + name: "increment by 4 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 1, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 2) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 2, + name: "increment by 4 (offset 2) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 2, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 3) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 3, + name: "increment by 4 (offset 3) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 3, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, + name: "increment by 5 (offset 0) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 0, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, + name: "increment by 5 (offset 1) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 1, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 2) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 2, + name: "increment by 5 (offset 2) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 2, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 3) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 3, + name: "increment by 5 (offset 3) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 3, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 4) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 4, + name: "increment by 5 (offset 4) events distribute fairly across 4 slots", + pollPeriods: 4, + startEvent: 4, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 250, + expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, { // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, + name: "increment by 2 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, // should disperse into five slots, with an approxmiate count of [ 200, 200, 200, 200, 200 ] - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 2 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, + name: "increment by 2 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 1, eventIncrement: 2, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, + name: "increment by 3 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, + name: "increment by 3 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 1, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 3 (offset 2) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 2, + name: "increment by 3 (offset 2) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 2, eventIncrement: 3, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, + name: "increment by 4 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, + name: "increment by 4 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 1, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 2) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 2, + name: "increment by 4 (offset 2) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 2, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 4 (offset 3) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 3, + name: "increment by 4 (offset 3) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 3, eventIncrement: 4, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, + name: "increment by 5 (offset 0) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 0, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, + name: "increment by 5 (offset 1) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 1, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 2) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 2, + name: "increment by 5 (offset 2) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 2, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 3) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 3, + name: "increment by 5 (offset 3) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 3, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, { - name: "increment by 5 (offset 4) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 4, + name: "increment by 5 (offset 4) events distribute fairly across 5 slots", + pollPeriods: 5, + startEvent: 4, eventIncrement: 5, - eventCount: 1000, + eventCount: 1000, - expectedSlotCount: 200, + expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, } { @@ -972,14 +958,17 @@ func TestEvenDispersion(t *testing.T) { t.Logf("pollPeriod %d, count = %d", pollPeriod, slotCount[pollPeriod]) } for slot, count := range slotCount { - require.LessOrEqual(t, tt.expectedSlotCount - tt.permissibleCountError, count, + require.LessOrEqual(t, tt.expectedSlotCount-tt.permissibleCountError, count, "for slot %d, expecting at least %d polls, but received %d polls", - slot, tt.expectedSlotCount - tt.permissibleCountError, count) - require.GreaterOrEqual(t, tt.expectedSlotCount + tt.permissibleCountError, count, + slot, tt.expectedSlotCount-tt.permissibleCountError, count) + require.GreaterOrEqual(t, tt.expectedSlotCount+tt.permissibleCountError, count, "for slot %d, expecting no more than %d polls, but received %d polls", - slot, tt.expectedSlotCount + tt.permissibleCountError, count) + slot, tt.expectedSlotCount+tt.permissibleCountError, count) } }) } } + +func TestBoundaryBuilder(t *testing.T) { +} From aad74449c3b8e44c5713a685032aacb62774fa84 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Fri, 27 Sep 2024 10:40:27 -0700 Subject: [PATCH 08/32] Add in boundary builder unit tests. Signed-off-by: Edwin Buck --- pkg/server/endpoints/eventTracker.go | 36 +++--- pkg/server/endpoints/eventTracker_test.go | 147 ++++++++++++++++++++++ 2 files changed, 167 insertions(+), 16 deletions(-) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index e0a42abc01..36f85ace92 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -1,7 +1,7 @@ package endpoints import ( - // "fmt" + "maps" "slices" "time" ) @@ -65,30 +65,34 @@ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { pollPeriods := PollPeriods(pollTime, trackTime) - pollBoundaries := make([]uint, 0) // number of polls in a minute pollsPerMinute := uint(time.Duration(1) * time.Minute / pollTime) // number of polls in ten minutes pollsPerTenMinutes := uint(time.Duration(10) * time.Minute / pollTime) - // for first minute, poll at cacheReloadInterval - pollBoundaries = append(pollBoundaries, pollsPerMinute) - // next 9 minutes, poll at 1/2 minute - currentBoundary := pollsPerMinute - for currentBoundary < pollsPerTenMinutes { - pollBoundaries = append(pollBoundaries, currentBoundary+(pollsPerMinute/2)) - pollBoundaries = append(pollBoundaries, currentBoundary+pollsPerMinute) - currentBoundary += pollsPerMinute - } - // rest of polling at 1 minute + // initialize poll boundaries one minute out + boundaries := make(map[uint]struct{}) + currentBoundary := uint(pollsPerMinute) for currentBoundary < pollPeriods { - pollBoundaries = append(pollBoundaries, currentBoundary+pollsPerMinute) + if currentBoundary < pollsPerTenMinutes { + boundaries[currentBoundary] = struct{}{} + boundaries[currentBoundary+(pollsPerMinute/2)] = struct{}{} + } else { + boundaries[currentBoundary] = struct{}{} + } currentBoundary += pollsPerMinute } - // always poll at end of transaction timeout - pollBoundaries = append(pollBoundaries, pollPeriods-1) + if 0 < len(boundaries) { + boundaries[pollPeriods-1] = struct{}{} + } + + boundaryList := slices.Collect(maps.Keys(boundaries)) + slices.Sort(boundaryList) + if boundaryList == nil { + boundaryList = []uint{} + } - return pollBoundaries + return boundaryList } func NewEventTracker(pollPeriods uint, boundaries []uint) *eventTracker { diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index 5957eac164..061e7bd555 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -971,4 +971,151 @@ func TestEvenDispersion(t *testing.T) { } func TestBoundaryBuilder(t *testing.T) { + for _, tt := range []struct { + name string + pollInterval string + pollDuration string + + expectedPollPeriods uint + expectedBoundaries []uint + }{ + { + name: "poll every second, over 1 minute", + pollInterval: "1s", + pollDuration: "1m", + + expectedPollPeriods: 60, + expectedBoundaries: []uint{}, + }, + { + name: "poll every second, over 10 minutes", + pollInterval: "1s", + pollDuration: "10m", + + expectedPollPeriods: 600, + expectedBoundaries: []uint{ + 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, + 360, 390, 420, 450, 480, 510, 540, 570, 599, + }, + }, + { + name: "poll every second, over 20 minutes", + pollInterval: "1s", + pollDuration: "20m", + + expectedPollPeriods: 1200, + expectedBoundaries: []uint{ + 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, + 360, 390, 420, 450, 480, 510, 540, 570, + 600, 660, 720, 780, 840, 900, 960, 1020, + 1080, 1140, 1199, + }, + }, + { + name: "poll every 5 seconds, over 1 minute", + pollInterval: "5s", + pollDuration: "1m", + + expectedPollPeriods: 12, + expectedBoundaries: []uint{}, + }, + { + name: "poll every 5 seconds, over 10 minutes", + pollInterval: "5s", + pollDuration: "10m", + + expectedPollPeriods: 120, + expectedBoundaries: []uint{ + 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, + 72, 78, 84, 90, 96, 102, 108, 114, 119, + }, + }, + { + name: "poll every 5 seconds, over 20 minutes", + pollInterval: "5s", + pollDuration: "20m", + + expectedPollPeriods: 240, + expectedBoundaries: []uint{ + 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, + 72, 78, 84, 90, 96, 102, 108, 114, 120, + 132, 144, 156, 168, 180, 192, 204, 216, 228, 239, + }, + }, + { + name: "poll every 10 seconds, over 1 minute", + pollInterval: "10s", + pollDuration: "1m", + + expectedPollPeriods: 6, + expectedBoundaries: []uint{}, + }, + { + name: "poll every 10 seconds, over 10 minutes", + pollInterval: "10s", + pollDuration: "10m", + + expectedPollPeriods: 60, + expectedBoundaries: []uint{ + 6, 9, 12, 15, 18, 21, 24, 27, 30, + 33, 36, 39, 42, 45, 48, 51, 54, 57, 59, + }, + }, + { + name: "poll every 10 seconds, over 20 minutes", + pollInterval: "10s", + pollDuration: "20m", + + expectedPollPeriods: 120, + expectedBoundaries: []uint{ + 6, 9, 12, 15, 18, 21, 24, 27, 30, + 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, + 66, 72, 78, 84, 90, 96, 102, 108, 114, 119, + }, + }, + { + name: "poll every 20 seconds, over 1 minute", + pollInterval: "20s", + pollDuration: "1m", + + expectedPollPeriods: 3, + expectedBoundaries: []uint{}, + }, + { + name: "poll every 20 seconds, over 10 minutes", + pollInterval: "20s", + pollDuration: "10m", + + expectedPollPeriods: 30, + expectedBoundaries: []uint{ + 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, + 18, 19, 21, 22, 24, 25, 27, 28, 29, + }, + }, + { + name: "poll every 20 seconds, over 20 minutes", + pollInterval: "20s", + pollDuration: "20m", + + expectedPollPeriods: 60, + expectedBoundaries: []uint{ + 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, + 18, 19, 21, 22, 24, 25, 27, 28, 30, + 33, 36, 39, 42, 45, 48, 51, 54, 57, 59, + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + pollInterval, err := time.ParseDuration(tt.pollInterval) + require.NoError(t, err, "error in specifying test poll interval") + pollDuration, err := time.ParseDuration(tt.pollDuration) + require.NoError(t, err, "error in specifying test poll duration") + pollPeriods := endpoints.PollPeriods(pollInterval, pollDuration) + + require.Equal(t, tt.expectedPollPeriods, pollPeriods) + boundaries := endpoints.BoundaryBuilder(pollInterval, pollDuration) + require.Equal(t, tt.expectedBoundaries, boundaries) + }) + } } From 806ebf736a4dd236f43b03732202e725e931a41c Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Sun, 29 Sep 2024 12:45:24 -0700 Subject: [PATCH 09/32] Add unit tests for updating the node cache from the database. Signed-off-by: Edwin Buck --- .../authorized_entryfetcher_attested_nodes.go | 5 +- ...orized_entryfetcher_attested_nodes_test.go | 384 +++++++++++++++++- ...rized_entryfetcher_registration_entries.go | 6 +- 3 files changed, 389 insertions(+), 6 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 055cc90012..c69117f565 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -222,7 +222,8 @@ func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { // Node was deleted if node == nil { a.cache.RemoveAgent(spiffeId) - return nil + delete(a.fetchNodes, spiffeId) + continue } selectors, err := a.ds.GetNodeSelectors(ctx, spiffeId, datastore.RequireCurrent) @@ -233,7 +234,7 @@ func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { agentExpiresAt := time.Unix(node.CertNotAfter, 0) a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) - delete(a.fetchNodes, node.SpiffeId) + delete(a.fetchNodes, spiffeId) } return nil diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 1003710297..638442b135 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -812,10 +812,8 @@ func TestSelectedPolledEvents(t *testing.T) { attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) - t.Logf("startup expected fetches %v\n", slices.Collect(maps.Keys(attestedNodes.fetchNodes))) // initialize the event tracker for _, event := range tt.polling { - t.Logf("polling %d\n", event) attestedNodes.eventTracker.StartTracking(event) } // poll the events @@ -1081,6 +1079,388 @@ func TestScanForNewEvents(t *testing.T) { } func TestUpdateAttestedNodesCache(t *testing.T) { + for _, tt := range []struct { + name string + setup *scenarioSetup + createAttestedNodes []*common.AttestedNode // Nodes created after setup + deleteAttestedNodes []string // Nodes delted after setup + fetchNodes []string + + expectedAuthorizedEntries []string + }{ + { + name: "empty cache, no fetch nodes", + fetchNodes: []string{}, + + expectedAuthorizedEntries: []string{}, + }, + { + name: "empty cache, fetch one node, as a new entry", + createAttestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_3", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_3", + }, + }, + { + name: "empty cache, fetch one node, as a delete", + fetchNodes: []string{ + "spiffe://example.org/test_node_3", + }, + }, + { + name: "empty cache, fetch five nodes, all new entries", + createAttestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + }, + { + name: "empty cache, fetch five nodes, three new and two deletes", + createAttestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + }, + }, + { + name: "empty cache, fetch five nodes, all deletes", + fetchNodes: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + + expectedAuthorizedEntries: []string{}, + }, + { + name: "one node in cache, no fetch nodes", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_3", + }, + }, + { + name: "one node in cache, fetch one node, as new entry", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + createAttestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_4", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + }, + }, + { + name: "one node in cache, fetch one node, as an update", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_3", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_3", + }, + }, + { + name: "one node in cache, fetch one node, as a delete", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + deleteAttestedNodes: []string{ + "spiffe://example.org/test_node_3", + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_3", + }, + + expectedAuthorizedEntries: []string{}, + }, + { + name: "one node in cache, fetch five nodes, all new entries", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + createAttestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_6", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + "spiffe://example.org/test_node_6", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + "spiffe://example.org/test_node_6", + }, + }, + { + name: "one node in cache, fetch five nodes, four new entries and one update", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + createAttestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_4", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_5", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + }, + { + name: "one node in cache, fetch five nodes, two new and three deletes", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + createAttestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_1", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_2", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + deleteAttestedNodes: []string{ + "spiffe://example.org/test_node_3", + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + + expectedAuthorizedEntries: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + }, + }, + { + name: "one node in cache, fetch five nodes, all deletes", + setup: &scenarioSetup{ + attestedNodes: []*common.AttestedNode{ + &common.AttestedNode{ + SpiffeId: "spiffe://example.org/test_node_3", + CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), + }, + }, + }, + deleteAttestedNodes: []string{ + "spiffe://example.org/test_node_3", + }, + fetchNodes: []string{ + "spiffe://example.org/test_node_1", + "spiffe://example.org/test_node_2", + "spiffe://example.org/test_node_3", + "spiffe://example.org/test_node_4", + "spiffe://example.org/test_node_5", + }, + + expectedAuthorizedEntries: []string{}, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + scenario := NewScenario(t, tt.setup) + attestedNodes, err := scenario.buildAttestedNodesCache() + require.NoError(t, err) + for _, attestedNode := range tt.createAttestedNodes { + scenario.ds.CreateAttestedNode(scenario.ctx, attestedNode) + } + for _, attestedNode := range tt.deleteAttestedNodes { + _, err = scenario.ds.DeleteAttestedNode(scenario.ctx, attestedNode) + require.NoError(t, err) + } + for _, fetchNode := range tt.fetchNodes { + attestedNodes.fetchNodes[fetchNode] = struct{}{} + } + // clear out the events, to prove updates are not event based + scenario.ds.PruneAttestedNodesEvents(scenario.ctx, time.Duration(-5)*time.Hour) + + err = attestedNodes.updateCachedNodes(scenario.ctx) + require.NoError(t, err) + + cacheStats := attestedNodes.cache.Stats() + require.Equal(t, len(tt.expectedAuthorizedEntries), cacheStats.AgentsByID, "wrong number of agents by ID") + + // for now, the only way to ensure the desired agent ids are prsent is + // to remove the desired ids and check the count it zero. + for _, expectedAuthorizedId := range tt.expectedAuthorizedEntries { + attestedNodes.cache.RemoveAgent(expectedAuthorizedId) + } + cacheStats = attestedNodes.cache.Stats() + require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear ccache") + }) + } } // utility functions diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index b18b17bd76..99b78bec4b 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -233,14 +233,16 @@ func (a *registrationEntries) updateCachedEntries(ctx context.Context) error { if commonEntry == nil { a.cache.RemoveEntry(entryId) - return nil + delete(a.fetchEntries, entryId) + continue } entry, err := api.RegistrationEntryToProto(commonEntry) if err != nil { a.cache.RemoveEntry(entryId) + delete(a.fetchEntries, entryId) a.log.WithField(telemetry.RegistrationID, entryId).Warn("Removed malformed registration entry from cache") - return nil + continue } a.cache.UpdateEntry(entry) From 8ab4d6818947cbdf20b3f7d3c45ef82b16a15af5 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 30 Sep 2024 18:07:19 -0700 Subject: [PATCH 10/32] Added loadCache testing for authorized entries registered entry. Signed-off-by: Edwin Buck --- .../telemetry/server/datastore/event.go | 8 +- .../telemetry/server/datastore/wrapper.go | 12 +- pkg/server/datastore/datastore.go | 8 +- pkg/server/datastore/sqlstore/sqlstore.go | 18 +- .../endpoints/authorized_entryfetcher.go | 4 +- ...orized_entryfetcher_attested_nodes_test.go | 120 ++--- ...rized_entryfetcher_registration_entries.go | 8 +- ..._entryfetcher_registration_entries_test.go | 489 +++++++++++++++--- pkg/server/endpoints/entryfetcher.go | 4 +- 9 files changed, 522 insertions(+), 149 deletions(-) diff --git a/pkg/common/telemetry/server/datastore/event.go b/pkg/common/telemetry/server/datastore/event.go index b1fdc59913..24ad4f6714 100644 --- a/pkg/common/telemetry/server/datastore/event.go +++ b/pkg/common/telemetry/server/datastore/event.go @@ -4,15 +4,15 @@ import ( "github.com/spiffe/spire/pkg/common/telemetry" ) -// StartListRegistrationEntriesEventsCall return metric +// StartListRegistrationEntryEventsCall return metric // for server's datastore, on listing registration entry events. -func StartListRegistrationEntriesEventsCall(m telemetry.Metrics) *telemetry.CallCounter { +func StartListRegistrationEntryEventsCall(m telemetry.Metrics) *telemetry.CallCounter { return telemetry.StartCall(m, telemetry.Datastore, telemetry.RegistrationEntryEvent, telemetry.List) } -// StartPruneRegistrationEntriesEventsCall return metric +// StartPruneRegistrationEntryEventsCall return metric // for server's datastore, on pruning registration entry events. -func StartPruneRegistrationEntriesEventsCall(m telemetry.Metrics) *telemetry.CallCounter { +func StartPruneRegistrationEntryEventsCall(m telemetry.Metrics) *telemetry.CallCounter { return telemetry.StartCall(m, telemetry.Datastore, telemetry.RegistrationEntryEvent, telemetry.Prune) } diff --git a/pkg/common/telemetry/server/datastore/wrapper.go b/pkg/common/telemetry/server/datastore/wrapper.go index 96f84bd0b0..0fb8edd305 100644 --- a/pkg/common/telemetry/server/datastore/wrapper.go +++ b/pkg/common/telemetry/server/datastore/wrapper.go @@ -203,10 +203,10 @@ func (w metricsWrapper) ListRegistrationEntries(ctx context.Context, req *datast return w.ds.ListRegistrationEntries(ctx, req) } -func (w metricsWrapper) ListRegistrationEntriesEvents(ctx context.Context, req *datastore.ListRegistrationEntriesEventsRequest) (_ *datastore.ListRegistrationEntriesEventsResponse, err error) { - callCounter := StartListRegistrationEntriesEventsCall(w.m) +func (w metricsWrapper) ListRegistrationEntryEvents(ctx context.Context, req *datastore.ListRegistrationEntryEventsRequest) (_ *datastore.ListRegistrationEntryEventsResponse, err error) { + callCounter := StartListRegistrationEntryEventsCall(w.m) defer callCounter.Done(&err) - return w.ds.ListRegistrationEntriesEvents(ctx, req) + return w.ds.ListRegistrationEntryEvents(ctx, req) } func (w metricsWrapper) CountAttestedNodes(ctx context.Context, req *datastore.CountAttestedNodesRequest) (_ int32, err error) { @@ -251,10 +251,10 @@ func (w metricsWrapper) PruneRegistrationEntries(ctx context.Context, expiresBef return w.ds.PruneRegistrationEntries(ctx, expiresBefore) } -func (w metricsWrapper) PruneRegistrationEntriesEvents(ctx context.Context, olderThan time.Duration) (err error) { - callCounter := StartPruneRegistrationEntriesEventsCall(w.m) +func (w metricsWrapper) PruneRegistrationEntryEvents(ctx context.Context, olderThan time.Duration) (err error) { + callCounter := StartPruneRegistrationEntryEventsCall(w.m) defer callCounter.Done(&err) - return w.ds.PruneRegistrationEntriesEvents(ctx, olderThan) + return w.ds.PruneRegistrationEntryEvents(ctx, olderThan) } func (w metricsWrapper) SetBundle(ctx context.Context, bundle *common.Bundle) (_ *common.Bundle, err error) { diff --git a/pkg/server/datastore/datastore.go b/pkg/server/datastore/datastore.go index 6cc3cfca5a..671b971aa2 100644 --- a/pkg/server/datastore/datastore.go +++ b/pkg/server/datastore/datastore.go @@ -40,8 +40,8 @@ type DataStore interface { UpdateRegistrationEntry(context.Context, *common.RegistrationEntry, *common.RegistrationEntryMask) (*common.RegistrationEntry, error) // Entries Events - ListRegistrationEntriesEvents(ctx context.Context, req *ListRegistrationEntriesEventsRequest) (*ListRegistrationEntriesEventsResponse, error) - PruneRegistrationEntriesEvents(ctx context.Context, olderThan time.Duration) error + ListRegistrationEntryEvents(ctx context.Context, req *ListRegistrationEntryEventsRequest) (*ListRegistrationEntryEventsResponse, error) + PruneRegistrationEntryEvents(ctx context.Context, olderThan time.Duration) error FetchRegistrationEntryEvent(ctx context.Context, eventID uint) (*RegistrationEntryEvent, error) CreateRegistrationEntryEventForTesting(ctx context.Context, event *RegistrationEntryEvent) error DeleteRegistrationEntryEventForTesting(ctx context.Context, eventID uint) error @@ -223,7 +223,7 @@ type ListRegistrationEntriesResponse struct { Pagination *Pagination } -type ListRegistrationEntriesEventsRequest struct { +type ListRegistrationEntryEventsRequest struct { GreaterThanEventID uint LessThanEventID uint } @@ -233,7 +233,7 @@ type RegistrationEntryEvent struct { EntryID string } -type ListRegistrationEntriesEventsResponse struct { +type ListRegistrationEntryEventsResponse struct { Events []RegistrationEntryEvent } diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index 8253d1a89c..54e8ae2126 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -580,10 +580,10 @@ func (ds *Plugin) PruneRegistrationEntries(ctx context.Context, expiresBefore ti }) } -// ListRegistrationEntriesEvents lists all registration entry events -func (ds *Plugin) ListRegistrationEntriesEvents(ctx context.Context, req *datastore.ListRegistrationEntriesEventsRequest) (resp *datastore.ListRegistrationEntriesEventsResponse, err error) { +// ListRegistrationEntryEvents lists all registration entry events +func (ds *Plugin) ListRegistrationEntryEvents(ctx context.Context, req *datastore.ListRegistrationEntryEventsRequest) (resp *datastore.ListRegistrationEntryEventsResponse, err error) { if err = ds.withReadTx(ctx, func(tx *gorm.DB) (err error) { - resp, err = listRegistrationEntriesEvents(tx, req) + resp, err = listRegistrationEntryEvents(tx, req) return err }); err != nil { return nil, err @@ -591,10 +591,10 @@ func (ds *Plugin) ListRegistrationEntriesEvents(ctx context.Context, req *datast return resp, nil } -// PruneRegistrationEntriesEvents deletes all registration entry events older than a specified duration (i.e. more than 24 hours old) -func (ds *Plugin) PruneRegistrationEntriesEvents(ctx context.Context, olderThan time.Duration) (err error) { +// PruneRegistrationEntryEvents deletes all registration entry events older than a specified duration (i.e. more than 24 hours old) +func (ds *Plugin) PruneRegistrationEntryEvents(ctx context.Context, olderThan time.Duration) (err error) { return ds.withWriteTx(ctx, func(tx *gorm.DB) (err error) { - err = pruneRegistrationEntriesEvents(tx, olderThan) + err = pruneRegistrationEntryEvents(tx, olderThan) return err }) } @@ -4092,7 +4092,7 @@ func deleteRegistrationEntryEvent(tx *gorm.DB, eventID uint) error { return nil } -func listRegistrationEntriesEvents(tx *gorm.DB, req *datastore.ListRegistrationEntriesEventsRequest) (*datastore.ListRegistrationEntriesEventsResponse, error) { +func listRegistrationEntryEvents(tx *gorm.DB, req *datastore.ListRegistrationEntryEventsRequest) (*datastore.ListRegistrationEntryEventsResponse, error) { var events []RegisteredEntryEvent if req.GreaterThanEventID != 0 || req.LessThanEventID != 0 { @@ -4110,7 +4110,7 @@ func listRegistrationEntriesEvents(tx *gorm.DB, req *datastore.ListRegistrationE } } - resp := &datastore.ListRegistrationEntriesEventsResponse{ + resp := &datastore.ListRegistrationEntryEventsResponse{ Events: make([]datastore.RegistrationEntryEvent, len(events)), } for i, event := range events { @@ -4121,7 +4121,7 @@ func listRegistrationEntriesEvents(tx *gorm.DB, req *datastore.ListRegistrationE return resp, nil } -func pruneRegistrationEntriesEvents(tx *gorm.DB, olderThan time.Duration) error { +func pruneRegistrationEntryEvents(tx *gorm.DB, olderThan time.Duration) error { if err := tx.Where("created_at < ?", time.Now().Add(-olderThan)).Delete(&RegisteredEntryEvent{}).Error; err != nil { return sqlError.Wrap(err) } diff --git a/pkg/server/endpoints/authorized_entryfetcher.go b/pkg/server/endpoints/authorized_entryfetcher.go index 50edc9426b..cf9116a2c7 100644 --- a/pkg/server/endpoints/authorized_entryfetcher.go +++ b/pkg/server/endpoints/authorized_entryfetcher.go @@ -95,10 +95,10 @@ func (a *AuthorizedEntryFetcherWithEventsBasedCache) PruneEventsTask(ctx context } func (a *AuthorizedEntryFetcherWithEventsBasedCache) pruneEvents(ctx context.Context, olderThan time.Duration) error { - pruneRegistrationEntriesEventsErr := a.ds.PruneRegistrationEntriesEvents(ctx, olderThan) + pruneRegistrationEntryEventsErr := a.ds.PruneRegistrationEntryEvents(ctx, olderThan) pruneAttestedNodesEventsErr := a.ds.PruneAttestedNodesEvents(ctx, olderThan) - return errors.Join(pruneRegistrationEntriesEventsErr, pruneAttestedNodesEventsErr) + return errors.Join(pruneRegistrationEntryEventsErr, pruneAttestedNodesEventsErr) } func (a *AuthorizedEntryFetcherWithEventsBasedCache) updateCache(ctx context.Context) error { diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 638442b135..23660db39b 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -60,28 +60,28 @@ type expectedGauge struct { Value int } -func TestLoadCache(t *testing.T) { +func TestLoadNodeCache(t *testing.T) { for _, tt := range []struct { name string - setup *scenarioSetup + setup *nodeScenarioSetup - expectedError error + expectedError string expectedAuthorizedEntries []string expectedGauges []expectedGauge }{ { name: "initial load returns an error", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ err: errors.New("any error, doesn't matter"), }, - expectedError: errors.New("any error, doesn't matter"), + expectedError: "any error, doesn't matter", }, { name: "initial load loads nothing", }, { name: "initial load loads one attested node", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_1", @@ -100,7 +100,7 @@ func TestLoadCache(t *testing.T) { }, { name: "initial load loads five attested nodes", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_1", @@ -134,7 +134,7 @@ func TestLoadCache(t *testing.T) { }, { name: "initial load loads five attested nodes, one expired", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_1", @@ -167,7 +167,7 @@ func TestLoadCache(t *testing.T) { }, { name: "initial load loads five attested nodes, all expired", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_1", @@ -196,11 +196,11 @@ func TestLoadCache(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { - scenario := NewScenario(t, tt.setup) + scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() - if tt.expectedError != nil { - require.Error(t, err, tt.expectedError) + if tt.expectedError != "" { + require.ErrorContains(t, err, tt.expectedError) return } require.NoError(t, err) @@ -214,7 +214,7 @@ func TestLoadCache(t *testing.T) { attestedNodes.cache.RemoveAgent(expectedAuthorizedId) } cacheStats = attestedNodes.cache.Stats() - require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear ccache") + require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear cache") var lastMetrics map[string]int = make(map[string]int) for _, metricItem := range scenario.metrics.AllMetrics() { @@ -239,7 +239,7 @@ func TestLoadCache(t *testing.T) { func TestSearchBeforeFirstEvent(t *testing.T) { for _, tt := range []struct { name string - setup *scenarioSetup + setup *nodeScenarioSetup waitToPoll time.Duration eventsBeforeFirst []uint @@ -258,7 +258,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "before first event arrived, after transaction timeout", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -278,7 +278,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "no before first events", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -290,7 +290,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "new before first event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -307,7 +307,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "new after last event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -324,7 +324,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "previously seen before first event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -342,7 +342,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "previously seen before first event and after last event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -364,7 +364,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "five new before first events", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -403,7 +403,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { { name: "five new before first events, one after last event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -440,7 +440,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "five before first events, two previously seen", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -478,7 +478,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "five before first events, two previously seen, one after last event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -514,7 +514,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "five before first events, five previously seen", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -548,7 +548,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, { name: "five before first events, five previously seen, with after last event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, attestedNodeEvents: defaultEventsStartingAt60, }, @@ -587,7 +587,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { - scenario := NewScenario(t, tt.setup) + scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) @@ -619,7 +619,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { func TestSelectedPolledEvents(t *testing.T) { for _, tt := range []struct { name string - setup *scenarioSetup + setup *nodeScenarioSetup polling []uint events []*datastore.AttestedNodeEvent @@ -632,7 +632,7 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "nothing to poll, no action take, one event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 100, @@ -643,7 +643,7 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "nothing to poll, no action taken, five events", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -670,7 +670,7 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "polling one item, not found", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -694,7 +694,7 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "polling five items, not found", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -710,7 +710,7 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "polling one item, found", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -734,7 +734,7 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "polling five items, two found", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -763,7 +763,7 @@ func TestSelectedPolledEvents(t *testing.T) { }, { name: "polling five items, five found", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -808,7 +808,7 @@ func TestSelectedPolledEvents(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { - scenario := NewScenario(t, tt.setup) + scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) @@ -828,7 +828,7 @@ func TestSelectedPolledEvents(t *testing.T) { func TestScanForNewEvents(t *testing.T) { for _, tt := range []struct { name string - setup *scenarioSetup + setup *nodeScenarioSetup newEvents []*datastore.AttestedNodeEvent @@ -843,7 +843,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "no new event, with first event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -857,7 +857,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "one new event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -879,7 +879,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "one new event, skipping an event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -901,7 +901,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "two new events, same attested node", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -927,7 +927,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "two new events, different attested nodes", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -954,7 +954,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "two new events, with a skipped event", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -981,7 +981,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "two new events, with three skipped events", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -1008,7 +1008,7 @@ func TestScanForNewEvents(t *testing.T) { }, { name: "five events, four new events, two skip regions", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodeEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 101, @@ -1061,7 +1061,7 @@ func TestScanForNewEvents(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { - scenario := NewScenario(t, tt.setup) + scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) @@ -1081,7 +1081,7 @@ func TestScanForNewEvents(t *testing.T) { func TestUpdateAttestedNodesCache(t *testing.T) { for _, tt := range []struct { name string - setup *scenarioSetup + setup *nodeScenarioSetup createAttestedNodes []*common.AttestedNode // Nodes created after setup deleteAttestedNodes []string // Nodes delted after setup fetchNodes []string @@ -1200,7 +1200,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, no fetch nodes", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1215,7 +1215,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, fetch one node, as new entry", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1240,7 +1240,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, fetch one node, as an update", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1258,7 +1258,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, fetch one node, as a delete", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1277,7 +1277,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, fetch five nodes, all new entries", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1326,7 +1326,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, fetch five nodes, four new entries and one update", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1370,7 +1370,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, fetch five nodes, two new and three deletes", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1406,7 +1406,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { }, { name: "one node in cache, fetch five nodes, all deletes", - setup: &scenarioSetup{ + setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ &common.AttestedNode{ SpiffeId: "spiffe://example.org/test_node_3", @@ -1430,7 +1430,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { } { tt := tt t.Run(tt.name, func(t *testing.T) { - scenario := NewScenario(t, tt.setup) + scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) for _, attestedNode := range tt.createAttestedNodes { @@ -1458,7 +1458,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { attestedNodes.cache.RemoveAgent(expectedAuthorizedId) } cacheStats = attestedNodes.cache.Stats() - require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear ccache") + require.Equal(t, 0, cacheStats.AgentsByID, "clearing all expected agent ids didn't clear cache") }) } } @@ -1474,13 +1474,13 @@ type scenario struct { ds *fakedatastore.DataStore } -type scenarioSetup struct { +type nodeScenarioSetup struct { attestedNodes []*common.AttestedNode attestedNodeEvents []*datastore.AttestedNodeEvent err error } -func NewScenario(t *testing.T, setup *scenarioSetup) *scenario { +func NewNodeScenario(t *testing.T, setup *nodeScenarioSetup) *scenario { t.Helper() ctx := context.Background() log, hook := test.NewNullLogger() @@ -1491,7 +1491,7 @@ func NewScenario(t *testing.T, setup *scenarioSetup) *scenario { ds := fakedatastore.New(t) if setup == nil { - setup = &scenarioSetup{} + setup = &nodeScenarioSetup{} } // initialize the database diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index 99b78bec4b..790d17adf7 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -59,7 +59,7 @@ func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { func (a *registrationEntries) searchBeforeFirstEvent(ctx context.Context) error { // First event detected, and startup was less than a transaction timout away. if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) <= a.sqlTransactionTimeout { - resp, err := a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ + resp, err := a.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{ LessThanEventID: a.firstEvent, }) if err != nil { @@ -105,12 +105,12 @@ func (a *registrationEntries) selectPolledEvents(ctx context.Context) { func (a *registrationEntries) scanNewEvents(ctx context.Context) error { // If we haven't seen an event, scan for all events; otherwise, scan from the last event. - var resp *datastore.ListRegistrationEntriesEventsResponse + var resp *datastore.ListRegistrationEntryEventsResponse var err error if a.firstEventTime.IsZero() { - resp, err = a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err = a.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) } else { - resp, err = a.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ + resp, err = a.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{ GreaterThanEventID: a.lastEvent, }) } diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 6431299e26..807bdad216 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -1,13 +1,15 @@ package endpoints -/* import ( + "context" + "errors" + "strings" "testing" + "time" "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" - "github.com/spiffe/go-spiffe/v2/spiffeid" - "github.com/spiffe/spire/pkg/common/idutil" + "github.com/spiffe/spire/pkg/common/telemetry" "github.com/spiffe/spire/pkg/server/authorizedentries" "github.com/spiffe/spire/pkg/server/datastore" "github.com/spiffe/spire/proto/spire/common" @@ -15,90 +17,375 @@ import ( "github.com/spiffe/spire/test/fakes/fakedatastore" "github.com/spiffe/spire/test/fakes/fakemetrics" "github.com/stretchr/testify/require" -) + /* + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/spire/pkg/common/idutil" + */) -*/ -/* -func TestBuildRegistrationEntriesCache(t *testing.T) { - ctx := context.Background() - log, _ := test.NewNullLogger() - clk := clock.NewMock(t) - ds := fakedatastore.New(t) +var ( + NodeAliasesByEntryID = []string{telemetry.Entry, telemetry.NodeAliasesByEntryIDCache, telemetry.Count} + NodeAliasesBySelector = []string{telemetry.Entry, telemetry.NodeAliasesBySelectorCache, telemetry.Count} + EntriesByEntryID = []string{telemetry.Entry, telemetry.EntriesByEntryIDCache, telemetry.Count} + EntriesByParentID = []string{telemetry.Entry, telemetry.EntriesByParentIDCache, telemetry.Count} + SkippedEntryEventID = []string{telemetry.Entry, telemetry.SkippedEntryEventIDs, telemetry.Count} +) - agentID, err := spiffeid.FromString("spiffe://example.org/myagent") - require.NoError(t, err) +func TestLoadEntryCache(t *testing.T) { + for _, tt := range []struct { + name string + setup *entryScenarioSetup - // Create registration entries - numEntries := 10 - for i := 0; i < numEntries; i++ { - _, err := ds.CreateRegistrationEntry(ctx, &common.RegistrationEntry{ - SpiffeId: "spiffe://example.org/workload" + strconv.Itoa(i), - ParentId: agentID.String(), - Selectors: []*common.Selector{ - { - Type: "workload", - Value: "one", + expectedError string + expectedRegistrationEntries []string + expectedGauges []expectedGauge + }{ + { + name: "initial load returns an error", + setup: &entryScenarioSetup{ + err: errors.New("any error, doesn't matter"), + }, + expectedError: "any error, doesn't matter", + }, + { + name: "loading nothing with a page size of zero raises an error", + setup: &entryScenarioSetup{ + pageSize: 0, + }, + expectedError: "cannot paginate with pagesize = 0", + }, + { + name: "initial load loads nothing", + setup: &entryScenarioSetup{ + pageSize: 1000, + }, + }, + { + name: "one registration entry with a page size of zero raises an error", + setup: &entryScenarioSetup{ + pageSize: 0, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, }, }, - }) - require.NoError(t, err) - } - - for _, tt := range []struct { - name string - pageSize int32 - err string - }{ + expectedError: "cannot paginate with pagesize = 0", + }, { - name: "Page size of 0", - pageSize: 0, - err: "cannot paginate with pagesize = 0", + name: "initial load loads one registration entry", + setup: &entryScenarioSetup{ + pageSize: 1000, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + }, + }, + expectedRegistrationEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + expectedGauges: []expectedGauge{ + expectedGauge{Key: SkippedEntryEventID, Value: 0}, + expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: NodeAliasesBySelector, Value: 0}, + expectedGauge{Key: EntriesByEntryID, Value: 1}, + expectedGauge{Key: EntriesByParentID, Value: 1}, + }, + }, + { + name: "five registration entries with a page size of zero raises an error", + setup: &entryScenarioSetup{ + pageSize: 0, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "354c16f4-4e61-4c17-8596-7baa7744d504", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_5", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "5"}, + }, + }, + }, + }, + expectedError: "cannot paginate with pagesize = 0", }, { - name: "Page size of half the entries", - pageSize: int32(numEntries / 2), + name: "initial load loads five registration entries", + setup: &entryScenarioSetup{ + pageSize: 1000, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "354c16f4-4e61-4c17-8596-7baa7744d504", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_5", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "5"}, + }, + }, + }, + }, + expectedRegistrationEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + expectedGauges: []expectedGauge{ + expectedGauge{Key: SkippedEntryEventID, Value: 0}, + expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: NodeAliasesBySelector, Value: 0}, + expectedGauge{Key: EntriesByEntryID, Value: 5}, + expectedGauge{Key: EntriesByParentID, Value: 5}, + }, }, { - name: "Page size of all the entries", - pageSize: int32(numEntries), + name: "initial load loads five registration entries, in one page exact", + setup: &entryScenarioSetup{ + pageSize: 5, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "354c16f4-4e61-4c17-8596-7baa7744d504", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_5", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "5"}, + }, + }, + }, + }, + expectedRegistrationEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + expectedGauges: []expectedGauge{ + expectedGauge{Key: SkippedEntryEventID, Value: 0}, + expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: NodeAliasesBySelector, Value: 0}, + expectedGauge{Key: EntriesByEntryID, Value: 5}, + expectedGauge{Key: EntriesByParentID, Value: 5}, + }, }, { - name: "Page size of all the entries + 1", - pageSize: int32(numEntries + 1), + name: "initial load loads five registration entries, in 2 pages", + setup: &entryScenarioSetup{ + pageSize: 3, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "354c16f4-4e61-4c17-8596-7baa7744d504", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_5", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "5"}, + }, + }, + }, + }, + expectedRegistrationEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + expectedGauges: []expectedGauge{ + expectedGauge{Key: SkippedEntryEventID, Value: 0}, + expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: NodeAliasesBySelector, Value: 0}, + expectedGauge{Key: EntriesByEntryID, Value: 5}, + expectedGauge{Key: EntriesByParentID, Value: 5}, + }, }, } { tt := tt t.Run(tt.name, func(t *testing.T) { - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() + scenario := NewEntryScenario(t, tt.setup) + attestedNodes, err := scenario.buildRegistrationEntriesCache() - registrationEntries, err := buildRegistrationEntriesCache(ctx, log, metrics, ds, clk, cache, tt.pageSize, defaultSQLTransactionTimeout) - if tt.err != "" { - require.ErrorContains(t, err, tt.err) + if tt.expectedError != "" { + t.Logf("expecting error: %s\n", tt.expectedError) + require.ErrorContains(t, err, tt.expectedError) return } - require.NoError(t, err) - require.False(t, registrationEntries.firstEventTime.IsZero()) - entries := cache.GetAuthorizedEntries(agentID) - require.Equal(t, numEntries, len(entries)) + cacheStats := attestedNodes.cache.Stats() + t.Logf("%s: cache stats %+v\n", tt.name, cacheStats) + require.Equal(t, len(tt.expectedRegistrationEntries), cacheStats.EntriesByEntryID, + "wrong number of entries by ID") + + // for now, the only way to ensure the desired agent ids are prsent is + // to remove the desired ids and check the count it zero. + for _, expectedRegistrationEntry := range tt.expectedRegistrationEntries { + attestedNodes.cache.RemoveEntry(expectedRegistrationEntry) + } + cacheStats = attestedNodes.cache.Stats() + require.Equal(t, 0, cacheStats.EntriesByEntryID, + "clearing all expected entry ids didn't clear cache") - spiffeIDs := make([]string, 0, numEntries) - for _, entry := range entries { - spiffeID, err := idutil.IDFromProto(entry.SpiffeId) - require.NoError(t, err) - spiffeIDs = append(spiffeIDs, spiffeID.String()) + var lastMetrics map[string]int = make(map[string]int) + for _, metricItem := range scenario.metrics.AllMetrics() { + if metricItem.Type == fakemetrics.SetGaugeType { + key := strings.Join(metricItem.Key, " ") + lastMetrics[key] = int(metricItem.Val) + t.Logf("metricItem: %+v\n", metricItem) + } } - sort.Strings(spiffeIDs) - for i, spiffeID := range spiffeIDs { - require.Equal(t, "spiffe://example.org/workload"+strconv.Itoa(i), spiffeID) + for _, expectedGauge := range tt.expectedGauges { + key := strings.Join(expectedGauge.Key, " ") + value, exists := lastMetrics[key] + require.True(t, exists, "No metric value for %q", key) + require.Equal(t, expectedGauge.Value, value, "unexpected final metric value for %q", key) } + + require.Zero(t, scenario.hook.Entries) }) } } +/* func TestRegistrationEntriesCacheMissedEventNotFound(t *testing.T) { ctx := context.Background() log, hook := test.NewNullLogger() @@ -154,3 +441,89 @@ func TestRegistrationEntriesSavesMissedStartupEvents(t *testing.T) { require.Equal(t, 0, len(hook.AllEntries())) } */ + +type entryScenario struct { + ctx context.Context + log *logrus.Logger + hook *test.Hook + clk *clock.Mock + cache *authorizedentries.Cache + metrics *fakemetrics.FakeMetrics + ds *fakedatastore.DataStore + pageSize int32 +} + +type entryScenarioSetup struct { + attestedNodes []*common.AttestedNode + attestedNodeEvents []*datastore.AttestedNodeEvent + registrationEntries []*common.RegistrationEntry + registrationEntryEvents []*datastore.RegistrationEntryEvent + err error + pageSize int32 +} + +func NewEntryScenario(t *testing.T, setup *entryScenarioSetup) *entryScenario { + t.Helper() + ctx := context.Background() + log, hook := test.NewNullLogger() + log.SetLevel(logrus.DebugLevel) + clk := clock.NewMock(t) + cache := authorizedentries.NewCache(clk) + metrics := fakemetrics.New() + ds := fakedatastore.New(t) + + t.Log("NEW SCENARIO\n") + if setup == nil { + setup = &entryScenarioSetup{} + } + + for _, attestedNode := range setup.attestedNodes { + t.Logf("creating attested node: %+v\n", attestedNode) + ds.CreateAttestedNode(ctx, attestedNode) + } + // prune autocreated node events, to test the event logic in more scenarios + // than possible with autocreated node events. + ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + // and then add back the specified node events + for _, event := range setup.attestedNodeEvents { + ds.CreateAttestedNodeEventForTesting(ctx, event) + } + // initialize the database + for _, registrationEntry := range setup.registrationEntries { + t.Logf("creating entry: %+v\n", registrationEntry) + ds.CreateRegistrationEntry(ctx, registrationEntry) + } + // prune autocreated entry events, to test the event logic in more + // scenarios than possible with autocreated entry events. + ds.PruneRegistrationEntryEvents(ctx, time.Duration(-5)*time.Hour) + // and then add back the specified node events + for _, event := range setup.registrationEntryEvents { + ds.CreateRegistrationEntryEventForTesting(ctx, event) + } + // inject db error for buildRegistrationEntriesCache call + if setup.err != nil { + ds.AppendNextError(setup.err) + } + + return &entryScenario{ + ctx: ctx, + log: log, + hook: hook, + clk: clk, + cache: cache, + metrics: metrics, + ds: ds, + pageSize: setup.pageSize, + } +} + +func (s *entryScenario) buildRegistrationEntriesCache() (*registrationEntries, error) { + registrationEntries, err := buildRegistrationEntriesCache(s.ctx, s.log, s.metrics, s.ds, s.clk, s.cache, s.pageSize, defaultCacheReloadInterval, defaultSQLTransactionTimeout) + if registrationEntries != nil { + // clear out the fetches + for node, _ := range registrationEntries.fetchEntries { + delete(registrationEntries.fetchEntries, node) + } + } + return registrationEntries, err +} diff --git a/pkg/server/endpoints/entryfetcher.go b/pkg/server/endpoints/entryfetcher.go index cfa9c471d5..4f249818e9 100644 --- a/pkg/server/endpoints/entryfetcher.go +++ b/pkg/server/endpoints/entryfetcher.go @@ -96,8 +96,8 @@ func (a *AuthorizedEntryFetcherWithFullCache) PruneEventsTask(ctx context.Contex } func (a *AuthorizedEntryFetcherWithFullCache) pruneEvents(ctx context.Context, olderThan time.Duration) error { - pruneRegistrationEntriesEventsErr := a.ds.PruneRegistrationEntriesEvents(ctx, olderThan) + pruneRegistrationEntryEventsErr := a.ds.PruneRegistrationEntryEvents(ctx, olderThan) pruneAttestedNodesEventsErr := a.ds.PruneAttestedNodesEvents(ctx, olderThan) - return errors.Join(pruneRegistrationEntriesEventsErr, pruneAttestedNodesEventsErr) + return errors.Join(pruneRegistrationEntryEventsErr, pruneAttestedNodesEventsErr) } From b5a068a71c9a44a8baad94c14194987df9d320a8 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 30 Sep 2024 18:09:15 -0700 Subject: [PATCH 11/32] Add in updates to fakedatastore. Signed-off-by: Edwin Buck --- test/fakes/fakedatastore/fakedatastore.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/fakes/fakedatastore/fakedatastore.go b/test/fakes/fakedatastore/fakedatastore.go index 283a2b61ee..6332a4f8d0 100644 --- a/test/fakes/fakedatastore/fakedatastore.go +++ b/test/fakes/fakedatastore/fakedatastore.go @@ -312,18 +312,18 @@ func (s *DataStore) PruneRegistrationEntries(ctx context.Context, expiresBefore return s.ds.PruneRegistrationEntries(ctx, expiresBefore) } -func (s *DataStore) ListRegistrationEntriesEvents(ctx context.Context, req *datastore.ListRegistrationEntriesEventsRequest) (*datastore.ListRegistrationEntriesEventsResponse, error) { +func (s *DataStore) ListRegistrationEntryEvents(ctx context.Context, req *datastore.ListRegistrationEntryEventsRequest) (*datastore.ListRegistrationEntryEventsResponse, error) { if err := s.getNextError(); err != nil { return nil, err } - return s.ds.ListRegistrationEntriesEvents(ctx, req) + return s.ds.ListRegistrationEntryEvents(ctx, req) } -func (s *DataStore) PruneRegistrationEntriesEvents(ctx context.Context, olderThan time.Duration) error { +func (s *DataStore) PruneRegistrationEntryEvents(ctx context.Context, olderThan time.Duration) error { if err := s.getNextError(); err != nil { return err } - return s.ds.PruneRegistrationEntriesEvents(ctx, olderThan) + return s.ds.PruneRegistrationEntryEvents(ctx, olderThan) } func (s *DataStore) CreateRegistrationEntryEventForTesting(ctx context.Context, event *datastore.RegistrationEntryEvent) error { From a43e9ef1dd7a99e2d375fc6e09c143eb3c77e295 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 30 Sep 2024 20:32:15 -0700 Subject: [PATCH 12/32] Add unit test for authorized entryfetcher registration entries. Renamed items in authorized entryfetcher attested nodes to not conflict. Signed-off-by: Edwin Buck --- ...orized_entryfetcher_attested_nodes_test.go | 48 +- ..._entryfetcher_registration_entries_test.go | 443 +++++++++++++++++- 2 files changed, 460 insertions(+), 31 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 23660db39b..e290b259aa 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -39,7 +39,7 @@ var ( CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, } - defaultEventsStartingAt60 = []*datastore.AttestedNodeEvent{ + defaultNodeEventsStartingAt60 = []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ EventID: 60, SpiffeID: "spiffe://example.org/test_node_2", @@ -49,10 +49,10 @@ var ( SpiffeID: "spiffe://example.org/test_node_3", }, } - defaultFirstEvent = uint(60) - defaultLastEvent = uint(61) + defaultFirstNodeEvent = uint(60) + defaultLastNodeEvent = uint(61) - NoFetches = []string{} + NoNodeFetches = []string{} ) type expectedGauge struct { @@ -236,7 +236,7 @@ func TestLoadNodeCache(t *testing.T) { } } -func TestSearchBeforeFirstEvent(t *testing.T) { +func TestSearchBeforeFirstNodeEvent(t *testing.T) { for _, tt := range []struct { name string setup *nodeScenarioSetup @@ -260,7 +260,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { name: "before first event arrived, after transaction timeout", setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, waitToPoll: time.Duration(2) * defaultSQLTransactionTimeout, @@ -273,14 +273,14 @@ func TestSearchBeforeFirstEvent(t *testing.T) { }, expectedEventsBeforeFirst: []uint{}, - expectedFetches: NoFetches, + expectedFetches: NoNodeFetches, }, { name: "no before first events", setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, polledEvents: []*datastore.AttestedNodeEvent{}, @@ -292,7 +292,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ @@ -309,7 +309,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ @@ -326,7 +326,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, eventsBeforeFirst: []uint{58}, polledEvents: []*datastore.AttestedNodeEvent{ @@ -344,21 +344,21 @@ func TestSearchBeforeFirstEvent(t *testing.T) { setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, eventsBeforeFirst: []uint{58}, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ - EventID: defaultFirstEvent - 2, + EventID: defaultFirstNodeEvent - 2, SpiffeID: "spiffe://example.org/test_node_1", }, &datastore.AttestedNodeEvent{ - EventID: defaultLastEvent + 2, + EventID: defaultLastNodeEvent + 2, SpiffeID: "spiffe://example.org/test_node_4", }, }, - expectedEventsBeforeFirst: []uint{defaultFirstEvent - 2}, + expectedEventsBeforeFirst: []uint{defaultFirstNodeEvent - 2}, expectedFetches: []string{}, }, { @@ -366,7 +366,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ @@ -405,7 +405,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, polledEvents: []*datastore.AttestedNodeEvent{ &datastore.AttestedNodeEvent{ @@ -425,7 +425,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { SpiffeID: "spiffe://example.org/test_node_13", }, &datastore.AttestedNodeEvent{ - EventID: defaultLastEvent + 1, + EventID: defaultLastNodeEvent + 1, SpiffeID: "spiffe://example.org/test_node_14", }, }, @@ -442,7 +442,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { name: "five before first events, two previously seen", setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, eventsBeforeFirst: []uint{48, 49}, @@ -480,7 +480,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { name: "five before first events, two previously seen, one after last event", setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, eventsBeforeFirst: []uint{48, 49}, polledEvents: []*datastore.AttestedNodeEvent{ @@ -501,7 +501,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { SpiffeID: "spiffe://example.org/test_node_13", }, &datastore.AttestedNodeEvent{ - EventID: defaultLastEvent + 1, + EventID: defaultLastNodeEvent + 1, SpiffeID: "spiffe://example.org/test_node_14", }, }, @@ -516,7 +516,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { name: "five before first events, five previously seen", setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, @@ -550,7 +550,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { name: "five before first events, five previously seen, with after last event", setup: &nodeScenarioSetup{ attestedNodes: defaultAttestedNodes, - attestedNodeEvents: defaultEventsStartingAt60, + attestedNodeEvents: defaultNodeEventsStartingAt60, }, eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, @@ -576,7 +576,7 @@ func TestSearchBeforeFirstEvent(t *testing.T) { SpiffeID: "spiffe://example.org/test_node_14", }, &datastore.AttestedNodeEvent{ - EventID: defaultLastEvent + 1, + EventID: defaultLastNodeEvent + 1, SpiffeID: "spiffe://example.org/test_node_28", }, }, diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 807bdad216..13764a261a 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -3,6 +3,8 @@ package endpoints import ( "context" "errors" + "maps" + "slices" "strings" "testing" "time" @@ -28,6 +30,39 @@ var ( EntriesByEntryID = []string{telemetry.Entry, telemetry.EntriesByEntryIDCache, telemetry.Count} EntriesByParentID = []string{telemetry.Entry, telemetry.EntriesByParentIDCache, telemetry.Count} SkippedEntryEventID = []string{telemetry.Entry, telemetry.SkippedEntryEventIDs, telemetry.Count} + + defaultRegistrationEntries = []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + } + defaultRegistrationEntryEventsStartingAt60 = []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 60, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 61, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + } + defaultFirstEntryEvent = uint(60) + defaultLastEntryEvent = uint(61) + + NoEntryFetches = []string{} ) func TestLoadEntryCache(t *testing.T) { @@ -341,7 +376,7 @@ func TestLoadEntryCache(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { scenario := NewEntryScenario(t, tt.setup) - attestedNodes, err := scenario.buildRegistrationEntriesCache() + registrationEntries, err := scenario.buildRegistrationEntriesCache() if tt.expectedError != "" { t.Logf("expecting error: %s\n", tt.expectedError) @@ -350,7 +385,7 @@ func TestLoadEntryCache(t *testing.T) { } require.NoError(t, err) - cacheStats := attestedNodes.cache.Stats() + cacheStats := registrationEntries.cache.Stats() t.Logf("%s: cache stats %+v\n", tt.name, cacheStats) require.Equal(t, len(tt.expectedRegistrationEntries), cacheStats.EntriesByEntryID, "wrong number of entries by ID") @@ -358,9 +393,9 @@ func TestLoadEntryCache(t *testing.T) { // for now, the only way to ensure the desired agent ids are prsent is // to remove the desired ids and check the count it zero. for _, expectedRegistrationEntry := range tt.expectedRegistrationEntries { - attestedNodes.cache.RemoveEntry(expectedRegistrationEntry) + registrationEntries.cache.RemoveEntry(expectedRegistrationEntry) } - cacheStats = attestedNodes.cache.Stats() + cacheStats = registrationEntries.cache.Stats() require.Equal(t, 0, cacheStats.EntriesByEntryID, "clearing all expected entry ids didn't clear cache") @@ -385,6 +420,403 @@ func TestLoadEntryCache(t *testing.T) { } } +func TestSearchBeforeFirstEntryEvent(t *testing.T) { + for _, tt := range []struct { + name string + setup *entryScenarioSetup + + waitToPoll time.Duration + eventsBeforeFirst []uint + polledEvents []*datastore.RegistrationEntryEvent + errors []error + + expectedError error + expectedEventsBeforeFirst []uint + expectedFetches []string + }{ + { + name: "first event not loaded", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: []string{}, + }, + { + name: "before first event arrived, after transaction timeout", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + + waitToPoll: time.Duration(2) * defaultSQLTransactionTimeout, + // even with new before first events, they shouldn't load + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 58, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: NoEntryFetches, + }, + { + name: "no before first events", + + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + polledEvents: []*datastore.RegistrationEntryEvent{}, + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: []string{}, + }, + { + name: "new before first event", + + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 58, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + + expectedEventsBeforeFirst: []uint{58}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + { + name: "new after last event", + + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 64, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + + expectedEventsBeforeFirst: []uint{}, + expectedFetches: []string{}, + }, + { + name: "previously seen before first event", + + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + eventsBeforeFirst: []uint{58}, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 58, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + + expectedEventsBeforeFirst: []uint{58}, + expectedFetches: []string{}, + }, + { + name: "previously seen before first event and after last event", + + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + eventsBeforeFirst: []uint{58}, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: defaultFirstEntryEvent - 2, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: defaultLastEntryEvent + 2, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + + expectedEventsBeforeFirst: []uint{defaultFirstEntryEvent - 2}, + expectedFetches: []string{}, + }, + { + name: "five new before first events", + + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 48, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 49, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 53, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 56, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 57, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + { + name: "five new before first events, one after last event", + + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 48, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 49, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 53, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 56, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: defaultLastEntryEvent + 1, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + }, + { + name: "five before first events, two previously seen", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + + eventsBeforeFirst: []uint{48, 49}, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 48, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 49, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 53, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 56, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 57, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + { + name: "five before first events, two previously seen, one after last event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + eventsBeforeFirst: []uint{48, 49}, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 48, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 49, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 53, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 56, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: defaultLastEntryEvent + 1, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56}, + expectedFetches: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + }, + { + name: "five before first events, five previously seen", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + + eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 48, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 49, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 53, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 56, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 57, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{}, + }, + { + name: "five before first events, five previously seen, with after last event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: defaultRegistrationEntries, + registrationEntryEvents: defaultRegistrationEntryEventsStartingAt60, + }, + + eventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + polledEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 48, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 49, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 53, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 56, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 57, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + &datastore.RegistrationEntryEvent{ + EventID: defaultLastEntryEvent + 1, + EntryID: "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + }, + }, + + expectedEventsBeforeFirst: []uint{48, 49, 53, 56, 57}, + expectedFetches: []string{}, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + scenario := NewEntryScenario(t, tt.setup) + registrationEntries, err := scenario.buildRegistrationEntriesCache() + + require.NoError(t, err) + + if tt.waitToPoll == 0 { + scenario.clk.Add(time.Duration(1) * defaultCacheReloadInterval) + } else { + scenario.clk.Add(tt.waitToPoll) + } + + for _, event := range tt.eventsBeforeFirst { + registrationEntries.eventsBeforeFirst[event] = struct{}{} + } + + for _, event := range tt.polledEvents { + scenario.ds.CreateRegistrationEntryEventForTesting(scenario.ctx, event) + } + + registrationEntries.searchBeforeFirstEvent(scenario.ctx) + + require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(registrationEntries.eventsBeforeFirst)), "expected events before tracking mismatch") + require.ElementsMatch(t, tt.expectedFetches, slices.Collect[string](maps.Keys(registrationEntries.fetchEntries)), "expected fetches mismatch") + + require.Zero(t, scenario.hook.Entries) + }) + } +} + /* func TestRegistrationEntriesCacheMissedEventNotFound(t *testing.T) { ctx := context.Background() @@ -472,13 +904,11 @@ func NewEntryScenario(t *testing.T, setup *entryScenarioSetup) *entryScenario { metrics := fakemetrics.New() ds := fakedatastore.New(t) - t.Log("NEW SCENARIO\n") if setup == nil { setup = &entryScenarioSetup{} } for _, attestedNode := range setup.attestedNodes { - t.Logf("creating attested node: %+v\n", attestedNode) ds.CreateAttestedNode(ctx, attestedNode) } // prune autocreated node events, to test the event logic in more scenarios @@ -490,7 +920,6 @@ func NewEntryScenario(t *testing.T, setup *entryScenarioSetup) *entryScenario { } // initialize the database for _, registrationEntry := range setup.registrationEntries { - t.Logf("creating entry: %+v\n", registrationEntry) ds.CreateRegistrationEntry(ctx, registrationEntry) } // prune autocreated entry events, to test the event logic in more From 2eb66d1088bc9ab9babfb3d362e28631749996a9 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 30 Sep 2024 21:24:28 -0700 Subject: [PATCH 13/32] Add poll entries testing to authorized entryfetcher registration entries. Renamed similar test in attested nodes unit test to not conflict. Signed-off-by: Edwin Buck --- ...orized_entryfetcher_attested_nodes_test.go | 2 +- ..._entryfetcher_registration_entries_test.go | 264 ++++++++++++++---- 2 files changed, 214 insertions(+), 52 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index e290b259aa..2f8803a58c 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -616,7 +616,7 @@ func TestSearchBeforeFirstNodeEvent(t *testing.T) { } } -func TestSelectedPolledEvents(t *testing.T) { +func TestSelectedPolledNodeEvents(t *testing.T) { for _, tt := range []struct { name string setup *nodeScenarioSetup diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 13764a261a..13c549559c 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -817,62 +817,224 @@ func TestSearchBeforeFirstEntryEvent(t *testing.T) { } } -/* -func TestRegistrationEntriesCacheMissedEventNotFound(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - ds := fakedatastore.New(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() +func TestSelectedPolledEntryEvents(t *testing.T) { + for _, tt := range []struct { + name string + setup *entryScenarioSetup + + polling []uint + events []*datastore.RegistrationEntryEvent + expectedFetches []string + }{ + // polling is based on the eventTracker, not on events in the database + { + name: "nothing after to poll, no action taken, no events", + events: []*datastore.RegistrationEntryEvent{}, + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + }, + { + name: "nothing to poll, no action take, one event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 100, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + }, + { + name: "nothing to poll, no action taken, five events", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 104, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 105, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + }, + }, + { + name: "polling one item, not found", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 104, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 105, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + }, + polling: []uint{103}, + }, + { + name: "polling five items, not found", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 107, + EntryID: "c3f4ada0-3f8d-421e-b5d1-83aaee203d94", + }, + }, + }, + polling: []uint{102, 103, 104, 105, 106}, + }, + { + name: "polling one item, found", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + }, + }, + polling: []uint{102}, - registrationEntries, err := buildRegistrationEntriesCache(ctx, log, metrics, ds, clk, cache, buildCachePageSize, defaultSQLTransactionTimeout) - require.NoError(t, err) - require.NotNil(t, registrationEntries) + expectedFetches: []string{ + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + { + name: "polling five items, two found", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 106, + EntryID: "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + }, + &datastore.RegistrationEntryEvent{ + EventID: 107, + EntryID: "c3f4ada0-3f8d-421e-b5d1-83aaee203d94", + }, + }, + }, + polling: []uint{102, 103, 104, 105, 106}, - registrationEntries.missedEvents[1] = clk.Now() - registrationEntries.replayMissedEvents(ctx) - require.Zero(t, len(hook.Entries)) -} + expectedFetches: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + }, + }, + { + name: "polling five items, five found", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 104, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 105, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + &datastore.RegistrationEntryEvent{ + EventID: 106, + EntryID: "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + }, + &datastore.RegistrationEntryEvent{ + EventID: 107, + EntryID: "c3f4ada0-3f8d-421e-b5d1-83aaee203d94", + }, + }, + }, + polling: []uint{102, 103, 104, 105, 106}, -func TestRegistrationEntriesSavesMissedStartupEvents(t *testing.T) { - ctx := context.Background() - log, hook := test.NewNullLogger() - log.SetLevel(logrus.DebugLevel) - clk := clock.NewMock(t) - ds := fakedatastore.New(t) - cache := authorizedentries.NewCache(clk) - metrics := fakemetrics.New() + expectedFetches: []string{ + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + scenario := NewEntryScenario(t, tt.setup) + registrationEntries, err := scenario.buildRegistrationEntriesCache() + require.NoError(t, err) + + // initialize the event tracker + for _, event := range tt.polling { + registrationEntries.eventTracker.StartTracking(event) + } + // poll the events + registrationEntries.selectPolledEvents(scenario.ctx) - err := ds.CreateRegistrationEntryEventForTesting(ctx, &datastore.RegistrationEntryEvent{ - EventID: 3, - EntryID: "test", - }) - require.NoError(t, err) - - registrationEntries, err := buildRegistrationEntriesCache(ctx, log, metrics, ds, clk, cache, buildCachePageSize, defaultSQLTransactionTimeout) - require.NoError(t, err) - require.NotNil(t, registrationEntries) - require.Equal(t, uint(3), registrationEntries.firstEventID) - - err = ds.CreateRegistrationEntryEventForTesting(ctx, &datastore.RegistrationEntryEvent{ - EventID: 2, - EntryID: "test", - }) - require.NoError(t, err) - - err = registrationEntries.missedStartupEvents(ctx) - require.NoError(t, err) - - // Make sure no dupliate calls are made - ds.AppendNextError(nil) - ds.AppendNextError(errors.New("Duplicate call")) - err = registrationEntries.missedStartupEvents(ctx) - require.NoError(t, err) - require.Equal(t, 0, len(hook.AllEntries())) + require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(registrationEntries.fetchEntries))) + require.Zero(t, scenario.hook.Entries) + }) + } } -*/ type entryScenario struct { ctx context.Context From 7c645c48778a1fdcccb6b4ec7891c65979c6fc76 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 30 Sep 2024 22:01:48 -0700 Subject: [PATCH 14/32] Add in new entry unit tests for auth entry fetcher registraiton entries. Rename equivalent attested nodes unit test to avoid collision. Rename registration entries func call to match attested nodes pattern. Signed-off-by: Edwin Buck --- ...orized_entryfetcher_attested_nodes_test.go | 2 +- ...rized_entryfetcher_registration_entries.go | 4 +- ..._entryfetcher_registration_entries_test.go | 264 ++++++++++++++++++ 3 files changed, 267 insertions(+), 3 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 2f8803a58c..97fda53a9b 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -825,7 +825,7 @@ func TestSelectedPolledNodeEvents(t *testing.T) { } } -func TestScanForNewEvents(t *testing.T) { +func TestScanForNewNodeEvents(t *testing.T) { for _, tt := range []struct { name string setup *nodeScenarioSetup diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index 790d17adf7..39c937abfa 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -49,7 +49,7 @@ func (a *registrationEntries) captureChangedEntries(ctx context.Context) error { return err } a.selectPolledEvents(ctx) - if err := a.scanNewEvents(ctx); err != nil { + if err := a.scanForNewEvents(ctx); err != nil { return err } @@ -103,7 +103,7 @@ func (a *registrationEntries) selectPolledEvents(ctx context.Context) { } } -func (a *registrationEntries) scanNewEvents(ctx context.Context) error { +func (a *registrationEntries) scanForNewEvents(ctx context.Context) error { // If we haven't seen an event, scan for all events; otherwise, scan from the last event. var resp *datastore.ListRegistrationEntryEventsResponse var err error diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 13c549559c..3dbda538ba 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -1036,6 +1036,270 @@ func TestSelectedPolledEntryEvents(t *testing.T) { } } +func TestScanForNewEntryEvents(t *testing.T) { + for _, tt := range []struct { + name string + setup *entryScenarioSetup + + newEvents []*datastore.RegistrationEntryEvent + + expectedTrackedEvents []uint + expectedFetches []string + }{ + { + name: "no new events, no first event", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{}, + }, + { + name: "no new event, with first event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{}, + }, + { + name: "one new event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + newEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + { + name: "one new event, skipping an event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + newEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + + expectedTrackedEvents: []uint{102}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + { + name: "two new events, same registered event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + newEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + { + name: "two new events, different attested nodes", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + newEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + + expectedTrackedEvents: []uint{}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + { + name: "two new events, with a skipped event", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + newEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 104, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + + expectedTrackedEvents: []uint{103}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + { + name: "two new events, with three skipped events", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + }, + }, + newEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 106, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + + expectedTrackedEvents: []uint{103, 104, 105}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + { + name: "five events, four new events, two skip regions", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntryEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 101, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 102, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 103, + EntryID: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + &datastore.RegistrationEntryEvent{ + EventID: 104, + EntryID: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + &datastore.RegistrationEntryEvent{ + EventID: 105, + EntryID: "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + }, + newEvents: []*datastore.RegistrationEntryEvent{ + &datastore.RegistrationEntryEvent{ + EventID: 108, + EntryID: "6837984a-bc44-462b-9ca6-5cd59be35066", + }, + &datastore.RegistrationEntryEvent{ + EventID: 109, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 110, + EntryID: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + &datastore.RegistrationEntryEvent{ + EventID: 112, + EntryID: "c3f4ada0-3f8d-421e-b5d1-83aaee203d94", + }, + }, + + expectedTrackedEvents: []uint{106, 107, 111}, + expectedFetches: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "c3f4ada0-3f8d-421e-b5d1-83aaee203d94", + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + scenario := NewEntryScenario(t, tt.setup) + attestedEntries, err := scenario.buildRegistrationEntriesCache() + require.NoError(t, err) + + for _, newEvent := range tt.newEvents { + scenario.ds.CreateRegistrationEntryEventForTesting(scenario.ctx, newEvent) + } + err = attestedEntries.scanForNewEvents(scenario.ctx) + require.NoError(t, err) + + require.ElementsMatch(t, tt.expectedTrackedEvents, slices.Collect(maps.Keys(attestedEntries.eventTracker.events))) + require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedEntries.fetchEntries))) + require.Zero(t, scenario.hook.Entries) + }) + } +} + type entryScenario struct { ctx context.Context log *logrus.Logger From 9278f369687a8dc69b89973d03d8dee25437d5ae Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 00:33:30 -0700 Subject: [PATCH 15/32] Update cache unit test for authorized entryfetcher registration entries. Signed-off-by: Edwin Buck --- ..._entryfetcher_registration_entries_test.go | 533 +++++++++++++++++- 1 file changed, 530 insertions(+), 3 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 3dbda538ba..9d84654753 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -1144,7 +1144,7 @@ func TestScanForNewEntryEvents(t *testing.T) { }, }, { - name: "two new events, different attested nodes", + name: "two new events, different attested entries", setup: &entryScenarioSetup{ pageSize: 1024, registrationEntryEvents: []*datastore.RegistrationEntryEvent{ @@ -1300,6 +1300,533 @@ func TestScanForNewEntryEvents(t *testing.T) { } } +func TestUpdateRegistrationEntriesCache(t *testing.T) { + for _, tt := range []struct { + name string + setup *entryScenarioSetup + createRegistrationEntries []*common.RegistrationEntry // Entries created after setup + deleteRegistrationEntries []string // Entries delted after setup + fetchEntries []string + + expectedAuthorizedEntries []string + }{ + { + name: "empty cache, no fetch entries", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + fetchEntries: []string{}, + + expectedAuthorizedEntries: []string{}, + }, + { + name: "empty cache, fetch one entry, as a new entry", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + createRegistrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + fetchEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + + expectedAuthorizedEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + }, + { + name: "empty cache, fetch one entry, as a delete", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + fetchEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + }, + { + name: "empty cache, fetch five entries, all new entries", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + createRegistrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "354c16f4-4e61-4c17-8596-7baa7744d504", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_5", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "5"}, + }, + }, + }, + fetchEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + + expectedAuthorizedEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + { + name: "empty cache, fetch five entries, three new and two deletes", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + createRegistrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + }, + fetchEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + + expectedAuthorizedEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + }, + { + name: "empty cache, fetch five entries, all deletes", + setup: &entryScenarioSetup{ + pageSize: 1024, + }, + fetchEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + + expectedAuthorizedEntries: []string{}, + }, + { + name: "one entry in cache, no fetch entries", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + + expectedAuthorizedEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + }, + { + name: "one entry in cache, fetch one entry, as new entry", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + createRegistrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + }, + fetchEntries: []string{ + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + + expectedAuthorizedEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + }, + }, + { + name: "one entry in cache, fetch one entry, as an update", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + fetchEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + + expectedAuthorizedEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + }, + { + name: "one entry in cache, fetch one entry, as a delete", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + deleteRegistrationEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + fetchEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + + expectedAuthorizedEntries: []string{}, + }, + { + name: "one entry in cache, fetch five entries, all new entries", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + createRegistrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "354c16f4-4e61-4c17-8596-7baa7744d504", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_5", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "5"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + ParentId: "spiffe://example.org/test_node_3", + SpiffeId: "spiffe://example.org/test_job_6", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "6"}, + }, + }, + }, + fetchEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + }, + + expectedAuthorizedEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + "aeb603b2-e1d1-4832-8809-60a1d14b42e0", + }, + }, + { + name: "one entry in cache, fetch five entries, four new entries and one update", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + createRegistrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "8cbf7d48-9d43-41ae-ab63-77d66891f948", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_4", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "4"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "354c16f4-4e61-4c17-8596-7baa7744d504", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_5", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "5"}, + }, + }, + }, + fetchEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + + expectedAuthorizedEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + }, + { + name: "one entry in cache, fetch five entries, two new and three deletes", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + createRegistrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "6837984a-bc44-462b-9ca6-5cd59be35066", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_1", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "1"}, + }, + }, + &common.RegistrationEntry{ + EntryId: "47c96201-a4b1-4116-97fe-8aa9c2440aad", + ParentId: "spiffe://example.org/test_node_1", + SpiffeId: "spiffe://example.org/test_job_2", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "2"}, + }, + }, + }, + deleteRegistrationEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + fetchEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + + expectedAuthorizedEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + }, + }, + { + name: "one entry in cache, fetch five entries, all deletes", + setup: &entryScenarioSetup{ + pageSize: 1024, + registrationEntries: []*common.RegistrationEntry{ + &common.RegistrationEntry{ + EntryId: "1d78521b-cc92-47c1-85a5-28ce47f121f2", + ParentId: "spiffe://example.org/test_node_2", + SpiffeId: "spiffe://example.org/test_job_3", + Selectors: []*common.Selector{ + {Type: "testjob", Value: "3"}, + }, + }, + }, + }, + deleteRegistrationEntries: []string{ + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + }, + fetchEntries: []string{ + "6837984a-bc44-462b-9ca6-5cd59be35066", + "47c96201-a4b1-4116-97fe-8aa9c2440aad", + "1d78521b-cc92-47c1-85a5-28ce47f121f2", + "8cbf7d48-9d43-41ae-ab63-77d66891f948", + "354c16f4-4e61-4c17-8596-7baa7744d504", + }, + + expectedAuthorizedEntries: []string{}, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + scenario := NewEntryScenario(t, tt.setup) + registeredEntries, err := scenario.buildRegistrationEntriesCache() + require.NoError(t, err) + for _, registrationEntry := range tt.createRegistrationEntries { + scenario.ds.CreateRegistrationEntry(scenario.ctx, registrationEntry) + } + for _, registrationEntry := range tt.deleteRegistrationEntries { + _, err = scenario.ds.DeleteRegistrationEntry(scenario.ctx, registrationEntry) + require.NoError(t, err) + } + for _, fetchEntry := range tt.fetchEntries { + registeredEntries.fetchEntries[fetchEntry] = struct{}{} + } + // clear out the events, to prove updates are not event based + scenario.ds.PruneRegistrationEntryEvents(scenario.ctx, time.Duration(-5)*time.Hour) + + err = registeredEntries.updateCachedEntries(scenario.ctx) + require.NoError(t, err) + + cacheStats := registeredEntries.cache.Stats() + require.Equal(t, len(tt.expectedAuthorizedEntries), cacheStats.EntriesByEntryID, "wrong number of registered entries by ID") + + // for now, the only way to ensure the desired agent ids are prsent is + // to remove the desired ids and check the count it zero. + for _, expectedAuthorizedId := range tt.expectedAuthorizedEntries { + registeredEntries.cache.RemoveEntry(expectedAuthorizedId) + } + cacheStats = registeredEntries.cache.Stats() + require.Equal(t, 0, cacheStats.EntriesByEntryID, "clearing all expected registered entries didn't clear cache") + }) + } +} + type entryScenario struct { ctx context.Context log *logrus.Logger @@ -1376,8 +1903,8 @@ func (s *entryScenario) buildRegistrationEntriesCache() (*registrationEntries, e registrationEntries, err := buildRegistrationEntriesCache(s.ctx, s.log, s.metrics, s.ds, s.clk, s.cache, s.pageSize, defaultCacheReloadInterval, defaultSQLTransactionTimeout) if registrationEntries != nil { // clear out the fetches - for node, _ := range registrationEntries.fetchEntries { - delete(registrationEntries.fetchEntries, node) + for entry, _ := range registrationEntries.fetchEntries { + delete(registrationEntries.fetchEntries, entry) } } return registrationEntries, err From 673a480742a7ce24f8b9f2640e67a6df93156e6a Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 00:48:17 -0700 Subject: [PATCH 16/32] Fix datastore wrapper_test. Signed-off-by: Edwin Buck --- pkg/common/telemetry/server/datastore/wrapper_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/common/telemetry/server/datastore/wrapper_test.go b/pkg/common/telemetry/server/datastore/wrapper_test.go index a0b44885a2..6de907fe76 100644 --- a/pkg/common/telemetry/server/datastore/wrapper_test.go +++ b/pkg/common/telemetry/server/datastore/wrapper_test.go @@ -167,7 +167,7 @@ func TestWithMetrics(t *testing.T) { }, { key: "datastore.registration_entry_event.list", - methodName: "ListRegistrationEntriesEvents", + methodName: "ListRegistrationEntryEvents", }, { key: "datastore.federation_relationship.list", @@ -191,7 +191,7 @@ func TestWithMetrics(t *testing.T) { }, { key: "datastore.registration_entry_event.prune", - methodName: "PruneRegistrationEntriesEvents", + methodName: "PruneRegistrationEntryEvents", }, { key: "datastore.bundle.set", @@ -461,8 +461,8 @@ func (ds *fakeDataStore) ListRegistrationEntries(context.Context, *datastore.Lis return &datastore.ListRegistrationEntriesResponse{}, ds.err } -func (ds *fakeDataStore) ListRegistrationEntriesEvents(context.Context, *datastore.ListRegistrationEntriesEventsRequest) (*datastore.ListRegistrationEntriesEventsResponse, error) { - return &datastore.ListRegistrationEntriesEventsResponse{}, ds.err +func (ds *fakeDataStore) ListRegistrationEntryEvents(context.Context, *datastore.ListRegistrationEntryEventsRequest) (*datastore.ListRegistrationEntryEventsResponse, error) { + return &datastore.ListRegistrationEntryEventsResponse{}, ds.err } func (ds *fakeDataStore) PruneAttestedNodesEvents(context.Context, time.Duration) error { @@ -481,7 +481,7 @@ func (ds *fakeDataStore) PruneRegistrationEntries(context.Context, time.Time) er return ds.err } -func (ds *fakeDataStore) PruneRegistrationEntriesEvents(context.Context, time.Duration) error { +func (ds *fakeDataStore) PruneRegistrationEntryEvents(context.Context, time.Duration) error { return ds.err } From 9d0986bf91561f53e0dd6269cd6c9ed749135dbb Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 00:50:29 -0700 Subject: [PATCH 17/32] Fix unit tests. Signed-off-by: Edwin Buck --- .../datastore/sqlstore/sqlstore_test.go | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/server/datastore/sqlstore/sqlstore_test.go b/pkg/server/datastore/sqlstore/sqlstore_test.go index 979469298c..abb6883ea7 100644 --- a/pkg/server/datastore/sqlstore/sqlstore_test.go +++ b/pkg/server/datastore/sqlstore/sqlstore_test.go @@ -2111,7 +2111,7 @@ func (s *PluginSuite) TestPruneRegistrationEntries() { } prunedLogMessage := "Pruned an expired registration" - resp, err := s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err := s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) s.Require().NoError(err) s.Require().Equal(1, len(resp.Events)) s.Require().Equal(createdRegistrationEntry.EntryId, resp.Events[0].EntryID) @@ -2152,7 +2152,7 @@ func (s *PluginSuite) TestPruneRegistrationEntries() { tt := tt s.T().Run(tt.name, func(t *testing.T) { // Get latest event id - resp, err := s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err := s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) require.NoError(t, err) require.Greater(t, len(resp.Events), 0) lastEventID := resp.Events[len(resp.Events)-1].EventID @@ -2165,7 +2165,7 @@ func (s *PluginSuite) TestPruneRegistrationEntries() { assert.Equal(t, tt.expectedRegistrationEntry, fetchedRegistrationEntry) // Verify pruning triggers event creation - resp, err = s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ + resp, err = s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{ GreaterThanEventID: lastEventID, }) require.NoError(t, err) @@ -3977,7 +3977,7 @@ func (s *PluginSuite) TestDeleteBundleDissociateRegistrationEntries() { s.Require().Empty(entry.FederatesWith) } -func (s *PluginSuite) TestListRegistrationEntriesEvents() { +func (s *PluginSuite) TestListRegistrationEntryEvents() { var expectedEvents []datastore.RegistrationEntryEvent var expectedEventID uint = 1 @@ -3995,7 +3995,7 @@ func (s *PluginSuite) TestListRegistrationEntriesEvents() { }) expectedEventID++ - resp, err := s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err := s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) s.Require().NoError(err) s.Require().Equal(expectedEvents, resp.Events) @@ -4013,7 +4013,7 @@ func (s *PluginSuite) TestListRegistrationEntriesEvents() { }) expectedEventID++ - resp, err = s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err = s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) s.Require().NoError(err) s.Require().Equal(expectedEvents, resp.Events) @@ -4026,7 +4026,7 @@ func (s *PluginSuite) TestListRegistrationEntriesEvents() { }) expectedEventID++ - resp, err = s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err = s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) s.Require().NoError(err) s.Require().Equal(expectedEvents, resp.Events) @@ -4037,7 +4037,7 @@ func (s *PluginSuite) TestListRegistrationEntriesEvents() { EntryID: entry2.EntryId, }) - resp, err = s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err = s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) s.Require().NoError(err) s.Require().Equal(expectedEvents, resp.Events) @@ -4086,7 +4086,7 @@ func (s *PluginSuite) TestListRegistrationEntriesEvents() { } for _, test := range tests { s.T().Run(test.name, func(t *testing.T) { - resp, err = s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{ + resp, err = s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{ GreaterThanEventID: test.greaterThanEventID, LessThanEventID: test.lessThanEventID, }) @@ -4105,7 +4105,7 @@ func (s *PluginSuite) TestListRegistrationEntriesEvents() { } } -func (s *PluginSuite) TestPruneRegistrationEntriesEvents() { +func (s *PluginSuite) TestPruneRegistrationEntryEvents() { entry := &common.RegistrationEntry{ Selectors: []*common.Selector{ {Type: "Type1", Value: "Value1"}, @@ -4115,7 +4115,7 @@ func (s *PluginSuite) TestPruneRegistrationEntriesEvents() { } createdRegistrationEntry := s.createRegistrationEntry(entry) - resp, err := s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err := s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) s.Require().NoError(err) s.Require().Equal(createdRegistrationEntry.EntryId, resp.Events[0].EntryID) @@ -4142,9 +4142,9 @@ func (s *PluginSuite) TestPruneRegistrationEntriesEvents() { } { s.T().Run(tt.name, func(t *testing.T) { s.Require().Eventuallyf(func() bool { - err = s.ds.PruneRegistrationEntriesEvents(ctx, tt.olderThan) + err = s.ds.PruneRegistrationEntryEvents(ctx, tt.olderThan) s.Require().NoError(err) - resp, err := s.ds.ListRegistrationEntriesEvents(ctx, &datastore.ListRegistrationEntriesEventsRequest{}) + resp, err := s.ds.ListRegistrationEntryEvents(ctx, &datastore.ListRegistrationEntryEventsRequest{}) s.Require().NoError(err) return reflect.DeepEqual(tt.expectedEvents, resp.Events) }, 10*time.Second, 50*time.Millisecond, "Failed to prune entries correctly") From 978fbabe6c6236e74ab53e8e3a8965d2163d975b Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 01:16:59 -0700 Subject: [PATCH 18/32] Fix some linting issues. Signed-off-by: Edwin Buck --- .../authorized_entryfetcher_attested_nodes.go | 4 +-- ...orized_entryfetcher_attested_nodes_test.go | 35 +++++++++++------- pkg/server/endpoints/eventTracker.go | 6 ++-- pkg/server/endpoints/eventTracker_test.go | 36 +++++++++---------- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index c69117f565..2393b9c956 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -3,7 +3,6 @@ package endpoints import ( "context" "fmt" - "sync" "time" "github.com/andres-erbsen/clock" @@ -24,7 +23,6 @@ type attestedNodes struct { ds datastore.DataStore log logrus.FieldLogger metrics telemetry.Metrics - mu sync.RWMutex eventsBeforeFirst map[uint]struct{} @@ -100,7 +98,7 @@ func (a *attestedNodes) selectPolledEvents(ctx context.Context) { } a.fetchNodes[event.SpiffeID] = struct{}{} - a.eventTracker.StopTracking(uint(eventID)) + a.eventTracker.StopTracking(eventID) } } diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 97fda53a9b..04d54df0e9 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -198,7 +198,6 @@ func TestLoadNodeCache(t *testing.T) { t.Run(tt.name, func(t *testing.T) { scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() - if tt.expectedError != "" { require.ErrorContains(t, err, tt.expectedError) return @@ -246,7 +245,7 @@ func TestSearchBeforeFirstNodeEvent(t *testing.T) { polledEvents []*datastore.AttestedNodeEvent errors []error - expectedError error + expectedError string expectedEventsBeforeFirst []uint expectedFetches []string }{ @@ -589,7 +588,10 @@ func TestSearchBeforeFirstNodeEvent(t *testing.T) { t.Run(tt.name, func(t *testing.T) { scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() - + if tt.expectedError != "" { + require.ErrorContains(t, err, tt.expectedError) + return + } require.NoError(t, err) if tt.waitToPoll == 0 { @@ -603,10 +605,12 @@ func TestSearchBeforeFirstNodeEvent(t *testing.T) { } for _, event := range tt.polledEvents { - scenario.ds.CreateAttestedNodeEventForTesting(scenario.ctx, event) + err = scenario.ds.CreateAttestedNodeEventForTesting(scenario.ctx, event) + require.NoError(t, err, "error while setting up test") } - attestedNodes.searchBeforeFirstEvent(scenario.ctx) + err = attestedNodes.searchBeforeFirstEvent(scenario.ctx) + require.NoError(t, err, "error while running test") require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(attestedNodes.eventsBeforeFirst)), "expected events before tracking mismatch") require.ElementsMatch(t, tt.expectedFetches, slices.Collect[string](maps.Keys(attestedNodes.fetchNodes)), "expected fetches mismatch") @@ -1066,10 +1070,11 @@ func TestScanForNewNodeEvents(t *testing.T) { require.NoError(t, err) for _, newEvent := range tt.newEvents { - scenario.ds.CreateAttestedNodeEventForTesting(scenario.ctx, newEvent) + err = scenario.ds.CreateAttestedNodeEventForTesting(scenario.ctx, newEvent) + require.NoError(t, err, "error while setting up test") } err = attestedNodes.scanForNewEvents(scenario.ctx) - require.NoError(t, err) + require.NoError(t, err, "error while running test") require.ElementsMatch(t, tt.expectedTrackedEvents, slices.Collect(maps.Keys(attestedNodes.eventTracker.events))) require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedNodes.fetchNodes))) @@ -1433,18 +1438,21 @@ func TestUpdateAttestedNodesCache(t *testing.T) { scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() require.NoError(t, err) + for _, attestedNode := range tt.createAttestedNodes { - scenario.ds.CreateAttestedNode(scenario.ctx, attestedNode) + _, err = scenario.ds.CreateAttestedNode(scenario.ctx, attestedNode) + require.NoError(t, err, "error while setting up test") } for _, attestedNode := range tt.deleteAttestedNodes { _, err = scenario.ds.DeleteAttestedNode(scenario.ctx, attestedNode) - require.NoError(t, err) + require.NoError(t, err, "error while setting up test") } for _, fetchNode := range tt.fetchNodes { attestedNodes.fetchNodes[fetchNode] = struct{}{} } // clear out the events, to prove updates are not event based - scenario.ds.PruneAttestedNodesEvents(scenario.ctx, time.Duration(-5)*time.Hour) + err = scenario.ds.PruneAttestedNodesEvents(scenario.ctx, time.Duration(-5)*time.Hour) + require.NoError(t, err, "error while setting up test") err = attestedNodes.updateCachedNodes(scenario.ctx) require.NoError(t, err) @@ -1494,16 +1502,19 @@ func NewNodeScenario(t *testing.T, setup *nodeScenarioSetup) *scenario { setup = &nodeScenarioSetup{} } + var err error // initialize the database for _, attestedNode := range setup.attestedNodes { - ds.CreateAttestedNode(ctx, attestedNode) + _, err = ds.CreateAttestedNode(ctx, attestedNode) + require.NoError(t, err, "error while setting up test") } // prune autocreated node events, to test the event logic in more scenarios // than possible with autocreated node events. ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) // and then add back the specified node events for _, event := range setup.attestedNodeEvents { - ds.CreateAttestedNodeEventForTesting(ctx, event) + err = ds.CreateAttestedNodeEventForTesting(ctx, event) + require.NoError(t, err, "error while setting up test") } // inject db error for buildAttestedNodesCache call if setup.err != nil { diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 36f85ace92..933fb8cf8d 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -9,7 +9,7 @@ import ( /** * Tracks events as they indivicually walk through a list of event boundaries. * - * An event track is defined with a set of foundaries, which are indexes to + * An event track is defined with a set of boundaries, which are indexes to * virtual hash tables, with the event's hash determining the position within * that hash table where the event will be selected to be polled. * For eventTRackers that lack boundaries, or polls that exist prior to @@ -72,7 +72,7 @@ func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { // initialize poll boundaries one minute out boundaries := make(map[uint]struct{}) - currentBoundary := uint(pollsPerMinute) + currentBoundary := pollsPerMinute for currentBoundary < pollPeriods { if currentBoundary < pollsPerTenMinutes { boundaries[currentBoundary] = struct{}{} @@ -243,7 +243,7 @@ func (et *eventTracker) EventCount() uint { * previously found on the internet. It avoids the factorization problem * (even events only go into even slots) by repeatedly mixing high order * bits into the low order bits ( h^h >> (number)). The high order bits - * are primarly set by the low order bits repeatedly by multipliation with + * are primarily set by the low order bits repeatedly by multipliation with * a number designed to mix bits deterministicly for better hash dispersion. */ func hash(event uint) uint { diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index 061e7bd555..abab79a21f 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -214,7 +214,7 @@ func TestNewEventTracker(t *testing.T) { require.Equal(t, tt.expectedPollPeriods, eventTracker.PollPeriods(), "expcting %d poll periods; but, %d poll periods reported", eventTracker.PollPeriods(), tt.expectedPollPeriods) require.Equal(t, tt.expectedBoundaries, eventTracker.PollBoundaries(), "expected %v boundaries, not %v boundaries", eventTracker.PollBoundaries(), tt.expectedBoundaries) - require.Equal(t, tt.expectedInitialPolls, eventTracker.InitialPolls(), "inital polls of %d when requesting %d", eventTracker.InitialPolls(), tt.expectedInitialPolls) + require.Equal(t, tt.expectedInitialPolls, eventTracker.InitialPolls(), "initial polls of %d when requesting %d", eventTracker.InitialPolls(), tt.expectedInitialPolls) require.Equal(t, tt.expectedPolls, eventTracker.Polls(), "polling each element %d times, when expecting %d times", tt.expectedPolls, eventTracker.Polls()) }) } @@ -357,7 +357,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 2, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -369,7 +369,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 2, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -381,7 +381,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 3, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -393,7 +393,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 3, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -405,7 +405,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 3, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -417,7 +417,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 4, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -429,7 +429,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 4, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -441,7 +441,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 4, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -453,7 +453,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 4, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -465,7 +465,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 5, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -477,7 +477,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 5, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -489,7 +489,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 5, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -501,7 +501,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 5, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -513,7 +513,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 5, eventCount: 1000, - // should disperse into two slots, with an approxmiate count of [ 500, 500 ] + // should disperse into two slots, with an approximate count of [ 500, 500 ] expectedSlotCount: 500, permissibleCountError: 50, // 5% of 1000 }, @@ -525,7 +525,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 2, eventCount: 1000, - // should disperse into three slots, with an approxmiate count of [ 333, 333, 333 ] + // should disperse into three slots, with an approximate count of [ 333, 333, 333 ] expectedSlotCount: 333, permissibleCountError: 50, // 5% of 1000 }, @@ -667,7 +667,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 2, eventCount: 1000, - // should disperse into four slots, with an approxmiate count of [ 250, 250, 250, 250 ] + // should disperse into four slots, with an approximate count of [ 250, 250, 250, 250 ] expectedSlotCount: 250, permissibleCountError: 50, // 5% of 1000 }, @@ -809,7 +809,7 @@ func TestEvenDispersion(t *testing.T) { eventIncrement: 2, eventCount: 1000, - // should disperse into five slots, with an approxmiate count of [ 200, 200, 200, 200, 200 ] + // should disperse into five slots, with an approximate count of [ 200, 200, 200, 200, 200 ] expectedSlotCount: 200, permissibleCountError: 50, // 5% of 1000 }, From a0a2d49ab07eaabe6a29ecb156a24fab01be8142 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 06:52:15 -0700 Subject: [PATCH 19/32] Fix linting issues. Signed-off-by: Edwin Buck --- .../authorized_entryfetcher_attested_nodes.go | 1 - ...orized_entryfetcher_attested_nodes_test.go | 3 +- ...rized_entryfetcher_registration_entries.go | 5 +-- ..._entryfetcher_registration_entries_test.go | 38 ++++++++++++------- pkg/server/endpoints/eventTracker_test.go | 1 - 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 2393b9c956..f121591ff1 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -233,7 +233,6 @@ func (a *attestedNodes) updateCachedNodes(ctx context.Context) error { agentExpiresAt := time.Unix(node.CertNotAfter, 0) a.cache.UpdateAgent(node.SpiffeId, agentExpiresAt, api.ProtoFromSelectors(node.Selectors)) delete(a.fetchNodes, spiffeId) - } return nil } diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 04d54df0e9..b2529db6e2 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -1510,7 +1510,8 @@ func NewNodeScenario(t *testing.T, setup *nodeScenarioSetup) *scenario { } // prune autocreated node events, to test the event logic in more scenarios // than possible with autocreated node events. - ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + err = ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + require.NoError(t, err, "error while setting up test") // and then add back the specified node events for _, event := range setup.attestedNodeEvents { err = ds.CreateAttestedNodeEventForTesting(ctx, event) diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index 39c937abfa..b689a3444a 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -3,7 +3,6 @@ package endpoints import ( "context" "fmt" - "sync" "time" "github.com/andres-erbsen/clock" @@ -23,7 +22,6 @@ type registrationEntries struct { ds datastore.DataStore log logrus.FieldLogger metrics telemetry.Metrics - mu sync.RWMutex eventsBeforeFirst map[uint]struct{} @@ -99,7 +97,7 @@ func (a *registrationEntries) selectPolledEvents(ctx context.Context) { } a.fetchEntries[event.EntryID] = struct{}{} - a.eventTracker.StopTracking(uint(eventID)) + a.eventTracker.StopTracking(eventID) } } @@ -274,5 +272,4 @@ func (a *registrationEntries) emitMetrics() { a.lastCacheStats.EntriesByParentID = cacheStats.EntriesByParentID server_telemetry.SetEntriesByParentIDCacheCountGauge(a.metrics, a.lastCacheStats.EntriesByParentID) } - } diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 9d84654753..59a69308b1 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -804,10 +804,12 @@ func TestSearchBeforeFirstEntryEvent(t *testing.T) { } for _, event := range tt.polledEvents { - scenario.ds.CreateRegistrationEntryEventForTesting(scenario.ctx, event) + err = scenario.ds.CreateRegistrationEntryEventForTesting(scenario.ctx, event) + require.NoError(t, err, "error while setting up test") } - registrationEntries.searchBeforeFirstEvent(scenario.ctx) + err = registrationEntries.searchBeforeFirstEvent(scenario.ctx) + require.NoError(t, err, "error while running the test") require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(registrationEntries.eventsBeforeFirst)), "expected events before tracking mismatch") require.ElementsMatch(t, tt.expectedFetches, slices.Collect[string](maps.Keys(registrationEntries.fetchEntries)), "expected fetches mismatch") @@ -1288,10 +1290,11 @@ func TestScanForNewEntryEvents(t *testing.T) { require.NoError(t, err) for _, newEvent := range tt.newEvents { - scenario.ds.CreateRegistrationEntryEventForTesting(scenario.ctx, newEvent) + err = scenario.ds.CreateRegistrationEntryEventForTesting(scenario.ctx, newEvent) + require.NoError(t, err, "error while setting up test") } err = attestedEntries.scanForNewEvents(scenario.ctx) - require.NoError(t, err) + require.NoError(t, err, "error while running the test") require.ElementsMatch(t, tt.expectedTrackedEvents, slices.Collect(maps.Keys(attestedEntries.eventTracker.events))) require.ElementsMatch(t, tt.expectedFetches, slices.Collect(maps.Keys(attestedEntries.fetchEntries))) @@ -1798,17 +1801,19 @@ func TestUpdateRegistrationEntriesCache(t *testing.T) { registeredEntries, err := scenario.buildRegistrationEntriesCache() require.NoError(t, err) for _, registrationEntry := range tt.createRegistrationEntries { - scenario.ds.CreateRegistrationEntry(scenario.ctx, registrationEntry) + _, err = scenario.ds.CreateRegistrationEntry(scenario.ctx, registrationEntry) + require.NoError(t, err, "error while setting up test") } for _, registrationEntry := range tt.deleteRegistrationEntries { _, err = scenario.ds.DeleteRegistrationEntry(scenario.ctx, registrationEntry) - require.NoError(t, err) + require.NoError(t, err, "error while setting up test") } for _, fetchEntry := range tt.fetchEntries { registeredEntries.fetchEntries[fetchEntry] = struct{}{} } // clear out the events, to prove updates are not event based - scenario.ds.PruneRegistrationEntryEvents(scenario.ctx, time.Duration(-5)*time.Hour) + err = scenario.ds.PruneRegistrationEntryEvents(scenario.ctx, time.Duration(-5)*time.Hour) + require.NoError(t, err, "error while running the test") err = registeredEntries.updateCachedEntries(scenario.ctx) require.NoError(t, err) @@ -1861,26 +1866,33 @@ func NewEntryScenario(t *testing.T, setup *entryScenarioSetup) *entryScenario { setup = &entryScenarioSetup{} } + var err error for _, attestedNode := range setup.attestedNodes { - ds.CreateAttestedNode(ctx, attestedNode) + _, err = ds.CreateAttestedNode(ctx, attestedNode) + require.NoError(t, err, "error while setting up test") } // prune autocreated node events, to test the event logic in more scenarios // than possible with autocreated node events. - ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + err = ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + require.NoError(t, err, "error while setting up test") // and then add back the specified node events for _, event := range setup.attestedNodeEvents { - ds.CreateAttestedNodeEventForTesting(ctx, event) + err = ds.CreateAttestedNodeEventForTesting(ctx, event) + require.NoError(t, err, "error while setting up test") } // initialize the database for _, registrationEntry := range setup.registrationEntries { - ds.CreateRegistrationEntry(ctx, registrationEntry) + _, err = ds.CreateRegistrationEntry(ctx, registrationEntry) + require.NoError(t, err, "error while setting up test") } // prune autocreated entry events, to test the event logic in more // scenarios than possible with autocreated entry events. - ds.PruneRegistrationEntryEvents(ctx, time.Duration(-5)*time.Hour) + err = ds.PruneRegistrationEntryEvents(ctx, time.Duration(-5)*time.Hour) + require.NoError(t, err, "error while setting up test") // and then add back the specified node events for _, event := range setup.registrationEntryEvents { - ds.CreateRegistrationEntryEventForTesting(ctx, event) + err = ds.CreateRegistrationEntryEventForTesting(ctx, event) + require.NoError(t, err, "error while setting up test") } // inject db error for buildRegistrationEntriesCache call if setup.err != nil { diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index abab79a21f..466d2a44b1 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -965,7 +965,6 @@ func TestEvenDispersion(t *testing.T) { "for slot %d, expecting no more than %d polls, but received %d polls", slot, tt.expectedSlotCount+tt.permissibleCountError, count) } - }) } } From 0a21834333bda61b481667dcad2f1e854d94a9a8 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 07:18:31 -0700 Subject: [PATCH 20/32] Fix grammar in func calls / structs. Signed-off-by: Edwin Buck --- pkg/common/telemetry/server/datastore/event.go | 8 ++++---- .../telemetry/server/datastore/wrapper.go | 12 ++++++------ .../telemetry/server/datastore/wrapper_test.go | 10 +++++----- pkg/server/datastore/datastore.go | 8 ++++---- pkg/server/datastore/sqlstore/sqlstore.go | 18 +++++++++--------- pkg/server/datastore/sqlstore/sqlstore_test.go | 14 +++++++------- .../endpoints/authorized_entryfetcher.go | 4 ++-- .../authorized_entryfetcher_attested_nodes.go | 8 ++++---- ...horized_entryfetcher_attested_nodes_test.go | 4 ++-- ...d_entryfetcher_registration_entries_test.go | 2 +- pkg/server/endpoints/entryfetcher.go | 4 ++-- test/fakes/fakedatastore/fakedatastore.go | 8 ++++---- 12 files changed, 50 insertions(+), 50 deletions(-) diff --git a/pkg/common/telemetry/server/datastore/event.go b/pkg/common/telemetry/server/datastore/event.go index 24ad4f6714..b331ee3c3d 100644 --- a/pkg/common/telemetry/server/datastore/event.go +++ b/pkg/common/telemetry/server/datastore/event.go @@ -34,15 +34,15 @@ func StartFetchRegistrationEntryEventCall(m telemetry.Metrics) *telemetry.CallCo return telemetry.StartCall(m, telemetry.Datastore, telemetry.RegistrationEntryEvent, telemetry.Fetch) } -// StartListAttestedNodesEventsCall return metric +// StartListAttestedNodeEventsCall return metric // for server's datastore, on listing attested node events. -func StartListAttestedNodesEventsCall(m telemetry.Metrics) *telemetry.CallCounter { +func StartListAttestedNodeEventsCall(m telemetry.Metrics) *telemetry.CallCounter { return telemetry.StartCall(m, telemetry.Datastore, telemetry.NodeEvent, telemetry.List) } -// StartPruneAttestedNodesEventsCall return metric +// StartPruneAttestedNodeEventsCall return metric // for server's datastore, on pruning attested node events. -func StartPruneAttestedNodesEventsCall(m telemetry.Metrics) *telemetry.CallCounter { +func StartPruneAttestedNodeEventsCall(m telemetry.Metrics) *telemetry.CallCounter { return telemetry.StartCall(m, telemetry.Datastore, telemetry.NodeEvent, telemetry.Prune) } diff --git a/pkg/common/telemetry/server/datastore/wrapper.go b/pkg/common/telemetry/server/datastore/wrapper.go index 0fb8edd305..d9f06e218b 100644 --- a/pkg/common/telemetry/server/datastore/wrapper.go +++ b/pkg/common/telemetry/server/datastore/wrapper.go @@ -179,10 +179,10 @@ func (w metricsWrapper) ListAttestedNodes(ctx context.Context, req *datastore.Li return w.ds.ListAttestedNodes(ctx, req) } -func (w metricsWrapper) ListAttestedNodesEvents(ctx context.Context, req *datastore.ListAttestedNodesEventsRequest) (_ *datastore.ListAttestedNodesEventsResponse, err error) { - callCounter := StartListAttestedNodesEventsCall(w.m) +func (w metricsWrapper) ListAttestedNodeEvents(ctx context.Context, req *datastore.ListAttestedNodeEventsRequest) (_ *datastore.ListAttestedNodeEventsResponse, err error) { + callCounter := StartListAttestedNodeEventsCall(w.m) defer callCounter.Done(&err) - return w.ds.ListAttestedNodesEvents(ctx, req) + return w.ds.ListAttestedNodeEvents(ctx, req) } func (w metricsWrapper) ListBundles(ctx context.Context, req *datastore.ListBundlesRequest) (_ *datastore.ListBundlesResponse, err error) { @@ -227,10 +227,10 @@ func (w metricsWrapper) CountRegistrationEntries(ctx context.Context, req *datas return w.ds.CountRegistrationEntries(ctx, req) } -func (w metricsWrapper) PruneAttestedNodesEvents(ctx context.Context, olderThan time.Duration) (err error) { - callCounter := StartPruneAttestedNodesEventsCall(w.m) +func (w metricsWrapper) PruneAttestedNodeEvents(ctx context.Context, olderThan time.Duration) (err error) { + callCounter := StartPruneAttestedNodeEventsCall(w.m) defer callCounter.Done(&err) - return w.ds.PruneAttestedNodesEvents(ctx, olderThan) + return w.ds.PruneAttestedNodeEvents(ctx, olderThan) } func (w metricsWrapper) PruneBundle(ctx context.Context, trustDomainID string, expiresBefore time.Time) (_ bool, err error) { diff --git a/pkg/common/telemetry/server/datastore/wrapper_test.go b/pkg/common/telemetry/server/datastore/wrapper_test.go index 6de907fe76..79c1a87f8e 100644 --- a/pkg/common/telemetry/server/datastore/wrapper_test.go +++ b/pkg/common/telemetry/server/datastore/wrapper_test.go @@ -151,7 +151,7 @@ func TestWithMetrics(t *testing.T) { }, { key: "datastore.node_event.list", - methodName: "ListAttestedNodesEvents", + methodName: "ListAttestedNodeEvents", }, { key: "datastore.bundle.list", @@ -175,7 +175,7 @@ func TestWithMetrics(t *testing.T) { }, { key: "datastore.node_event.prune", - methodName: "PruneAttestedNodesEvents", + methodName: "PruneAttestedNodeEvents", }, { key: "datastore.bundle.prune", @@ -445,8 +445,8 @@ func (ds *fakeDataStore) ListAttestedNodes(context.Context, *datastore.ListAttes return &datastore.ListAttestedNodesResponse{}, ds.err } -func (ds *fakeDataStore) ListAttestedNodesEvents(context.Context, *datastore.ListAttestedNodesEventsRequest) (*datastore.ListAttestedNodesEventsResponse, error) { - return &datastore.ListAttestedNodesEventsResponse{}, ds.err +func (ds *fakeDataStore) ListAttestedNodeEvents(context.Context, *datastore.ListAttestedNodeEventsRequest) (*datastore.ListAttestedNodeEventsResponse, error) { + return &datastore.ListAttestedNodeEventsResponse{}, ds.err } func (ds *fakeDataStore) ListBundles(context.Context, *datastore.ListBundlesRequest) (*datastore.ListBundlesResponse, error) { @@ -465,7 +465,7 @@ func (ds *fakeDataStore) ListRegistrationEntryEvents(context.Context, *datastore return &datastore.ListRegistrationEntryEventsResponse{}, ds.err } -func (ds *fakeDataStore) PruneAttestedNodesEvents(context.Context, time.Duration) error { +func (ds *fakeDataStore) PruneAttestedNodeEvents(context.Context, time.Duration) error { return ds.err } diff --git a/pkg/server/datastore/datastore.go b/pkg/server/datastore/datastore.go index 671b971aa2..1f89841210 100644 --- a/pkg/server/datastore/datastore.go +++ b/pkg/server/datastore/datastore.go @@ -55,8 +55,8 @@ type DataStore interface { UpdateAttestedNode(context.Context, *common.AttestedNode, *common.AttestedNodeMask) (*common.AttestedNode, error) // Nodes Events - ListAttestedNodesEvents(ctx context.Context, req *ListAttestedNodesEventsRequest) (*ListAttestedNodesEventsResponse, error) - PruneAttestedNodesEvents(ctx context.Context, olderThan time.Duration) error + ListAttestedNodeEvents(ctx context.Context, req *ListAttestedNodeEventsRequest) (*ListAttestedNodeEventsResponse, error) + PruneAttestedNodeEvents(ctx context.Context, olderThan time.Duration) error FetchAttestedNodeEvent(ctx context.Context, eventID uint) (*AttestedNodeEvent, error) CreateAttestedNodeEventForTesting(ctx context.Context, event *AttestedNodeEvent) error DeleteAttestedNodeEventForTesting(ctx context.Context, eventID uint) error @@ -169,7 +169,7 @@ type ListAttestedNodesResponse struct { Pagination *Pagination } -type ListAttestedNodesEventsRequest struct { +type ListAttestedNodeEventsRequest struct { GreaterThanEventID uint LessThanEventID uint } @@ -179,7 +179,7 @@ type AttestedNodeEvent struct { SpiffeID string } -type ListAttestedNodesEventsResponse struct { +type ListAttestedNodeEventsResponse struct { Events []AttestedNodeEvent } diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index 54e8ae2126..c3180a9b1a 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -382,10 +382,10 @@ func (ds *Plugin) DeleteAttestedNode(ctx context.Context, spiffeID string) (atte return attestedNode, nil } -// ListAttestedNodesEvents lists all attested node events -func (ds *Plugin) ListAttestedNodesEvents(ctx context.Context, req *datastore.ListAttestedNodesEventsRequest) (resp *datastore.ListAttestedNodesEventsResponse, err error) { +// ListAttestedNodeEvents lists all attested node events +func (ds *Plugin) ListAttestedNodeEvents(ctx context.Context, req *datastore.ListAttestedNodeEventsRequest) (resp *datastore.ListAttestedNodeEventsResponse, err error) { if err = ds.withReadTx(ctx, func(tx *gorm.DB) (err error) { - resp, err = listAttestedNodesEvents(tx, req) + resp, err = listAttestedNodeEvents(tx, req) return err }); err != nil { return nil, err @@ -393,10 +393,10 @@ func (ds *Plugin) ListAttestedNodesEvents(ctx context.Context, req *datastore.Li return resp, nil } -// PruneAttestedNodesEvents deletes all attested node events older than a specified duration (i.e. more than 24 hours old) -func (ds *Plugin) PruneAttestedNodesEvents(ctx context.Context, olderThan time.Duration) (err error) { +// PruneAttestedNodeEvents deletes all attested node events older than a specified duration (i.e. more than 24 hours old) +func (ds *Plugin) PruneAttestedNodeEvents(ctx context.Context, olderThan time.Duration) (err error) { return ds.withWriteTx(ctx, func(tx *gorm.DB) (err error) { - err = pruneAttestedNodesEvents(tx, olderThan) + err = pruneAttestedNodeEvents(tx, olderThan) return err }) } @@ -1707,7 +1707,7 @@ func createAttestedNodeEvent(tx *gorm.DB, event *datastore.AttestedNodeEvent) er return nil } -func listAttestedNodesEvents(tx *gorm.DB, req *datastore.ListAttestedNodesEventsRequest) (*datastore.ListAttestedNodesEventsResponse, error) { +func listAttestedNodeEvents(tx *gorm.DB, req *datastore.ListAttestedNodeEventsRequest) (*datastore.ListAttestedNodeEventsResponse, error) { var events []AttestedNodeEvent if req.GreaterThanEventID != 0 || req.LessThanEventID != 0 { @@ -1725,7 +1725,7 @@ func listAttestedNodesEvents(tx *gorm.DB, req *datastore.ListAttestedNodesEvents } } - resp := &datastore.ListAttestedNodesEventsResponse{ + resp := &datastore.ListAttestedNodeEventsResponse{ Events: make([]datastore.AttestedNodeEvent, len(events)), } for i, event := range events { @@ -1736,7 +1736,7 @@ func listAttestedNodesEvents(tx *gorm.DB, req *datastore.ListAttestedNodesEvents return resp, nil } -func pruneAttestedNodesEvents(tx *gorm.DB, olderThan time.Duration) error { +func pruneAttestedNodeEvents(tx *gorm.DB, olderThan time.Duration) error { if err := tx.Where("created_at < ?", time.Now().Add(-olderThan)).Delete(&AttestedNodeEvent{}).Error; err != nil { return sqlError.Wrap(err) } diff --git a/pkg/server/datastore/sqlstore/sqlstore_test.go b/pkg/server/datastore/sqlstore/sqlstore_test.go index abb6883ea7..4f18f0c32c 100644 --- a/pkg/server/datastore/sqlstore/sqlstore_test.go +++ b/pkg/server/datastore/sqlstore/sqlstore_test.go @@ -1498,7 +1498,7 @@ func (s *PluginSuite) TestDeleteAttestedNode() { }) } -func (s *PluginSuite) TestListAttestedNodesEvents() { +func (s *PluginSuite) TestListAttestedNodeEvents() { var expectedEvents []datastore.AttestedNodeEvent // Create an attested node @@ -1601,7 +1601,7 @@ func (s *PluginSuite) TestListAttestedNodesEvents() { } for _, test := range tests { s.T().Run(test.name, func(t *testing.T) { - resp, err := s.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ + resp, err := s.ds.ListAttestedNodeEvents(ctx, &datastore.ListAttestedNodeEventsRequest{ GreaterThanEventID: test.greaterThanEventID, LessThanEventID: test.lessThanEventID, }) @@ -1620,7 +1620,7 @@ func (s *PluginSuite) TestListAttestedNodesEvents() { } } -func (s *PluginSuite) TestPruneAttestedNodesEvents() { +func (s *PluginSuite) TestPruneAttestedNodeEvents() { node, err := s.ds.CreateAttestedNode(ctx, &common.AttestedNode{ SpiffeId: "foo", AttestationDataType: "aws-tag", @@ -1629,7 +1629,7 @@ func (s *PluginSuite) TestPruneAttestedNodesEvents() { }) s.Require().NoError(err) - resp, err := s.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) + resp, err := s.ds.ListAttestedNodeEvents(ctx, &datastore.ListAttestedNodeEventsRequest{}) s.Require().NoError(err) s.Require().Equal(node.SpiffeId, resp.Events[0].SpiffeID) @@ -1656,9 +1656,9 @@ func (s *PluginSuite) TestPruneAttestedNodesEvents() { } { s.T().Run(tt.name, func(t *testing.T) { s.Require().Eventuallyf(func() bool { - err = s.ds.PruneAttestedNodesEvents(ctx, tt.olderThan) + err = s.ds.PruneAttestedNodeEvents(ctx, tt.olderThan) s.Require().NoError(err) - resp, err := s.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) + resp, err := s.ds.ListAttestedNodeEvents(ctx, &datastore.ListAttestedNodeEventsRequest{}) s.Require().NoError(err) return reflect.DeepEqual(tt.expectedEvents, resp.Events) }, 10*time.Second, 50*time.Millisecond, "Failed to prune entries correctly") @@ -5287,7 +5287,7 @@ func (s *PluginSuite) checkAttestedNodeEvents(expectedEvents []datastore.Atteste SpiffeID: spiffeID, }) - resp, err := s.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) + resp, err := s.ds.ListAttestedNodeEvents(ctx, &datastore.ListAttestedNodeEventsRequest{}) s.Require().NoError(err) s.Require().Equal(expectedEvents, resp.Events) diff --git a/pkg/server/endpoints/authorized_entryfetcher.go b/pkg/server/endpoints/authorized_entryfetcher.go index cf9116a2c7..0d31853129 100644 --- a/pkg/server/endpoints/authorized_entryfetcher.go +++ b/pkg/server/endpoints/authorized_entryfetcher.go @@ -96,9 +96,9 @@ func (a *AuthorizedEntryFetcherWithEventsBasedCache) PruneEventsTask(ctx context func (a *AuthorizedEntryFetcherWithEventsBasedCache) pruneEvents(ctx context.Context, olderThan time.Duration) error { pruneRegistrationEntryEventsErr := a.ds.PruneRegistrationEntryEvents(ctx, olderThan) - pruneAttestedNodesEventsErr := a.ds.PruneAttestedNodesEvents(ctx, olderThan) + pruneAttestedNodeEventsErr := a.ds.PruneAttestedNodeEvents(ctx, olderThan) - return errors.Join(pruneRegistrationEntryEventsErr, pruneAttestedNodesEventsErr) + return errors.Join(pruneRegistrationEntryEventsErr, pruneAttestedNodeEventsErr) } func (a *AuthorizedEntryFetcherWithEventsBasedCache) updateCache(ctx context.Context) error { diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index f121591ff1..106ac70564 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -58,7 +58,7 @@ func (a *attestedNodes) captureChangedNodes(ctx context.Context) error { func (a *attestedNodes) searchBeforeFirstEvent(ctx context.Context) error { // First event detected, and startup was less than a transaction timout away. if !a.firstEventTime.IsZero() && a.clk.Now().Sub(a.firstEventTime) <= a.sqlTransactionTimeout { - resp, err := a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ + resp, err := a.ds.ListAttestedNodeEvents(ctx, &datastore.ListAttestedNodeEventsRequest{ LessThanEventID: a.firstEvent, }) if err != nil { @@ -104,12 +104,12 @@ func (a *attestedNodes) selectPolledEvents(ctx context.Context) { func (a *attestedNodes) scanForNewEvents(ctx context.Context) error { // If we haven't seen an event, scan for all events; otherwise, scan from the last event. - var resp *datastore.ListAttestedNodesEventsResponse + var resp *datastore.ListAttestedNodeEventsResponse var err error if a.firstEventTime.IsZero() { - resp, err = a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{}) + resp, err = a.ds.ListAttestedNodeEvents(ctx, &datastore.ListAttestedNodeEventsRequest{}) } else { - resp, err = a.ds.ListAttestedNodesEvents(ctx, &datastore.ListAttestedNodesEventsRequest{ + resp, err = a.ds.ListAttestedNodeEvents(ctx, &datastore.ListAttestedNodeEventsRequest{ GreaterThanEventID: a.lastEvent, }) } diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index b2529db6e2..140aa59b24 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -1451,7 +1451,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { attestedNodes.fetchNodes[fetchNode] = struct{}{} } // clear out the events, to prove updates are not event based - err = scenario.ds.PruneAttestedNodesEvents(scenario.ctx, time.Duration(-5)*time.Hour) + err = scenario.ds.PruneAttestedNodeEvents(scenario.ctx, time.Duration(-5)*time.Hour) require.NoError(t, err, "error while setting up test") err = attestedNodes.updateCachedNodes(scenario.ctx) @@ -1510,7 +1510,7 @@ func NewNodeScenario(t *testing.T, setup *nodeScenarioSetup) *scenario { } // prune autocreated node events, to test the event logic in more scenarios // than possible with autocreated node events. - err = ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + err = ds.PruneAttestedNodeEvents(ctx, time.Duration(-5)*time.Hour) require.NoError(t, err, "error while setting up test") // and then add back the specified node events for _, event := range setup.attestedNodeEvents { diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 59a69308b1..422680d457 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -1873,7 +1873,7 @@ func NewEntryScenario(t *testing.T, setup *entryScenarioSetup) *entryScenario { } // prune autocreated node events, to test the event logic in more scenarios // than possible with autocreated node events. - err = ds.PruneAttestedNodesEvents(ctx, time.Duration(-5)*time.Hour) + err = ds.PruneAttestedNodeEvents(ctx, time.Duration(-5)*time.Hour) require.NoError(t, err, "error while setting up test") // and then add back the specified node events for _, event := range setup.attestedNodeEvents { diff --git a/pkg/server/endpoints/entryfetcher.go b/pkg/server/endpoints/entryfetcher.go index 4f249818e9..19e2e0b6b9 100644 --- a/pkg/server/endpoints/entryfetcher.go +++ b/pkg/server/endpoints/entryfetcher.go @@ -97,7 +97,7 @@ func (a *AuthorizedEntryFetcherWithFullCache) PruneEventsTask(ctx context.Contex func (a *AuthorizedEntryFetcherWithFullCache) pruneEvents(ctx context.Context, olderThan time.Duration) error { pruneRegistrationEntryEventsErr := a.ds.PruneRegistrationEntryEvents(ctx, olderThan) - pruneAttestedNodesEventsErr := a.ds.PruneAttestedNodesEvents(ctx, olderThan) + pruneAttestedNodeEventsErr := a.ds.PruneAttestedNodeEvents(ctx, olderThan) - return errors.Join(pruneRegistrationEntryEventsErr, pruneAttestedNodesEventsErr) + return errors.Join(pruneRegistrationEntryEventsErr, pruneAttestedNodeEventsErr) } diff --git a/test/fakes/fakedatastore/fakedatastore.go b/test/fakes/fakedatastore/fakedatastore.go index 6332a4f8d0..13728a13ea 100644 --- a/test/fakes/fakedatastore/fakedatastore.go +++ b/test/fakes/fakedatastore/fakedatastore.go @@ -162,18 +162,18 @@ func (s *DataStore) DeleteAttestedNode(ctx context.Context, spiffeID string) (*c return s.ds.DeleteAttestedNode(ctx, spiffeID) } -func (s *DataStore) ListAttestedNodesEvents(ctx context.Context, req *datastore.ListAttestedNodesEventsRequest) (*datastore.ListAttestedNodesEventsResponse, error) { +func (s *DataStore) ListAttestedNodeEvents(ctx context.Context, req *datastore.ListAttestedNodeEventsRequest) (*datastore.ListAttestedNodeEventsResponse, error) { if err := s.getNextError(); err != nil { return nil, err } - return s.ds.ListAttestedNodesEvents(ctx, req) + return s.ds.ListAttestedNodeEvents(ctx, req) } -func (s *DataStore) PruneAttestedNodesEvents(ctx context.Context, olderThan time.Duration) error { +func (s *DataStore) PruneAttestedNodeEvents(ctx context.Context, olderThan time.Duration) error { if err := s.getNextError(); err != nil { return err } - return s.ds.PruneAttestedNodesEvents(ctx, olderThan) + return s.ds.PruneAttestedNodeEvents(ctx, olderThan) } func (s *DataStore) CreateAttestedNodeEventForTesting(ctx context.Context, event *datastore.AttestedNodeEvent) error { From 61feaa47a6ea02e0870f2ce4e712440a9e35c2aa Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 16:35:30 -0700 Subject: [PATCH 21/32] Fix spelling items in comments for code review. Signed-off-by: Edwin Buck --- .../endpoints/authorized_entryfetcher_attested_nodes.go | 4 ++-- .../endpoints/authorized_entryfetcher_registration_entries.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 106ac70564..54e863d755 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -127,7 +127,7 @@ func (a *attestedNodes) scanForNewEvents(ctx context.Context) error { continue } - // track any skipped event ids, should the appear later. + // track any skipped event ids, should they appear later. for skipped := a.lastEvent + 1; skipped < event.EventID; skipped++ { a.eventTracker.StartTracking(skipped) } @@ -178,7 +178,7 @@ func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metric eventTracker: NewEventTracker(pollPeriods, pollBoundaries), - // initialize guages to nonsense values to force a change. + // initialize gauges to nonsense values to force a change. skippedNodeEvents: -1, lastCacheStats: authorizedentries.CacheStats{ AgentsByID: -1, diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index b689a3444a..9b72826451 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -126,7 +126,7 @@ func (a *registrationEntries) scanForNewEvents(ctx context.Context) error { continue } - // track any skipped event ids, should the appear later. + // track any skipped event ids, should they appear later. for skipped := a.lastEvent + 1; skipped < event.EventID; skipped++ { a.eventTracker.StartTracking(skipped) } From c5457cd87b97569186d15c9802dedb6cc48fbdea Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 1 Oct 2024 17:03:33 -0700 Subject: [PATCH 22/32] Better commentary around BoundaryBuilder. Signed-off-by: Edwin Buck --- pkg/server/endpoints/eventTracker.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 933fb8cf8d..0a8cc765d8 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -61,6 +61,11 @@ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { * Poll everything at poll rate for at least one minute, then poll everything * twice a minute for 9 minute, then onece a minute for rest of time, with a * guaranteed poll just before no longer tracking. + * + * This strategy is completely arbitrary. Future boundary building approaches + * may be added if necessary, like linear (5, 10, 15, 20, 25, 30, 35, ...), + * exponential (2, 4, 8, 16, 32, 64, 128, 256, ...), exponential capped at a + * limit (2, 4, 8, 16, 30, 60, 90, 120, ...), cube root, etc. */ func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { pollPeriods := PollPeriods(pollTime, trackTime) From d4225bf0c2a2af3fce061ee6252a1cbb56355885 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 7 Oct 2024 08:31:34 -0700 Subject: [PATCH 23/32] Fix update of poll count when an item is polled. This directs the item to look into the next poll boundary, which prevents a bug where the items wasn't finding the index to report, as it believed it was waiting on a future event, that occurred in the past. Signed-off-by: Edwin Buck --- pkg/server/endpoints/eventTracker.go | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 0a8cc765d8..fed696bde1 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -7,14 +7,28 @@ import ( ) /** - * Tracks events as they indivicually walk through a list of event boundaries. + * A dummy event tracker that polls every item every time it can. * - * An event track is defined with a set of boundaries, which are indexes to - * virtual hash tables, with the event's hash determining the position within - * that hash table where the event will be selected to be polled. - * For eventTRackers that lack boundaries, or polls that exist prior to - * boundaries, the event is always polled. + * This is a stub, and only exists for future replacement when + * be entangled in slow-to commit transactions. + * + * If an event is discovered to exist, please call "StopTracking(id)" to release + * the item from the tracker prior to hitting that item's remaining "PollPeriods" + * limit of time slices. */ +type EventTracker interface { + /* Starts tracking an event. */ + StartTracking(event uint) + /* Stops tracking an event. */ + StopTracking(event uint) + /* The nubmer of times an item will be considered for polling */ + PollPeriods() uint + /* The nubmer of times an item will be considered for polling */ + InitialPolls() uint + /* The maximum number of times an item will be polled */ + Polls() uint +} + type eventTracker struct { /* Times the event is polled before entering a boundary */ initialPolls uint @@ -212,6 +226,7 @@ func (et *eventTracker) SelectEvents() []uint { boundaryPosition := eventStats.hash % boundaryWidth if eventStats.ticks == et.boundaries[boundaryIndex]+boundaryPosition { pollList = append(pollList, event) + eventStats.polls++ } // last boundary case boundaryIndex < uint(len(et.boundaries)): @@ -219,6 +234,7 @@ func (et *eventTracker) SelectEvents() []uint { boundaryPosition := eventStats.hash % boundaryWidth if eventStats.ticks == et.boundaries[boundaryIndex]+boundaryPosition { pollList = append(pollList, event) + eventStats.polls++ } } eventStats.ticks++ From 55f82548ccfaa3fccce27bb382527fdabc90a019 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 7 Oct 2024 14:02:19 -0700 Subject: [PATCH 24/32] Fix eventTracker for multiple boundaries. Added unit testing for first three boundaries. Signed-off-by: Edwin Buck --- pkg/server/endpoints/eventTracker.go | 26 ++++++----------------- pkg/server/endpoints/eventTracker_test.go | 9 ++------ 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index fed696bde1..b7c17020ec 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -7,28 +7,14 @@ import ( ) /** - * A dummy event tracker that polls every item every time it can. + * Tracks events as they indivicually walk through a list of event boundaries. * - * This is a stub, and only exists for future replacement when - * be entangled in slow-to commit transactions. - * - * If an event is discovered to exist, please call "StopTracking(id)" to release - * the item from the tracker prior to hitting that item's remaining "PollPeriods" - * limit of time slices. + * An event track is defined with a set of boundaries, which are indexes to + * virtual hash tables, with the event's hash determining the position within + * that hash table where the event will be selected to be polled. + * For eventTRackers that lack boundaries, or polls that exist prior to + * boundaries, the event is always polled. */ -type EventTracker interface { - /* Starts tracking an event. */ - StartTracking(event uint) - /* Stops tracking an event. */ - StopTracking(event uint) - /* The nubmer of times an item will be considered for polling */ - PollPeriods() uint - /* The nubmer of times an item will be considered for polling */ - InitialPolls() uint - /* The maximum number of times an item will be polled */ - Polls() uint -} - type eventTracker struct { /* Times the event is polled before entering a boundary */ initialPolls uint diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index 466d2a44b1..f107f60ae6 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -66,7 +66,6 @@ func TestPollPeriods(t *testing.T) { expectedPollPeriods: 4, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { pollPeriods := endpoints.PollPeriods(tt.pollInterval, tt.pollDuration) @@ -207,7 +206,6 @@ func TestNewEventTracker(t *testing.T) { expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { eventTracker := endpoints.NewEventTracker(tt.pollPeriods, tt.boundaries) @@ -300,7 +298,6 @@ func TestEvenTrackerPolling(t *testing.T) { }, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { eventTracker := endpoints.NewEventTracker(tt.pollPeriods, tt.boundaries) require.Equal(t, tt.expectedPolls, eventTracker.Polls(), @@ -944,15 +941,14 @@ func TestEvenDispersion(t *testing.T) { permissibleCountError: 50, // 5% of 1000 }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { - eventTracker := endpoints.NewEventTracker(tt.pollPeriods, []uint{0}) + eventTracker := endpoints.NewEventTracker(3*tt.pollPeriods, []uint{0, tt.pollPeriods, 2 * tt.pollPeriods}) slotCount := make(map[uint]uint) for item := range tt.eventCount { event := tt.startEvent + tt.eventIncrement*item eventTracker.StartTracking(event) } - for pollPeriod := range tt.pollPeriods { + for pollPeriod := range 3 * tt.pollPeriods { events := eventTracker.SelectEvents() slotCount[pollPeriod] = uint(len(events)) t.Logf("pollPeriod %d, count = %d", pollPeriod, slotCount[pollPeriod]) @@ -1104,7 +1100,6 @@ func TestBoundaryBuilder(t *testing.T) { }, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { pollInterval, err := time.ParseDuration(tt.pollInterval) require.NoError(t, err, "error in specifying test poll interval") From 9a9a942e9dfb3635abf239f78a9c67f294c343ac Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Mon, 7 Oct 2024 17:10:11 -0700 Subject: [PATCH 25/32] Removed inner loop assignment as capture problem fixed in language. Signed-off-by: Edwin Buck --- .../authorized_entryfetcher_attested_nodes_test.go | 1 - .../authorized_entryfetcher_registration_entries_test.go | 6 +----- pkg/server/endpoints/middleware_test.go | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 140aa59b24..db53c7d161 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -194,7 +194,6 @@ func TestLoadNodeCache(t *testing.T) { expectedAuthorizedEntries: []string{}, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { scenario := NewNodeScenario(t, tt.setup) attestedNodes, err := scenario.buildAttestedNodesCache() diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 422680d457..4bfc08d28c 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -19,10 +19,7 @@ import ( "github.com/spiffe/spire/test/fakes/fakedatastore" "github.com/spiffe/spire/test/fakes/fakemetrics" "github.com/stretchr/testify/require" - /* - "github.com/spiffe/go-spiffe/v2/spiffeid" - "github.com/spiffe/spire/pkg/common/idutil" - */) +) var ( NodeAliasesByEntryID = []string{telemetry.Entry, telemetry.NodeAliasesByEntryIDCache, telemetry.Count} @@ -373,7 +370,6 @@ func TestLoadEntryCache(t *testing.T) { }, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { scenario := NewEntryScenario(t, tt.setup) registrationEntries, err := scenario.buildRegistrationEntriesCache() diff --git a/pkg/server/endpoints/middleware_test.go b/pkg/server/endpoints/middleware_test.go index 84d3862fc9..d3b668e2a1 100644 --- a/pkg/server/endpoints/middleware_test.go +++ b/pkg/server/endpoints/middleware_test.go @@ -227,7 +227,6 @@ func TestAgentAuthorizer(t *testing.T) { }, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { log, hook := test.NewNullLogger() ds := fakedatastore.New(t) From ee1ba09b77f41267ed293df2e7f63a751480129f Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Wed, 9 Oct 2024 12:20:26 -0700 Subject: [PATCH 26/32] Fixed small typos and style choices for code review. Signed-off-by: Edwin Buck --- pkg/server/datastore/sqlstore/sqlstore.go | 2 - ...orized_entryfetcher_attested_nodes_test.go | 103 +++++++++--------- pkg/server/endpoints/eventTracker.go | 14 +-- pkg/server/endpoints/eventTracker_test.go | 10 +- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index c3180a9b1a..797ec6023d 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -295,8 +295,6 @@ func (ds *Plugin) CreateAttestedNode(ctx context.Context, node *common.AttestedN if err != nil { return err } - // TODO: this is at the wrong level of the software stack. - // It should be created in the caller of the datastore interface. return createAttestedNodeEvent(tx, &datastore.AttestedNodeEvent{ SpiffeID: node.SpiffeId, }) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index db53c7d161..9270b69f74 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "maps" + "reflect" "slices" "strings" "testing" @@ -30,11 +31,11 @@ var ( // defaults used to setup a small initial load of attested nodes and events. defaultAttestedNodes = []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -83,7 +84,7 @@ func TestLoadNodeCache(t *testing.T) { name: "initial load loads one attested node", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, @@ -102,23 +103,23 @@ func TestLoadNodeCache(t *testing.T) { name: "initial load loads five attested nodes", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_5", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, @@ -136,23 +137,23 @@ func TestLoadNodeCache(t *testing.T) { name: "initial load loads five attested nodes, one expired", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_5", CertNotAfter: time.Now().Add(time.Duration(5) * time.Hour).Unix(), }, @@ -169,23 +170,23 @@ func TestLoadNodeCache(t *testing.T) { name: "initial load loads five attested nodes, all expired", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_5", CertNotAfter: time.Now().Add(time.Duration(-5) * time.Hour).Unix(), }, @@ -206,8 +207,8 @@ func TestLoadNodeCache(t *testing.T) { cacheStats := attestedNodes.cache.Stats() require.Equal(t, len(tt.expectedAuthorizedEntries), cacheStats.AgentsByID, "wrong number of agents by ID") - // for now, the only way to ensure the desired agent ids are prsent is - // to remove the desired ids and check the count it zero. + // for now, the only way to ensure the desired agent ids are present is + // to remove the desired ids and check the count is zero. for _, expectedAuthorizedId := range tt.expectedAuthorizedEntries { attestedNodes.cache.RemoveAgent(expectedAuthorizedId) } @@ -594,7 +595,7 @@ func TestSearchBeforeFirstNodeEvent(t *testing.T) { require.NoError(t, err) if tt.waitToPoll == 0 { - scenario.clk.Add(time.Duration(1) * defaultCacheReloadInterval) + scenario.clk.Add(defaultCacheReloadInterval) } else { scenario.clk.Add(tt.waitToPoll) } @@ -611,6 +612,8 @@ func TestSearchBeforeFirstNodeEvent(t *testing.T) { err = attestedNodes.searchBeforeFirstEvent(scenario.ctx) require.NoError(t, err, "error while running test") + t.Log(reflect.TypeOf(maps.Keys(attestedNodes.eventsBeforeFirst))) + require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(attestedNodes.eventsBeforeFirst)), "expected events before tracking mismatch") require.ElementsMatch(t, tt.expectedEventsBeforeFirst, slices.Collect(maps.Keys(attestedNodes.eventsBeforeFirst)), "expected events before tracking mismatch") require.ElementsMatch(t, tt.expectedFetches, slices.Collect[string](maps.Keys(attestedNodes.fetchNodes)), "expected fetches mismatch") @@ -1101,7 +1104,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { { name: "empty cache, fetch one node, as a new entry", createAttestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1123,23 +1126,23 @@ func TestUpdateAttestedNodesCache(t *testing.T) { { name: "empty cache, fetch five nodes, all new entries", createAttestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_5", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1163,15 +1166,15 @@ func TestUpdateAttestedNodesCache(t *testing.T) { { name: "empty cache, fetch five nodes, three new and two deletes", createAttestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1206,7 +1209,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, no fetch nodes", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1221,14 +1224,14 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, fetch one node, as new entry", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, }, }, createAttestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1246,7 +1249,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, fetch one node, as an update", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1264,7 +1267,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, fetch one node, as a delete", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1283,30 +1286,30 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, fetch five nodes, all new entries", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, }, }, createAttestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_5", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_6", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1332,26 +1335,26 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, fetch five nodes, four new entries and one update", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, }, }, createAttestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_4", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_5", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1376,18 +1379,18 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, fetch five nodes, two new and three deletes", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, }, }, createAttestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_1", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_2", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, @@ -1412,7 +1415,7 @@ func TestUpdateAttestedNodesCache(t *testing.T) { name: "one node in cache, fetch five nodes, all deletes", setup: &nodeScenarioSetup{ attestedNodes: []*common.AttestedNode{ - &common.AttestedNode{ + { SpiffeId: "spiffe://example.org/test_node_3", CertNotAfter: time.Now().Add(time.Duration(240) * time.Hour).Unix(), }, diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index b7c17020ec..17d978d19a 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -12,7 +12,7 @@ import ( * An event track is defined with a set of boundaries, which are indexes to * virtual hash tables, with the event's hash determining the position within * that hash table where the event will be selected to be polled. - * For eventTRackers that lack boundaries, or polls that exist prior to + * For eventTrackers that lack boundaries, or polls that exist prior to * boundaries, the event is always polled. */ type eventTracker struct { @@ -46,11 +46,11 @@ type eventStats struct { * @returns One tick, or the smallest number of ticks that just exceeds the trackTime.. */ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { - if pollTime < (time.Duration(1) * time.Second) { - pollTime = time.Duration(1) * time.Second + if pollTime < time.Second { + pollTime = time.Second } - if trackTime < (time.Duration(1) * time.Second) { - trackTime = time.Duration(1) * time.Second + if trackTime < time.Second { + trackTime = time.Second } return uint(1 + (trackTime-1)/pollTime) } @@ -59,7 +59,7 @@ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { * The default boundary strategy. * * Poll everything at poll rate for at least one minute, then poll everything - * twice a minute for 9 minute, then onece a minute for rest of time, with a + * twice a minute for 9 minute, then once a minute for rest of time, with a * guaranteed poll just before no longer tracking. * * This strategy is completely arbitrary. Future boundary building approaches @@ -71,7 +71,7 @@ func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { pollPeriods := PollPeriods(pollTime, trackTime) // number of polls in a minute - pollsPerMinute := uint(time.Duration(1) * time.Minute / pollTime) + pollsPerMinute := uint(time.Minute / pollTime) // number of polls in ten minutes pollsPerTenMinutes := uint(time.Duration(10) * time.Minute / pollTime) diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index f107f60ae6..4ab1bc2b49 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -18,14 +18,14 @@ func TestPollPeriods(t *testing.T) { }{ { name: "polling always polls at least once, even for zero duration", - pollInterval: time.Duration(1) * time.Minute, + pollInterval: time.Minute, pollDuration: time.Duration(0) * time.Minute, expectedPollPeriods: 1, }, { name: "polling always polls at least once, even for negative durations", - pollInterval: time.Duration(1) * time.Minute, + pollInterval: time.Minute, pollDuration: time.Duration(-10) * time.Minute, expectedPollPeriods: 1, @@ -46,15 +46,15 @@ func TestPollPeriods(t *testing.T) { }, { name: "polling every minute in two mintues", - pollInterval: time.Minute * time.Duration(1), + pollInterval: time.Minute, pollDuration: time.Minute * time.Duration(2), expectedPollPeriods: 2, }, { name: "polling every minute of an hours", - pollInterval: time.Minute * time.Duration(1), - pollDuration: time.Hour * time.Duration(1), + pollInterval: time.Minute, + pollDuration: time.Hour, expectedPollPeriods: 60, }, From 8fc09e3561fb545c14a288ca8775ae244b67cf0a Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Wed, 9 Oct 2024 12:33:50 -0700 Subject: [PATCH 27/32] Fixed export of metrics selector name sets in unit tests. Signed-off-by: Edwin Buck --- ...orized_entryfetcher_attested_nodes_test.go | 16 +++--- ..._entryfetcher_registration_entries_test.go | 50 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go index 9270b69f74..ba50386079 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes_test.go @@ -25,9 +25,9 @@ import ( ) var ( - CachedAgentsByID = []string{telemetry.Node, telemetry.AgentsByIDCache, telemetry.Count} - CachedAgentsByExpiresAt = []string{telemetry.Node, telemetry.AgentsByExpiresAtCache, telemetry.Count} - SkippedNodeEventID = []string{telemetry.Node, telemetry.SkippedNodeEventIDs, telemetry.Count} + cachedAgentsByID = []string{telemetry.Node, telemetry.AgentsByIDCache, telemetry.Count} + cachedAgentsByExpiresAt = []string{telemetry.Node, telemetry.AgentsByExpiresAtCache, telemetry.Count} + skippedNodeEventID = []string{telemetry.Node, telemetry.SkippedNodeEventIDs, telemetry.Count} // defaults used to setup a small initial load of attested nodes and events. defaultAttestedNodes = []*common.AttestedNode{ @@ -53,7 +53,7 @@ var ( defaultFirstNodeEvent = uint(60) defaultLastNodeEvent = uint(61) - NoNodeFetches = []string{} + noNodeFetches = []string{} ) type expectedGauge struct { @@ -94,9 +94,9 @@ func TestLoadNodeCache(t *testing.T) { "spiffe://example.org/test_node_1", }, expectedGauges: []expectedGauge{ - expectedGauge{Key: SkippedNodeEventID, Value: 0}, - expectedGauge{Key: CachedAgentsByID, Value: 1}, - expectedGauge{Key: CachedAgentsByExpiresAt, Value: 1}, + expectedGauge{Key: skippedNodeEventID, Value: 0}, + expectedGauge{Key: cachedAgentsByID, Value: 1}, + expectedGauge{Key: cachedAgentsByExpiresAt, Value: 1}, }, }, { @@ -272,7 +272,7 @@ func TestSearchBeforeFirstNodeEvent(t *testing.T) { }, expectedEventsBeforeFirst: []uint{}, - expectedFetches: NoNodeFetches, + expectedFetches: noNodeFetches, }, { name: "no before first events", diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go index 4bfc08d28c..2bd21cf98a 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries_test.go @@ -22,11 +22,11 @@ import ( ) var ( - NodeAliasesByEntryID = []string{telemetry.Entry, telemetry.NodeAliasesByEntryIDCache, telemetry.Count} - NodeAliasesBySelector = []string{telemetry.Entry, telemetry.NodeAliasesBySelectorCache, telemetry.Count} - EntriesByEntryID = []string{telemetry.Entry, telemetry.EntriesByEntryIDCache, telemetry.Count} - EntriesByParentID = []string{telemetry.Entry, telemetry.EntriesByParentIDCache, telemetry.Count} - SkippedEntryEventID = []string{telemetry.Entry, telemetry.SkippedEntryEventIDs, telemetry.Count} + nodeAliasesByEntryID = []string{telemetry.Entry, telemetry.NodeAliasesByEntryIDCache, telemetry.Count} + nodeAliasesBySelector = []string{telemetry.Entry, telemetry.NodeAliasesBySelectorCache, telemetry.Count} + entriesByEntryID = []string{telemetry.Entry, telemetry.EntriesByEntryIDCache, telemetry.Count} + entriesByParentID = []string{telemetry.Entry, telemetry.EntriesByParentIDCache, telemetry.Count} + skippedEntryEventID = []string{telemetry.Entry, telemetry.SkippedEntryEventIDs, telemetry.Count} defaultRegistrationEntries = []*common.RegistrationEntry{ &common.RegistrationEntry{ @@ -127,11 +127,11 @@ func TestLoadEntryCache(t *testing.T) { "6837984a-bc44-462b-9ca6-5cd59be35066", }, expectedGauges: []expectedGauge{ - expectedGauge{Key: SkippedEntryEventID, Value: 0}, - expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, - expectedGauge{Key: NodeAliasesBySelector, Value: 0}, - expectedGauge{Key: EntriesByEntryID, Value: 1}, - expectedGauge{Key: EntriesByParentID, Value: 1}, + expectedGauge{Key: skippedEntryEventID, Value: 0}, + expectedGauge{Key: nodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: nodeAliasesBySelector, Value: 0}, + expectedGauge{Key: entriesByEntryID, Value: 1}, + expectedGauge{Key: entriesByParentID, Value: 1}, }, }, { @@ -238,11 +238,11 @@ func TestLoadEntryCache(t *testing.T) { "354c16f4-4e61-4c17-8596-7baa7744d504", }, expectedGauges: []expectedGauge{ - expectedGauge{Key: SkippedEntryEventID, Value: 0}, - expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, - expectedGauge{Key: NodeAliasesBySelector, Value: 0}, - expectedGauge{Key: EntriesByEntryID, Value: 5}, - expectedGauge{Key: EntriesByParentID, Value: 5}, + expectedGauge{Key: skippedEntryEventID, Value: 0}, + expectedGauge{Key: nodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: nodeAliasesBySelector, Value: 0}, + expectedGauge{Key: entriesByEntryID, Value: 5}, + expectedGauge{Key: entriesByParentID, Value: 5}, }, }, { @@ -300,11 +300,11 @@ func TestLoadEntryCache(t *testing.T) { "354c16f4-4e61-4c17-8596-7baa7744d504", }, expectedGauges: []expectedGauge{ - expectedGauge{Key: SkippedEntryEventID, Value: 0}, - expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, - expectedGauge{Key: NodeAliasesBySelector, Value: 0}, - expectedGauge{Key: EntriesByEntryID, Value: 5}, - expectedGauge{Key: EntriesByParentID, Value: 5}, + expectedGauge{Key: skippedEntryEventID, Value: 0}, + expectedGauge{Key: nodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: nodeAliasesBySelector, Value: 0}, + expectedGauge{Key: entriesByEntryID, Value: 5}, + expectedGauge{Key: entriesByParentID, Value: 5}, }, }, { @@ -362,11 +362,11 @@ func TestLoadEntryCache(t *testing.T) { "354c16f4-4e61-4c17-8596-7baa7744d504", }, expectedGauges: []expectedGauge{ - expectedGauge{Key: SkippedEntryEventID, Value: 0}, - expectedGauge{Key: NodeAliasesByEntryID, Value: 0}, - expectedGauge{Key: NodeAliasesBySelector, Value: 0}, - expectedGauge{Key: EntriesByEntryID, Value: 5}, - expectedGauge{Key: EntriesByParentID, Value: 5}, + expectedGauge{Key: skippedEntryEventID, Value: 0}, + expectedGauge{Key: nodeAliasesByEntryID, Value: 0}, + expectedGauge{Key: nodeAliasesBySelector, Value: 0}, + expectedGauge{Key: entriesByEntryID, Value: 5}, + expectedGauge{Key: entriesByParentID, Value: 5}, }, }, } { From ac075b80cee58414964f162d1fe0a53c836d3d61 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Wed, 9 Oct 2024 12:44:13 -0700 Subject: [PATCH 28/32] Add in sync.Pool semantics to avoid memory trashing / overallocation. Signed-off-by: Edwin Buck --- pkg/server/endpoints/eventTracker.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 17d978d19a..718f09df1e 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -3,6 +3,7 @@ package endpoints import ( "maps" "slices" + "sync" "time" ) @@ -24,6 +25,8 @@ type eventTracker struct { events map[uint]*eventStats /* The leading index of boundaries in which an event should only report once */ boundaries []uint + + pool sync.Pool } /** @@ -130,6 +133,11 @@ func NewEventTracker(pollPeriods uint, boundaries []uint) *eventTracker { pollPeriods: pollPeriods, boundaries: filteredBounds, events: make(map[uint]*eventStats), + pool: sync.Pool{ + New: func() any { + return []uint(nil) + }, + }, } } @@ -193,7 +201,7 @@ func (et *eventTracker) StopTracking(event uint) { * the poll list. */ func (et *eventTracker) SelectEvents() []uint { - pollList := make([]uint, 0) + pollList := et.pool.Get().([]uint) for event, _ := range et.events { if et.events[event].ticks >= et.pollPeriods { et.StopTracking(event) From 59f2b7a47e3c14703dc917c47c00e58966e37bac Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Tue, 15 Oct 2024 12:19:32 -0700 Subject: [PATCH 29/32] Remove backoff polling. This limits the solution to full polling as before, but still preserves the work that would close #5349. Signed-off-by: Edwin Buck --- .../authorized_entryfetcher_attested_nodes.go | 3 +- ...rized_entryfetcher_registration_entries.go | 3 +- pkg/server/endpoints/eventTracker.go | 219 +---- pkg/server/endpoints/eventTracker_test.go | 912 +----------------- 4 files changed, 34 insertions(+), 1103 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 54e863d755..4ba853daf1 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -163,7 +163,6 @@ func (a *attestedNodes) loadCache(ctx context.Context) error { // It runs once at startup. func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, cacheReloadInterval, sqlTransactionTimeout time.Duration) (*attestedNodes, error) { pollPeriods := PollPeriods(cacheReloadInterval, sqlTransactionTimeout) - pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) attestedNodes := &attestedNodes{ cache: cache, @@ -176,7 +175,7 @@ func buildAttestedNodesCache(ctx context.Context, log logrus.FieldLogger, metric eventsBeforeFirst: make(map[uint]struct{}), fetchNodes: make(map[string]struct{}), - eventTracker: NewEventTracker(pollPeriods, pollBoundaries), + eventTracker: NewEventTracker(pollPeriods), // initialize gauges to nonsense values to force a change. skippedNodeEvents: -1, diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index 9b72826451..9ce315a7fa 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -173,7 +173,6 @@ func (a *registrationEntries) loadCache(ctx context.Context, pageSize int32) err // buildRegistrationEntriesCache Fetches all registration entries and adds them to the cache func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, metrics telemetry.Metrics, ds datastore.DataStore, clk clock.Clock, cache *authorizedentries.Cache, pageSize int32, cacheReloadInterval, sqlTransactionTimeout time.Duration) (*registrationEntries, error) { pollPeriods := PollPeriods(cacheReloadInterval, sqlTransactionTimeout) - pollBoundaries := BoundaryBuilder(cacheReloadInterval, sqlTransactionTimeout) registrationEntries := ®istrationEntries{ cache: cache, @@ -186,7 +185,7 @@ func buildRegistrationEntriesCache(ctx context.Context, log logrus.FieldLogger, eventsBeforeFirst: make(map[uint]struct{}), fetchEntries: make(map[string]struct{}), - eventTracker: NewEventTracker(pollPeriods, pollBoundaries), + eventTracker: NewEventTracker(pollPeriods), skippedEntryEvents: -1, lastCacheStats: authorizedentries.CacheStats{ diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 718f09df1e..6c3614690e 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -1,53 +1,18 @@ package endpoints import ( - "maps" - "slices" "sync" "time" ) -/** - * Tracks events as they indivicually walk through a list of event boundaries. - * - * An event track is defined with a set of boundaries, which are indexes to - * virtual hash tables, with the event's hash determining the position within - * that hash table where the event will be selected to be polled. - * For eventTrackers that lack boundaries, or polls that exist prior to - * boundaries, the event is always polled. - */ type eventTracker struct { - /* Times the event is polled before entering a boundary */ - initialPolls uint - /* Times the event is polled */ pollPeriods uint - /* Per event context of each event's walk across the boudaries */ - events map[uint]*eventStats - /* The leading index of boundaries in which an event should only report once */ - boundaries []uint - pool sync.Pool -} + events map[uint]uint -/** - * Tracks event context in its walk of the event boundaries. - */ -type eventStats struct { - /* The event's hash for hash table calcuations */ - hash uint - /* The number of times the event was considered for polling */ - ticks uint - /* The number of times the event was selected for polling */ - polls uint + pool sync.Pool } -/** - * A utility function to get the number of PollTimes (ticks) in an interval. - * - * Subsecond inputs are adjusted to a minimum value one second. - * - * @returns One tick, or the smallest number of ticks that just exceeds the trackTime.. - */ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { if pollTime < time.Second { pollTime = time.Second @@ -58,81 +23,14 @@ func PollPeriods(pollTime time.Duration, trackTime time.Duration) uint { return uint(1 + (trackTime-1)/pollTime) } -/** - * The default boundary strategy. - * - * Poll everything at poll rate for at least one minute, then poll everything - * twice a minute for 9 minute, then once a minute for rest of time, with a - * guaranteed poll just before no longer tracking. - * - * This strategy is completely arbitrary. Future boundary building approaches - * may be added if necessary, like linear (5, 10, 15, 20, 25, 30, 35, ...), - * exponential (2, 4, 8, 16, 32, 64, 128, 256, ...), exponential capped at a - * limit (2, 4, 8, 16, 30, 60, 90, 120, ...), cube root, etc. - */ -func BoundaryBuilder(pollTime time.Duration, trackTime time.Duration) []uint { - pollPeriods := PollPeriods(pollTime, trackTime) - - // number of polls in a minute - pollsPerMinute := uint(time.Minute / pollTime) - // number of polls in ten minutes - pollsPerTenMinutes := uint(time.Duration(10) * time.Minute / pollTime) - - // initialize poll boundaries one minute out - boundaries := make(map[uint]struct{}) - currentBoundary := pollsPerMinute - for currentBoundary < pollPeriods { - if currentBoundary < pollsPerTenMinutes { - boundaries[currentBoundary] = struct{}{} - boundaries[currentBoundary+(pollsPerMinute/2)] = struct{}{} - } else { - boundaries[currentBoundary] = struct{}{} - } - currentBoundary += pollsPerMinute - } - if 0 < len(boundaries) { - boundaries[pollPeriods-1] = struct{}{} - } - - boundaryList := slices.Collect(maps.Keys(boundaries)) - slices.Sort(boundaryList) - if boundaryList == nil { - boundaryList = []uint{} - } - - return boundaryList -} - -func NewEventTracker(pollPeriods uint, boundaries []uint) *eventTracker { +func NewEventTracker(pollPeriods uint) *eventTracker { if pollPeriods < 1 { pollPeriods = 1 } - // cleanup boundaries into incrasing slice of no duplicates - boundaryMap := make(map[uint]bool) - filteredBounds := []uint{} - for _, boundary := range boundaries { - // trim duplicates and boundaries outside of polling range - if _, found := boundaryMap[boundary]; !found && boundary < pollPeriods { - boundaryMap[boundary] = true - filteredBounds = append(filteredBounds, boundary) - } - } - slices.Sort(filteredBounds) - - initialPolls := uint(0) - switch { - case boundaries == nil, len(filteredBounds) == 0: - initialPolls = pollPeriods - default: - initialPolls = filteredBounds[0] - } - return &eventTracker{ - initialPolls: initialPolls, - pollPeriods: pollPeriods, - boundaries: filteredBounds, - events: make(map[uint]*eventStats), + pollPeriods: pollPeriods, + events: make(map[uint]uint), pool: sync.Pool{ New: func() any { return []uint(nil) @@ -145,128 +43,31 @@ func (et *eventTracker) PollPeriods() uint { return et.pollPeriods } -func (et *eventTracker) InitialPolls() uint { - return et.initialPolls -} - -func (et *eventTracker) PollBoundaries() []uint { - return et.boundaries -} - func (et *eventTracker) Polls() uint { - return et.initialPolls + uint(len(et.boundaries)) + return et.pollPeriods } -/** - * Starts tracking an event's walk through the boundaries. - */ func (et *eventTracker) StartTracking(event uint) { - et.events[event] = &eventStats{ - hash: hash(event), - ticks: 0, - polls: 0, - } + et.events[event] = 0 } -/** - * Remove an event from the tracker. - * - * Events not explicitly removed will remove themselves - * after SelectEvents has been called PollPeriods() number - * of times. - */ func (et *eventTracker) StopTracking(event uint) { delete(et.events, event) } -/** - * Selects the events one should pool for the next poll cycle. - * - * This algorithm determines if the events should be polled, and - * increments each event's time, allowing every event to act on - * the time the event was inserted into eventTracker. - * - * The event's boundary Index is computed, and if it is below the - * number of initial polls, the event is polled without further - * analysis. - * - * If the boundary index is inside a defined boundary, the width - * of the boundary is computed and the event's position within - * the virtual hash table is computed from "hash(event) % width". - * As the hash and width are stable, an event will always remain - * in the same slot within a boundary. - * - * If the event's ticks (the events local sense of time) match - * the event's slot within the boundary, the event is added to - * the poll list. - */ func (et *eventTracker) SelectEvents() []uint { pollList := et.pool.Get().([]uint) for event, _ := range et.events { - if et.events[event].ticks >= et.pollPeriods { + if et.events[event] >= et.pollPeriods { et.StopTracking(event) continue } - eventStats := et.events[event] - boundaryIndex := eventStats.polls - et.initialPolls - switch { - // before boundaries - case eventStats.polls < et.initialPolls: - pollList = append(pollList, event) - eventStats.polls++ - // between boundaries - case boundaryIndex+1 < uint(len(et.boundaries)): - boundaryWidth := et.boundaries[1+boundaryIndex] - et.boundaries[boundaryIndex] - boundaryPosition := eventStats.hash % boundaryWidth - if eventStats.ticks == et.boundaries[boundaryIndex]+boundaryPosition { - pollList = append(pollList, event) - eventStats.polls++ - } - // last boundary - case boundaryIndex < uint(len(et.boundaries)): - boundaryWidth := et.pollPeriods - et.boundaries[boundaryIndex] - boundaryPosition := eventStats.hash % boundaryWidth - if eventStats.ticks == et.boundaries[boundaryIndex]+boundaryPosition { - pollList = append(pollList, event) - eventStats.polls++ - } - } - eventStats.ticks++ + pollList = append(pollList, event) + et.events[event]++ } return pollList } -/** - * Returns the count of events being tracked. - * - * @return the events being tracked. - */ func (et *eventTracker) EventCount() uint { return uint(len(et.events)) } - -/** - * A hash function for uint. - * - * The slots within a boundary are conceptually a hash table, even - * though the hash table doesn't exist as an struct. This means that - * each event being polled must distribute within the conceptual hash - * table evenly, or the conceptual hash table will only have entries in - * a subset of slots. - * - * This hashing algorithm is the modification of a number of algorithms - * previously found on the internet. It avoids the factorization problem - * (even events only go into even slots) by repeatedly mixing high order - * bits into the low order bits ( h^h >> (number)). The high order bits - * are primarily set by the low order bits repeatedly by multipliation with - * a number designed to mix bits deterministicly for better hash dispersion. - */ -func hash(event uint) uint { - h := event - h ^= h >> 16 - h *= 0x119de1f3 - h ^= h >> 15 - h *= 0x119de1f3 - h ^= h >> 16 - return h -} diff --git a/pkg/server/endpoints/eventTracker_test.go b/pkg/server/endpoints/eventTracker_test.go index 4ab1bc2b49..be86bce4b9 100644 --- a/pkg/server/endpoints/eventTracker_test.go +++ b/pkg/server/endpoints/eventTracker_test.go @@ -78,141 +78,58 @@ func TestNewEventTracker(t *testing.T) { for _, tt := range []struct { name string pollPeriods uint - boundaries []uint - expectedInitialPolls uint - expectedPollPeriods uint - expectedPolls uint - expectedBoundaries []uint + expectedPollPeriods uint + expectedPolls uint }{ { name: "polling always polls at least once", pollPeriods: 0, - boundaries: []uint{}, - - expectedInitialPolls: 1, - expectedPollPeriods: 1, - expectedPolls: 1, - expectedBoundaries: []uint{}, - }, - { - name: "polling once, pre-boundary", - pollPeriods: 1, - boundaries: []uint{}, - expectedInitialPolls: 1, - expectedPollPeriods: 1, - expectedPolls: 1, - expectedBoundaries: []uint{}, + expectedPollPeriods: 1, + expectedPolls: 1, }, { - name: "polling once, in one bucket boundary", + name: "polling once", pollPeriods: 1, - boundaries: []uint{0}, - - expectedInitialPolls: 0, - expectedPollPeriods: 1, - expectedPolls: 1, - expectedBoundaries: []uint{0}, - }, - { - name: "polling twice, both initial", - pollPeriods: 2, - boundaries: []uint{}, - - expectedInitialPolls: 2, - expectedPollPeriods: 2, - expectedPolls: 2, - expectedBoundaries: []uint{}, - }, - { - name: "polling twice, once initial, once in one bucket boundary", - pollPeriods: 2, - boundaries: []uint{1}, - expectedInitialPolls: 1, - expectedPollPeriods: 2, - expectedPolls: 2, - expectedBoundaries: []uint{1}, + expectedPollPeriods: 1, + expectedPolls: 1, }, { - name: "polling once, in two bucket boundary", + name: "polling twice", pollPeriods: 2, - boundaries: []uint{0}, - expectedInitialPolls: 0, - expectedPollPeriods: 2, - expectedPolls: 1, - expectedBoundaries: []uint{0}, + expectedPollPeriods: 2, + expectedPolls: 2, }, { - name: "polling once, in three bucket boundary", + name: "polling three times", pollPeriods: 3, - boundaries: []uint{0}, - expectedInitialPolls: 0, - expectedPollPeriods: 3, - expectedPolls: 1, - expectedBoundaries: []uint{0}, + expectedPollPeriods: 3, + expectedPolls: 3, }, { - name: "polling six times in exponential backoff", + name: "polling 120 times", pollPeriods: 120, - boundaries: []uint{0, 2, 6, 14, 30, 62}, - expectedInitialPolls: 0, - expectedPollPeriods: 120, - expectedPolls: 6, - expectedBoundaries: []uint{0, 2, 6, 14, 30, 62}, - }, - { - name: "distributed linear polling for a while, then exponential", - pollPeriods: 600, - boundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, - - expectedInitialPolls: 0, - expectedPollPeriods: 600, - expectedPolls: 10, - expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, - }, - { - name: "clip boundaries outside of poll periods", - pollPeriods: 600, - boundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480, 9600}, - - expectedInitialPolls: 0, - expectedPollPeriods: 600, - expectedPolls: 10, - expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, - }, - { - name: "order of boundaries doesn't matter", - pollPeriods: 600, - boundaries: []uint{240, 480, 9600, 0, 10, 50, 60, 120, 20, 30, 40}, - - expectedInitialPolls: 0, - expectedPollPeriods: 600, - expectedPolls: 10, - expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, + expectedPollPeriods: 120, + expectedPolls: 120, }, { - name: "duplicate boundaries are collapsed", + name: "polling 600 times", pollPeriods: 600, - boundaries: []uint{0, 10, 10, 10, 20, 30, 40, 50, 60, 60, 120, 240, 480, 240, 9600}, - expectedInitialPolls: 0, - expectedPollPeriods: 600, - expectedPolls: 10, - expectedBoundaries: []uint{0, 10, 20, 30, 40, 50, 60, 120, 240, 480}, + expectedPollPeriods: 600, + expectedPolls: 600, }, } { t.Run(tt.name, func(t *testing.T) { - eventTracker := endpoints.NewEventTracker(tt.pollPeriods, tt.boundaries) + eventTracker := endpoints.NewEventTracker(tt.pollPeriods) - require.Equal(t, tt.expectedPollPeriods, eventTracker.PollPeriods(), "expcting %d poll periods; but, %d poll periods reported", eventTracker.PollPeriods(), tt.expectedPollPeriods) + require.Equal(t, tt.expectedPollPeriods, eventTracker.PollPeriods(), "expecting %d poll periods; but, %d poll periods reported", eventTracker.PollPeriods(), tt.expectedPollPeriods) - require.Equal(t, tt.expectedBoundaries, eventTracker.PollBoundaries(), "expected %v boundaries, not %v boundaries", eventTracker.PollBoundaries(), tt.expectedBoundaries) - require.Equal(t, tt.expectedInitialPolls, eventTracker.InitialPolls(), "initial polls of %d when requesting %d", eventTracker.InitialPolls(), tt.expectedInitialPolls) require.Equal(t, tt.expectedPolls, eventTracker.Polls(), "polling each element %d times, when expecting %d times", tt.expectedPolls, eventTracker.Polls()) }) } @@ -222,7 +139,6 @@ func TestEvenTrackerPolling(t *testing.T) { for _, tt := range []struct { name string pollPeriods uint - boundaries []uint trackEvents [][]uint expectedPolls uint @@ -231,7 +147,6 @@ func TestEvenTrackerPolling(t *testing.T) { { name: "every event is polled at least once, even when zero polling periods", pollPeriods: 0, - boundaries: []uint{}, trackEvents: [][]uint{ {5, 11, 12, 15}, {6, 7, 8, 9, 10}, @@ -247,7 +162,6 @@ func TestEvenTrackerPolling(t *testing.T) { { name: "polling each event once, initial period", pollPeriods: 1, - boundaries: []uint{}, trackEvents: [][]uint{ {5, 11, 12, 15}, {6, 7, 8, 9, 10}, @@ -263,7 +177,6 @@ func TestEvenTrackerPolling(t *testing.T) { { name: "polling each event twice, initial period", pollPeriods: 2, - boundaries: []uint{}, trackEvents: [][]uint{ {5, 11, 12, 15}, {6, 7, 8, 9, 10}, @@ -280,7 +193,6 @@ func TestEvenTrackerPolling(t *testing.T) { { name: "polling each event thrice, initial period", pollPeriods: 3, - boundaries: []uint{}, trackEvents: [][]uint{ {5, 11, 12, 15}, {6, 7, 8, 9, 10}, @@ -299,7 +211,7 @@ func TestEvenTrackerPolling(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - eventTracker := endpoints.NewEventTracker(tt.pollPeriods, tt.boundaries) + eventTracker := endpoints.NewEventTracker(tt.pollPeriods) require.Equal(t, tt.expectedPolls, eventTracker.Polls(), "expecting %d polls per event, but event tracker reports %d polls per event", tt.expectedPolls, eventTracker.Polls()) @@ -333,783 +245,3 @@ func TestEvenTrackerPolling(t *testing.T) { }) } } - -func TestEvenDispersion(t *testing.T) { - for _, tt := range []struct { - name string - pollPeriods uint - - startEvent uint - eventIncrement uint - eventCount uint - - expectedSlotCount uint - permissibleCountError uint - }{ - { - // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, - eventIncrement: 2, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 1, 3, 5, 7, 9, 11, 13, ... - name: "increment by 2 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, - eventIncrement: 2, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 0, 3, 6, 9, 12, 15, ... - name: "increment by 3 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, - eventIncrement: 3, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 1, 4, 7, 10, 13, 16, ... - name: "increment by 3 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, - eventIncrement: 3, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 2, 5, 8, 11, 14, 17, ... - name: "increment by 3 (offset 2) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 2, - eventIncrement: 3, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 0, 4, 8, 12, 16, 20, ... - name: "increment by 4 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, - eventIncrement: 4, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 1, 5, 9, 13, 17, 21, ... - name: "increment by 4 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, - eventIncrement: 4, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 2, 6, 10, 14, 18, 22, ... - name: "increment by 4 (offset 2) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 2, - eventIncrement: 4, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 3, 7, 11, 15, 19, 23, ... - name: "increment by 4 (offset 3) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 3, - eventIncrement: 4, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 0, 5, 10, 15, 20, 25, ... - name: "increment by 5 (offset 0) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 0, - eventIncrement: 5, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 1, 6, 11, 16, 21, 26, ... - name: "increment by 5 (offset 1) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 1, - eventIncrement: 5, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 2, 7, 12, 17, 22, 27, ... - name: "increment by 5 (offset 2) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 2, - eventIncrement: 5, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 3, 8, 13, 18, 23, 28, ... - name: "increment by 5 (offset 3) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 3, - eventIncrement: 5, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 4, 9, 14, 19, 24, 29, ... - name: "increment by 5 (offset 4) events distribute fairly across 2 slots", - pollPeriods: 2, - startEvent: 4, - eventIncrement: 5, - eventCount: 1000, - - // should disperse into two slots, with an approximate count of [ 500, 500 ] - expectedSlotCount: 500, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, - eventIncrement: 2, - eventCount: 1000, - - // should disperse into three slots, with an approximate count of [ 333, 333, 333 ] - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 2 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, - eventIncrement: 2, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 2) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 2, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 2) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 2, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 3) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 3, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 0) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 0, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 1) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 1, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 2) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 2, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 3) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 3, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 4) events distribute fairly across 3 slots", - pollPeriods: 3, - startEvent: 4, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 333, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, - eventIncrement: 2, - eventCount: 1000, - - // should disperse into four slots, with an approximate count of [ 250, 250, 250, 250 ] - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 2 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, - eventIncrement: 2, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 2) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 2, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 2) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 2, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 3) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 3, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 0) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 0, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 1) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 1, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 2) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 2, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 3) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 3, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 4) events distribute fairly across 4 slots", - pollPeriods: 4, - startEvent: 4, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 250, - permissibleCountError: 50, // 5% of 1000 - }, - { - // sequence of Events: 0, 2, 4, 6, 8, 10, 12, ... - name: "increment by 2 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, - eventIncrement: 2, - eventCount: 1000, - - // should disperse into five slots, with an approximate count of [ 200, 200, 200, 200, 200 ] - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 2 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, - eventIncrement: 2, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 3 (offset 2) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 2, - eventIncrement: 3, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 2) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 2, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 4 (offset 3) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 3, - eventIncrement: 4, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 0) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 0, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 1) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 1, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 2) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 2, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 3) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 3, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - { - name: "increment by 5 (offset 4) events distribute fairly across 5 slots", - pollPeriods: 5, - startEvent: 4, - eventIncrement: 5, - eventCount: 1000, - - expectedSlotCount: 200, - permissibleCountError: 50, // 5% of 1000 - }, - } { - t.Run(tt.name, func(t *testing.T) { - eventTracker := endpoints.NewEventTracker(3*tt.pollPeriods, []uint{0, tt.pollPeriods, 2 * tt.pollPeriods}) - slotCount := make(map[uint]uint) - for item := range tt.eventCount { - event := tt.startEvent + tt.eventIncrement*item - eventTracker.StartTracking(event) - } - for pollPeriod := range 3 * tt.pollPeriods { - events := eventTracker.SelectEvents() - slotCount[pollPeriod] = uint(len(events)) - t.Logf("pollPeriod %d, count = %d", pollPeriod, slotCount[pollPeriod]) - } - for slot, count := range slotCount { - require.LessOrEqual(t, tt.expectedSlotCount-tt.permissibleCountError, count, - "for slot %d, expecting at least %d polls, but received %d polls", - slot, tt.expectedSlotCount-tt.permissibleCountError, count) - require.GreaterOrEqual(t, tt.expectedSlotCount+tt.permissibleCountError, count, - "for slot %d, expecting no more than %d polls, but received %d polls", - slot, tt.expectedSlotCount+tt.permissibleCountError, count) - } - }) - } -} - -func TestBoundaryBuilder(t *testing.T) { - for _, tt := range []struct { - name string - pollInterval string - pollDuration string - - expectedPollPeriods uint - expectedBoundaries []uint - }{ - { - name: "poll every second, over 1 minute", - pollInterval: "1s", - pollDuration: "1m", - - expectedPollPeriods: 60, - expectedBoundaries: []uint{}, - }, - { - name: "poll every second, over 10 minutes", - pollInterval: "1s", - pollDuration: "10m", - - expectedPollPeriods: 600, - expectedBoundaries: []uint{ - 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, - 360, 390, 420, 450, 480, 510, 540, 570, 599, - }, - }, - { - name: "poll every second, over 20 minutes", - pollInterval: "1s", - pollDuration: "20m", - - expectedPollPeriods: 1200, - expectedBoundaries: []uint{ - 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, - 360, 390, 420, 450, 480, 510, 540, 570, - 600, 660, 720, 780, 840, 900, 960, 1020, - 1080, 1140, 1199, - }, - }, - { - name: "poll every 5 seconds, over 1 minute", - pollInterval: "5s", - pollDuration: "1m", - - expectedPollPeriods: 12, - expectedBoundaries: []uint{}, - }, - { - name: "poll every 5 seconds, over 10 minutes", - pollInterval: "5s", - pollDuration: "10m", - - expectedPollPeriods: 120, - expectedBoundaries: []uint{ - 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, - 72, 78, 84, 90, 96, 102, 108, 114, 119, - }, - }, - { - name: "poll every 5 seconds, over 20 minutes", - pollInterval: "5s", - pollDuration: "20m", - - expectedPollPeriods: 240, - expectedBoundaries: []uint{ - 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, - 72, 78, 84, 90, 96, 102, 108, 114, 120, - 132, 144, 156, 168, 180, 192, 204, 216, 228, 239, - }, - }, - { - name: "poll every 10 seconds, over 1 minute", - pollInterval: "10s", - pollDuration: "1m", - - expectedPollPeriods: 6, - expectedBoundaries: []uint{}, - }, - { - name: "poll every 10 seconds, over 10 minutes", - pollInterval: "10s", - pollDuration: "10m", - - expectedPollPeriods: 60, - expectedBoundaries: []uint{ - 6, 9, 12, 15, 18, 21, 24, 27, 30, - 33, 36, 39, 42, 45, 48, 51, 54, 57, 59, - }, - }, - { - name: "poll every 10 seconds, over 20 minutes", - pollInterval: "10s", - pollDuration: "20m", - - expectedPollPeriods: 120, - expectedBoundaries: []uint{ - 6, 9, 12, 15, 18, 21, 24, 27, 30, - 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, - 66, 72, 78, 84, 90, 96, 102, 108, 114, 119, - }, - }, - { - name: "poll every 20 seconds, over 1 minute", - pollInterval: "20s", - pollDuration: "1m", - - expectedPollPeriods: 3, - expectedBoundaries: []uint{}, - }, - { - name: "poll every 20 seconds, over 10 minutes", - pollInterval: "20s", - pollDuration: "10m", - - expectedPollPeriods: 30, - expectedBoundaries: []uint{ - 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, - 18, 19, 21, 22, 24, 25, 27, 28, 29, - }, - }, - { - name: "poll every 20 seconds, over 20 minutes", - pollInterval: "20s", - pollDuration: "20m", - - expectedPollPeriods: 60, - expectedBoundaries: []uint{ - 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, - 18, 19, 21, 22, 24, 25, 27, 28, 30, - 33, 36, 39, 42, 45, 48, 51, 54, 57, 59, - }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - pollInterval, err := time.ParseDuration(tt.pollInterval) - require.NoError(t, err, "error in specifying test poll interval") - pollDuration, err := time.ParseDuration(tt.pollDuration) - require.NoError(t, err, "error in specifying test poll duration") - pollPeriods := endpoints.PollPeriods(pollInterval, pollDuration) - - require.Equal(t, tt.expectedPollPeriods, pollPeriods) - boundaries := endpoints.BoundaryBuilder(pollInterval, pollDuration) - require.Equal(t, tt.expectedBoundaries, boundaries) - }) - } -} From de28e0de7c5bf0275269504259c89d672f530043 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Wed, 16 Oct 2024 14:44:39 -0700 Subject: [PATCH 30/32] Add in buffer resetting for eventTracker, remove TODO items. Signed-off-by: Edwin Buck --- pkg/server/datastore/sqlstore/sqlstore.go | 4 ---- .../endpoints/authorized_entryfetcher_attested_nodes.go | 1 + .../endpoints/authorized_entryfetcher_registration_entries.go | 1 + pkg/server/endpoints/eventTracker.go | 4 ++++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/server/datastore/sqlstore/sqlstore.go b/pkg/server/datastore/sqlstore/sqlstore.go index 797ec6023d..21ebeda1cb 100644 --- a/pkg/server/datastore/sqlstore/sqlstore.go +++ b/pkg/server/datastore/sqlstore/sqlstore.go @@ -351,8 +351,6 @@ func (ds *Plugin) UpdateAttestedNode(ctx context.Context, n *common.AttestedNode if err != nil { return err } - // TODO: this is at the wrong level of the software stack. - // It should be created in the caller of the datastore interface. return createAttestedNodeEvent(tx, &datastore.AttestedNodeEvent{ SpiffeID: n.SpiffeId, }) @@ -369,8 +367,6 @@ func (ds *Plugin) DeleteAttestedNode(ctx context.Context, spiffeID string) (atte if err != nil { return err } - // TODO: this is at the wrong level of the software stack. - // It should be created in the caller of the datastore interface. return createAttestedNodeEvent(tx, &datastore.AttestedNodeEvent{ SpiffeID: spiffeID, }) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index 4ba853daf1..d938e23b7b 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -100,6 +100,7 @@ func (a *attestedNodes) selectPolledEvents(ctx context.Context) { a.fetchNodes[event.SpiffeID] = struct{}{} a.eventTracker.StopTracking(eventID) } + a.eventTracker.FreeEvents() } func (a *attestedNodes) scanForNewEvents(ctx context.Context) error { diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index 9ce315a7fa..2faa0da696 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -99,6 +99,7 @@ func (a *registrationEntries) selectPolledEvents(ctx context.Context) { a.fetchEntries[event.EntryID] = struct{}{} a.eventTracker.StopTracking(eventID) } + a.eventTracker.FreeEvents() } func (a *registrationEntries) scanForNewEvents(ctx context.Context) error { diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 6c3614690e..8e6e218a6f 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -68,6 +68,10 @@ func (et *eventTracker) SelectEvents() []uint { return pollList } +func (et *eventTracker) FreeEvents(events []uint) { + t.pool.Put(events[:0]) +} + func (et *eventTracker) EventCount() uint { return uint(len(et.events)) } From 28bd44d11d907119478a92ba44574f520aa2e964 Mon Sep 17 00:00:00 2001 From: Edwin Buck Date: Thu, 17 Oct 2024 09:20:29 -0700 Subject: [PATCH 31/32] Fixes for freeing the shared buffer between eventTracker and its users. Signed-off-by: Edwin Buck --- .../endpoints/authorized_entryfetcher_attested_nodes.go | 5 +++-- .../authorized_entryfetcher_registration_entries.go | 5 +++-- pkg/server/endpoints/eventTracker.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go index d938e23b7b..32b854f213 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go +++ b/pkg/server/endpoints/authorized_entryfetcher_attested_nodes.go @@ -84,7 +84,8 @@ func (a *attestedNodes) searchBeforeFirstEvent(ctx context.Context) error { func (a *attestedNodes) selectPolledEvents(ctx context.Context) { // check if the polled events have appeared out-of-order - for _, eventID := range a.eventTracker.SelectEvents() { + selectedEvents := a.eventTracker.SelectEvents() + for _, eventID := range selectedEvents { log := a.log.WithField(telemetry.EventID, eventID) event, err := a.ds.FetchAttestedNodeEvent(ctx, eventID) @@ -100,7 +101,7 @@ func (a *attestedNodes) selectPolledEvents(ctx context.Context) { a.fetchNodes[event.SpiffeID] = struct{}{} a.eventTracker.StopTracking(eventID) } - a.eventTracker.FreeEvents() + a.eventTracker.FreeEvents(selectedEvents) } func (a *attestedNodes) scanForNewEvents(ctx context.Context) error { diff --git a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go index 2faa0da696..3fd9914c6d 100644 --- a/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go +++ b/pkg/server/endpoints/authorized_entryfetcher_registration_entries.go @@ -83,7 +83,8 @@ func (a *registrationEntries) searchBeforeFirstEvent(ctx context.Context) error func (a *registrationEntries) selectPolledEvents(ctx context.Context) { // check if the polled events have appeared out-of-order - for _, eventID := range a.eventTracker.SelectEvents() { + selectedEvents := a.eventTracker.SelectEvents() + for _, eventID := range selectedEvents { log := a.log.WithField(telemetry.EventID, eventID) event, err := a.ds.FetchRegistrationEntryEvent(ctx, eventID) @@ -99,7 +100,7 @@ func (a *registrationEntries) selectPolledEvents(ctx context.Context) { a.fetchEntries[event.EntryID] = struct{}{} a.eventTracker.StopTracking(eventID) } - a.eventTracker.FreeEvents() + a.eventTracker.FreeEvents(selectedEvents) } func (a *registrationEntries) scanForNewEvents(ctx context.Context) error { diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 8e6e218a6f..2516ca3f1b 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -69,7 +69,7 @@ func (et *eventTracker) SelectEvents() []uint { } func (et *eventTracker) FreeEvents(events []uint) { - t.pool.Put(events[:0]) + et.pool.Put(events[:0]) } func (et *eventTracker) EventCount() uint { From f0cbc4babb13926b32cb04b793eb4e150fdbc7af Mon Sep 17 00:00:00 2001 From: Andrew Harding Date: Thu, 17 Oct 2024 11:12:27 -0600 Subject: [PATCH 32/32] fix pool usage to satisfy linter Signed-off-by: Andrew Harding --- pkg/server/endpoints/eventTracker.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/server/endpoints/eventTracker.go b/pkg/server/endpoints/eventTracker.go index 2516ca3f1b..7be1913bb1 100644 --- a/pkg/server/endpoints/eventTracker.go +++ b/pkg/server/endpoints/eventTracker.go @@ -33,7 +33,8 @@ func NewEventTracker(pollPeriods uint) *eventTracker { events: make(map[uint]uint), pool: sync.Pool{ New: func() any { - return []uint(nil) + // See https://staticcheck.dev/docs/checks#SA6002. + return new([]uint) }, }, } @@ -56,7 +57,7 @@ func (et *eventTracker) StopTracking(event uint) { } func (et *eventTracker) SelectEvents() []uint { - pollList := et.pool.Get().([]uint) + pollList := *et.pool.Get().(*[]uint) for event, _ := range et.events { if et.events[event] >= et.pollPeriods { et.StopTracking(event) @@ -69,7 +70,8 @@ func (et *eventTracker) SelectEvents() []uint { } func (et *eventTracker) FreeEvents(events []uint) { - et.pool.Put(events[:0]) + events = events[:0] + et.pool.Put(&events) } func (et *eventTracker) EventCount() uint {