Skip to content
This repository has been archived by the owner on Feb 28, 2024. It is now read-only.

Handle wsrep_gtid_mode enabled #5

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/httprate v0.7.4
github.com/go-logr/logr v1.2.4
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.3.0
go.uber.org/zap v1.25.0
k8s.io/api v0.28.1
Expand All @@ -25,7 +26,6 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
37 changes: 29 additions & 8 deletions pkg/galera/galera.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type GaleraState struct {
Version string `json:"version"`
UUID string `json:"uuid"`
Seqno int `json:"seqno"`
GTID *GTID `json:"gtid"`
SafeToBootstrap bool `json:"safeToBootstrap"`
}

Expand All @@ -60,25 +61,27 @@ func (g *GaleraState) Compare(other GaleraRecoverer) int {
return 0
}

func (g *GaleraState) Marshal() ([]byte, error) {
func (g *GaleraState) MarshalText() ([]byte, error) {
matthieugusmini marked this conversation as resolved.
Show resolved Hide resolved
if _, err := guuid.Parse(g.UUID); err != nil {
return nil, fmt.Errorf("invalid uuid: %v", err)
}
type tplOpts struct {
Version string
UUID string
Seqno int
GTID *GTID
SafeToBootstrap int
}
tpl := createTpl("grastate.dat", `version: {{ .Version }}
uuid: {{ .UUID }}
seqno: {{ .Seqno }}
seqno: {{ .Seqno }}{{ if .GTID }},{{ .GTID }}{{ end }}
safe_to_bootstrap: {{ .SafeToBootstrap }}`)
buf := new(bytes.Buffer)
err := tpl.Execute(buf, tplOpts{
Version: g.Version,
UUID: g.UUID,
Seqno: g.Seqno,
GTID: g.GTID,
SafeToBootstrap: func() int {
if g.SafeToBootstrap {
return 1
Expand All @@ -92,14 +95,17 @@ safe_to_bootstrap: {{ .SafeToBootstrap }}`)
return buf.Bytes(), nil
}

func (g *GaleraState) Unmarshal(text []byte) error {
func (g *GaleraState) UnmarshalText(text []byte) error {
fileScanner := bufio.NewScanner(bytes.NewReader(text))
fileScanner.Split(bufio.ScanLines)

var version *string
var uuid *string
var seqno *int
var safeToBootstrap *bool
var (
version *string
uuid *string
seqno *int
gtid *GTID
safeToBootstrap *bool
)

for fileScanner.Scan() {
parts := strings.Split(fileScanner.Text(), ":")
Expand All @@ -118,7 +124,18 @@ func (g *GaleraState) Unmarshal(text []byte) error {
}
uuid = &value
case "seqno":
i, err := strconv.Atoi(value)
// When the `wsrep_gtid_mode` is set to `ON`, the `seqno` is
// actually a string of the form `seqno,gtid`.
seqnoStr, gtidStr, found := strings.Cut(value, ",")
if found {
gtid = &GTID{}
err := gtid.UnmarshalText([]byte(gtidStr))
if err != nil {
return fmt.Errorf("error parsing gtid: %v", err)
}

}
i, err := strconv.Atoi(seqnoStr)
if err != nil {
return fmt.Errorf("error parsing seqno: %v", err)
}
Expand All @@ -141,6 +158,10 @@ func (g *GaleraState) Unmarshal(text []byte) error {
g.Version = *version
g.UUID = *uuid
g.Seqno = *seqno
// Only set the GTID if it was found in the file.
if gtid != nil {
g.GTID = gtid
}
g.SafeToBootstrap = *safeToBootstrap
return nil
}
Expand Down
105 changes: 68 additions & 37 deletions pkg/galera/galera_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,26 @@ seqno: -1
safe_to_bootstrap: 0`,
wantErr: false,
},
{
name: "wsrep_gtid_mode enabled",
galeraState: &GaleraState{
Version: "2.1",
UUID: "05f061bd-02a3-11ee-857c-aa370ff6666b",
Seqno: 1,
GTID: &GTID{DomainID: 0, ServerID: 1, SequenceNumber: 2},
SafeToBootstrap: true,
},
want: `version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1,0-1-2
safe_to_bootstrap: 1`,
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bytes, err := tt.galeraState.Marshal()
bytes, err := tt.galeraState.MarshalText()
if tt.wantErr && err == nil {
t.Fatal("error expected, got nil")
}
Expand All @@ -93,17 +108,17 @@ func TestGaleraStateUnmarshal(t *testing.T) {
{
name: "empty",
bytes: []byte(`
`),
`),
want: GaleraState{},
wantErr: true,
},
{
name: "comment",
bytes: []byte(`# GALERA saved state
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 1`),
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 1`),
want: GaleraState{
Version: "2.1",
UUID: "05f061bd-02a3-11ee-857c-aa370ff6666b",
Expand All @@ -115,10 +130,10 @@ safe_to_bootstrap: 1`),
{
name: "indentation",
bytes: []byte(`# GALERA saved state
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 1`),
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 1`),
want: GaleraState{
Version: "2.1",
UUID: "05f061bd-02a3-11ee-857c-aa370ff6666b",
Expand All @@ -130,39 +145,39 @@ safe_to_bootstrap: 1`),
{
name: "invalid uuid",
bytes: []byte(`# GALERA saved state
version: 2.1
uuid: foo
seqno: -1
safe_to_bootstrap: 1`),
version: 2.1
uuid: foo
seqno: -1
safe_to_bootstrap: 1`),
want: GaleraState{},
wantErr: true,
},
{
name: "invalid seqno",
bytes: []byte(`# GALERA saved state
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: foo
safe_to_bootstrap: 1`),
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: foo
safe_to_bootstrap: 1`),
want: GaleraState{},
wantErr: true,
},
{
name: "invalid safe_to_bootstrap",
bytes: []byte(`# GALERA saved state
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: true`),
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: true`),
want: GaleraState{},
wantErr: true,
},
{
name: "safe_to_bootstrap true",
bytes: []byte(`version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 1`),
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 1`),
want: GaleraState{
Version: "2.1",
UUID: "05f061bd-02a3-11ee-857c-aa370ff6666b",
Expand All @@ -174,9 +189,9 @@ safe_to_bootstrap: 1`),
{
name: "safe_to_bootstrap false",
bytes: []byte(`version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 0`),
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1
safe_to_bootstrap: 0`),
want: GaleraState{
Version: "2.1",
UUID: "05f061bd-02a3-11ee-857c-aa370ff6666b",
Expand All @@ -188,9 +203,9 @@ safe_to_bootstrap: 0`),
{
name: "negative seqno",
bytes: []byte(`version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: -1
safe_to_bootstrap: 0`),
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: -1
safe_to_bootstrap: 0`),
want: GaleraState{
Version: "2.1",
UUID: "05f061bd-02a3-11ee-857c-aa370ff6666b",
Expand All @@ -202,25 +217,41 @@ safe_to_bootstrap: 0`),
{
name: "missing safe_to_bootstrap",
bytes: []byte(`version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
safe_to_bootstrap: 0`),
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
safe_to_bootstrap: 0`),
want: GaleraState{},
wantErr: true,
},
{
name: "missing seqno",
bytes: []byte(`version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
safe_to_bootstrap: 0`),
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
safe_to_bootstrap: 0`),
want: GaleraState{},
wantErr: true,
},
{
// See the following threadh: https://mariadb-operator.slack.com/archives/C056RAECH0W/p1699350363009529
name: "wsrep_gtid_mode enabled",
bytes: []byte(`# GALERA saved state
version: 2.1
uuid: 05f061bd-02a3-11ee-857c-aa370ff6666b
seqno: 1,0-1-2
safe_to_bootstrap: 1`),
want: GaleraState{
Version: "2.1",
UUID: "05f061bd-02a3-11ee-857c-aa370ff6666b",
Seqno: 1,
GTID: &GTID{DomainID: 0, ServerID: 1, SequenceNumber: 2},
SafeToBootstrap: true,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var galeraState GaleraState
err := galeraState.Unmarshal(tt.bytes)
err := galeraState.UnmarshalText(tt.bytes)
if tt.wantErr && err == nil {
t.Fatal("error expected, got nil")
}
Expand Down Expand Up @@ -412,7 +443,7 @@ Warning: Memory not freed: 280
2023-06-04 8:24:18 0 [Note] Plugin 'FEEDBACK' is disabled.
2023-06-04 8:24:18 0 [Note] Server socket created on IP: '0.0.0.0'.
2023-06-04 8:24:18 0 [Note] WSREP: Recovered position: 08dd3b99-ac6b-46f8-84bd-8cb8f9f949b0:3
Warning: Memory not freed: 280
Warning: Memory not freed: 280
`),
want: Bootstrap{
UUID: "08dd3b99-ac6b-46f8-84bd-8cb8f9f949b0",
Expand Down
46 changes: 46 additions & 0 deletions pkg/galera/gtid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package galera

import (
"fmt"
"strconv"
"strings"
)

// GTID represents a MariaDB global transaction identifier.
// See: https://mariadb.com/kb/en/gtid/
type GTID struct {
DomainID uint32 `json:"domainId"`
ServerID uint32 `json:"serverId"`
SequenceNumber uint64 `json:"sequenceNumber"`
}

func (gtid *GTID) String() string {
return fmt.Sprintf("%d-%d-%d", gtid.DomainID, gtid.ServerID, gtid.SequenceNumber)
}

func (gtid *GTID) MarshalText() ([]byte, error) {
return []byte(gtid.String()), nil
}

func (gtid *GTID) UnmarshalText(text []byte) error {
parts := strings.Split(string(text), "-")
if len(parts) != 3 {
return fmt.Errorf("invalid gtid: %s", text)
}
domainID, err := strconv.ParseUint(parts[0], 10, 32)
if err != nil {
return fmt.Errorf("invalid domain id: %v", err)
}
serverID, err := strconv.ParseUint(parts[1], 10, 32)
if err != nil {
return fmt.Errorf("invalid server id: %v", err)
}
seqno, err := strconv.ParseUint(parts[2], 10, 64)
if err != nil {
return fmt.Errorf("invalid seqno: %v", err)
}
gtid.DomainID = uint32(domainID)
gtid.ServerID = uint32(serverID)
gtid.SequenceNumber = seqno
return nil
}
Loading
Loading