Files
tidb/pkg/tidb-binlog/pump_client/client_test.go

336 lines
8.6 KiB
Go

// Copyright 2022 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 client
import (
"context"
"fmt"
"net"
"os"
"testing"
"time"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/tidb-binlog/node"
"github.com/pingcap/tipb/go-binlog"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var (
testMaxRecvMsgSize = 1024
testRetryTime = 5
)
type testCase struct {
binlogs []*binlog.Binlog
choosePumps []*PumpStatus
setAvliable []bool
setNodeID []string
}
func TestSelector(t *testing.T) {
strategys := []string{Hash, Range}
for _, strategy := range strategys {
testSelector(t, strategy)
}
}
func testSelector(t *testing.T, strategy string) {
pumpsClient := &PumpsClient{
Pumps: NewPumpInfos(),
Selector: NewSelector(strategy),
BinlogWriteTimeout: DefaultBinlogWriteTimeout,
}
pumps := []*PumpStatus{{}, {}, {}}
for i, pump := range pumps {
pump.NodeID = fmt.Sprintf("pump%d", i)
pump.State = node.Offline
// set pump client to avoid create grpc client.
pump.Client = binlog.NewPumpClient(nil)
}
for _, pump := range pumps {
pumpsClient.addPump(pump, false)
}
pumpsClient.Selector.SetPumps(copyPumps(pumpsClient.Pumps.AvaliablePumps))
tCase := &testCase{}
tCase.binlogs = []*binlog.Binlog{
{
Tp: binlog.BinlogType_Prewrite,
StartTs: 1,
}, {
Tp: binlog.BinlogType_Commit,
StartTs: 1,
CommitTs: 2,
}, {
Tp: binlog.BinlogType_Prewrite,
StartTs: 3,
}, {
Tp: binlog.BinlogType_Commit,
StartTs: 3,
CommitTs: 4,
}, {
Tp: binlog.BinlogType_Prewrite,
StartTs: 5,
}, {
Tp: binlog.BinlogType_Commit,
StartTs: 5,
CommitTs: 6,
},
}
tCase.setNodeID = []string{"pump0", "", "pump0", "pump1", "", "pump2"}
tCase.setAvliable = []bool{true, false, false, true, false, true}
tCase.choosePumps = []*PumpStatus{pumpsClient.Pumps.Pumps["pump0"], pumpsClient.Pumps.Pumps["pump0"], nil,
nil, pumpsClient.Pumps.Pumps["pump1"], pumpsClient.Pumps.Pumps["pump1"]}
for i, nodeID := range tCase.setNodeID {
if nodeID != "" {
pumpsClient.setPumpAvailable(pumpsClient.Pumps.Pumps[nodeID], tCase.setAvliable[i])
}
pump := pumpsClient.Selector.Select(tCase.binlogs[i], 0)
pumpsClient.Selector.Feedback(tCase.binlogs[i].StartTs, tCase.binlogs[i].Tp, pump)
require.Equal(t, pump, tCase.choosePumps[i])
}
for j := 0; j < 10; j++ {
prewriteBinlog := &binlog.Binlog{
Tp: binlog.BinlogType_Prewrite,
StartTs: int64(j),
}
commitBinlog := &binlog.Binlog{
Tp: binlog.BinlogType_Commit,
StartTs: int64(j),
}
pump1 := pumpsClient.Selector.Select(prewriteBinlog, 0)
if j%2 == 0 {
pump1 = pumpsClient.Selector.Select(prewriteBinlog, 1)
}
pumpsClient.Selector.Feedback(prewriteBinlog.StartTs, prewriteBinlog.Tp, pump1)
pumpsClient.setPumpAvailable(pump1, false)
pump2 := pumpsClient.Selector.Select(commitBinlog, 0)
pumpsClient.Selector.Feedback(commitBinlog.StartTs, commitBinlog.Tp, pump2)
// prewrite binlog and commit binlog with same start ts should choose same pump
require.Equal(t, pump1.NodeID, pump2.NodeID)
pumpsClient.setPumpAvailable(pump1, true)
// after change strategy, prewrite binlog and commit binlog will choose same pump
pump1 = pumpsClient.Selector.Select(prewriteBinlog, 0)
pumpsClient.Selector.Feedback(prewriteBinlog.StartTs, prewriteBinlog.Tp, pump1)
if strategy == Range {
err := pumpsClient.SetSelectStrategy(Hash)
require.NoError(t, err)
} else {
err := pumpsClient.SetSelectStrategy(Range)
require.NoError(t, err)
}
pump2 = pumpsClient.Selector.Select(commitBinlog, 0)
require.Equal(t, pump1.NodeID, pump2.NodeID)
// set back
err := pumpsClient.SetSelectStrategy(strategy)
require.NoError(t, err)
}
}
func TestWriteBinlog(t *testing.T) {
pumpServerConfig := []struct {
addr string
serverMode string
}{
{
"/tmp/mock-pump.sock",
"unix",
}, {
"127.0.0.1:15049",
"tcp",
},
}
// make test faster
RetryInterval = 100 * time.Millisecond
CommitBinlogTimeout = time.Second
for _, cfg := range pumpServerConfig {
pumpServer, err := createMockPumpServer(cfg.addr, cfg.serverMode, true)
require.NoError(t, err)
opt := grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout(cfg.serverMode, addr, timeout)
})
clientCon, err := grpc.Dial(cfg.addr, opt, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
require.NotNil(t, clientCon)
pumpClient := mockPumpsClient(binlog.NewPumpClient(clientCon), true)
// test binlog size bigger than grpc's MaxRecvMsgSize
blog := &binlog.Binlog{
Tp: binlog.BinlogType_Prewrite,
PrewriteValue: make([]byte, testMaxRecvMsgSize+1),
}
err = pumpClient.WriteBinlog(blog)
require.Error(t, err)
for i := 0; i < 10; i++ {
// test binlog size smaller than grpc's MaxRecvMsgSize
blog = &binlog.Binlog{
Tp: binlog.BinlogType_Prewrite,
PrewriteValue: make([]byte, 1),
}
err = pumpClient.WriteBinlog(blog)
require.NoError(t, err)
}
// after write some binlog, the pump without grpc client will move to unavailable list in pump client.
require.Len(t, pumpClient.Pumps.UnAvaliablePumps, 1)
// test write commit binlog, will not return error although write binlog failed.
preWriteBinlog := &binlog.Binlog{
Tp: binlog.BinlogType_Prewrite,
StartTs: 123,
PrewriteValue: make([]byte, 1),
}
commitBinlog := &binlog.Binlog{
Tp: binlog.BinlogType_Commit,
StartTs: 123,
CommitTs: 123,
PrewriteValue: make([]byte, 1),
}
err = pumpClient.WriteBinlog(preWriteBinlog)
require.NoError(t, err)
// test when pump is down
pumpServer.Close()
// write commit binlog failed will not return error
err = pumpClient.WriteBinlog(commitBinlog)
require.NoError(t, err)
err = pumpClient.WriteBinlog(blog)
require.Error(t, err)
}
}
type mockPumpServer struct {
mode string
addr string
server *grpc.Server
withError bool
retryTime int
}
// WriteBinlog implements PumpServer interface.
func (p *mockPumpServer) WriteBinlog(ctx context.Context, req *binlog.WriteBinlogReq) (*binlog.WriteBinlogResp, error) {
if !p.withError {
return &binlog.WriteBinlogResp{}, nil
}
p.retryTime++
if p.retryTime < testRetryTime {
return &binlog.WriteBinlogResp{}, errors.New("fake error")
}
// only the last retry will return succuess
p.retryTime = 0
return &binlog.WriteBinlogResp{}, nil
}
// PullBinlogs implements PumpServer interface.
func (p *mockPumpServer) PullBinlogs(req *binlog.PullBinlogReq, srv binlog.Pump_PullBinlogsServer) error {
return nil
}
func (p *mockPumpServer) Close() {
p.server.Stop()
if p.mode == "unix" {
os.Remove(p.addr)
}
}
func createMockPumpServer(addr string, mode string, withError bool) (*mockPumpServer, error) {
if mode == "unix" {
os.Remove(addr)
}
l, err := net.Listen(mode, addr)
if err != nil {
return nil, err
}
serv := grpc.NewServer(grpc.MaxRecvMsgSize(testMaxRecvMsgSize))
pump := &mockPumpServer{
mode: mode,
addr: addr,
server: serv,
withError: withError,
}
binlog.RegisterPumpServer(serv, pump)
go serv.Serve(l)
return pump, nil
}
// mockPumpsClient creates a PumpsClient, used for test.
func mockPumpsClient(client binlog.PumpClient, withBadPump bool) *PumpsClient {
// add a available pump
nodeID1 := "pump-1"
pump1 := &PumpStatus{
Status: node.Status{
NodeID: nodeID1,
State: node.Online,
},
Client: client,
}
pumps := []*PumpStatus{pump1}
// add a pump without grpc client
nodeID2 := "pump-2"
pump2 := &PumpStatus{
Status: node.Status{
NodeID: nodeID2,
State: node.Online,
},
}
pumpInfos := NewPumpInfos()
pumpInfos.Pumps[nodeID1] = pump1
pumpInfos.AvaliablePumps[nodeID1] = pump1
if withBadPump {
pumpInfos.Pumps[nodeID2] = pump2
pumpInfos.AvaliablePumps[nodeID2] = pump2
pumps = append(pumps, pump2)
}
pCli := &PumpsClient{
ClusterID: 1,
Pumps: pumpInfos,
Selector: NewSelector(Range),
BinlogWriteTimeout: time.Second,
}
pCli.Selector.SetPumps(pumps)
return pCli
}