github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/election/storage_orm_test.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package election
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"testing"
    20  
    21  	"github.com/DATA-DOG/go-sqlmock"
    22  	ormUtil "github.com/pingcap/tiflow/engine/pkg/orm"
    23  	"github.com/pingcap/tiflow/pkg/errors"
    24  	"github.com/stretchr/testify/require"
    25  	"gorm.io/gorm"
    26  )
    27  
    28  func newORMStorageAndMock(t *testing.T) (*ORMStorage, sqlmock.Sqlmock) {
    29  	backendDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
    30  	require.NoError(t, err)
    31  
    32  	mock.ExpectQuery("SELECT VERSION()").
    33  		WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).AddRow("5.7.35-log"))
    34  	db, err := ormUtil.NewGormDB(backendDB, "mysql")
    35  	require.NoError(t, err)
    36  	mock.ExpectQuery("SELECT SCHEMA_NAME from Information_schema.SCHEMATA " +
    37  		"where SCHEMA_NAME LIKE ? ORDER BY SCHEMA_NAME=? DESC limit 1").WillReturnRows(
    38  		sqlmock.NewRows([]string{"SCHEMA_NAME"}))
    39  	mock.ExpectExec("CREATE TABLE `test` (`id` int(10) unsigned,`leader_id` text NOT NULL," +
    40  		"`record` text,`version` bigint(20) unsigned NOT NULL,PRIMARY KEY (`id`))").
    41  		WillReturnResult(sqlmock.NewResult(0, 0))
    42  
    43  	s, err := NewORMStorage(db, "test")
    44  	require.NoError(t, err)
    45  
    46  	return s, mock
    47  }
    48  
    49  func TestORMStorageGetEmptyRecord(t *testing.T) {
    50  	s, mock := newORMStorageAndMock(t)
    51  
    52  	mock.ExpectQuery("SELECT * FROM `test` WHERE id = ? LIMIT 1").
    53  		WithArgs(1).WillReturnRows(sqlmock.NewRows([]string{"id", "leader_id", "record", "version"}))
    54  	record, err := s.Get(context.Background())
    55  	require.NoError(t, err)
    56  	require.Equal(t, &Record{}, record)
    57  }
    58  
    59  func TestORMStorageGetExistingRecord(t *testing.T) {
    60  	s, mock := newORMStorageAndMock(t)
    61  
    62  	expectedRecord := &Record{
    63  		LeaderID: "id1",
    64  		Members: []*Member{
    65  			{
    66  				ID:   "id1",
    67  				Name: "name1",
    68  			},
    69  			{
    70  				ID:   "id2",
    71  				Name: "name2",
    72  			},
    73  		},
    74  		Version: 1,
    75  	}
    76  	recordBytes, err := json.Marshal(expectedRecord)
    77  	require.NoError(t, err)
    78  
    79  	mock.ExpectQuery("SELECT * FROM `test` WHERE id = ? LIMIT 1").
    80  		WithArgs(1).WillReturnRows(sqlmock.NewRows([]string{"id", "leader_id", "record", "version"}).
    81  		AddRow(1, "id1", recordBytes, 1))
    82  	record, err := s.Get(context.Background())
    83  	require.NoError(t, err)
    84  	require.Equal(t, expectedRecord, record)
    85  }
    86  
    87  func TestORMStorageInsertRecord(t *testing.T) {
    88  	s, mock := newORMStorageAndMock(t)
    89  
    90  	record := &Record{
    91  		LeaderID: "id1",
    92  		Members: []*Member{
    93  			{
    94  				ID:   "id1",
    95  				Name: "name1",
    96  			},
    97  		},
    98  		Version: 0, // 0 means record not created before.
    99  	}
   100  	recordBytes, err := json.Marshal(record)
   101  	require.NoError(t, err)
   102  
   103  	mock.ExpectBegin()
   104  	mock.ExpectExec("INSERT INTO `test` (`leader_id`,`record`,`version`,`id`) VALUES (?,?,?,?),(?,?,?,?)").
   105  		WithArgs("id1", recordBytes, 1, recordRowID, "id1", nil, 1, leaderRowID).
   106  		WillReturnResult(sqlmock.NewResult(1, 2))
   107  	mock.ExpectCommit()
   108  
   109  	err = s.Update(context.Background(), record, true)
   110  	require.NoError(t, err)
   111  }
   112  
   113  func TestORMStorageUpdateMember(t *testing.T) {
   114  	leaderNotChanged := false
   115  	s, mock := newORMStorageAndMock(t)
   116  
   117  	record := &Record{
   118  		LeaderID: "id1",
   119  		Members: []*Member{
   120  			{
   121  				ID:   "id1",
   122  				Name: "name1",
   123  			},
   124  		},
   125  		Version: 1,
   126  	}
   127  	recordBytes, err := json.Marshal(record)
   128  	require.NoError(t, err)
   129  
   130  	mock.ExpectBegin()
   131  	mock.ExpectExec("UPDATE `test` SET `leader_id`=?,`record`=?,`version`=? WHERE id = ? AND version = ?").
   132  		WithArgs("id1", recordBytes, 2, recordRowID, 1).WillReturnResult(sqlmock.NewResult(0, 0))
   133  	mock.ExpectRollback()
   134  	err = s.Update(context.Background(), record, leaderNotChanged)
   135  	require.ErrorIs(t, err, errors.ErrElectionRecordConflict)
   136  
   137  	mock.ExpectBegin()
   138  	mock.ExpectExec("UPDATE `test` SET `leader_id`=?,`record`=?,`version`=? WHERE id = ? AND version = ?").
   139  		WithArgs("id1", recordBytes, 2, recordRowID, 1).WillReturnResult(sqlmock.NewResult(0, 1))
   140  	mock.ExpectCommit()
   141  	err = s.Update(context.Background(), record, leaderNotChanged)
   142  	require.NoError(t, err)
   143  }
   144  
   145  func TestORMStorageUpdateLeader(t *testing.T) {
   146  	leaderChanged := true
   147  	s, mock := newORMStorageAndMock(t)
   148  
   149  	record := &Record{
   150  		LeaderID: "id1",
   151  		Members: []*Member{
   152  			{
   153  				ID:   "id1",
   154  				Name: "name1",
   155  			},
   156  		},
   157  		Version: 1,
   158  	}
   159  	recordBytes, err := json.Marshal(record)
   160  	require.NoError(t, err)
   161  
   162  	mock.ExpectBegin()
   163  	mock.ExpectExec("UPDATE `test` SET `leader_id`=?,`record`=?,`version`=? WHERE id = ? AND version = ?").
   164  		WithArgs("id1", recordBytes, 2, recordRowID, 1).WillReturnResult(sqlmock.NewResult(0, 0))
   165  	mock.ExpectRollback()
   166  	err = s.Update(context.Background(), record, leaderChanged)
   167  	require.ErrorIs(t, err, errors.ErrElectionRecordConflict)
   168  
   169  	mock.ExpectBegin()
   170  	mock.ExpectExec("UPDATE `test` SET `leader_id`=?,`record`=?,`version`=? WHERE id = ? AND version = ?").
   171  		WithArgs("id1", recordBytes, 2, recordRowID, 1).WillReturnResult(sqlmock.NewResult(0, 1))
   172  	mock.ExpectExec("UPDATE `test` SET `leader_id`=?,`version`=? WHERE id = ?").
   173  		WithArgs("id1", 2, leaderRowID).WillReturnResult(sqlmock.NewResult(0, 0))
   174  	mock.ExpectRollback()
   175  	err = s.Update(context.Background(), record, leaderChanged)
   176  	require.ErrorIs(t, err, errors.ErrElectionRecordConflict)
   177  
   178  	mock.ExpectBegin()
   179  	mock.ExpectExec("UPDATE `test` SET `leader_id`=?,`record`=?,`version`=? WHERE id = ? AND version = ?").
   180  		WithArgs("id1", recordBytes, 2, recordRowID, 1).WillReturnResult(sqlmock.NewResult(0, 1))
   181  	mock.ExpectExec("UPDATE `test` SET `leader_id`=?,`version`=? WHERE id = ?").
   182  		WithArgs("id1", 2, leaderRowID).WillReturnResult(sqlmock.NewResult(0, 1))
   183  	mock.ExpectCommit()
   184  	err = s.Update(context.Background(), record, leaderChanged)
   185  	require.NoError(t, err)
   186  }
   187  
   188  func TestORMStorageTxnWithLeaderCheck(t *testing.T) {
   189  	s, mock := newORMStorageAndMock(t)
   190  
   191  	mock.ExpectBegin()
   192  	mock.ExpectQuery("SELECT `leader_id` FROM `test` WHERE id = ? and leader_id = ? LIMIT 1 LOCK IN SHARE MODE").
   193  		WithArgs(leaderRowID, "leader1").WillReturnRows(sqlmock.NewRows([]string{"leader_id"}))
   194  	mock.ExpectRollback()
   195  	doNothing := func(*gorm.DB) error {
   196  		return nil
   197  	}
   198  	err := s.TxnWithLeaderLock(context.Background(), "leader1", doNothing)
   199  	require.ErrorIs(t, err, errors.ErrElectorNotLeader)
   200  
   201  	mock.ExpectBegin()
   202  	mock.ExpectQuery("SELECT `leader_id` FROM `test` WHERE id = ? and leader_id = ? LIMIT 1 LOCK IN SHARE MODE").
   203  		WithArgs(leaderRowID, "leader1").
   204  		WillReturnRows(sqlmock.NewRows([]string{"leader_id"}).AddRow("leader1"))
   205  	mock.ExpectQuery("SELECT * FROM `test` WHERE id = ? LIMIT 1").
   206  		WithArgs(1).WillReturnRows(sqlmock.NewRows([]string{"id", "leader_id", "record", "version"}))
   207  	mock.ExpectCommit()
   208  	doTxn := func(tx *gorm.DB) error {
   209  		_, err := s.Get(context.Background())
   210  		require.NoError(t, err)
   211  		return nil
   212  	}
   213  	err = s.TxnWithLeaderLock(context.Background(), "leader1", doTxn)
   214  	require.NoError(t, err)
   215  }