From e94163e475825bb642e218dd627436bbcf3303e7 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 12 Apr 2024 09:28:16 -0700 Subject: [PATCH] add regeneration intent log (#26354) --- vault/activity_log.go | 26 +++++++++++ vault/activity_log_test.go | 96 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/vault/activity_log.go b/vault/activity_log.go index 12a9496450..cdfa9d97d6 100644 --- a/vault/activity_log.go +++ b/vault/activity_log.go @@ -1175,6 +1175,32 @@ func (c *Core) setupActivityLogLocked(ctx context.Context, wg *sync.WaitGroup) e return nil } +func (a *ActivityLog) createRegenerationIntentLog(ctx context.Context, now time.Time) (*ActivityIntentLog, error) { + intentLog := &ActivityIntentLog{} + segments, err := a.availableLogs(ctx) + if err != nil { + return nil, fmt.Errorf("error fetching available logs: %w", err) + } + + for i, segment := range segments { + if timeutil.IsCurrentMonth(segment, now) { + continue + } + + intentLog.PreviousMonth = segment.Unix() + if i > 0 { + intentLog.NextMonth = segments[i-1].Unix() + break + } + } + + if intentLog.NextMonth == 0 || intentLog.PreviousMonth == 0 { + return nil, fmt.Errorf("insufficient data to create a regeneration intent log") + } + + return intentLog, nil +} + // stopActivityLogLocked removes the ActivityLog from Core // and frees any resources. // this function should be called with activityLogLock diff --git a/vault/activity_log_test.go b/vault/activity_log_test.go index f1aae77a2d..644284c9bb 100644 --- a/vault/activity_log_test.go +++ b/vault/activity_log_test.go @@ -662,6 +662,102 @@ func TestActivityLog_availableLogs(t *testing.T) { } } +// TestActivityLog_createRegenerationIntentLog tests that we can correctly create a regeneration intent log given the segments in storage +func TestActivityLog_createRegenerationIntentLog(t *testing.T) { + testCases := []struct { + name string + times []time.Time + expectedLog *ActivityIntentLog + expectedError bool + }{ + { + "no segments", + []time.Time{}, + nil, + true, + }, + { + "one segment", + []time.Time{ + time.Date(2024, 4, 4, 10, 54, 12, 0, time.UTC), + }, + nil, + true, + }, + { + "most recent segment is 3 months ago", + []time.Time{ + time.Date(2024, 1, 4, 10, 54, 12, 0, time.UTC), + time.Date(2024, 1, 3, 10, 54, 12, 0, time.UTC), + }, + &ActivityIntentLog{NextMonth: 1704365652, PreviousMonth: 1704279252}, + false, + }, + { + "lots of segments", + []time.Time{ + // two this month + time.Date(2024, 4, 4, 10, 54, 12, 0, time.UTC), + time.Date(2024, 4, 6, 10, 54, 12, 0, time.UTC), + // three last month + time.Date(2024, 3, 3, 10, 54, 12, 0, time.UTC), + time.Date(2024, 3, 6, 10, 54, 12, 0, time.UTC), + time.Date(2024, 3, 14, 10, 54, 12, 0, time.UTC), + // two the month before that + time.Date(2024, 2, 10, 10, 54, 12, 0, time.UTC), + time.Date(2024, 2, 17, 10, 54, 12, 0, time.UTC), + }, + &ActivityIntentLog{NextMonth: 1712228052, PreviousMonth: 1710413652}, + false, + }, + } + + core, _, _ := TestCoreUnsealed(t) + a := core.activityLog + now := time.Date(2024, 4, 10, 10, 54, 12, 0, time.UTC) + ctx := context.Background() + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + deletePaths := make([]string, 0) + + // insert the times we're given + paths := make([]string, 0, len(tc.times)) + for _, tm := range tc.times { + paths = append(paths, fmt.Sprintf("entity/%d/1", tm.Unix())) + } + + for _, subPath := range paths { + fullPath := ActivityLogPrefix + subPath + WriteToStorage(t, core, fullPath, []byte("test")) + deletePaths = append(deletePaths, fullPath) + } + + // regenerate the log + intentLog, err := a.createRegenerationIntentLog(context.Background(), now) + if tc.expectedError && err == nil { + t.Fatal("expected an error and got none") + } + if !tc.expectedError && err != nil { + t.Fatal(err) + } + + // verify it's what we expect + if diff := deep.Equal(intentLog, tc.expectedLog); len(diff) != 0 { + t.Errorf("got=%v, expected=%v, diff=%v", intentLog, tc.expectedLog, diff) + } + + // delete everything we wrote so the next test starts fresh + for _, p := range deletePaths { + err := core.barrier.Delete(ctx, p) + if err != nil { + t.Fatal(err) + } + } + }) + } +} + // TestActivityLog_MultipleFragmentsAndSegments adds 4000 clients to a fragment // and saves it and reads it. The test then adds 4000 more clients and calls // receivedFragment with 200 more entities. The current segment is saved to