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 }