closes #403 Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org> Reviewed-on: #407 Co-authored-by: Vasiliy Tolstov <v.tolstov@unistack.org> Co-committed-by: Vasiliy Tolstov <v.tolstov@unistack.org>
172 lines
5.1 KiB
Go
172 lines
5.1 KiB
Go
package sql
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"golang.yandex/hasql/v2"
|
|
)
|
|
|
|
func TestNewCluster(t *testing.T) {
|
|
dbMaster, dbMasterMock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dbMaster.Close()
|
|
dbMasterMock.MatchExpectationsInOrder(false)
|
|
|
|
dbMasterMock.ExpectQuery(`.*pg_is_in_recovery.*`).WillReturnRows(
|
|
sqlmock.NewRowsWithColumnDefinition(
|
|
sqlmock.NewColumn("role").OfType("int8", 0),
|
|
sqlmock.NewColumn("replication_lag").OfType("int8", 0)).
|
|
AddRow(1, 0)).
|
|
RowsWillBeClosed().
|
|
WithoutArgs()
|
|
|
|
dbMasterMock.ExpectQuery(`SELECT node_name as name`).WillReturnRows(
|
|
sqlmock.NewRows([]string{"name"}).
|
|
AddRow("master-dc1"))
|
|
|
|
dbDRMaster, dbDRMasterMock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dbDRMaster.Close()
|
|
dbDRMasterMock.MatchExpectationsInOrder(false)
|
|
|
|
dbDRMasterMock.ExpectQuery(`.*pg_is_in_recovery.*`).WillReturnRows(
|
|
sqlmock.NewRowsWithColumnDefinition(
|
|
sqlmock.NewColumn("role").OfType("int8", 0),
|
|
sqlmock.NewColumn("replication_lag").OfType("int8", 0)).
|
|
AddRow(2, 40)).
|
|
RowsWillBeClosed().
|
|
WithoutArgs()
|
|
|
|
dbDRMasterMock.ExpectQuery(`SELECT node_name as name`).WillReturnRows(
|
|
sqlmock.NewRows([]string{"name"}).
|
|
AddRow("drmaster1-dc2"))
|
|
|
|
dbDRMasterMock.ExpectQuery(`SELECT node_name as name`).WillReturnRows(
|
|
sqlmock.NewRows([]string{"name"}).
|
|
AddRow("drmaster"))
|
|
|
|
dbSlaveDC1, dbSlaveDC1Mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dbSlaveDC1.Close()
|
|
dbSlaveDC1Mock.MatchExpectationsInOrder(false)
|
|
|
|
dbSlaveDC1Mock.ExpectQuery(`.*pg_is_in_recovery.*`).WillReturnRows(
|
|
sqlmock.NewRowsWithColumnDefinition(
|
|
sqlmock.NewColumn("role").OfType("int8", 0),
|
|
sqlmock.NewColumn("replication_lag").OfType("int8", 0)).
|
|
AddRow(2, 50)).
|
|
RowsWillBeClosed().
|
|
WithoutArgs()
|
|
|
|
dbSlaveDC1Mock.ExpectQuery(`SELECT node_name as name`).WillReturnRows(
|
|
sqlmock.NewRows([]string{"name"}).
|
|
AddRow("slave-dc1"))
|
|
|
|
dbSlaveDC2, dbSlaveDC2Mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer dbSlaveDC2.Close()
|
|
dbSlaveDC1Mock.MatchExpectationsInOrder(false)
|
|
|
|
dbSlaveDC2Mock.ExpectQuery(`.*pg_is_in_recovery.*`).WillReturnRows(
|
|
sqlmock.NewRowsWithColumnDefinition(
|
|
sqlmock.NewColumn("role").OfType("int8", 0),
|
|
sqlmock.NewColumn("replication_lag").OfType("int8", 0)).
|
|
AddRow(2, 50)).
|
|
RowsWillBeClosed().
|
|
WithoutArgs()
|
|
|
|
dbSlaveDC2Mock.ExpectQuery(`SELECT node_name as name`).WillReturnRows(
|
|
sqlmock.NewRows([]string{"name"}).
|
|
AddRow("slave-dc1"))
|
|
|
|
tctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
c, err := NewCluster[Querier](
|
|
WithClusterContext(tctx),
|
|
WithClusterNodeChecker(hasql.PostgreSQLChecker),
|
|
WithClusterNodePicker(NewCustomPicker[Querier](
|
|
CustomPickerMaxLag(100),
|
|
)),
|
|
WithClusterNodes(
|
|
ClusterNode{"slave-dc1", dbSlaveDC1, 1},
|
|
ClusterNode{"master-dc1", dbMaster, 1},
|
|
ClusterNode{"slave-dc2", dbSlaveDC2, 2},
|
|
ClusterNode{"drmaster1-dc2", dbDRMaster, 0},
|
|
),
|
|
WithClusterOptions(
|
|
hasql.WithUpdateInterval[Querier](2*time.Second),
|
|
hasql.WithUpdateTimeout[Querier](1*time.Second),
|
|
),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
if err = c.WaitForNodes(tctx, hasql.Primary, hasql.Standby); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
node1Name := ""
|
|
fmt.Printf("check for Standby\n")
|
|
if row := c.QueryRowContext(NodeStateCriterion(tctx, hasql.Standby), "SELECT node_name as name"); row.Err() != nil {
|
|
t.Fatal(row.Err())
|
|
} else if err = row.Scan(&node1Name); err != nil {
|
|
t.Fatal(err)
|
|
} else if "slave-dc1" != node1Name {
|
|
t.Fatalf("invalid node name %s != %s", "slave-dc1", node1Name)
|
|
}
|
|
|
|
dbSlaveDC1Mock.ExpectQuery(`SELECT node_name as name`).WillReturnRows(
|
|
sqlmock.NewRows([]string{"name"}).
|
|
AddRow("slave-dc1"))
|
|
|
|
node2Name := ""
|
|
fmt.Printf("check for PreferStandby\n")
|
|
if row := c.QueryRowContext(NodeStateCriterion(tctx, hasql.PreferStandby), "SELECT node_name as name"); row.Err() != nil {
|
|
t.Fatal(row.Err())
|
|
} else if err = row.Scan(&node2Name); err != nil {
|
|
t.Fatal(err)
|
|
} else if "slave-dc1" != node2Name {
|
|
t.Fatalf("invalid node name %s != %s", "slave-dc1", node2Name)
|
|
}
|
|
|
|
node3Name := ""
|
|
fmt.Printf("check for PreferPrimary\n")
|
|
if row := c.QueryRowContext(NodeStateCriterion(tctx, hasql.PreferPrimary), "SELECT node_name as name"); row.Err() != nil {
|
|
t.Fatal(row.Err())
|
|
} else if err = row.Scan(&node3Name); err != nil {
|
|
t.Fatal(err)
|
|
} else if "master-dc1" != node3Name {
|
|
t.Fatalf("invalid node name %s != %s", "master-dc1", node3Name)
|
|
}
|
|
|
|
dbSlaveDC1Mock.ExpectQuery(`.*`).WillReturnRows(sqlmock.NewRows([]string{"role"}).RowError(1, fmt.Errorf("row error")))
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
fmt.Printf("check for PreferStandby\n")
|
|
if row := c.QueryRowContext(NodeStateCriterion(tctx, hasql.PreferStandby), "SELECT node_name as name"); row.Err() == nil {
|
|
t.Fatal("must return error")
|
|
}
|
|
|
|
if dbMasterErr := dbMasterMock.ExpectationsWereMet(); dbMasterErr != nil {
|
|
t.Error(dbMasterErr)
|
|
}
|
|
}
|