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

Commit

Permalink
Handle wsrep_gtid_mode enabled
Browse files Browse the repository at this point in the history
- Changed the parsing logic of the seqno field in the Galera state file
- Added a struct to serialize/deserialize the GTID tied to the sequence number
- Updated the unit tests
  • Loading branch information
Matthieu` committed Nov 8, 2023
1 parent be7d5cc commit 3997fea
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 51 deletions.
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) {
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

0 comments on commit 3997fea

Please sign in to comment.