diff --git a/internal/app/data.go b/internal/app/data.go index 30db8d10..137490b8 100644 --- a/internal/app/data.go +++ b/internal/app/data.go @@ -9,6 +9,7 @@ import ( "time" "github.com/yandex/mysync/internal/mysql" + "github.com/yandex/mysync/internal/mysql/gtids" ) type appState string @@ -128,6 +129,19 @@ func (ns *NodeState) IsReplicationPermanentlyBroken() (bool, int) { return false, 0 } +func (ns *NodeState) CalcGTIDDiffWithMaster() (string, error) { + if ns.SlaveState == nil { + return "", fmt.Errorf("slave state not defined") + } + replicaGTID := gtids.ParseGtidSet(ns.SlaveState.ExecutedGtidSet) + if ns.MasterState == nil { + return "", fmt.Errorf("master state not defined") + } + sourceGTID := gtids.ParseGtidSet(ns.MasterState.ExecutedGtidSet) + + return gtids.GTIDDiff(replicaGTID, sourceGTID) +} + func (ns *NodeState) String() string { ping := "ok" if !ns.PingOk { @@ -140,17 +154,21 @@ func (ns *NodeState) String() string { repl := unknown gtid := unknown lag := 0.0 - if ns.MasterState != nil { - repl = "master" - gtid = strings.ReplaceAll(ns.MasterState.ExecutedGtidSet, "\n", "") - } else if ns.SlaveState != nil { + if ns.SlaveState != nil { repl = ns.SlaveState.ReplicationState - gtid = strings.ReplaceAll(ns.SlaveState.ExecutedGtidSet, "\n", "") + var err error + gtid, err = ns.CalcGTIDDiffWithMaster() + if err != nil { + gtid = fmt.Sprintf("%s", err) + } if ns.SlaveState.ReplicationLag != nil { lag = *ns.SlaveState.ReplicationLag } else { lag = math.NaN() } + } else if ns.MasterState != nil { + repl = "master" + gtid = strings.ReplaceAll(ns.MasterState.ExecutedGtidSet, "\n", "") } sync := unknown if ns.SemiSyncState != nil { diff --git a/internal/app/util.go b/internal/app/util.go index 3e878b98..2ffc2b5d 100644 --- a/internal/app/util.go +++ b/internal/app/util.go @@ -220,6 +220,15 @@ func getNodeStatesInParallel(hosts []string, getter func(string) (*NodeState, er if err != nil { return nil, err } + + // adding information about source to each replica + for host := range clusterState { + if clusterState[host].SlaveState == nil { + continue + } + masterHost := clusterState[host].SlaveState.MasterHost + clusterState[host].MasterState = clusterState[masterHost].MasterState + } return clusterState, nil } diff --git a/internal/mysql/gtids/wrapper.go b/internal/mysql/gtids/wrapper.go index 8933d6a7..919e64eb 100644 --- a/internal/mysql/gtids/wrapper.go +++ b/internal/mysql/gtids/wrapper.go @@ -1,6 +1,8 @@ package gtids import ( + "fmt" + "github.com/go-mysql-org/go-mysql/mysql" ) @@ -13,3 +15,39 @@ func ParseGtidSet(gtidset string) mysql.GTIDSet { } return parsed } + +func GTIDDiff(replicaGTIDSet, sourceGTIDSet mysql.GTIDSet) (string, error) { + mysqlReplicaGTIDSet := replicaGTIDSet.(*mysql.MysqlGTIDSet) + mysqlSourceGTIDSet := sourceGTIDSet.(*mysql.MysqlGTIDSet) + // check standard case + diffWithSource := mysqlSourceGTIDSet.Clone().(*mysql.MysqlGTIDSet) + err := diffWithSource.Minus(*mysqlReplicaGTIDSet) + if err != nil { + return "", err + } + + // check reverse case + diffWithReplica := mysqlReplicaGTIDSet.Clone().(*mysql.MysqlGTIDSet) + err = diffWithReplica.Minus(*mysqlSourceGTIDSet) + if err != nil { + return "", err + } + + if diffWithSource.String() == "" && diffWithReplica.String() == "" { + return "replica gtid equal source", nil + } + + if diffWithSource.String() != "" && diffWithReplica.String() == "" { + return fmt.Sprintf("source ahead on: %s", diffWithSource.String()), nil + } + + if diffWithSource.String() != "" && diffWithReplica.String() != "" { + return fmt.Sprintf("split brain! source ahead on: %s; replica ahead on: %s", diffWithSource.String(), diffWithReplica.String()), nil + } + + if diffWithSource.String() == "" && diffWithReplica.String() != "" { + return fmt.Sprintf("replica ahead on: %s", diffWithReplica.String()), nil + } + + return "", fmt.Errorf("an indefinite case was obtained") +} diff --git a/internal/mysql/gtids/wrapper_test.go b/internal/mysql/gtids/wrapper_test.go new file mode 100644 index 00000000..3c2b372e --- /dev/null +++ b/internal/mysql/gtids/wrapper_test.go @@ -0,0 +1,50 @@ +package gtids + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGTIDDiff(t *testing.T) { + sourceGTID := ParseGtidSet("00000000-0000-0000-0000-000000000000:1-100,11111111-1111-1111-1111-111111111111:1-100") + + // equal + replicaGTID := ParseGtidSet("00000000-0000-0000-0000-000000000000:1-100,11111111-1111-1111-1111-111111111111:1-100") + diff, err := GTIDDiff(replicaGTID, sourceGTID) + require.NoError(t, err) + require.Equal(t, "replica gtid equal source", diff) + + replicaGTID = ParseGtidSet("11111111-1111-1111-1111-111111111111:1-100,00000000-0000-0000-0000-000000000000:1-100") + diff, err = GTIDDiff(replicaGTID, sourceGTID) + require.NoError(t, err) + require.Equal(t, "replica gtid equal source", diff) + + // source ahead + replicaGTID = ParseGtidSet("00000000-0000-0000-0000-000000000000:1-90,11111111-1111-1111-1111-111111111111:1-100") + diff, err = GTIDDiff(replicaGTID, sourceGTID) + require.NoError(t, err) + require.Equal(t, "source ahead on: 00000000-0000-0000-0000-000000000000:91-100", diff) + + replicaGTID = ParseGtidSet("00000000-0000-0000-0000-000000000000:1-90,11111111-1111-1111-1111-111111111111:1-90") + diff, err = GTIDDiff(replicaGTID, sourceGTID) + require.NoError(t, err) + require.Equal(t, "source ahead on: 00000000-0000-0000-0000-000000000000:91-100,11111111-1111-1111-1111-111111111111:91-100", diff) + + // replica ahead + replicaGTID = ParseGtidSet("00000000-0000-0000-0000-000000000000:1-110,11111111-1111-1111-1111-111111111111:1-100") + diff, err = GTIDDiff(replicaGTID, sourceGTID) + require.NoError(t, err) + require.Equal(t, "replica ahead on: 00000000-0000-0000-0000-000000000000:101-110", diff) + + // split brain + replicaGTID = ParseGtidSet("00000000-0000-0000-0000-000000000000:1-90,11111111-1111-1111-1111-111111111111:1-110") + diff, err = GTIDDiff(replicaGTID, sourceGTID) + require.NoError(t, err) + require.Equal(t, "split brain! source ahead on: 00000000-0000-0000-0000-000000000000:91-100; replica ahead on: 11111111-1111-1111-1111-111111111111:101-110", diff) + + replicaGTID = ParseGtidSet("00000000-0000-0000-0000-000000000000:1-100,22222222-2222-2222-2222-222222222222:1-110") + diff, err = GTIDDiff(replicaGTID, sourceGTID) + require.NoError(t, err) + require.Equal(t, "split brain! source ahead on: 11111111-1111-1111-1111-111111111111:1-100; replica ahead on: 22222222-2222-2222-2222-222222222222:1-110", diff) +}