mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-29 07:49:14 +01:00
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/netbirdio/netbird/management/server/testutil"
|
|
)
|
|
|
|
// queryCanceledCode is the SQLSTATE Postgres reports when statement_timeout fires.
|
|
const queryCanceledCode = "57014"
|
|
|
|
func TestPostgresMillis(t *testing.T) {
|
|
cases := map[time.Duration]string{
|
|
time.Minute: "60000",
|
|
1500 * time.Microsecond: "2",
|
|
500 * time.Microsecond: "1",
|
|
720 * time.Hour: "2147483647",
|
|
}
|
|
for d, want := range cases {
|
|
assert.Equal(t, want, postgresMillis(d), "milliseconds for %v", d)
|
|
}
|
|
}
|
|
|
|
func TestSessionTimeouts_NothingToApply(t *testing.T) {
|
|
t.Setenv(statementTimeoutEnv, "0")
|
|
t.Setenv(transactionTimeoutEnv, "0")
|
|
ctx := context.Background()
|
|
|
|
assert.NoError(t, sessionTimeouts()(ctx, nil), "the hook should not touch the session when both timeouts are disabled")
|
|
}
|
|
|
|
func TestOpenPostgres_BoundsEverySession(t *testing.T) {
|
|
t.Setenv(statementTimeoutEnv, "500ms")
|
|
t.Setenv(transactionTimeoutEnv, "3m")
|
|
conn := openPostgresTestConn(t)
|
|
ctx := context.Background()
|
|
|
|
requireStatementTimeout(t, conn.DB(nil).Exec("SELECT pg_sleep(1)").Error)
|
|
|
|
_, err := conn.Pool(nil).Exec(ctx, "SELECT pg_sleep(1)")
|
|
requireStatementTimeout(t, err)
|
|
|
|
var inTx string
|
|
require.NoError(t, conn.RunInTx(ctx, func(tx *Tx) error {
|
|
return conn.DB(tx).Raw("SHOW statement_timeout").Scan(&inTx).Error
|
|
}))
|
|
assert.Equal(t, "1min", inTx, "transactions should keep their own 1 minute bound")
|
|
|
|
assert.Equal(t, "3min", showSetting(t, conn.DB(nil), "idle_in_transaction_session_timeout"),
|
|
"the idle transaction timeout should follow the transaction timeout")
|
|
}
|
|
|
|
func TestOpenPostgres_NoStatementTimeoutKeepsServerSetting(t *testing.T) {
|
|
for name, value := range map[string]string{"unset": "", "zero": "0"} {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Setenv(statementTimeoutEnv, value)
|
|
if value == "" {
|
|
require.NoError(t, os.Unsetenv(statementTimeoutEnv))
|
|
}
|
|
ctx := context.Background()
|
|
dsn := postgresTestDSN(t)
|
|
admin, err := pgx.Connect(ctx, dsn)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = admin.Close(ctx) })
|
|
_, err = admin.Exec(ctx, "ALTER ROLE CURRENT_USER SET statement_timeout = '7s'")
|
|
require.NoError(t, err)
|
|
|
|
conn, err := OpenPostgres(ctx, dsn, DefaultPoolConfig)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = conn.Close() })
|
|
|
|
assert.Equal(t, "7s", showSetting(t, conn.DB(nil), "statement_timeout"), "sessions should keep the server setting")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOpenPostgres_CapsTimeoutsAtServerMaximum(t *testing.T) {
|
|
t.Setenv(transactionTimeoutEnv, "720h")
|
|
conn := openPostgresTestConn(t)
|
|
|
|
assert.Equal(t, "2147483647ms", showSetting(t, conn.DB(nil), "idle_in_transaction_session_timeout"),
|
|
"a timeout above the server maximum should be capped instead of failing the connection")
|
|
}
|
|
|
|
func TestOpenPostgresWithoutPool_BoundsSessions(t *testing.T) {
|
|
t.Setenv(statementTimeoutEnv, "500ms")
|
|
conn, err := OpenPostgresWithoutPool(context.Background(), postgresTestDSN(t))
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = conn.Close() })
|
|
|
|
requireStatementTimeout(t, conn.DB(nil).Exec("SELECT pg_sleep(1)").Error)
|
|
}
|
|
|
|
func TestOpenPgxPool_BoundsSessions(t *testing.T) {
|
|
t.Setenv(statementTimeoutEnv, "500ms")
|
|
ctx := context.Background()
|
|
pool, err := OpenPgxPool(ctx, postgresTestDSN(t), DefaultPoolConfig)
|
|
require.NoError(t, err)
|
|
t.Cleanup(pool.Close)
|
|
|
|
_, err = pool.Exec(ctx, "SELECT pg_sleep(1)")
|
|
requireStatementTimeout(t, err)
|
|
}
|
|
|
|
func postgresTestDSN(t *testing.T) string {
|
|
t.Helper()
|
|
if os.Getenv("NETBIRD_STORE_ENGINE") != string(PostgresStoreEngine) {
|
|
t.Skip("requires NETBIRD_STORE_ENGINE=postgres")
|
|
}
|
|
cleanup, dsn, err := testutil.CreatePostgresTestContainer()
|
|
require.NoError(t, err)
|
|
t.Cleanup(cleanup)
|
|
return dsn
|
|
}
|
|
|
|
func openPostgresTestConn(t *testing.T) *Conn {
|
|
t.Helper()
|
|
conn, err := OpenPostgres(context.Background(), postgresTestDSN(t), DefaultPoolConfig)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = conn.Close() })
|
|
return conn
|
|
}
|
|
|
|
func requireStatementTimeout(t *testing.T, err error) {
|
|
t.Helper()
|
|
var pgErr *pgconn.PgError
|
|
require.True(t, errors.As(err, &pgErr), "expected a Postgres error, got %v", err)
|
|
assert.Equal(t, queryCanceledCode, pgErr.Code, "the statement should be cancelled by statement_timeout")
|
|
}
|
|
|
|
func showSetting(t *testing.T, db *gorm.DB, name string) string {
|
|
t.Helper()
|
|
var value string
|
|
require.NoError(t, db.Raw("SHOW "+name).Scan(&value).Error)
|
|
return value
|
|
}
|