diff --git a/ddl/cluster.go b/ddl/cluster.go index 996ea4bf2eb53..e3d7363304ab5 100644 --- a/ddl/cluster.go +++ b/ddl/cluster.go @@ -20,6 +20,7 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/tidb/domain/infosync" + "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" @@ -84,6 +85,9 @@ func ValidateFlashbackTS(ctx context.Context, sctx sessionctx.Context, flashBack if oracle.GetTimeFromTS(flashBackTS).After(oracle.GetTimeFromTS(currentTS)) { return errors.Errorf("cannot set flashback timestamp to future time") } + if oracle.GetTimeFromTS(flashBackTS).After(expression.GetMinSafeTime(sctx)) { + return errors.Errorf("cannot set flashback timestamp to too close to present time") + } gcSafePoint, err := gcutil.GetGCSafePoint(sctx) if err != nil { return err diff --git a/ddl/cluster_test.go b/ddl/cluster_test.go index 454b60e48cf63..0621ae64dcfb7 100644 --- a/ddl/cluster_test.go +++ b/ddl/cluster_test.go @@ -18,7 +18,9 @@ import ( "context" "fmt" "testing" + "time" + "github.com/pingcap/failpoint" "github.com/pingcap/tidb/ddl" "github.com/pingcap/tidb/domain/infosync" "github.com/pingcap/tidb/errno" @@ -113,6 +115,12 @@ func TestFlashbackCloseAndResetPDSchedule(t *testing.T) { originHook := dom.DDL().GetHook() tk := testkit.NewTestKit(t, store) + injectSafeTS := oracle.GoTimeToTS(time.Now().Add(10 * time.Second)) + require.NoError(t, failpoint.Enable("tikvclient/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/expression/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) + oldValue := map[string]interface{}{ "hot-region-schedule-limit": 1, } @@ -145,6 +153,9 @@ func TestFlashbackCloseAndResetPDSchedule(t *testing.T) { finishValue, err := infosync.GetPDScheduleConfig(context.Background()) require.NoError(t, err) require.EqualValues(t, finishValue["hot-region-schedule-limit"], 1) + + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/expression/injectSafeTS")) + require.NoError(t, failpoint.Disable("tikvclient/injectSafeTS")) } func TestCancelFlashbackCluster(t *testing.T) { @@ -154,6 +165,12 @@ func TestCancelFlashbackCluster(t *testing.T) { ts, err := tk.Session().GetStore().GetOracle().GetTimestamp(context.Background(), &oracle.Option{}) require.NoError(t, err) + injectSafeTS := oracle.GoTimeToTS(oracle.GetTimeFromTS(ts).Add(10 * time.Second)) + require.NoError(t, failpoint.Enable("tikvclient/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/expression/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) + timeBeforeDrop, _, safePointSQL, resetGC := MockGC(tk) defer resetGC() tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop)) @@ -175,4 +192,7 @@ func TestCancelFlashbackCluster(t *testing.T) { hook.MustCancelFailed(t) dom.DDL().SetHook(originHook) + + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/expression/injectSafeTS")) + require.NoError(t, failpoint.Disable("tikvclient/injectSafeTS")) } diff --git a/executor/recover_test.go b/executor/recover_test.go index ae8e84b41f73b..8d9f3d169e6be 100644 --- a/executor/recover_test.go +++ b/executor/recover_test.go @@ -15,6 +15,7 @@ package executor_test import ( + "context" "fmt" "testing" "time" @@ -29,6 +30,7 @@ import ( "github.com/pingcap/tidb/testkit" "github.com/pingcap/tidb/util/gcutil" "github.com/stretchr/testify/require" + "github.com/tikv/client-go/v2/oracle" ) func TestRecoverTable(t *testing.T) { @@ -278,7 +280,7 @@ func TestRecoverTableMeetError(t *testing.T) { tk.MustExec("insert into t_recover values (1),(2),(3)") tk.MustExec("drop table t_recover") - //set GC safe point + // Set GC safe point tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop)) // Should recover, and we can drop it straight away. @@ -295,6 +297,14 @@ func TestRecoverTableMeetError(t *testing.T) { func TestRecoverClusterMeetError(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) + ts, _ := tk.Session().GetStore().GetOracle().GetTimestamp(context.Background(), &oracle.Option{}) + flashbackTs := oracle.GetTimeFromTS(ts) + + injectSafeTS := oracle.GoTimeToTS(flashbackTs.Add(10 * time.Second)) + require.NoError(t, failpoint.Enable("tikvclient/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/expression/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) // Get GC safe point error. tk.MustContainErrMsg(fmt.Sprintf("flashback cluster as of timestamp '%s'", time.Now().Add(30*time.Second)), "cannot set flashback timestamp to future time") @@ -303,7 +313,7 @@ func TestRecoverClusterMeetError(t *testing.T) { timeBeforeDrop, _, safePointSQL, resetGC := MockGC(tk) defer resetGC() - //set GC safe point. + // Set GC safe point. tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop)) // out of GC safe point range. @@ -319,21 +329,88 @@ func TestRecoverClusterMeetError(t *testing.T) { // Flashback failed because of ddl history. tk.MustExec("use test;") tk.MustExec("create table t(a int);") - tk.MustContainErrMsg(fmt.Sprintf("flashback cluster as of timestamp '%s'", time.Now().Add(0-30*time.Second)), "schema version not same, have done ddl during [flashbackTS, now)") + tk.MustContainErrMsg(fmt.Sprintf("flashback cluster as of timestamp '%s'", flashbackTs), "schema version not same, have done ddl during [flashbackTS, now)") + + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/expression/injectSafeTS")) + require.NoError(t, failpoint.Disable("tikvclient/injectSafeTS")) } func TestRecoverClusterWithTiFlash(t *testing.T) { store := testkit.CreateMockStore(t, withMockTiFlash(1)) tk := testkit.NewTestKit(t, store) + injectSafeTS := oracle.GoTimeToTS(time.Now().Add(-10 * time.Second)) + require.NoError(t, failpoint.Enable("tikvclient/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/expression/injectSafeTS", + fmt.Sprintf("return(%v)", injectSafeTS))) + timeBeforeDrop, _, safePointSQL, resetGC := MockGC(tk) defer resetGC() - //set GC safe point + // Set GC safe point tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop)) tk.MustContainErrMsg(fmt.Sprintf("flashback cluster as of timestamp '%s'", time.Now().Add(0-30*time.Second)), "not support flash back cluster with TiFlash stores") + + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/expression/injectSafeTS")) + require.NoError(t, failpoint.Disable("tikvclient/injectSafeTS")) +} + +func TestFlashbackWithSafeTs(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + timeBeforeDrop, _, safePointSQL, resetGC := MockGC(tk) + defer resetGC() + + // Set GC safe point. + tk.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop)) + + ts, _ := tk.Session().GetStore().GetOracle().GetTimestamp(context.Background(), &oracle.Option{}) + flashbackTs := oracle.GetTimeFromTS(ts) + testcases := []struct { + name string + sql string + injectSafeTS uint64 + // compareWithSafeTS will be 0 if FlashbackTS==SafeTS, -1 if FlashbackTS < SafeTS, and +1 if FlashbackTS > SafeTS. + compareWithSafeTS int + }{ + { + name: "10 seconds ago to now, safeTS 5 secs ago", + sql: fmt.Sprintf("flashback cluster as of timestamp '%s'", flashbackTs), + injectSafeTS: oracle.GoTimeToTS(flashbackTs.Add(10 * time.Second)), + compareWithSafeTS: -1, + }, + { + name: "5 seconds ago to now, safeTS 10 secs ago", + sql: fmt.Sprintf("flashback cluster as of timestamp '%s'", flashbackTs), + injectSafeTS: oracle.GoTimeToTS(flashbackTs.Add(-10 * time.Second)), + compareWithSafeTS: 1, + }, + { + name: "5 seconds ago to now, safeTS 5 secs ago", + sql: fmt.Sprintf("flashback cluster as of timestamp '%s'", flashbackTs), + injectSafeTS: oracle.GoTimeToTS(flashbackTs), + compareWithSafeTS: 0, + }, + } + for _, testcase := range testcases { + t.Log(testcase.name) + require.NoError(t, failpoint.Enable("tikvclient/injectSafeTS", + fmt.Sprintf("return(%v)", testcase.injectSafeTS))) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/expression/injectSafeTS", + fmt.Sprintf("return(%v)", testcase.injectSafeTS))) + if testcase.compareWithSafeTS == 1 { + tk.MustContainErrMsg(testcase.sql, + "cannot set flashback timestamp to too close to present time") + } else { + tk.MustExec(testcase.sql) + } + } + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/expression/injectSafeTS")) + require.NoError(t, failpoint.Disable("tikvclient/injectSafeTS")) } // MockGC is used to make GC work in the test environment.