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.
1294 lines
40 KiB
1294 lines
40 KiB
// Copyright 2018 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,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"database/sql"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
. "github.com/pingcap/check"
|
|
"github.com/pingcap/failpoint"
|
|
zaplog "github.com/pingcap/log"
|
|
"github.com/pingcap/parser/model"
|
|
"github.com/pingcap/parser/mysql"
|
|
"github.com/pingcap/tidb/config"
|
|
"github.com/pingcap/tidb/ddl"
|
|
"github.com/pingcap/tidb/domain"
|
|
"github.com/pingcap/tidb/kv"
|
|
"github.com/pingcap/tidb/meta"
|
|
"github.com/pingcap/tidb/session"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/sessionctx/binloginfo"
|
|
"github.com/pingcap/tidb/sessionctx/stmtctx"
|
|
"github.com/pingcap/tidb/sessionctx/variable"
|
|
"github.com/pingcap/tidb/store/helper"
|
|
"github.com/pingcap/tidb/store/mockstore"
|
|
"github.com/pingcap/tidb/store/mockstore/mocktikv"
|
|
"github.com/pingcap/tidb/store/tikv"
|
|
"github.com/pingcap/tidb/tablecodec"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/codec"
|
|
"github.com/pingcap/tidb/util/rowcodec"
|
|
"github.com/pingcap/tidb/util/versioninfo"
|
|
log "github.com/sirupsen/logrus"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type basicHTTPHandlerTestSuite struct {
|
|
*testServerClient
|
|
server *Server
|
|
store kv.Storage
|
|
domain *domain.Domain
|
|
tidbdrv *TiDBDriver
|
|
}
|
|
|
|
type HTTPHandlerTestSuite struct {
|
|
*basicHTTPHandlerTestSuite
|
|
}
|
|
|
|
type HTTPHandlerTestSerialSuite struct {
|
|
*basicHTTPHandlerTestSuite
|
|
}
|
|
|
|
var _ = Suite(&HTTPHandlerTestSuite{&basicHTTPHandlerTestSuite{}})
|
|
|
|
var _ = SerialSuites(&HTTPHandlerTestSerialSuite{&basicHTTPHandlerTestSuite{}})
|
|
|
|
func (ts *basicHTTPHandlerTestSuite) SetUpSuite(c *C) {
|
|
ts.testServerClient = newTestServerClient()
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestRegionIndexRange(c *C) {
|
|
sTableID := int64(3)
|
|
sIndex := int64(11)
|
|
eTableID := int64(9)
|
|
recordID := int64(133)
|
|
indexValues := []types.Datum{
|
|
types.NewIntDatum(100),
|
|
types.NewBytesDatum([]byte("foobar")),
|
|
types.NewFloat64Datum(-100.25),
|
|
}
|
|
expectIndexValues := make([]string, 0, len(indexValues))
|
|
for _, v := range indexValues {
|
|
str, err := v.ToString()
|
|
if err != nil {
|
|
str = fmt.Sprintf("%d-%v", v.Kind(), v.GetValue())
|
|
}
|
|
expectIndexValues = append(expectIndexValues, str)
|
|
}
|
|
encodedValue, err := codec.EncodeKey(&stmtctx.StatementContext{TimeZone: time.Local}, nil, indexValues...)
|
|
c.Assert(err, IsNil)
|
|
|
|
startKey := tablecodec.EncodeIndexSeekKey(sTableID, sIndex, encodedValue)
|
|
recordPrefix := tablecodec.GenTableRecordPrefix(eTableID)
|
|
endKey := tablecodec.EncodeRecordKey(recordPrefix, recordID)
|
|
|
|
region := &tikv.KeyLocation{
|
|
Region: tikv.RegionVerID{},
|
|
StartKey: startKey,
|
|
EndKey: endKey,
|
|
}
|
|
r, err := helper.NewRegionFrameRange(region)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(r.First.IndexID, Equals, sIndex)
|
|
c.Assert(r.First.IsRecord, IsFalse)
|
|
c.Assert(r.First.RecordID, Equals, int64(0))
|
|
c.Assert(r.First.IndexValues, DeepEquals, expectIndexValues)
|
|
c.Assert(r.Last.RecordID, Equals, recordID)
|
|
c.Assert(r.Last.IndexValues, IsNil)
|
|
|
|
testCases := []struct {
|
|
tableID int64
|
|
indexID int64
|
|
isCover bool
|
|
}{
|
|
{2, 0, false},
|
|
{3, 0, true},
|
|
{9, 0, true},
|
|
{10, 0, false},
|
|
{2, 10, false},
|
|
{3, 10, false},
|
|
{3, 11, true},
|
|
{3, 20, true},
|
|
{9, 10, true},
|
|
{10, 1, false},
|
|
}
|
|
for _, t := range testCases {
|
|
var f *helper.FrameItem
|
|
if t.indexID == 0 {
|
|
f = r.GetRecordFrame(t.tableID, "", "")
|
|
} else {
|
|
f = r.GetIndexFrame(t.tableID, t.indexID, "", "", "")
|
|
}
|
|
if t.isCover {
|
|
c.Assert(f, NotNil)
|
|
} else {
|
|
c.Assert(f, IsNil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestRegionIndexRangeWithEndNoLimit(c *C) {
|
|
sTableID := int64(15)
|
|
startKey := tablecodec.GenTableRecordPrefix(sTableID)
|
|
endKey := []byte("z_aaaaafdfd")
|
|
region := &tikv.KeyLocation{
|
|
Region: tikv.RegionVerID{},
|
|
StartKey: startKey,
|
|
EndKey: endKey,
|
|
}
|
|
r, err := helper.NewRegionFrameRange(region)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(r.First.IsRecord, IsTrue)
|
|
c.Assert(r.Last.IsRecord, IsTrue)
|
|
c.Assert(r.GetRecordFrame(300, "", ""), NotNil)
|
|
c.Assert(r.GetIndexFrame(200, 100, "", "", ""), NotNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestRegionIndexRangeWithStartNoLimit(c *C) {
|
|
eTableID := int64(9)
|
|
startKey := []byte("m_aaaaafdfd")
|
|
endKey := tablecodec.GenTableRecordPrefix(eTableID)
|
|
region := &tikv.KeyLocation{
|
|
Region: tikv.RegionVerID{},
|
|
StartKey: startKey,
|
|
EndKey: endKey,
|
|
}
|
|
r, err := helper.NewRegionFrameRange(region)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(r.First.IsRecord, IsFalse)
|
|
c.Assert(r.Last.IsRecord, IsTrue)
|
|
c.Assert(r.GetRecordFrame(3, "", ""), NotNil)
|
|
c.Assert(r.GetIndexFrame(8, 1, "", "", ""), NotNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestRegionsAPI(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/tables/information_schema/SCHEMATA/regions")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
defer resp.Body.Close()
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
var data TableRegions
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(data.RecordRegions) > 0, IsTrue)
|
|
|
|
// list region
|
|
for _, region := range data.RecordRegions {
|
|
c.Assert(ts.regionContainsTable(c, region.ID, data.TableID), IsTrue)
|
|
}
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestRangesAPI(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
ts.prepareData(c)
|
|
resp, err := ts.fetchStatus("/tables/information_schema/SCHEMATA/ranges")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
defer resp.Body.Close()
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
var data TableRanges
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(data.TableName, Equals, "SCHEMATA")
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) regionContainsTable(c *C, regionID uint64, tableID int64) bool {
|
|
resp, err := ts.fetchStatus(fmt.Sprintf("/regions/%d", regionID))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
defer resp.Body.Close()
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var data RegionDetail
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
for _, index := range data.Frames {
|
|
if index.TableID == tableID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestListTableRegions(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
ts.prepareData(c)
|
|
// Test list table regions with error
|
|
resp, err := ts.fetchStatus("/tables/fdsfds/aaa/regions")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
|
|
resp, err = ts.fetchStatus("/tables/tidb/pt/regions")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
|
|
var data []*TableRegions
|
|
dec := json.NewDecoder(resp.Body)
|
|
err = dec.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
|
|
region := data[1]
|
|
_, err = ts.fetchStatus(fmt.Sprintf("/regions/%d", region.TableID))
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestListTableRanges(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
ts.prepareData(c)
|
|
// Test list table regions with error
|
|
resp, err := ts.fetchStatus("/tables/fdsfds/aaa/ranges")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
|
|
resp, err = ts.fetchStatus("/tables/tidb/pt/ranges")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
|
|
var data []*TableRanges
|
|
dec := json.NewDecoder(resp.Body)
|
|
err = dec.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(data), Equals, 3)
|
|
for i, partition := range data {
|
|
c.Assert(partition.TableName, Equals, fmt.Sprintf("p%d", i))
|
|
}
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestGetRegionByIDWithError(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/regions/xxx")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
defer resp.Body.Close()
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestBinlogRecover(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
binloginfo.EnableSkipBinlogFlag()
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, true)
|
|
resp, err := ts.fetchStatus("/binlog/recover")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, false)
|
|
|
|
// Invalid operation will use the default operation.
|
|
binloginfo.EnableSkipBinlogFlag()
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, true)
|
|
resp, err = ts.fetchStatus("/binlog/recover?op=abc")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, false)
|
|
|
|
binloginfo.EnableSkipBinlogFlag()
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, true)
|
|
resp, err = ts.fetchStatus("/binlog/recover?op=abc&seconds=1")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, false)
|
|
|
|
binloginfo.EnableSkipBinlogFlag()
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, true)
|
|
binloginfo.AddOneSkippedCommitter()
|
|
resp, err = ts.fetchStatus("/binlog/recover?op=abc&seconds=1")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, false)
|
|
binloginfo.RemoveOneSkippedCommitter()
|
|
|
|
binloginfo.AddOneSkippedCommitter()
|
|
c.Assert(binloginfo.SkippedCommitterCount(), Equals, int32(1))
|
|
resp, err = ts.fetchStatus("/binlog/recover?op=reset")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(binloginfo.SkippedCommitterCount(), Equals, int32(0))
|
|
|
|
binloginfo.EnableSkipBinlogFlag()
|
|
resp, err = ts.fetchStatus("/binlog/recover?op=nowait")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, false)
|
|
|
|
// Only the first should work.
|
|
binloginfo.EnableSkipBinlogFlag()
|
|
resp, err = ts.fetchStatus("/binlog/recover?op=nowait&op=reset")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(binloginfo.IsBinlogSkipped(), Equals, false)
|
|
|
|
resp, err = ts.fetchStatus("/binlog/recover?op=status")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestRegionsFromMeta(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/regions/meta")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
|
|
// Verify the resp body.
|
|
decoder := json.NewDecoder(resp.Body)
|
|
metas := make([]RegionMeta, 0)
|
|
err = decoder.Decode(&metas)
|
|
c.Assert(err, IsNil)
|
|
for _, meta := range metas {
|
|
c.Assert(meta.ID != 0, IsTrue)
|
|
}
|
|
|
|
// test no panic
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/server/errGetRegionByIDEmpty", `return(true)`), IsNil)
|
|
resp1, err := ts.fetchStatus("/regions/meta")
|
|
c.Assert(err, IsNil)
|
|
defer resp1.Body.Close()
|
|
c.Assert(failpoint.Disable("github.com/pingcap/tidb/server/errGetRegionByIDEmpty"), IsNil)
|
|
}
|
|
|
|
func (ts *basicHTTPHandlerTestSuite) startServer(c *C) {
|
|
mvccStore := mocktikv.MustNewMVCCStore()
|
|
var err error
|
|
ts.store, err = mockstore.NewMockTikvStore(mockstore.WithMVCCStore(mvccStore))
|
|
c.Assert(err, IsNil)
|
|
ts.domain, err = session.BootstrapSession(ts.store)
|
|
c.Assert(err, IsNil)
|
|
ts.tidbdrv = NewTiDBDriver(ts.store)
|
|
|
|
cfg := config.NewConfig()
|
|
cfg.Port = ts.port
|
|
cfg.Store = "tikv"
|
|
cfg.Status.StatusPort = ts.statusPort
|
|
cfg.Status.ReportStatus = true
|
|
|
|
server, err := NewServer(cfg, ts.tidbdrv)
|
|
c.Assert(err, IsNil, Commentf("??? %v", fmt.Sprintf("%+v", err)))
|
|
ts.port, ts.statusPort = getPortFromTCPAddr(server.listener.Addr()), getPortFromTCPAddr(server.statusListener.Addr())
|
|
ts.server = server
|
|
go server.Run()
|
|
ts.waitUntilServerOnline()
|
|
}
|
|
|
|
func (ts *basicHTTPHandlerTestSuite) stopServer(c *C) {
|
|
if ts.domain != nil {
|
|
ts.domain.Close()
|
|
}
|
|
if ts.store != nil {
|
|
ts.store.Close()
|
|
}
|
|
if ts.server != nil {
|
|
ts.server.Close()
|
|
}
|
|
}
|
|
|
|
func (ts *basicHTTPHandlerTestSuite) prepareData(c *C) {
|
|
db, err := sql.Open("mysql", ts.getDSN())
|
|
c.Assert(err, IsNil, Commentf("Error connecting"))
|
|
defer db.Close()
|
|
dbt := &DBTest{c, db}
|
|
|
|
dbt.mustExec("create database tidb;")
|
|
dbt.mustExec("use tidb;")
|
|
dbt.mustExec("create table tidb.test (a int auto_increment primary key, b varchar(20));")
|
|
dbt.mustExec("insert tidb.test values (1, 1);")
|
|
txn1, err := dbt.db.Begin()
|
|
c.Assert(err, IsNil)
|
|
_, err = txn1.Exec("update tidb.test set b = b + 1 where a = 1;")
|
|
c.Assert(err, IsNil)
|
|
_, err = txn1.Exec("insert tidb.test values (2, 2);")
|
|
c.Assert(err, IsNil)
|
|
_, err = txn1.Exec("insert tidb.test (a) values (3);")
|
|
c.Assert(err, IsNil)
|
|
_, err = txn1.Exec("insert tidb.test values (4, '');")
|
|
c.Assert(err, IsNil)
|
|
err = txn1.Commit()
|
|
c.Assert(err, IsNil)
|
|
dbt.mustExec("alter table tidb.test add index idx1 (a, b);")
|
|
dbt.mustExec("alter table tidb.test add unique index idx2 (a, b);")
|
|
|
|
dbt.mustExec(`create table tidb.pt (a int primary key, b varchar(20), key idx(a, b))
|
|
partition by range (a)
|
|
(partition p0 values less than (256),
|
|
partition p1 values less than (512),
|
|
partition p2 values less than (1024))`)
|
|
|
|
txn2, err := dbt.db.Begin()
|
|
c.Assert(err, IsNil)
|
|
txn2.Exec("insert into tidb.pt values (42, '123')")
|
|
txn2.Exec("insert into tidb.pt values (256, 'b')")
|
|
txn2.Exec("insert into tidb.pt values (666, 'def')")
|
|
err = txn2.Commit()
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func decodeKeyMvcc(closer io.ReadCloser, c *C, valid bool) {
|
|
decoder := json.NewDecoder(closer)
|
|
var data mvccKV
|
|
err := decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
if valid {
|
|
c.Assert(data.Value.Info, NotNil)
|
|
c.Assert(len(data.Value.Info.Writes), Greater, 0)
|
|
} else {
|
|
c.Assert(data.Value.Info.Lock, IsNil)
|
|
c.Assert(data.Value.Info.Writes, IsNil)
|
|
c.Assert(data.Value.Info.Values, IsNil)
|
|
}
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestGetTableMVCC(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
|
|
resp, err := ts.fetchStatus(fmt.Sprintf("/mvcc/key/tidb/test/1"))
|
|
c.Assert(err, IsNil)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var data mvccKV
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(data.Value, NotNil)
|
|
info := data.Value.Info
|
|
c.Assert(info, NotNil)
|
|
c.Assert(len(info.Writes), Greater, 0)
|
|
startTs := info.Writes[2].StartTs
|
|
|
|
resp, err = ts.fetchStatus(fmt.Sprintf("/mvcc/txn/%d/tidb/test", startTs))
|
|
c.Assert(err, IsNil)
|
|
var p2 mvccKV
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&p2)
|
|
c.Assert(err, IsNil)
|
|
|
|
for i, expect := range info.Values {
|
|
v2 := p2.Value.Info.Values[i].Value
|
|
c.Assert(v2, BytesEquals, expect.Value)
|
|
}
|
|
|
|
hexKey := p2.Key
|
|
resp, err = ts.fetchStatus("/mvcc/hex/" + hexKey)
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
var data2 mvccKV
|
|
err = decoder.Decode(&data2)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(data2, DeepEquals, data)
|
|
|
|
resp, err = ts.fetchStatus(fmt.Sprintf("/mvcc/key/tidb/test/1?decode=true"))
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
var data3 map[string]interface{}
|
|
err = decoder.Decode(&data3)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(data3["key"], NotNil)
|
|
c.Assert(data3["info"], NotNil)
|
|
c.Assert(data3["data"], NotNil)
|
|
c.Assert(data3["decode_error"], IsNil)
|
|
|
|
resp, err = ts.fetchStatus("/mvcc/key/tidb/pt(p0)/42?decode=true")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
decoder = json.NewDecoder(resp.Body)
|
|
var data4 map[string]interface{}
|
|
err = decoder.Decode(&data4)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(data4["key"], NotNil)
|
|
c.Assert(data4["info"], NotNil)
|
|
c.Assert(data4["data"], NotNil)
|
|
c.Assert(data4["decode_error"], IsNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestGetMVCCNotFound(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus(fmt.Sprintf("/mvcc/key/tidb/test/1234"))
|
|
c.Assert(err, IsNil)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var data mvccKV
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(data.Value.Info.Lock, IsNil)
|
|
c.Assert(data.Value.Info.Writes, IsNil)
|
|
c.Assert(data.Value.Info.Values, IsNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestTiFlashReplica(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
|
|
db, err := sql.Open("mysql", ts.getDSN())
|
|
c.Assert(err, IsNil, Commentf("Error connecting"))
|
|
defer db.Close()
|
|
dbt := &DBTest{c, db}
|
|
|
|
defer func(originGC bool) {
|
|
if originGC {
|
|
ddl.EmulatorGCEnable()
|
|
} else {
|
|
ddl.EmulatorGCDisable()
|
|
}
|
|
}(ddl.IsEmulatorGCEnable())
|
|
|
|
// Disable emulator GC.
|
|
// Otherwise emulator GC will delete table record as soon as possible after execute drop table DDL.
|
|
ddl.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")
|
|
c.Assert(err, IsNil)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var data []tableFlashReplicaInfo
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(data), Equals, 0)
|
|
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/infoschema/mockTiFlashStoreCount", `return(true)`), IsNil)
|
|
defer 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")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(data), Equals, 1)
|
|
c.Assert(data[0].ReplicaCount, Equals, uint64(2))
|
|
c.Assert(strings.Join(data[0].LocationLabels, ","), Equals, "a,b")
|
|
c.Assert(data[0].Available, Equals, false)
|
|
|
|
resp, err = ts.postStatus("/tiflash/replica", "application/json", bytes.NewBuffer([]byte(`{"id":84,"region_count":3,"flash_region_count":3}`)))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp, NotNil)
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(string(body), Equals, "[schema:1146]Table which ID = 84 does not exist.")
|
|
|
|
t, err := ts.domain.InfoSchema().TableByName(model.NewCIStr("tidb"), model.NewCIStr("test"))
|
|
c.Assert(err, IsNil)
|
|
req := fmt.Sprintf(`{"id":%d,"region_count":3,"flash_region_count":3}`, t.Meta().ID)
|
|
resp, err = ts.postStatus("/tiflash/replica", "application/json", bytes.NewBuffer([]byte(req)))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp, NotNil)
|
|
body, err = ioutil.ReadAll(resp.Body)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(string(body), Equals, "")
|
|
|
|
resp, err = ts.fetchStatus("/tiflash/replica")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(len(data), Equals, 1)
|
|
c.Assert(data[0].ReplicaCount, Equals, uint64(2))
|
|
c.Assert(strings.Join(data[0].LocationLabels, ","), Equals, "a,b")
|
|
c.Assert(data[0].Available, Equals, true) // The status should be true now.
|
|
|
|
// 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")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(len(data), Equals, 1)
|
|
c.Assert(data[0].ReplicaCount, Equals, uint64(2))
|
|
c.Assert(strings.Join(data[0].LocationLabels, ","), Equals, "a,b")
|
|
c.Assert(data[0].Available, Equals, true) // The status should be true now.
|
|
}
|
|
|
|
// 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")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(len(data), Equals, 3)
|
|
c.Assert(data[0].ReplicaCount, Equals, uint64(2))
|
|
c.Assert(strings.Join(data[0].LocationLabels, ","), Equals, "a,b")
|
|
c.Assert(data[0].Available, Equals, false)
|
|
|
|
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", "application/json", bytes.NewBuffer([]byte(req)))
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
resp, err = ts.fetchStatus("/tiflash/replica")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(len(data), Equals, 3)
|
|
c.Assert(data[0].Available, Equals, false)
|
|
c.Assert(data[1].Available, Equals, true)
|
|
c.Assert(data[2].Available, Equals, false)
|
|
|
|
// 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", "application/json", bytes.NewBuffer([]byte(req)))
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
req = fmt.Sprintf(`{"id":%d,"region_count":3,"flash_region_count":3}`, pid2)
|
|
resp, err = ts.postStatus("/tiflash/replica", "application/json", bytes.NewBuffer([]byte(req)))
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
checkFunc = func() {
|
|
resp, err = ts.fetchStatus("/tiflash/replica")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(len(data), Equals, 3)
|
|
c.Assert(data[0].Available, Equals, true)
|
|
c.Assert(data[1].Available, Equals, true)
|
|
c.Assert(data[2].Available, Equals, true)
|
|
}
|
|
|
|
// Test for get truncated table tiflash replica info.
|
|
dbt.mustExec("truncate table pt")
|
|
dbt.mustExec("alter table pt set tiflash replica 0;")
|
|
checkFunc()
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestDecodeColumnValue(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
|
|
// column is a structure used for test
|
|
type column struct {
|
|
id int64
|
|
tp *types.FieldType
|
|
}
|
|
// Backfill columns.
|
|
c1 := &column{id: 1, tp: types.NewFieldType(mysql.TypeLonglong)}
|
|
c2 := &column{id: 2, tp: types.NewFieldType(mysql.TypeVarchar)}
|
|
c3 := &column{id: 3, tp: types.NewFieldType(mysql.TypeNewDecimal)}
|
|
c4 := &column{id: 4, tp: types.NewFieldType(mysql.TypeTimestamp)}
|
|
cols := []*column{c1, c2, c3, c4}
|
|
row := make([]types.Datum, len(cols))
|
|
row[0] = types.NewIntDatum(100)
|
|
row[1] = types.NewBytesDatum([]byte("abc"))
|
|
row[2] = types.NewDecimalDatum(types.NewDecFromInt(1))
|
|
row[3] = types.NewTimeDatum(types.NewTime(types.FromGoTime(time.Now()), mysql.TypeTimestamp, 6))
|
|
|
|
// Encode the row.
|
|
colIDs := make([]int64, 0, 3)
|
|
for _, col := range cols {
|
|
colIDs = append(colIDs, col.id)
|
|
}
|
|
rd := rowcodec.Encoder{Enable: true}
|
|
sc := &stmtctx.StatementContext{TimeZone: time.UTC}
|
|
bs, err := tablecodec.EncodeRow(sc, row, colIDs, nil, nil, &rd)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(bs, NotNil)
|
|
bin := base64.StdEncoding.EncodeToString(bs)
|
|
|
|
unitTest := func(col *column) {
|
|
path := fmt.Sprintf("/tables/%d/%v/%d/%d?rowBin=%s", col.id, col.tp.Tp, col.tp.Flag, col.tp.Flen, bin)
|
|
resp, err := ts.fetchStatus(path)
|
|
c.Assert(err, IsNil, Commentf("url:%s", ts.statusURL(path)))
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var data interface{}
|
|
err = decoder.Decode(&data)
|
|
c.Assert(err, IsNil, Commentf("url:%v\ndata%v", ts.statusURL(path), data))
|
|
colVal, err := types.DatumsToString([]types.Datum{row[col.id-1]}, false)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(data, Equals, colVal, Commentf("url:%v", ts.statusURL(path)))
|
|
}
|
|
|
|
for _, col := range cols {
|
|
unitTest(col)
|
|
}
|
|
|
|
// Test bin has `+`.
|
|
// 2018-03-08 16:01:00.315313
|
|
bin = "CAIIyAEIBAIGYWJjCAYGAQCBCAgJsZ+TgISg1M8Z"
|
|
row[3] = types.NewTimeDatum(types.NewTime(types.FromGoTime(time.Date(2018, 3, 8, 16, 1, 0, 315313000, time.UTC)), mysql.TypeTimestamp, 6))
|
|
unitTest(cols[3])
|
|
|
|
// Test bin has `/`.
|
|
// 2018-03-08 02:44:46.409199
|
|
bin = "CAIIyAEIBAIGYWJjCAYGAQCBCAgJ7/yY8LKF1M8Z"
|
|
row[3] = types.NewTimeDatum(types.NewTime(types.FromGoTime(time.Date(2018, 3, 8, 2, 44, 46, 409199000, time.UTC)), mysql.TypeTimestamp, 6))
|
|
unitTest(cols[3])
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestGetIndexMVCC(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
|
|
// tests for normal index key
|
|
resp, err := ts.fetchStatus("/mvcc/index/tidb/test/idx1/1?a=1&b=2")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, true)
|
|
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx2/1?a=1&b=2")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, true)
|
|
|
|
// tests for index key which includes null
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx1/3?a=3&b")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, true)
|
|
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx2/3?a=3&b")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, true)
|
|
|
|
// tests for index key which includes empty string
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx1/4?a=4&b=")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, true)
|
|
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx2/3?a=4&b=")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, true)
|
|
|
|
// tests for wrong key
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx1/5?a=5&b=1")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, false)
|
|
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx2/5?a=5&b=1")
|
|
c.Assert(err, IsNil)
|
|
decodeKeyMvcc(resp.Body, c, false)
|
|
|
|
// tests for missing column value
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx1/1?a=1")
|
|
c.Assert(err, IsNil)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var data1 mvccKV
|
|
err = decoder.Decode(&data1)
|
|
c.Assert(err, NotNil)
|
|
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/test/idx2/1?a=1")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
var data2 mvccKV
|
|
err = decoder.Decode(&data2)
|
|
c.Assert(err, NotNil)
|
|
|
|
resp, err = ts.fetchStatus("/mvcc/index/tidb/pt(p2)/idx/666?a=666&b=def")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
decodeKeyMvcc(resp.Body, c, true)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestGetSettings(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/settings")
|
|
c.Assert(err, IsNil)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var settings *config.Config
|
|
err = decoder.Decode(&settings)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(settings, DeepEquals, config.GetGlobalConfig())
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestGetSchema(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/schema")
|
|
c.Assert(err, IsNil)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
var dbs []*model.DBInfo
|
|
err = decoder.Decode(&dbs)
|
|
c.Assert(err, IsNil)
|
|
expects := []string{"information_schema", "metrics_schema", "mysql", "performance_schema", "test", "tidb"}
|
|
names := make([]string, len(dbs))
|
|
for i, v := range dbs {
|
|
names[i] = v.Name.L
|
|
}
|
|
sort.Strings(names)
|
|
c.Assert(names, DeepEquals, expects)
|
|
|
|
resp, err = ts.fetchStatus("/schema?table_id=5")
|
|
c.Assert(err, IsNil)
|
|
var t *model.TableInfo
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&t)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(t.Name.L, Equals, "user")
|
|
|
|
_, err = ts.fetchStatus("/schema?table_id=a")
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = ts.fetchStatus("/schema?table_id=1")
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = ts.fetchStatus("/schema?table_id=-1")
|
|
c.Assert(err, IsNil)
|
|
|
|
resp, err = ts.fetchStatus("/schema/tidb")
|
|
c.Assert(err, IsNil)
|
|
var lt []*model.TableInfo
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(<)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(lt), Greater, 0)
|
|
|
|
_, err = ts.fetchStatus("/schema/abc")
|
|
c.Assert(err, IsNil)
|
|
|
|
resp, err = ts.fetchStatus("/schema/tidb/test")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&t)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(t.Name.L, Equals, "test")
|
|
|
|
_, err = ts.fetchStatus("/schema/tidb/abc")
|
|
c.Assert(err, IsNil)
|
|
|
|
resp, err = ts.fetchStatus("/db-table/5")
|
|
c.Assert(err, IsNil)
|
|
var dbtbl *dbTableInfo
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&dbtbl)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(dbtbl.TableInfo.Name.L, Equals, "user")
|
|
c.Assert(dbtbl.DBInfo.Name.L, Equals, "mysql")
|
|
se, err := session.CreateSession(ts.store.(kv.Storage))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(dbtbl.SchemaVersion, Equals, domain.GetDomain(se.(sessionctx.Context)).InfoSchema().SchemaMetaVersion())
|
|
|
|
db, err := sql.Open("mysql", ts.getDSN())
|
|
c.Assert(err, IsNil, Commentf("Error connecting"))
|
|
defer db.Close()
|
|
dbt := &DBTest{c, db}
|
|
|
|
dbt.mustExec("create database if not exists test;")
|
|
dbt.mustExec("use test;")
|
|
dbt.mustExec(` create table t1 (id int KEY)
|
|
partition by range (id) (
|
|
PARTITION p0 VALUES LESS THAN (3),
|
|
PARTITION p1 VALUES LESS THAN (5),
|
|
PARTITION p2 VALUES LESS THAN (7),
|
|
PARTITION p3 VALUES LESS THAN (9))`)
|
|
|
|
resp, err = ts.fetchStatus("/schema/test/t1")
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&t)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(t.Name.L, Equals, "t1")
|
|
|
|
resp, err = ts.fetchStatus(fmt.Sprintf("/db-table/%v", t.GetPartitionInfo().Definitions[0].ID))
|
|
c.Assert(err, IsNil)
|
|
decoder = json.NewDecoder(resp.Body)
|
|
err = decoder.Decode(&dbtbl)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(dbtbl.TableInfo.Name.L, Equals, "t1")
|
|
c.Assert(dbtbl.DBInfo.Name.L, Equals, "test")
|
|
c.Assert(dbtbl.TableInfo, DeepEquals, t)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestAllHistory(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
_, err := ts.fetchStatus("/ddl/history/?limit=3")
|
|
c.Assert(err, IsNil)
|
|
_, err = ts.fetchStatus("/ddl/history/?limit=-1")
|
|
c.Assert(err, IsNil)
|
|
|
|
resp, err := ts.fetchStatus("/ddl/history")
|
|
c.Assert(err, IsNil)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
var jobs []*model.Job
|
|
s, _ := session.CreateSession(ts.server.newTikvHandlerTool().Store.(kv.Storage))
|
|
defer s.Close()
|
|
store := domain.GetDomain(s.(sessionctx.Context)).Store()
|
|
txn, _ := store.Begin()
|
|
txnMeta := meta.NewMeta(txn)
|
|
txnMeta.GetAllHistoryDDLJobs()
|
|
data, _ := txnMeta.GetAllHistoryDDLJobs()
|
|
err = decoder.Decode(&jobs)
|
|
|
|
c.Assert(err, IsNil)
|
|
c.Assert(jobs, DeepEquals, data)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestPostSettings(c *C) {
|
|
ts.startServer(c)
|
|
ts.prepareData(c)
|
|
defer ts.stopServer(c)
|
|
form := make(url.Values)
|
|
form.Set("log_level", "error")
|
|
form.Set("tidb_general_log", "1")
|
|
resp, err := ts.formStatus("/settings", form)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(log.GetLevel(), Equals, log.ErrorLevel)
|
|
c.Assert(zaplog.GetLevel(), Equals, zap.ErrorLevel)
|
|
c.Assert(config.GetGlobalConfig().Log.Level, Equals, "error")
|
|
c.Assert(atomic.LoadUint32(&variable.ProcessGeneralLog), Equals, uint32(1))
|
|
form = make(url.Values)
|
|
form.Set("log_level", "fatal")
|
|
form.Set("tidb_general_log", "0")
|
|
resp, err = ts.formStatus("/settings", form)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(atomic.LoadUint32(&variable.ProcessGeneralLog), Equals, uint32(0))
|
|
c.Assert(log.GetLevel(), Equals, log.FatalLevel)
|
|
c.Assert(zaplog.GetLevel(), Equals, zap.FatalLevel)
|
|
c.Assert(config.GetGlobalConfig().Log.Level, Equals, "fatal")
|
|
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)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(atomic.LoadUint32(&variable.DDLSlowOprThreshold), Equals, uint32(200))
|
|
|
|
// test check_mb4_value_in_utf8
|
|
db, err := sql.Open("mysql", ts.getDSN())
|
|
c.Assert(err, IsNil, Commentf("Error connecting"))
|
|
defer db.Close()
|
|
dbt := &DBTest{c, 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)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(config.GetGlobalConfig().CheckMb4ValueInUTF8, Equals, true)
|
|
txn1, err := dbt.db.Begin()
|
|
c.Assert(err, IsNil)
|
|
_, err = txn1.Exec("insert t2 values (unhex('F0A48BAE'));")
|
|
c.Assert(err, NotNil)
|
|
txn1.Commit()
|
|
|
|
// Disable CheckMb4ValueInUTF8.
|
|
form = make(url.Values)
|
|
form.Set("check_mb4_value_in_utf8", "0")
|
|
resp, err = ts.formStatus("/settings", form)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
c.Assert(config.GetGlobalConfig().CheckMb4ValueInUTF8, Equals, false)
|
|
dbt.mustExec("insert t2 values (unhex('f09f8c80'));")
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestPprof(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
retryTime := 100
|
|
for retry := 0; retry < retryTime; retry++ {
|
|
resp, err := ts.fetchStatus("/debug/pprof/heap")
|
|
if err == nil {
|
|
ioutil.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
return
|
|
}
|
|
time.Sleep(time.Millisecond * 10)
|
|
}
|
|
zaplog.Fatal("failed to get profile for %d retries in every 10 ms", zap.Int("retryTime", retryTime))
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestServerInfo(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/info")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
info := serverInfo{}
|
|
err = decoder.Decode(&info)
|
|
c.Assert(err, IsNil)
|
|
|
|
cfg := config.GetGlobalConfig()
|
|
c.Assert(info.IsOwner, IsTrue)
|
|
c.Assert(info.IP, Equals, cfg.AdvertiseAddress)
|
|
c.Assert(info.StatusPort, Equals, cfg.Status.StatusPort)
|
|
c.Assert(info.Lease, Equals, cfg.Lease)
|
|
c.Assert(info.Version, Equals, mysql.ServerVersion)
|
|
c.Assert(info.GitHash, Equals, versioninfo.TiDBGitHash)
|
|
|
|
store := ts.server.newTikvHandlerTool().Store.(kv.Storage)
|
|
do, err := session.GetDomain(store.(kv.Storage))
|
|
c.Assert(err, IsNil)
|
|
ddl := do.DDL()
|
|
c.Assert(info.ID, Equals, ddl.GetID())
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSerialSuite) TestAllServerInfo(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/info/all")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
clusterInfo := clusterServerInfo{}
|
|
err = decoder.Decode(&clusterInfo)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(clusterInfo.IsAllServerVersionConsistent, IsTrue)
|
|
c.Assert(clusterInfo.ServersNum, Equals, 1)
|
|
|
|
store := ts.server.newTikvHandlerTool().Store.(kv.Storage)
|
|
do, err := session.GetDomain(store.(kv.Storage))
|
|
c.Assert(err, IsNil)
|
|
ddl := do.DDL()
|
|
c.Assert(clusterInfo.OwnerID, Equals, ddl.GetID())
|
|
serverInfo, ok := clusterInfo.AllServersInfo[ddl.GetID()]
|
|
c.Assert(ok, Equals, true)
|
|
|
|
cfg := config.GetGlobalConfig()
|
|
c.Assert(serverInfo.IP, Equals, cfg.AdvertiseAddress)
|
|
c.Assert(serverInfo.StatusPort, Equals, cfg.Status.StatusPort)
|
|
c.Assert(serverInfo.Lease, Equals, cfg.Lease)
|
|
c.Assert(serverInfo.Version, Equals, mysql.ServerVersion)
|
|
c.Assert(serverInfo.GitHash, Equals, versioninfo.TiDBGitHash)
|
|
c.Assert(serverInfo.ID, Equals, ddl.GetID())
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestHotRegionInfo(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/regions/hot")
|
|
c.Assert(err, IsNil)
|
|
defer resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestDebugZip(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
resp, err := ts.fetchStatus("/debug/zip?seconds=1")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
b, err := httputil.DumpResponse(resp, true)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(b), Greater, 0)
|
|
c.Assert(resp.Body.Close(), IsNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestCheckCN(c *C) {
|
|
s := &Server{cfg: &config.Config{Security: config.Security{ClusterVerifyCN: []string{"a ", "b", "c"}}}}
|
|
tlsConfig := &tls.Config{}
|
|
s.setCNChecker(tlsConfig)
|
|
c.Assert(tlsConfig.VerifyPeerCertificate, NotNil)
|
|
err := tlsConfig.VerifyPeerCertificate(nil, [][]*x509.Certificate{{{Subject: pkix.Name{CommonName: "a"}}}})
|
|
c.Assert(err, IsNil)
|
|
err = tlsConfig.VerifyPeerCertificate(nil, [][]*x509.Certificate{{{Subject: pkix.Name{CommonName: "b"}}}})
|
|
c.Assert(err, IsNil)
|
|
err = tlsConfig.VerifyPeerCertificate(nil, [][]*x509.Certificate{{{Subject: pkix.Name{CommonName: "d"}}}})
|
|
c.Assert(err, NotNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestZipInfoForSQL(c *C) {
|
|
ts.startServer(c)
|
|
defer ts.stopServer(c)
|
|
|
|
db, err := sql.Open("mysql", ts.getDSN())
|
|
c.Assert(err, IsNil, Commentf("Error connecting"))
|
|
defer db.Close()
|
|
dbt := &DBTest{c, db}
|
|
|
|
dbt.mustExec("use test")
|
|
dbt.mustExec("create table if not exists t (a int)")
|
|
|
|
urlValues := url.Values{
|
|
"sql": {"select * from t"},
|
|
"current_db": {"test"},
|
|
}
|
|
resp, err := ts.formStatus("/debug/sub-optimal-plan", urlValues)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
b, err := httputil.DumpResponse(resp, true)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(b), Greater, 0)
|
|
c.Assert(resp.Body.Close(), IsNil)
|
|
|
|
resp, err = ts.formStatus("/debug/sub-optimal-plan?pprof_time=5&timeout=0", urlValues)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
b, err = httputil.DumpResponse(resp, true)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(b), Greater, 0)
|
|
c.Assert(resp.Body.Close(), IsNil)
|
|
|
|
resp, err = ts.formStatus("/debug/sub-optimal-plan?pprof_time=5", urlValues)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
b, err = httputil.DumpResponse(resp, true)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(b), Greater, 0)
|
|
c.Assert(resp.Body.Close(), IsNil)
|
|
|
|
resp, err = ts.formStatus("/debug/sub-optimal-plan?timeout=1", urlValues)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
b, err = httputil.DumpResponse(resp, true)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(len(b), Greater, 0)
|
|
c.Assert(resp.Body.Close(), IsNil)
|
|
|
|
urlValues.Set("current_db", "non_exists_db")
|
|
resp, err = ts.formStatus("/debug/sub-optimal-plan", urlValues)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusInternalServerError)
|
|
b, err = ioutil.ReadAll(resp.Body)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(string(b), Equals, "use database non_exists_db failed, err: [schema:1049]Unknown database 'non_exists_db'\n")
|
|
c.Assert(resp.Body.Close(), IsNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestFailpointHandler(c *C) {
|
|
defer ts.stopServer(c)
|
|
|
|
// start server without enabling failpoint integration
|
|
ts.startServer(c)
|
|
resp, err := ts.fetchStatus("/fail/")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusNotFound)
|
|
ts.stopServer(c)
|
|
|
|
// enable failpoint integration and start server
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/server/enableTestAPI", "return"), IsNil)
|
|
ts.startServer(c)
|
|
resp, err = ts.fetchStatus("/fail/")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(strings.Contains(string(b), "github.com/pingcap/tidb/server/enableTestAPI=return"), IsTrue)
|
|
c.Assert(resp.Body.Close(), IsNil)
|
|
}
|
|
|
|
func (ts *HTTPHandlerTestSuite) TestTestHandler(c *C) {
|
|
defer ts.stopServer(c)
|
|
|
|
// start server without enabling failpoint integration
|
|
ts.startServer(c)
|
|
resp, err := ts.fetchStatus("/test")
|
|
c.Assert(err, IsNil)
|
|
c.Assert(resp.StatusCode, Equals, http.StatusNotFound)
|
|
ts.stopServer(c)
|
|
|
|
// enable failpoint integration and start server
|
|
c.Assert(failpoint.Enable("github.com/pingcap/tidb/server/enableTestAPI", "return"), IsNil)
|
|
ts.startServer(c)
|
|
|
|
resp, err = ts.fetchStatus("/test/gc/gc")
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
|
|
resp, err = ts.fetchStatus("/test/gc/resolvelock")
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
|
|
resp, err = ts.fetchStatus("/test/gc/resolvelock?safepoint=a")
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
|
|
resp, err = ts.fetchStatus("/test/gc/resolvelock?physical=1")
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
|
|
resp, err = ts.fetchStatus("/test/gc/resolvelock?physical=true")
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)
|
|
|
|
resp, err = ts.fetchStatus("/test/gc/resolvelock?safepoint=10000&physical=true")
|
|
c.Assert(err, IsNil)
|
|
resp.Body.Close()
|
|
c.Assert(resp.StatusCode, Equals, http.StatusOK)
|
|
}
|
|
|