Files
tidb/server/http_handler_serial_test.go

584 lines
20 KiB
Go

// Copyright 2021 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"sort"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/pingcap/failpoint"
"github.com/pingcap/log"
"github.com/pingcap/tidb/config"
ddlutil "github.com/pingcap/tidb/ddl/util"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/parser/model"
"github.com/pingcap/tidb/parser/mysql"
"github.com/pingcap/tidb/session"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/testkit"
"github.com/pingcap/tidb/util/deadlockhistory"
"github.com/pingcap/tidb/util/versioninfo"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
func TestPostSettings(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
ts.startServer(t)
ts.prepareData(t)
defer ts.stopServer(t)
se, err := session.CreateSession(ts.store)
require.NoError(t, err)
form := make(url.Values)
form.Set("log_level", "error")
form.Set("tidb_general_log", "1")
form.Set("tidb_enable_async_commit", "1")
form.Set("tidb_enable_1pc", "1")
resp, err := ts.formStatus("/settings", form)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, resp.Body.Close())
require.Equal(t, zap.ErrorLevel, log.GetLevel())
require.Equal(t, "error", config.GetGlobalConfig().Log.Level)
require.True(t, variable.ProcessGeneralLog.Load())
val, err := se.GetSessionVars().GetGlobalSystemVar(context.Background(), variable.TiDBEnableAsyncCommit)
require.NoError(t, err)
require.Equal(t, variable.On, val)
val, err = se.GetSessionVars().GetGlobalSystemVar(context.Background(), variable.TiDBEnable1PC)
require.NoError(t, err)
require.Equal(t, variable.On, val)
form = make(url.Values)
form.Set("log_level", "fatal")
form.Set("tidb_general_log", "0")
form.Set("tidb_enable_async_commit", "0")
form.Set("tidb_enable_1pc", "0")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, resp.Body.Close())
require.False(t, variable.ProcessGeneralLog.Load())
require.Equal(t, zap.FatalLevel, log.GetLevel())
require.Equal(t, "fatal", config.GetGlobalConfig().Log.Level)
val, err = se.GetSessionVars().GetGlobalSystemVar(context.Background(), variable.TiDBEnableAsyncCommit)
require.NoError(t, err)
require.Equal(t, variable.Off, val)
val, err = se.GetSessionVars().GetGlobalSystemVar(context.Background(), variable.TiDBEnable1PC)
require.NoError(t, err)
require.Equal(t, variable.Off, val)
form.Set("log_level", os.Getenv("log_level"))
// test ddl_slow_threshold
form = make(url.Values)
form.Set("ddl_slow_threshold", "200")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, resp.Body.Close())
require.Equal(t, uint32(200), atomic.LoadUint32(&variable.DDLSlowOprThreshold))
// test check_mb4_value_in_utf8
db, err := sql.Open("mysql", ts.getDSN())
require.NoError(t, err)
defer func() {
err := db.Close()
require.NoError(t, err)
}()
dbt := testkit.NewDBTestKit(t, db)
dbt.MustExec("create database tidb_test;")
dbt.MustExec("use tidb_test;")
dbt.MustExec("drop table if exists t2;")
dbt.MustExec("create table t2(a varchar(100) charset utf8);")
form.Set("check_mb4_value_in_utf8", "1")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, resp.Body.Close())
require.Equal(t, true, config.GetGlobalConfig().Instance.CheckMb4ValueInUTF8.Load())
txn1, err := dbt.GetDB().Begin()
require.NoError(t, err)
_, err = txn1.Exec("insert t2 values (unhex('F0A48BAE'));")
require.Error(t, err)
err = txn1.Commit()
require.NoError(t, err)
// Disable CheckMb4ValueInUTF8.
form = make(url.Values)
form.Set("check_mb4_value_in_utf8", "0")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
require.NoError(t, resp.Body.Close())
require.Equal(t, false, config.GetGlobalConfig().Instance.CheckMb4ValueInUTF8.Load())
dbt.MustExec("insert t2 values (unhex('f09f8c80'));")
// test deadlock_history_capacity
deadlockhistory.GlobalDeadlockHistory.Resize(10)
for i := 0; i < 10; i++ {
deadlockhistory.GlobalDeadlockHistory.Push(dummyRecord())
}
form = make(url.Values)
form.Set("deadlock_history_capacity", "5")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 5, len(deadlockhistory.GlobalDeadlockHistory.GetAll()))
require.Equal(t, uint64(6), deadlockhistory.GlobalDeadlockHistory.GetAll()[0].ID)
require.Equal(t, uint64(10), deadlockhistory.GlobalDeadlockHistory.GetAll()[4].ID)
deadlockhistory.GlobalDeadlockHistory.Push(dummyRecord())
require.Equal(t, 5, len(deadlockhistory.GlobalDeadlockHistory.GetAll()))
require.Equal(t, uint64(7), deadlockhistory.GlobalDeadlockHistory.GetAll()[0].ID)
require.Equal(t, uint64(11), deadlockhistory.GlobalDeadlockHistory.GetAll()[4].ID)
form = make(url.Values)
form.Set("deadlock_history_capacity", "6")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
deadlockhistory.GlobalDeadlockHistory.Push(dummyRecord())
require.Equal(t, 6, len(deadlockhistory.GlobalDeadlockHistory.GetAll()))
require.Equal(t, uint64(7), deadlockhistory.GlobalDeadlockHistory.GetAll()[0].ID)
require.Equal(t, uint64(12), deadlockhistory.GlobalDeadlockHistory.GetAll()[5].ID)
// test deadlock_history_collect_retryable
form = make(url.Values)
form.Set("deadlock_history_collect_retryable", "true")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.True(t, config.GetGlobalConfig().PessimisticTxn.DeadlockHistoryCollectRetryable)
form = make(url.Values)
form.Set("deadlock_history_collect_retryable", "false")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.False(t, config.GetGlobalConfig().PessimisticTxn.DeadlockHistoryCollectRetryable)
form = make(url.Values)
form.Set("deadlock_history_collect_retryable", "123")
resp, err = ts.formStatus("/settings", form)
require.NoError(t, err)
require.Equal(t, 400, resp.StatusCode)
require.NoError(t, resp.Body.Close())
// restore original value.
config.GetGlobalConfig().Instance.CheckMb4ValueInUTF8.Store(true)
}
func TestAllServerInfo(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
ts.startServer(t)
defer ts.stopServer(t)
resp, err := ts.fetchStatus("/info/all")
require.NoError(t, err)
defer func() { require.NoError(t, resp.Body.Close()) }()
require.Equal(t, http.StatusOK, resp.StatusCode)
decoder := json.NewDecoder(resp.Body)
clusterInfo := clusterServerInfo{}
err = decoder.Decode(&clusterInfo)
require.NoError(t, err)
require.True(t, clusterInfo.IsAllServerVersionConsistent)
require.Equal(t, 1, clusterInfo.ServersNum)
store := ts.server.newTikvHandlerTool().Store.(kv.Storage)
do, err := session.GetDomain(store)
require.NoError(t, err)
ddl := do.DDL()
require.Equal(t, ddl.GetID(), clusterInfo.OwnerID)
serverInfo, ok := clusterInfo.AllServersInfo[ddl.GetID()]
require.Equal(t, true, ok)
cfg := config.GetGlobalConfig()
require.Equal(t, cfg.AdvertiseAddress, serverInfo.IP)
require.Equal(t, cfg.Status.StatusPort, serverInfo.StatusPort)
require.Equal(t, cfg.Lease, serverInfo.Lease)
require.Equal(t, mysql.ServerVersion, serverInfo.Version)
require.Equal(t, versioninfo.TiDBGitHash, serverInfo.GitHash)
require.Equal(t, ddl.GetID(), serverInfo.ID)
}
func TestRegionsFromMeta(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
ts.startServer(t)
defer ts.stopServer(t)
resp, err := ts.fetchStatus("/regions/meta")
require.NoError(t, err)
defer func() { require.NoError(t, resp.Body.Close()) }()
require.Equal(t, http.StatusOK, resp.StatusCode)
// Verify the resp body.
decoder := json.NewDecoder(resp.Body)
metas := make([]RegionMeta, 0)
err = decoder.Decode(&metas)
require.NoError(t, err)
for _, m := range metas {
require.True(t, m.ID != 0)
}
// test no panic
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/errGetRegionByIDEmpty", `return(true)`))
defer func() { require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/errGetRegionByIDEmpty")) }()
resp1, err := ts.fetchStatus("/regions/meta")
require.NoError(t, err)
defer func() { require.NoError(t, resp1.Body.Close()) }()
}
func TestTiFlashReplica(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
ts.startServer(t)
ts.prepareData(t)
defer ts.stopServer(t)
db, err := sql.Open("mysql", ts.getDSN())
require.NoError(t, err)
defer func() {
err := db.Close()
require.NoError(t, err)
}()
dbt := testkit.NewDBTestKit(t, db)
defer func(originGC bool) {
if originGC {
ddlutil.EmulatorGCEnable()
} else {
ddlutil.EmulatorGCDisable()
}
}(ddlutil.IsEmulatorGCEnable())
// Disable emulator GC.
// Otherwise emulator GC will delete table record as soon as possible after execute drop table DDL.
ddlutil.EmulatorGCDisable()
gcTimeFormat := "20060102-15:04:05 -0700 MST"
timeBeforeDrop := time.Now().Add(0 - 48*60*60*time.Second).Format(gcTimeFormat)
safePointSQL := `INSERT HIGH_PRIORITY INTO mysql.tidb VALUES ('tikv_gc_safe_point', '%[1]s', ''),('tikv_gc_enable','true','')
ON DUPLICATE KEY
UPDATE variable_value = '%[1]s'`
// Set GC safe point and enable GC.
dbt.MustExec(fmt.Sprintf(safePointSQL, timeBeforeDrop))
resp, err := ts.fetchStatus("/tiflash/replica-deprecated")
require.NoError(t, err)
decoder := json.NewDecoder(resp.Body)
var data []tableFlashReplicaInfo
err = decoder.Decode(&data)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 0, len(data))
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/infoschema/mockTiFlashStoreCount", `return(true)`))
defer func() {
require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/infoschema/mockTiFlashStoreCount"))
}()
dbt.MustExec("use tidb")
dbt.MustExec("alter table test set tiflash replica 2 location labels 'a','b';")
resp, err = ts.fetchStatus("/tiflash/replica-deprecated")
require.NoError(t, err)
decoder = json.NewDecoder(resp.Body)
err = decoder.Decode(&data)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 1, len(data))
require.Equal(t, uint64(2), data[0].ReplicaCount)
require.Equal(t, "a,b", strings.Join(data[0].LocationLabels, ","))
require.Equal(t, false, data[0].Available)
resp, err = ts.postStatus("/tiflash/replica-deprecated", "application/json", bytes.NewBuffer([]byte(`{"id":184,"region_count":3,"flash_region_count":3}`)))
require.NoError(t, err)
require.NotNil(t, resp)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, "[schema:1146]Table which ID = 184 does not exist.", string(body))
tbl, err := ts.domain.InfoSchema().TableByName(model.NewCIStr("tidb"), model.NewCIStr("test"))
require.NoError(t, err)
req := fmt.Sprintf(`{"id":%d,"region_count":3,"flash_region_count":3}`, tbl.Meta().ID)
resp, err = ts.postStatus("/tiflash/replica-deprecated", "application/json", bytes.NewBuffer([]byte(req)))
require.NoError(t, err)
require.NotNil(t, resp)
body, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, "", string(body))
resp, err = ts.fetchStatus("/tiflash/replica-deprecated")
require.NoError(t, err)
decoder = json.NewDecoder(resp.Body)
err = decoder.Decode(&data)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 1, len(data))
require.Equal(t, uint64(2), data[0].ReplicaCount)
require.Equal(t, "a,b", strings.Join(data[0].LocationLabels, ","))
require.Equal(t, true, data[0].Available)
// Should not take effect.
dbt.MustExec("alter table test set tiflash replica 2 location labels 'a','b';")
checkFunc := func() {
resp, err := ts.fetchStatus("/tiflash/replica-deprecated")
require.NoError(t, err)
decoder = json.NewDecoder(resp.Body)
err = decoder.Decode(&data)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 1, len(data))
require.Equal(t, uint64(2), data[0].ReplicaCount)
require.Equal(t, "a,b", strings.Join(data[0].LocationLabels, ","))
require.Equal(t, true, data[0].Available)
}
// Test for get dropped table tiflash replica info.
dbt.MustExec("drop table test")
checkFunc()
// Test unique table id replica info.
dbt.MustExec("flashback table test")
checkFunc()
dbt.MustExec("drop table test")
checkFunc()
dbt.MustExec("flashback table test")
checkFunc()
// Test for partition table.
dbt.MustExec("alter table pt set tiflash replica 2 location labels 'a','b';")
dbt.MustExec("alter table test set tiflash replica 0;")
resp, err = ts.fetchStatus("/tiflash/replica-deprecated")
require.NoError(t, err)
decoder = json.NewDecoder(resp.Body)
err = decoder.Decode(&data)
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
require.Equal(t, 3, len(data))
require.Equal(t, uint64(2), data[0].ReplicaCount)
require.Equal(t, "a,b", strings.Join(data[0].LocationLabels, ","))
require.Equal(t, false, data[0].Available)
pid0 := data[0].ID
pid1 := data[1].ID
pid2 := data[2].ID
// Mock for partition 1 replica was available.
req = fmt.Sprintf(`{"id":%d,"region_count":3,"flash_region_count":3}`, pid1)
resp, err = ts.postStatus("/tiflash/replica-deprecated", "application/json", bytes.NewBuffer([]byte(req)))
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
resp, err = ts.fetchStatus("/tiflash/replica-deprecated")
require.NoError(t, err)
decoder = json.NewDecoder(resp.Body)
err = decoder.Decode(&data)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 3, len(data))
require.Equal(t, false, data[0].Available)
require.Equal(t, true, data[1].Available)
require.Equal(t, false, data[2].Available)
// Mock for partition 0,2 replica was available.
req = fmt.Sprintf(`{"id":%d,"region_count":3,"flash_region_count":3}`, pid0)
resp, err = ts.postStatus("/tiflash/replica-deprecated", "application/json", bytes.NewBuffer([]byte(req)))
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
req = fmt.Sprintf(`{"id":%d,"region_count":3,"flash_region_count":3}`, pid2)
resp, err = ts.postStatus("/tiflash/replica-deprecated", "application/json", bytes.NewBuffer([]byte(req)))
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
checkFunc = func() {
resp, err := ts.fetchStatus("/tiflash/replica-deprecated")
require.NoError(t, err)
decoder = json.NewDecoder(resp.Body)
err = decoder.Decode(&data)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 3, len(data))
require.Equal(t, true, data[0].Available)
require.Equal(t, true, data[1].Available)
require.Equal(t, true, data[2].Available)
}
// Test for get truncated table tiflash replica info.
dbt.MustExec("truncate table pt")
dbt.MustExec("alter table pt set tiflash replica 0;")
checkFunc()
}
func TestFailpointHandler(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
// start server without enabling failpoint integration
ts.startServer(t)
defer ts.stopServer(t)
resp, err := ts.fetchStatus("/fail/")
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
require.NoError(t, resp.Body.Close())
ts.stopServer(t)
// enable failpoint integration and start server
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/enableTestAPI", "return"))
defer func() { require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/enableTestAPI")) }()
ts.startServer(t)
resp, err = ts.fetchStatus("/fail/")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.True(t, strings.Contains(string(b), "github.com/pingcap/tidb/server/enableTestAPI=return"))
require.NoError(t, resp.Body.Close())
}
func TestTestHandler(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
// start server without enabling failpoint integration
ts.startServer(t)
defer ts.stopServer(t)
resp, err := ts.fetchStatus("/test")
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, resp.StatusCode)
require.NoError(t, resp.Body.Close())
ts.stopServer(t)
// enable failpoint integration and start server
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/server/enableTestAPI", "return"))
defer func() { require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/server/enableTestAPI")) }()
ts.startServer(t)
resp, err = ts.fetchStatus("/test/gc/gc")
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
resp, err = ts.fetchStatus("/test/gc/resolvelock")
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
resp, err = ts.fetchStatus("/test/gc/resolvelock?safepoint=a")
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
resp, err = ts.fetchStatus("/test/gc/resolvelock?physical=1")
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
resp, err = ts.fetchStatus("/test/gc/resolvelock?physical=true")
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
resp, err = ts.fetchStatus("/test/gc/resolvelock?safepoint=10000&physical=true")
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestServerInfo(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
ts.startServer(t)
defer ts.stopServer(t)
resp, err := ts.fetchStatus("/info")
require.NoError(t, err)
defer func() { require.NoError(t, resp.Body.Close()) }()
require.Equal(t, http.StatusOK, resp.StatusCode)
decoder := json.NewDecoder(resp.Body)
info := serverInfo{}
err = decoder.Decode(&info)
require.NoError(t, err)
cfg := config.GetGlobalConfig()
require.True(t, info.IsOwner)
require.Equal(t, cfg.AdvertiseAddress, info.IP)
require.Equal(t, cfg.Status.StatusPort, info.StatusPort)
require.Equal(t, cfg.Lease, info.Lease)
require.Equal(t, mysql.ServerVersion, info.Version)
require.Equal(t, versioninfo.TiDBGitHash, info.GitHash)
store := ts.server.newTikvHandlerTool().Store.(kv.Storage)
do, err := session.GetDomain(store)
require.NoError(t, err)
d := do.DDL()
require.Equal(t, d.GetID(), info.ID)
}
func TestGetSchemaStorage(t *testing.T) {
ts := createBasicHTTPHandlerTestSuite()
ts.startServer(t)
ts.prepareData(t)
defer ts.stopServer(t)
do := ts.domain
h := do.StatsHandle()
do.SetStatsUpdating(true)
tk := testkit.NewTestKit(t, ts.store)
tk.MustExec("use test")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t (c int, d int, e char(5), index idx(e))")
tk.MustExec(`insert into t(c, d, e) values(1, 2, "c"), (2, 3, "d"), (3, 4, "e")`)
h.FlushStats()
resp, err := ts.fetchStatus("/schema_storage/test")
require.NoError(t, err)
decoder := json.NewDecoder(resp.Body)
var tables []*schemaTableStorage
err = decoder.Decode(&tables)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Len(t, tables, 1)
expects := []string{`t`}
names := make([]string, len(tables))
for i, v := range tables {
names[i] = v.TableName
}
sort.Strings(names)
require.Equal(t, expects, names)
require.Equal(t, []int64{3, 18, 54, 0, 6, 0}, []int64{
tables[0].TableRows,
tables[0].AvgRowLength,
tables[0].DataLength,
tables[0].MaxDataLength,
tables[0].IndexLength,
tables[0].DataFree,
})
}