You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/db/schema/manager_test.go

257 lines
6.9 KiB

package schema
import (
"context"
"database/sql"
"strings"
"testing"
"github.com/hashicorp/boundary/internal/db/schema/postgres"
"github.com/hashicorp/boundary/internal/docker"
"github.com/hashicorp/boundary/internal/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewManager(t *testing.T) {
dialect := "postgres"
c, u, _, err := docker.StartDbInDocker(dialect)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
d, err := sql.Open(dialect, u)
require.NoError(t, err)
ctx := context.Background()
_, err = NewManager(ctx, dialect, d)
require.NoError(t, err)
_, err = NewManager(ctx, "unknown", d)
assert.True(t, errors.Match(errors.T(errors.InvalidParameter), err))
d.Close()
_, err = NewManager(ctx, dialect, d)
assert.True(t, errors.Match(errors.T(errors.Op("schema.NewManager")), err))
}
func TestCurrentState(t *testing.T) {
dialect := "postgres"
c, u, _, err := docker.StartDbInDocker(dialect)
t.Cleanup(func() {
if err := c(); err != nil {
t.Fatalf("Got error at cleanup: %v", err)
}
})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
ctx := context.Background()
d, err := sql.Open(dialect, u)
require.NoError(t, err)
m, err := NewManager(ctx, dialect, d)
require.NoError(t, err)
want := &State{
BinarySchemaVersion: BinarySchemaVersion(dialect),
DatabaseSchemaVersion: nilVersion,
}
s, err := m.CurrentState(ctx)
require.NoError(t, err)
assert.Equal(t, want, s)
testDriver, err := postgres.New(ctx, d)
require.NoError(t, err)
require.NoError(t, testDriver.EnsureVersionTable(ctx))
require.NoError(t, testDriver.Run(ctx, strings.NewReader("select 1"), 2))
want = &State{
InitializationStarted: true,
BinarySchemaVersion: BinarySchemaVersion(dialect),
DatabaseSchemaVersion: 2,
}
s, err = m.CurrentState(ctx)
require.NoError(t, err)
assert.Equal(t, want, s)
}
func TestRollForward(t *testing.T) {
dialect := "postgres"
c, u, _, err := docker.StartDbInDocker(dialect)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
d, err := sql.Open(dialect, u)
require.NoError(t, err)
ctx := context.Background()
m, err := NewManager(ctx, dialect, d)
require.NoError(t, err)
assert.NoError(t, m.RollForward(ctx))
// Now set to dirty at an early version
_, err = postgres.New(ctx, d)
require.NoError(t, err)
// TODO: Extract out a way to mock the db to test failing rollforwards.
_, err = d.ExecContext(ctx, "TRUNCATE boundary_schema_version; INSERT INTO boundary_schema_version (version, dirty) VALUES (2, true)")
require.NoError(t, err)
assert.Error(t, m.RollForward(ctx))
}
func TestRollForward_NotFromFresh(t *testing.T) {
dialect := "postgres"
oState := migrationStates[dialect]
nState := createPartialMigrationState(oState, 8)
migrationStates[dialect] = nState
c, u, _, err := docker.StartDbInDocker(dialect)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
d, err := sql.Open(dialect, u)
require.NoError(t, err)
// Initialize the DB with only a portion of the current sql scripts.
ctx := context.Background()
m, err := NewManager(ctx, dialect, d)
require.NoError(t, err)
assert.NoError(t, m.RollForward(ctx))
state, err := m.CurrentState(ctx)
require.NoError(t, err)
assert.Equal(t, nState.binarySchemaVersion, state.DatabaseSchemaVersion)
assert.False(t, state.Dirty)
// Restore the full set of sql scripts and roll the rest of the way forward.
migrationStates[dialect] = oState
newM, err := NewManager(ctx, dialect, d)
require.NoError(t, err)
assert.NoError(t, newM.RollForward(ctx))
state, err = newM.CurrentState(ctx)
require.NoError(t, err)
assert.Equal(t, oState.binarySchemaVersion, state.DatabaseSchemaVersion)
assert.False(t, state.Dirty)
}
func TestRunMigration_canceledContext(t *testing.T) {
dialect := "postgres"
c, u, _, err := docker.StartDbInDocker(dialect)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
d, err := sql.Open(dialect, u)
require.NoError(t, err)
ctx := context.Background()
m, err := NewManager(ctx, dialect, d)
require.NoError(t, err)
// TODO: Find a way to test different parts of the runMigrations loop.
ctx, cancel := context.WithCancel(ctx)
cancel()
assert.Error(t, m.runMigrations(ctx, newStatementProvider(dialect, 0)))
}
func TestRollForward_BadSQL(t *testing.T) {
dialect := "postgres"
oState := migrationStates[dialect]
defer func() { migrationStates[dialect] = oState }()
nState := createPartialMigrationState(oState, 8)
nState.binarySchemaVersion = 10
nState.upMigrations[10] = []byte("SELECT 1 FROM NonExistantTable;")
migrationStates[dialect] = nState
c, u, _, err := docker.StartDbInDocker(dialect)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
d, err := sql.Open(dialect, u)
require.NoError(t, err)
// Initialize the DB with only a portion of the current sql scripts.
ctx := context.Background()
m, err := NewManager(ctx, dialect, d)
require.NoError(t, err)
assert.Error(t, m.RollForward(ctx))
state, err := m.CurrentState(ctx)
require.NoError(t, err)
assert.Equal(t, nilVersion, state.DatabaseSchemaVersion)
assert.False(t, state.Dirty)
}
func TestManager_ExclusiveLock(t *testing.T) {
ctx := context.Background()
dialect := "postgres"
c, u, _, err := docker.StartDbInDocker(dialect)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
d1, err := sql.Open(dialect, u)
require.NoError(t, err)
m1, err := NewManager(ctx, dialect, d1)
require.NoError(t, err)
d2, err := sql.Open(dialect, u)
require.NoError(t, err)
m2, err := NewManager(ctx, dialect, d2)
require.NoError(t, err)
assert.NoError(t, m1.ExclusiveLock(ctx))
assert.NoError(t, m1.ExclusiveLock(ctx))
assert.Error(t, m2.ExclusiveLock(ctx))
assert.Error(t, m2.SharedLock(ctx))
}
func TestManager_SharedLock(t *testing.T) {
ctx := context.Background()
c, u, _, err := docker.StartDbInDocker("postgres")
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, c())
})
d1, err := sql.Open("postgres", u)
require.NoError(t, err)
m1, err := NewManager(ctx, "postgres", d1)
require.NoError(t, err)
d2, err := sql.Open("postgres", u)
require.NoError(t, err)
m2, err := NewManager(ctx, "postgres", d2)
require.NoError(t, err)
assert.NoError(t, m1.SharedLock(ctx))
assert.NoError(t, m2.SharedLock(ctx))
assert.NoError(t, m1.SharedLock(ctx))
assert.NoError(t, m2.SharedLock(ctx))
assert.Error(t, m1.ExclusiveLock(ctx))
assert.Error(t, m2.ExclusiveLock(ctx))
}
// Creates a new migrationState only with the versions <= the provided maxVer
func createPartialMigrationState(om migrationState, maxVer int) migrationState {
nState := migrationState{
upMigrations: make(map[int][]byte),
}
for k := range om.upMigrations {
if k > maxVer {
// Don't store any versions past our test version.
continue
}
nState.upMigrations[k] = om.upMigrations[k]
if nState.binarySchemaVersion < k {
nState.binarySchemaVersion = k
}
}
return nState
}