257 lines
8.8 KiB
Go
257 lines
8.8 KiB
Go
|
/*
|
||
|
Copyright 2022 GitHub Inc.
|
||
|
See https://github.com/github/gh-ost/blob/master/LICENSE
|
||
|
*/
|
||
|
|
||
|
package logic
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/openark/golib/tests"
|
||
|
|
||
|
"github.com/github/gh-ost/go/base"
|
||
|
"github.com/github/gh-ost/go/binlog"
|
||
|
"github.com/github/gh-ost/go/sql"
|
||
|
)
|
||
|
|
||
|
func TestMigratorOnChangelogEvent(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
|
||
|
t.Run("heartbeat", func(t *testing.T) {
|
||
|
columnValues := sql.ToColumnValues([]interface{}{
|
||
|
123,
|
||
|
time.Now().Unix(),
|
||
|
"heartbeat",
|
||
|
"2022-08-16T00:45:10.52Z",
|
||
|
})
|
||
|
tests.S(t).ExpectNil(migrator.onChangelogEvent(&binlog.BinlogDMLEvent{
|
||
|
DatabaseName: "test",
|
||
|
DML: binlog.InsertDML,
|
||
|
NewColumnValues: columnValues,
|
||
|
}))
|
||
|
})
|
||
|
|
||
|
t.Run("state-AllEventsUpToLockProcessed", func(t *testing.T) {
|
||
|
var wg sync.WaitGroup
|
||
|
wg.Add(1)
|
||
|
go func(wg *sync.WaitGroup) {
|
||
|
defer wg.Done()
|
||
|
es := <-migrator.applyEventsQueue
|
||
|
tests.S(t).ExpectNotNil(es)
|
||
|
tests.S(t).ExpectNotNil(es.writeFunc)
|
||
|
}(&wg)
|
||
|
|
||
|
columnValues := sql.ToColumnValues([]interface{}{
|
||
|
123,
|
||
|
time.Now().Unix(),
|
||
|
"state",
|
||
|
AllEventsUpToLockProcessed,
|
||
|
})
|
||
|
tests.S(t).ExpectNil(migrator.onChangelogEvent(&binlog.BinlogDMLEvent{
|
||
|
DatabaseName: "test",
|
||
|
DML: binlog.InsertDML,
|
||
|
NewColumnValues: columnValues,
|
||
|
}))
|
||
|
wg.Wait()
|
||
|
})
|
||
|
|
||
|
t.Run("state-GhostTableMigrated", func(t *testing.T) {
|
||
|
go func() {
|
||
|
tests.S(t).ExpectTrue(<-migrator.ghostTableMigrated)
|
||
|
}()
|
||
|
|
||
|
columnValues := sql.ToColumnValues([]interface{}{
|
||
|
123,
|
||
|
time.Now().Unix(),
|
||
|
"state",
|
||
|
GhostTableMigrated,
|
||
|
})
|
||
|
tests.S(t).ExpectNil(migrator.onChangelogEvent(&binlog.BinlogDMLEvent{
|
||
|
DatabaseName: "test",
|
||
|
DML: binlog.InsertDML,
|
||
|
NewColumnValues: columnValues,
|
||
|
}))
|
||
|
})
|
||
|
|
||
|
t.Run("state-Migrated", func(t *testing.T) {
|
||
|
columnValues := sql.ToColumnValues([]interface{}{
|
||
|
123,
|
||
|
time.Now().Unix(),
|
||
|
"state",
|
||
|
Migrated,
|
||
|
})
|
||
|
tests.S(t).ExpectNil(migrator.onChangelogEvent(&binlog.BinlogDMLEvent{
|
||
|
DatabaseName: "test",
|
||
|
DML: binlog.InsertDML,
|
||
|
NewColumnValues: columnValues,
|
||
|
}))
|
||
|
})
|
||
|
|
||
|
t.Run("state-ReadMigrationRangeValues", func(t *testing.T) {
|
||
|
columnValues := sql.ToColumnValues([]interface{}{
|
||
|
123,
|
||
|
time.Now().Unix(),
|
||
|
"state",
|
||
|
ReadMigrationRangeValues,
|
||
|
})
|
||
|
tests.S(t).ExpectNil(migrator.onChangelogEvent(&binlog.BinlogDMLEvent{
|
||
|
DatabaseName: "test",
|
||
|
DML: binlog.InsertDML,
|
||
|
NewColumnValues: columnValues,
|
||
|
}))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestMigratorValidateStatement(t *testing.T) {
|
||
|
t.Run("add-column", func(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
tests.S(t).ExpectNil(migrator.parser.ParseAlterStatement(`ALTER TABLE test ADD test_new VARCHAR(64) NOT NULL`))
|
||
|
|
||
|
tests.S(t).ExpectNil(migrator.validateAlterStatement())
|
||
|
tests.S(t).ExpectEquals(len(migrator.migrationContext.DroppedColumnsMap), 0)
|
||
|
})
|
||
|
|
||
|
t.Run("drop-column", func(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
tests.S(t).ExpectNil(migrator.parser.ParseAlterStatement(`ALTER TABLE test DROP abc`))
|
||
|
|
||
|
tests.S(t).ExpectNil(migrator.validateAlterStatement())
|
||
|
tests.S(t).ExpectEquals(len(migrator.migrationContext.DroppedColumnsMap), 1)
|
||
|
_, exists := migrator.migrationContext.DroppedColumnsMap["abc"]
|
||
|
tests.S(t).ExpectTrue(exists)
|
||
|
})
|
||
|
|
||
|
t.Run("rename-column", func(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
tests.S(t).ExpectNil(migrator.parser.ParseAlterStatement(`ALTER TABLE test CHANGE test123 test1234 bigint unsigned`))
|
||
|
|
||
|
err := migrator.validateAlterStatement()
|
||
|
tests.S(t).ExpectNotNil(err)
|
||
|
tests.S(t).ExpectTrue(strings.HasPrefix(err.Error(), "gh-ost believes the ALTER statement renames columns"))
|
||
|
tests.S(t).ExpectEquals(len(migrator.migrationContext.DroppedColumnsMap), 0)
|
||
|
})
|
||
|
|
||
|
t.Run("rename-column-approved", func(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
migrator.migrationContext.ApproveRenamedColumns = true
|
||
|
tests.S(t).ExpectNil(migrator.parser.ParseAlterStatement(`ALTER TABLE test CHANGE test123 test1234 bigint unsigned`))
|
||
|
|
||
|
tests.S(t).ExpectNil(migrator.validateAlterStatement())
|
||
|
tests.S(t).ExpectEquals(len(migrator.migrationContext.DroppedColumnsMap), 0)
|
||
|
})
|
||
|
|
||
|
t.Run("rename-table", func(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
tests.S(t).ExpectNil(migrator.parser.ParseAlterStatement(`ALTER TABLE test RENAME TO test_new`))
|
||
|
|
||
|
err := migrator.validateAlterStatement()
|
||
|
tests.S(t).ExpectNotNil(err)
|
||
|
tests.S(t).ExpectTrue(errors.Is(err, ErrMigratorUnsupportedRenameAlter))
|
||
|
tests.S(t).ExpectEquals(len(migrator.migrationContext.DroppedColumnsMap), 0)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestMigratorCreateFlagFiles(t *testing.T) {
|
||
|
tmpdir, err := os.MkdirTemp("", t.Name())
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
defer os.RemoveAll(tmpdir)
|
||
|
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrationContext.PostponeCutOverFlagFile = filepath.Join(tmpdir, "cut-over.flag")
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
tests.S(t).ExpectNil(migrator.createFlagFiles())
|
||
|
tests.S(t).ExpectNil(migrator.createFlagFiles()) // twice to test already-exists
|
||
|
|
||
|
_, err = os.Stat(migrationContext.PostponeCutOverFlagFile)
|
||
|
tests.S(t).ExpectNil(err)
|
||
|
}
|
||
|
|
||
|
func TestMigratorGetProgressPercent(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
|
||
|
{
|
||
|
tests.S(t).ExpectEquals(migrator.getProgressPercent(0), float64(100.0))
|
||
|
}
|
||
|
{
|
||
|
migrationContext.TotalRowsCopied = 250
|
||
|
tests.S(t).ExpectEquals(migrator.getProgressPercent(1000), float64(25.0))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMigratorGetMigrationStateAndETA(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
now := time.Now()
|
||
|
migrationContext.RowCopyStartTime = now.Add(-time.Minute)
|
||
|
migrationContext.RowCopyEndTime = now
|
||
|
|
||
|
{
|
||
|
migrationContext.TotalRowsCopied = 456
|
||
|
state, eta, etaDuration := migrator.getMigrationStateAndETA(123456)
|
||
|
tests.S(t).ExpectEquals(state, "migrating")
|
||
|
tests.S(t).ExpectEquals(eta, "4h29m44s")
|
||
|
tests.S(t).ExpectEquals(etaDuration.String(), "4h29m44s")
|
||
|
}
|
||
|
{
|
||
|
migrationContext.TotalRowsCopied = 456
|
||
|
state, eta, etaDuration := migrator.getMigrationStateAndETA(456)
|
||
|
tests.S(t).ExpectEquals(state, "migrating")
|
||
|
tests.S(t).ExpectEquals(eta, "due")
|
||
|
tests.S(t).ExpectEquals(etaDuration.String(), "0s")
|
||
|
}
|
||
|
{
|
||
|
migrationContext.TotalRowsCopied = 123456
|
||
|
state, eta, etaDuration := migrator.getMigrationStateAndETA(456)
|
||
|
tests.S(t).ExpectEquals(state, "migrating")
|
||
|
tests.S(t).ExpectEquals(eta, "due")
|
||
|
tests.S(t).ExpectEquals(etaDuration.String(), "0s")
|
||
|
}
|
||
|
{
|
||
|
atomic.StoreInt64(&migrationContext.CountingRowsFlag, 1)
|
||
|
state, eta, etaDuration := migrator.getMigrationStateAndETA(123456)
|
||
|
tests.S(t).ExpectEquals(state, "counting rows")
|
||
|
tests.S(t).ExpectEquals(eta, "due")
|
||
|
tests.S(t).ExpectEquals(etaDuration.String(), "0s")
|
||
|
}
|
||
|
{
|
||
|
atomic.StoreInt64(&migrationContext.CountingRowsFlag, 0)
|
||
|
atomic.StoreInt64(&migrationContext.IsPostponingCutOver, 1)
|
||
|
state, eta, etaDuration := migrator.getMigrationStateAndETA(123456)
|
||
|
tests.S(t).ExpectEquals(state, "postponing cut-over")
|
||
|
tests.S(t).ExpectEquals(eta, "due")
|
||
|
tests.S(t).ExpectEquals(etaDuration.String(), "0s")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMigratorShouldPrintStatus(t *testing.T) {
|
||
|
migrationContext := base.NewMigrationContext()
|
||
|
migrator := NewMigrator(migrationContext, "1.2.3")
|
||
|
|
||
|
tests.S(t).ExpectTrue(migrator.shouldPrintStatus(NoPrintStatusRule, 10, time.Second)) // test 'rule != HeuristicPrintStatusRule' return
|
||
|
tests.S(t).ExpectTrue(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 10, time.Second)) // test 'etaDuration.Seconds() <= 60'
|
||
|
tests.S(t).ExpectTrue(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 90, time.Second)) // test 'etaDuration.Seconds() <= 60' again
|
||
|
tests.S(t).ExpectTrue(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 90, time.Minute)) // test 'etaDuration.Seconds() <= 180'
|
||
|
tests.S(t).ExpectTrue(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 60, 90*time.Second)) // test 'elapsedSeconds <= 180'
|
||
|
tests.S(t).ExpectFalse(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 61, 90*time.Second)) // test 'elapsedSeconds <= 180'
|
||
|
tests.S(t).ExpectFalse(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 99, 210*time.Second)) // test 'elapsedSeconds <= 180'
|
||
|
tests.S(t).ExpectFalse(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 12345, 86400*time.Second)) // test 'else'
|
||
|
tests.S(t).ExpectTrue(migrator.shouldPrintStatus(HeuristicPrintStatusRule, 30030, 86400*time.Second)) // test 'else' again
|
||
|
}
|