336 lines
8.6 KiB
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
|
|
}
|