github.com/hdt3213/godis@v1.2.9/database/replication_master_test.go (about)

     1  package database
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"strconv"
     9  	"strings"
    10  	"sync/atomic"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/hdt3213/godis/config"
    15  	rdb "github.com/hdt3213/rdb/parser"
    16  	"github.com/hdt3213/godis/lib/utils"
    17  	"github.com/hdt3213/godis/redis/connection"
    18  	"github.com/hdt3213/godis/redis/parser"
    19  	"github.com/hdt3213/godis/redis/protocol"
    20  	"github.com/hdt3213/godis/redis/protocol/asserts"
    21  )
    22  
    23  func mockServer() *Server {
    24  	server := &Server{}
    25  	server.dbSet = make([]*atomic.Value, 16)
    26  	for i := range server.dbSet {
    27  		singleDB := makeDB()
    28  		singleDB.index = i
    29  		holder := &atomic.Value{}
    30  		holder.Store(singleDB)
    31  		server.dbSet[i] = holder
    32  	}
    33  	server.slaveStatus = initReplSlaveStatus()
    34  	server.initMaster()
    35  	return server
    36  }
    37  
    38  func TestReplicationMasterSide(t *testing.T) {
    39  	tmpDir, err := ioutil.TempDir("", "godis")
    40  	if err != nil {
    41  		t.Error(err)
    42  		return
    43  	}
    44  	aofFilename := path.Join(tmpDir, "a.aof")
    45  	defer func() {
    46  		_ = os.Remove(aofFilename)
    47  	}()
    48  	config.Properties = &config.ServerProperties{
    49  		Databases:      16,
    50  		AppendOnly:     true,
    51  		AppendFilename: aofFilename,
    52  	}
    53  	master := mockServer()
    54  	aofHandler, err := NewPersister(master, config.Properties.AppendFilename, true, config.Properties.AppendFsync)
    55  	if err != nil {
    56  		panic(err)
    57  	}
    58  	master.bindPersister(aofHandler)
    59  	slave := mockServer()
    60  	replConn := connection.NewFakeConn()
    61  
    62  	// set data to master
    63  	masterConn := connection.NewFakeConn()
    64  	resp := master.Exec(masterConn, utils.ToCmdLine("SET", "a", "a"))
    65  	asserts.AssertNotError(t, resp)
    66  	time.Sleep(time.Millisecond * 100) // wait write aof
    67  
    68  	// full re-sync
    69  	master.Exec(replConn, utils.ToCmdLine("psync", "?", "-1"))
    70  	masterChan := parser.ParseStream(replConn)
    71  	psyncPayload := <-masterChan
    72  	if psyncPayload.Err != nil {
    73  		t.Errorf("master bad protocol: %v", psyncPayload.Err)
    74  		return
    75  	}
    76  	psyncHeader, ok := psyncPayload.Data.(*protocol.StatusReply)
    77  	if !ok {
    78  		t.Error("psync header is not a status reply")
    79  		return
    80  	}
    81  	headers := strings.Split(psyncHeader.Status, " ")
    82  	if len(headers) != 3 {
    83  		t.Errorf("illegal psync header: %s", psyncHeader.Status)
    84  		return
    85  	}
    86  
    87  	replId := headers[1]
    88  	replOffset, err := strconv.ParseInt(headers[2], 10, 64)
    89  	if err != nil {
    90  		t.Errorf("illegal offset: %s", headers[2])
    91  		return
    92  	}
    93  	t.Logf("repl id: %s, offset: %d", replId, replOffset)
    94  
    95  	rdbPayload := <-masterChan
    96  	if rdbPayload.Err != nil {
    97  		t.Error("read response failed: " + rdbPayload.Err.Error())
    98  		return
    99  	}
   100  	rdbReply, ok := rdbPayload.Data.(*protocol.BulkReply)
   101  	if !ok {
   102  		t.Error("illegal payload header: " + string(rdbPayload.Data.ToBytes()))
   103  		return
   104  	}
   105  
   106  	rdbDec := rdb.NewDecoder(bytes.NewReader(rdbReply.Arg))
   107  	err = slave.LoadRDB(rdbDec)
   108  	if err != nil {
   109  		t.Error("import rdb failed: " + err.Error())
   110  		return
   111  	}
   112  
   113  	// get a
   114  	slaveConn := connection.NewFakeConn()
   115  	resp = slave.Exec(slaveConn, utils.ToCmdLine("get", "a"))
   116  	asserts.AssertBulkReply(t, resp, "a")
   117  
   118  	/*----  test broadcast aof  ----*/
   119  	masterConn = connection.NewFakeConn()
   120  	resp = master.Exec(masterConn, utils.ToCmdLine("SET", "b", "b"))
   121  	time.Sleep(time.Millisecond * 100) // wait write aof
   122  	asserts.AssertNotError(t, resp)
   123  	master.masterCron()
   124  	for {
   125  		payload := <-masterChan
   126  		if payload.Err != nil {
   127  			t.Error(payload.Err)
   128  			return
   129  		}
   130  		cmdLine, ok := payload.Data.(*protocol.MultiBulkReply)
   131  		if !ok {
   132  			t.Error("unexpected payload: " + string(payload.Data.ToBytes()))
   133  			return
   134  		}
   135  		slave.Exec(replConn, cmdLine.Args)
   136  		n := len(cmdLine.ToBytes())
   137  		slave.slaveStatus.replOffset += int64(n)
   138  		if string(cmdLine.Args[0]) != "ping" {
   139  			break
   140  		}
   141  	}
   142  
   143  	resp = slave.Exec(slaveConn, utils.ToCmdLine("get", "b"))
   144  	asserts.AssertBulkReply(t, resp, "b")
   145  
   146  	/*----  test partial reconnect  ----*/
   147  	_ = replConn.Close() // mock disconnect
   148  
   149  	replConn = connection.NewFakeConn()
   150  
   151  	master.Exec(replConn, utils.ToCmdLine("psync", replId,
   152  		strconv.FormatInt(slave.slaveStatus.replOffset, 10)))
   153  	masterChan = parser.ParseStream(replConn)
   154  	psyncPayload = <-masterChan
   155  	if psyncPayload.Err != nil {
   156  		t.Errorf("master bad protocol: %v", psyncPayload.Err)
   157  		return
   158  	}
   159  	psyncHeader, ok = psyncPayload.Data.(*protocol.StatusReply)
   160  	if !ok {
   161  		t.Error("psync header is not a status reply")
   162  		return
   163  	}
   164  	headers = strings.Split(psyncHeader.Status, " ")
   165  	if len(headers) != 2 {
   166  		t.Errorf("illegal psync header: %s", psyncHeader.Status)
   167  		return
   168  	}
   169  	if headers[0] != "CONTINUE" {
   170  		t.Errorf("expect CONTINUE actual %s", headers[0])
   171  		return
   172  	}
   173  	replId = headers[1]
   174  	t.Logf("partial resync repl id: %s, offset: %d", replId, slave.slaveStatus.replOffset)
   175  
   176  	resp = master.Exec(masterConn, utils.ToCmdLine("SET", "c", "c"))
   177  	time.Sleep(time.Millisecond * 100) // wait write aof
   178  	asserts.AssertNotError(t, resp)
   179  	master.masterCron()
   180  	for {
   181  		payload := <-masterChan
   182  		if payload.Err != nil {
   183  			t.Error(payload.Err)
   184  			return
   185  		}
   186  		cmdLine, ok := payload.Data.(*protocol.MultiBulkReply)
   187  		if !ok {
   188  			t.Error("unexpected payload: " + string(payload.Data.ToBytes()))
   189  			return
   190  		}
   191  		slave.Exec(replConn, cmdLine.Args)
   192  		if string(cmdLine.Args[0]) != "ping" {
   193  			break
   194  		}
   195  	}
   196  
   197  	resp = slave.Exec(slaveConn, utils.ToCmdLine("get", "c"))
   198  	asserts.AssertBulkReply(t, resp, "c")
   199  }
   200  
   201  func TestReplicationMasterRewriteRDB(t *testing.T) {
   202  	tmpDir, err := ioutil.TempDir("", "godis")
   203  	if err != nil {
   204  		t.Error(err)
   205  		return
   206  	}
   207  	aofFilename := path.Join(tmpDir, "a.aof")
   208  	defer func() {
   209  		_ = os.Remove(aofFilename)
   210  	}()
   211  	config.Properties = &config.ServerProperties{
   212  		Databases:      16,
   213  		AppendOnly:     true,
   214  		AppendFilename: aofFilename,
   215  	}
   216  	master := mockServer()
   217  	aofHandler, err := NewPersister(master, config.Properties.AppendFilename, true, config.Properties.AppendFsync)
   218  	if err != nil {
   219  		panic(err)
   220  	}
   221  	master.bindPersister(aofHandler)
   222  
   223  	masterConn := connection.NewFakeConn()
   224  	resp := master.Exec(masterConn, utils.ToCmdLine("SET", "a", "a"))
   225  	asserts.AssertNotError(t, resp)
   226  	resp = master.Exec(masterConn, utils.ToCmdLine("SET", "b", "b"))
   227  	asserts.AssertNotError(t, resp)
   228  	time.Sleep(time.Millisecond * 100) // wait write aof
   229  
   230  	err = master.rewriteRDB()
   231  	if err != nil {
   232  		t.Error(err)
   233  		return
   234  	}
   235  	resp = master.Exec(masterConn, utils.ToCmdLine("SET", "c", "c"))
   236  	asserts.AssertNotError(t, resp)
   237  	time.Sleep(time.Millisecond * 100) // wait write aof
   238  
   239  	// set slave
   240  	slave := mockServer()
   241  	replConn := connection.NewFakeConn()
   242  	master.Exec(replConn, utils.ToCmdLine("psync", "?", "-1"))
   243  	masterChan := parser.ParseStream(replConn)
   244  	psyncPayload := <-masterChan
   245  	if psyncPayload.Err != nil {
   246  		t.Errorf("master bad protocol: %v", psyncPayload.Err)
   247  		return
   248  	}
   249  	psyncHeader, ok := psyncPayload.Data.(*protocol.StatusReply)
   250  	if !ok {
   251  		t.Error("psync header is not a status reply")
   252  		return
   253  	}
   254  	headers := strings.Split(psyncHeader.Status, " ")
   255  	if len(headers) != 3 {
   256  		t.Errorf("illegal psync header: %s", psyncHeader.Status)
   257  		return
   258  	}
   259  
   260  	replId := headers[1]
   261  	replOffset, err := strconv.ParseInt(headers[2], 10, 64)
   262  	if err != nil {
   263  		t.Errorf("illegal offset: %s", headers[2])
   264  		return
   265  	}
   266  	t.Logf("repl id: %s, offset: %d", replId, replOffset)
   267  
   268  	rdbPayload := <-masterChan
   269  	if rdbPayload.Err != nil {
   270  		t.Error("read response failed: " + rdbPayload.Err.Error())
   271  		return
   272  	}
   273  	rdbReply, ok := rdbPayload.Data.(*protocol.BulkReply)
   274  	if !ok {
   275  		t.Error("illegal payload header: " + string(rdbPayload.Data.ToBytes()))
   276  		return
   277  	}
   278  
   279  	rdbDec := rdb.NewDecoder(bytes.NewReader(rdbReply.Arg))
   280  	err = slave.LoadRDB(rdbDec)
   281  	if err != nil {
   282  		t.Error("import rdb failed: " + err.Error())
   283  		return
   284  	}
   285  
   286  	slaveConn := connection.NewFakeConn()
   287  	resp = slave.Exec(slaveConn, utils.ToCmdLine("get", "a"))
   288  	asserts.AssertBulkReply(t, resp, "a")
   289  	resp = slave.Exec(slaveConn, utils.ToCmdLine("get", "b"))
   290  	asserts.AssertBulkReply(t, resp, "b")
   291  
   292  	master.masterCron()
   293  	for {
   294  		payload := <-masterChan
   295  		if payload.Err != nil {
   296  			t.Error(payload.Err)
   297  			return
   298  		}
   299  		cmdLine, ok := payload.Data.(*protocol.MultiBulkReply)
   300  		if !ok {
   301  			t.Error("unexpected payload: " + string(payload.Data.ToBytes()))
   302  			return
   303  		}
   304  		slave.Exec(replConn, cmdLine.Args)
   305  		n := len(cmdLine.ToBytes())
   306  		slave.slaveStatus.replOffset += int64(n)
   307  		if string(cmdLine.Args[0]) != "ping" {
   308  			break
   309  		}
   310  	}
   311  	resp = slave.Exec(slaveConn, utils.ToCmdLine("get", "c"))
   312  	asserts.AssertBulkReply(t, resp, "c")
   313  }