This changes the periodic job that deletes terminated sessions to delete the those sessions in batches. Prior to this, a single SQL DELETE command was used to delete all qualifying terminated sessions. In systems under heavy load, the statement could timeout leading to a situation where no qualifying terminated sessions were ever being deleted.
186 lines
4.9 KiB
Go
186 lines
4.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package session
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/db"
|
|
"github.com/hashicorp/boundary/internal/iam"
|
|
"github.com/hashicorp/boundary/internal/kms"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRepository_getDeleteJobParams(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
iamRepo := iam.TestRepo(t, conn, wrapper)
|
|
kms := kms.TestKms(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kms)
|
|
composedOf := TestSessionParams(t, conn, wrapper, iamRepo)
|
|
|
|
const defaultBatchSize = 5000
|
|
|
|
cases := []struct {
|
|
sessionCount int
|
|
terminateCount int
|
|
threshold time.Duration
|
|
expectedBatchSize int
|
|
expectedTotalToDelete int
|
|
}{
|
|
{0, 0, time.Nanosecond, defaultBatchSize, 0},
|
|
{1, 1, time.Nanosecond, defaultBatchSize, 1},
|
|
{1, 1, time.Hour, defaultBatchSize, 0},
|
|
{10, 10, time.Nanosecond, defaultBatchSize, 10},
|
|
{10, 4, time.Nanosecond, defaultBatchSize, 4},
|
|
{10, 0, time.Nanosecond, defaultBatchSize, 0},
|
|
{10, 10, time.Hour, defaultBatchSize, 0},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(fmt.Sprintf("%d_%d_%s_%d", tc.sessionCount, tc.terminateCount, tc.threshold, tc.expectedBatchSize), func(t *testing.T) {
|
|
t.Cleanup(func() {
|
|
sdb, err := conn.SqlDB(ctx)
|
|
require.NoError(t, err)
|
|
_, err = sdb.Exec(`delete from session;`)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
for i := 0; i < tc.sessionCount; i++ {
|
|
s := TestSession(t, conn, wrapper, composedOf)
|
|
if i < tc.terminateCount {
|
|
_, err = repo.CancelSession(ctx, s.PublicId, s.Version)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
c, err := repo.TerminateCompletedSessions(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.terminateCount, c)
|
|
|
|
p, err := repo.getDeleteJobParams(ctx, tc.threshold)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.expectedBatchSize, p.BatchSize)
|
|
assert.Equal(t, tc.expectedTotalToDelete, p.TotalToDelete)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRepository_deleteTerminatedSessionsBatch(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
iamRepo := iam.TestRepo(t, conn, wrapper)
|
|
kms := kms.TestKms(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kms)
|
|
composedOf := TestSessionParams(t, conn, wrapper, iamRepo)
|
|
|
|
cases := []struct {
|
|
sessionCount int
|
|
terminateCount int
|
|
batchSize int
|
|
expected int
|
|
}{
|
|
{0, 0, 10, 0},
|
|
{1, 1, 10, 1},
|
|
{10, 10, 5, 5},
|
|
{10, 2, 5, 2},
|
|
{10, 0, 10, 0},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(fmt.Sprintf("%d_%d_%d", tc.sessionCount, tc.terminateCount, tc.batchSize), func(t *testing.T) {
|
|
t.Cleanup(func() {
|
|
sdb, err := conn.SqlDB(ctx)
|
|
require.NoError(t, err)
|
|
_, err = sdb.Exec(`delete from session;`)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
// add initial group of sessions and terminate some of them
|
|
{
|
|
for i := 0; i < tc.sessionCount; i++ {
|
|
s := TestSession(t, conn, wrapper, composedOf)
|
|
if i < tc.terminateCount {
|
|
_, err = repo.CancelSession(ctx, s.PublicId, s.Version)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
c, err := repo.TerminateCompletedSessions(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.terminateCount, c)
|
|
}
|
|
|
|
// get the job parameters
|
|
p, err := repo.getDeleteJobParams(ctx, time.Nanosecond)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.terminateCount, p.TotalToDelete)
|
|
|
|
// add more sessions to test the WindowStartTime
|
|
{
|
|
for i := 0; i < tc.sessionCount; i++ {
|
|
s := TestSession(t, conn, wrapper, composedOf)
|
|
if i < tc.terminateCount {
|
|
_, err = repo.CancelSession(ctx, s.PublicId, s.Version)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
c, err := repo.TerminateCompletedSessions(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.terminateCount, c)
|
|
}
|
|
|
|
c, err := repo.deleteTerminatedSessionsBatch(ctx, p.WindowStartTime, tc.batchSize)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.expected, c)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRepository_setDeleteJobBatchSize(t *testing.T) {
|
|
ctx := context.Background()
|
|
conn, _ := db.TestSetup(t, "postgres")
|
|
rw := db.New(conn)
|
|
wrapper := db.TestWrapper(t)
|
|
kms := kms.TestKms(t, conn, wrapper)
|
|
repo, err := NewRepository(ctx, rw, rw, kms)
|
|
|
|
cases := []struct {
|
|
batchSize int
|
|
expectErr bool
|
|
}{
|
|
{-1, true},
|
|
{0, true},
|
|
{1, false},
|
|
{9, false},
|
|
{10, false},
|
|
{10000, false},
|
|
{10001, false},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(fmt.Sprintf("%d_%t", tc.batchSize, tc.expectErr), func(t *testing.T) {
|
|
err = repo.setDeleteJobBatchSize(ctx, tc.batchSize)
|
|
if tc.expectErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
|
|
p, err := repo.getDeleteJobParams(ctx, time.Nanosecond)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.batchSize, p.BatchSize)
|
|
})
|
|
}
|
|
}
|