From 21ce98942979429acbb574d0e8b4dc9c8432aa8e Mon Sep 17 00:00:00 2001 From: moridin26 Date: Sat, 1 Apr 2023 20:06:15 +0300 Subject: [PATCH] fix replica/slave status different columns in answer (#12) * fix replica/slave status different columns in answer * fix GetSlaveOrReplicaStruct * fix version * fix version * fix version * fix version * fix version * fix version * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * add 8.0 docker tests flow * fix cache keys for base images * add code checkout for connect hashFiles output * add code checkout for connect hashFiles output * remove query_cache_size from cfg * remove innodb_use_global_flush_log_at_trx_commit from cfg * remove rpl_semi_sync_master_timeout from cfg * remove rpl_semi_sync_master_wait_for_slave_count from cfg * remove rpl_semi_sync_master_wait_for_slave_count from cfg * dont delete datadir on 8.0 * revert apt changes * fix apt for 8.0 * fix seconds behind master->replica * fix seconds slave/replica tests queries * fix seconds slave/replica tests queries * revert debug&zk downloads path * fix ReplicationLag for nil slavestatus * fix semysync for 8.0 * fix semysync for 8.0 * fix config for 8.0 * fix config for 8.0 * remove semisync_source.so&semisync_replica.so install * set mysql_native_password auth plugin as default for 8.0 tests * fix source state check in tests, some renaming * rename all 8-0 to 8.0 * some renames replica/slave-status&structs --------- Co-authored-by: suetin --- .github/workflows/docker-tests-8.0.yml | 102 +++++++++++++ .github/workflows/docker-tests.yml | 11 +- Makefile | 5 +- internal/app/app.go | 56 +++---- internal/app/util.go | 4 +- internal/app/util_test.go | 16 +- internal/config/config.go | 4 +- internal/mysql/data.go | 194 +++++++++++++++++++++---- internal/mysql/node.go | 48 +++--- internal/mysql/queries.go | 2 +- tests/features/repair.feature | 4 +- tests/images/base/Dockerfile | 3 +- tests/images/base/setup.sh | 10 +- tests/images/docker-compose.yaml | 12 ++ tests/images/mysql/Dockerfile | 5 +- tests/images/mysql/my.cnf | 13 +- tests/images/mysql/my.cnf.8.0 | 84 +++++++++++ tests/images/mysql/setup.sh | 17 ++- tests/images/mysql/start_mysql.sh | 3 +- tests/images/zookeeper/Dockerfile | 3 +- tests/mysync_test.go | 64 +++++--- 21 files changed, 527 insertions(+), 133 deletions(-) create mode 100644 .github/workflows/docker-tests-8.0.yml create mode 100644 tests/images/mysql/my.cnf.8.0 diff --git a/.github/workflows/docker-tests-8.0.yml b/.github/workflows/docker-tests-8.0.yml new file mode 100644 index 00000000..2de153da --- /dev/null +++ b/.github/workflows/docker-tests-8.0.yml @@ -0,0 +1,102 @@ +name: Docker tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + GO_VERSION: 1.19 + +jobs: + buildimages: + name: Build images + runs-on: ubuntu-20.04 + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v3 + + - name: Docker images caching + id: cache-images + uses: actions/cache@v3 + with: + path: ~/mysync-base-img8.0.tgz + key: mysync-base-img8.0-${{ hashFiles('tests/images/base/*') }} + + - name: Build images + if: steps.cache-images.outputs.cache-hit != 'true' + run: make base_img_8.0 + + - name: Export image + if: steps.cache-images.outputs.cache-hit != 'true' + run: docker save mysync-test-base8.0 | gzip -c > ~/mysync-base-img8.0.tgz + + test: + name: test + runs-on: ubuntu-20.04 + needs: [ buildimages ] + strategy: + matrix: + command: + - 'VERSION=8.0 GODOG_FEATURE=active_nodes.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=async.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=cascade_replicas.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=CLI.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=crash_recovery.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=events_reenable.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=failover.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=free_space.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=host_discovery.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=host_management.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=maintenance.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=offline_mode.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=priority.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=readonly_filesystem.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=recovery.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=repair.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=statefile.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=switchover_from.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=switchover_to.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=zk_failure.feature make test' + - 'VERSION=8.0 GODOG_FEATURE=zk_maintenance.feature make test' + fail-fast: false + + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v3 + + - name: Get dependencies + run: | + go get -v -t -d ./... + + - name: Load docker images + id: cache-images + uses: actions/cache@v3 + with: + path: ~/mysync-base-img8.0.tgz + key: mysync-base-img8.0-${{ hashFiles('tests/images/base/*') }} + + - name: Fail if no cached images + if: steps.cache-images.outputs.cache-hit != 'true' + run: | + echo "Failed to fetch cached docker images. Will now exit..." + exit 1 + + - name: Import image + run: docker load -i ~/mysync-base-img8.0.tgz + + - name: Run test + run: ${{ matrix.command }} + + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: logs + path: tests/logs diff --git a/.github/workflows/docker-tests.yml b/.github/workflows/docker-tests.yml index 88e2d9cf..76d12cfb 100644 --- a/.github/workflows/docker-tests.yml +++ b/.github/workflows/docker-tests.yml @@ -14,16 +14,15 @@ jobs: name: Build images runs-on: ubuntu-20.04 steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v3 + - name: Docker images caching id: cache-images uses: actions/cache@v3 with: path: ~/mysync-base-img.tgz - key: mysync-base-img - - - name: Check out code into the Go module directory - if: steps.cache-images.outputs.cache-hit != 'true' - uses: actions/checkout@v3 + key: mysync-base-img-${{ hashFiles('tests/images/base/*') }} - name: Build images if: steps.cache-images.outputs.cache-hit != 'true' @@ -82,7 +81,7 @@ jobs: uses: actions/cache@v3 with: path: ~/mysync-base-img.tgz - key: mysync-base-img + key: mysync-base-img-${{ hashFiles('tests/images/base/*') }} - name: Fail if no cached images if: steps.cache-images.outputs.cache-hit != 'true' diff --git a/Makefile b/Makefile index 4e1fd241..b1a6a364 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,10 @@ unittests: go test ./cmd/... ./internal/... base_img: - docker build --tag=mysync-test-base tests/images/base + docker build --tag=mysync-test-base tests/images/base --build-arg MYSQL_VERSION=5.7 + +base_img_8.0: + docker build --tag=mysync-test-base8.0 tests/images/base --build-arg MYSQL_VERSION=8.0 jepsen_base_img: docker build --tag=mysync-jepsen-test-base tests/images/jepsen_common diff --git a/internal/app/app.go b/internal/app/app.go index 003d18c3..3c910181 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -254,7 +254,7 @@ func (app *App) checkRecovery() { } localNode := app.cluster.Local() - sstatus, err := localNode.SlaveStatus() + sstatus, err := localNode.GetReplicaStatus() if err != nil { app.logger.Errorf("recovery: host %s failed to get slave status %v", localNode.Host(), err) return @@ -283,7 +283,7 @@ func (app *App) checkRecovery() { } app.logger.Infof("recovery: master %s has GTIDs %s", master, mgtids) - app.logger.Infof("recovery: local node %s has GTIDs %s", localNode.Host(), sstatus.ExecutedGtidSet) + app.logger.Infof("recovery: local node %s has GTIDs %s", localNode.Host(), sstatus.GetExecutedGtidSet()) if isSlavePermanentlyLost(sstatus, mgtids) { app.logger.Errorf("recovery: local node %s is NOT behind the master %s, need RESETUP", localNode.Host(), masterNode) @@ -315,7 +315,7 @@ func (app *App) checkCrashRecovery() { return } localNode := app.cluster.Local() - sstatus, err := localNode.SlaveStatus() + sstatus, err := localNode.GetReplicaStatus() if err != nil { app.logger.Errorf("recovery: host %s failed to get slave status %v", localNode.Host(), err) return @@ -331,7 +331,7 @@ func (app *App) checkCrashRecovery() { func (app *App) checkHAReplicasRunning(local *mysql.Node) bool { checker := func(host string) error { node := app.cluster.Get(host) - status, err := node.SlaveStatusWithTimeout(app.config.DBLostCheckTimeout) + status, err := node.ReplicaStatusWithTimeout(app.config.DBLostCheckTimeout) if err != nil { return err } @@ -341,7 +341,7 @@ func (app *App) checkHAReplicasRunning(local *mysql.Node) bool { if !status.ReplicationRunning() { return fmt.Errorf("replication on host %s is not running", host) } - if status.MasterHost != local.Host() { + if status.GetMasterHost() != local.Host() { return fmt.Errorf("replication on host %s doesn't streaming from master %s", host, local.Host()) } if !app.config.SemiSync { @@ -1300,7 +1300,7 @@ func (app *App) performSwitchover(clusterState map[string]*NodeState, activeNode // check if need recover old master oldMasterNode := app.cluster.Get(oldMaster) - oldMasterSlaveStatus, err := oldMasterNode.SlaveStatus() + oldMasterSlaveStatus, err := oldMasterNode.GetReplicaStatus() app.logger.Infof("switchover: old master slave status: %#v", oldMasterSlaveStatus) if err != nil || oldMasterSlaveStatus == nil || isSlavePermanentlyLost(oldMasterSlaveStatus, mostRecentGtidSet) { err = app.SetRecovery(oldMaster) @@ -1645,7 +1645,7 @@ func (app *App) repairCascadeNode(node *mysql.Node, clusterState map[string]*Nod cnc := cascadeTopology[host] if state.SlaveState == nil { - app.logger.Warnf("repair: current SlaveStatus is unknown. Blindly change master on %s to '%s'", host, cnc.StreamFrom) + app.logger.Warnf("repair: current Slave/Replica Status is unknown. Blindly change master on %s to '%s'", host, cnc.StreamFrom) err := app.performChangeMaster(host, cnc.StreamFrom) if err != nil { app.logger.Warnf("repair: failed to change master on host %s to new value %s", host, cnc.StreamFrom) @@ -1707,15 +1707,15 @@ func (app *App) repairCascadeNode(node *mysql.Node, clusterState map[string]*Nod // we have chosen candidate... wait till stream_from will have newer GTID than ours: // There is a race between GTID sets: order in which it was fetched is not defined. - // So, fetch cascade replica's SlaveStatus here + // So, fetch cascade replica's ReplicaStatus here // As a result, we know that myGITIDs fetched AFTER candidate's GTIDs... // We should wait until myGITIDs (fetched later) will be lower or equal to candidateGTIDs (fetched earlier) - mySlaveStatue, err := node.SlaveStatus() // retrieve fresh GTIDs + mySlaveStatus, err := node.GetReplicaStatus() // retrieve fresh GTIDs if err != nil { - app.logger.Warnf("repair: cannot obtain own SLAVE STATUS") + app.logger.Warnf("repair: cannot obtain own SLAVE/REPLICA STATUS") return } - myGITIDs := gtids.ParseGtidSet(mySlaveStatue.ExecutedGtidSet) + myGITIDs := gtids.ParseGtidSet(mySlaveStatus.GetExecutedGtidSet()) candidateState := clusterState[upstreamCandidate] var candidateGTIDs gtids.GTIDSet @@ -1861,10 +1861,10 @@ func (app *App) performChangeMaster(host, master string) error { return fmt.Errorf("failed to start slave on host %s: %s", host, err) } - deadline := time.Now().Add(app.config.WaitReplicationStarTimeout) - var sstatus *mysql.SlaveStatus + deadline := time.Now().Add(app.config.WaitReplicationStartTimeout) + var sstatus mysql.ReplicaStatus for time.Now().Before(deadline) { - sstatus, err = node.SlaveStatus() + sstatus, err = node.GetReplicaStatus() if err != nil { app.logger.Warnf("changemaster: failed to get slave status on host %s: %v", host, err) continue @@ -1911,22 +1911,22 @@ func (app *App) getNodeState(host string) *NodeState { if err != nil { return err } - slaveStatus, err := node.SlaveStatus() + slaveStatus, err := node.GetReplicaStatus() if err != nil { return err } if slaveStatus != nil { nodeState.IsMaster = false nodeState.SlaveState = new(SlaveState) - nodeState.SlaveState.ExecutedGtidSet = slaveStatus.ExecutedGtidSet - nodeState.SlaveState.RetrievedGtidSet = slaveStatus.RetrievedGtidSet - nodeState.SlaveState.MasterHost = slaveStatus.MasterHost + nodeState.SlaveState.ExecutedGtidSet = slaveStatus.GetExecutedGtidSet() + nodeState.SlaveState.RetrievedGtidSet = slaveStatus.GetRetrievedGtidSet() + nodeState.SlaveState.MasterHost = slaveStatus.GetMasterHost() nodeState.SlaveState.ReplicationState = slaveStatus.ReplicationState() - nodeState.SlaveState.MasterLogFile = slaveStatus.MasterLogFile - nodeState.SlaveState.MasterLogPos = slaveStatus.ReadMasterLogPos - nodeState.SlaveState.LastIOErrno = slaveStatus.LastIOErrno - nodeState.SlaveState.LastSQLErrno = slaveStatus.LastSQLErrno - lag, err2 := node.ReplicationLag() + nodeState.SlaveState.MasterLogFile = slaveStatus.GetMasterLogFile() + nodeState.SlaveState.MasterLogPos = slaveStatus.GetReadMasterLogPos() + nodeState.SlaveState.LastIOErrno = slaveStatus.GetLastIOErrno() + nodeState.SlaveState.LastSQLErrno = slaveStatus.GetLastSQLErrno() + lag, err2 := node.ReplicationLag(slaveStatus) if err2 != nil { return err2 } @@ -2129,11 +2129,11 @@ func (app *App) getNodePositions(activeNodes []string) ([]nodePosition, error) { var positionsMutex sync.Mutex errs := util.RunParallel(func(host string) error { node := app.cluster.Get(host) - sstatus, err := node.SlaveStatus() + sstatus, err := node.GetReplicaStatus() if err != nil || app.emulateError("freeze_slave_status") { return fmt.Errorf("failed to get slave status on host %s: %s", host, err) } - lag, err := node.ReplicationLag() + lag, err := node.ReplicationLag(sstatus) if err != nil || app.emulateError("freeze_slave_status2") { return fmt.Errorf("failed to get slave replication lag on host %s: %s", host, err) } @@ -2152,10 +2152,10 @@ func (app *App) getNodePositions(activeNodes []string) ([]nodePosition, error) { return fmt.Errorf("failed to get master status on host %s: %s", host, err) } } else { - gtidset = gtids.ParseGtidSet(sstatus.ExecutedGtidSet) - if sstatus.RetrievedGtidSet != "" { + gtidset = gtids.ParseGtidSet(sstatus.GetExecutedGtidSet()) + if sstatus.GetRetrievedGtidSet() != "" { // slave may have downloaded but not applied transactions - err := gtidset.Update(sstatus.RetrievedGtidSet) + err := gtidset.Update(sstatus.GetRetrievedGtidSet()) if err != nil { return fmt.Errorf("failed to parse RetrievedGtidSet from host %s: %s", host, err) } diff --git a/internal/app/util.go b/internal/app/util.go index ba6c25d8..4cb79076 100644 --- a/internal/app/util.go +++ b/internal/app/util.go @@ -221,11 +221,11 @@ func getNodeStatesInParallel(hosts []string, getter func(string) (*NodeState, er return clusterState, nil } -func isSlavePermanentlyLost(sstatus *mysql.SlaveStatus, masterGtidSet gtids.GTIDSet) bool { +func isSlavePermanentlyLost(sstatus mysql.ReplicaStatus, masterGtidSet gtids.GTIDSet) bool { if sstatus.ReplicationState() == mysql.ReplicationError { return true } - slaveGtidSet := gtids.ParseGtidSet(sstatus.ExecutedGtidSet) + slaveGtidSet := gtids.ParseGtidSet(sstatus.GetExecutedGtidSet()) return !isGTIDLessOrEqual(slaveGtidSet, masterGtidSet) } diff --git a/internal/app/util_test.go b/internal/app/util_test.go index ec3b098c..8a71e8cb 100644 --- a/internal/app/util_test.go +++ b/internal/app/util_test.go @@ -304,16 +304,20 @@ func TestCalcLagBytes(t *testing.T) { } func TestVersionGetQuery(t *testing.T) { - v := mysql.Version{MajorVersion: "8.0", FullVersion: "8.1.01"} + v := mysql.Version{MajorVersion: 8, MinorVersion: 0, PatchVersion: 1} + require.Equal(t, v.GetSlaveStatusQuery(), "slave_status") + v = mysql.Version{MajorVersion: 8, MinorVersion: 0, PatchVersion: 23} + require.Equal(t, v.GetSlaveStatusQuery(), "replica_status") + v = mysql.Version{MajorVersion: 8, MinorVersion: 0, PatchVersion: 111} require.Equal(t, v.GetSlaveStatusQuery(), "replica_status") - v = mysql.Version{MajorVersion: "8.0", FullVersion: "8.0.23"} + v = mysql.Version{MajorVersion: 8, MinorVersion: 2, PatchVersion: 2} require.Equal(t, v.GetSlaveStatusQuery(), "replica_status") - v = mysql.Version{MajorVersion: "8.0", FullVersion: "8.0.20"} + v = mysql.Version{MajorVersion: 8, MinorVersion: 0, PatchVersion: 20} require.Equal(t, v.GetSlaveStatusQuery(), "slave_status") - v = mysql.Version{MajorVersion: "5.7", FullVersion: "8.0.20"} + v = mysql.Version{MajorVersion: 5, MinorVersion: 7, PatchVersion: 111} + require.Equal(t, v.GetSlaveStatusQuery(), "slave_status") + v = mysql.Version{MajorVersion: 5, MinorVersion: 5, PatchVersion: 11} require.Equal(t, v.GetSlaveStatusQuery(), "slave_status") - v = mysql.Version{MajorVersion: "5.6", FullVersion: "5.6.25"} - require.Equal(t, v.GetSlaveStatusQuery(), "replica_status") } func getLogger() *log.Logger { diff --git a/internal/config/config.go b/internal/config/config.go index edd59ed3..ec161741 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -77,7 +77,7 @@ type Config struct { PriorityChoiceMaxLag time.Duration `config:"priority_choice_max_lag" yaml:"priority_choice_max_lag"` TestDiskUsageFile string `config:"test_disk_usage_file" yaml:"test_disk_usage_file"` RplSemiSyncMasterWaitForSlaveCount int `config:"rpl_semi_sync_master_wait_for_slave_count" yaml:"rpl_semi_sync_master_wait_for_slave_count"` - WaitReplicationStarTimeout time.Duration `config:"wait_start_replication_timeout" yaml:"wait_start_replication_timeout"` + WaitReplicationStartTimeout time.Duration `config:"wait_start_replication_timeout" yaml:"wait_start_replication_timeout"` ReplicationRepairAggressiveMode bool `config:"replication_repair_aggressive_mode" yaml:"replication_repair_aggressive_mode"` ReplicationRepairCooldown time.Duration `config:"replication_repair_cooldown" yaml:"replication_repair_cooldown"` ReplicationRepairMaxAttempts int `config:"replication_repair_max_attempts" yaml:"replication_repair_max_attempts"` @@ -148,7 +148,7 @@ func DefaultConfig() (Config, error) { PriorityChoiceMaxLag: 60 * time.Second, TestDiskUsageFile: "", // fake disk usage, only for docker tests RplSemiSyncMasterWaitForSlaveCount: 1, - WaitReplicationStarTimeout: 10 * time.Second, + WaitReplicationStartTimeout: 10 * time.Second, ReplicationRepairAggressiveMode: false, ReplicationRepairCooldown: 1 * time.Minute, ReplicationRepairMaxAttempts: 3, diff --git a/internal/mysql/data.go b/internal/mysql/data.go index 1f4ca03b..7e673b3e 100644 --- a/internal/mysql/data.go +++ b/internal/mysql/data.go @@ -40,18 +40,49 @@ type ResetupStatus struct { UpdateTime time.Time } -// SlaveStatus contains SHOW SLAVE/REPLICA STATUS response -type SlaveStatus struct { - MasterHost string `db:"Master_Host"` - MasterPort int `db:"Master_Port"` - MasterLogFile string `db:"Master_Log_File"` - ReadMasterLogPos int64 `db:"Read_Master_Log_Pos"` - SlaveIORunning string `db:"Slave_IO_Running"` - SlaveSQLRunning string `db:"Slave_SQL_Running"` - RetrievedGtidSet string `db:"Retrieved_Gtid_Set"` - ExecutedGtidSet string `db:"Executed_Gtid_Set"` - LastIOErrno int `db:"Last_IO_Errno"` - LastSQLErrno int `db:"Last_SQL_Errno"` +// SlaveStatusStruct contains SHOW SLAVE STATUS response +type SlaveStatusStruct struct { + MasterHost string `db:"Master_Host"` + MasterPort int `db:"Master_Port"` + MasterLogFile string `db:"Master_Log_File"` + ReadMasterLogPos int64 `db:"Read_Master_Log_Pos"` + SlaveIORunning string `db:"Slave_IO_Running"` + SlaveSQLRunning string `db:"Slave_SQL_Running"` + RetrievedGtidSet string `db:"Retrieved_Gtid_Set"` + ExecutedGtidSet string `db:"Executed_Gtid_Set"` + LastIOErrno int `db:"Last_IO_Errno"` + LastSQLErrno int `db:"Last_SQL_Errno"` + Lag sql.NullFloat64 `db:"Seconds_Behind_Master"` +} + +// ReplicaStatusStruct contains SHOW REPLICA STATUS response +type ReplicaStatusStruct struct { + SourceHost string `db:"Source_Host"` + SourcePort int `db:"Source_Port"` + SourceLogFile string `db:"Source_Log_File"` + ReadSourceLogPos int64 `db:"Read_Source_Log_Pos"` + ReplicaIORunning string `db:"Replica_IO_Running"` + ReplicaSQLRunning string `db:"Replica_SQL_Running"` + RetrievedGtidSet string `db:"Retrieved_Gtid_Set"` + ExecutedGtidSet string `db:"Executed_Gtid_Set"` + LastIOErrno int `db:"Last_IO_Errno"` + LastSQLErrno int `db:"Last_SQL_Errno"` + Lag sql.NullFloat64 `db:"Seconds_Behind_Source"` +} + +type ReplicaStatus interface { + ReplicationIORunning() bool + ReplicationSQLRunning() bool + ReplicationRunning() bool + ReplicationState() string + GetMasterHost() string + GetMasterLogFile() string + GetReadMasterLogPos() int64 + GetExecutedGtidSet() string + GetRetrievedGtidSet() string + GetLastIOErrno() int + GetLastSQLErrno() int + GetReplicationLag() sql.NullFloat64 } // SemiSyncStatus contains semi sync host settings @@ -61,23 +92,101 @@ type SemiSyncStatus struct { WaitSlaveCount int `db:"WaitSlaveCount"` } +func (ss *SlaveStatusStruct) GetMasterHost() string { + return ss.MasterHost +} + +func (ss *SlaveStatusStruct) GetMasterLogFile() string { + return ss.MasterLogFile +} + +func (ss *SlaveStatusStruct) GetReadMasterLogPos() int64 { + return ss.ReadMasterLogPos +} + +func (ss *SlaveStatusStruct) GetExecutedGtidSet() string { + return ss.ExecutedGtidSet +} + +func (ss *SlaveStatusStruct) GetRetrievedGtidSet() string { + return ss.RetrievedGtidSet +} + +func (ss *SlaveStatusStruct) GetLastIOErrno() int { + return ss.LastIOErrno +} + +func (ss *SlaveStatusStruct) GetLastSQLErrno() int { + return ss.LastSQLErrno +} + +func (ss *ReplicaStatusStruct) GetMasterHost() string { + return ss.SourceHost +} + +func (ss *ReplicaStatusStruct) GetMasterLogFile() string { + return ss.SourceLogFile +} + +func (ss *ReplicaStatusStruct) GetReadMasterLogPos() int64 { + return ss.ReadSourceLogPos +} + +func (ss *ReplicaStatusStruct) GetExecutedGtidSet() string { + return ss.ExecutedGtidSet +} + +func (ss *ReplicaStatusStruct) GetRetrievedGtidSet() string { + return ss.RetrievedGtidSet +} + +func (ss *ReplicaStatusStruct) GetLastIOErrno() int { + return ss.LastIOErrno +} + +func (ss *ReplicaStatusStruct) GetLastSQLErrno() int { + return ss.LastSQLErrno +} + // ReplicationIORunning ... -func (ss *SlaveStatus) ReplicationIORunning() bool { +func (ss *SlaveStatusStruct) ReplicationIORunning() bool { return ss.SlaveIORunning == yes } +func (ss *ReplicaStatusStruct) ReplicationIORunning() bool { + return ss.ReplicaIORunning == yes +} + // ReplicationSQLRunning ... -func (ss *SlaveStatus) ReplicationSQLRunning() bool { +func (ss *SlaveStatusStruct) ReplicationSQLRunning() bool { return ss.SlaveSQLRunning == yes } +func (ss *ReplicaStatusStruct) ReplicationSQLRunning() bool { + return ss.ReplicaSQLRunning == yes +} + // ReplicationRunning is true when both IO and SQL threads running -func (ss *SlaveStatus) ReplicationRunning() bool { +func (ss *SlaveStatusStruct) ReplicationRunning() bool { return ss.ReplicationIORunning() && ss.ReplicationSQLRunning() } +func (ss *ReplicaStatusStruct) ReplicationRunning() bool { + return ss.ReplicationIORunning() && ss.ReplicationSQLRunning() +} + +func (ss *SlaveStatusStruct) GetReplicationLag() sql.NullFloat64 { + return ss.Lag +} + +func (ss *ReplicaStatusStruct) GetReplicationLag() sql.NullFloat64 { + return ss.Lag +} + +//GetReplicationLag + // ReplicationState ... -func (ss *SlaveStatus) ReplicationState() string { +func (ss *SlaveStatusStruct) ReplicationState() string { switch { case ss.SlaveIORunning == yes && ss.SlaveSQLRunning == yes: return ReplicationRunning @@ -88,6 +197,17 @@ func (ss *SlaveStatus) ReplicationState() string { } } +func (ss *ReplicaStatusStruct) ReplicationState() string { + switch { + case ss.ReplicaIORunning == yes && ss.ReplicaSQLRunning == yes: + return ReplicationRunning + case (ss.ReplicaIORunning != yes || ss.ReplicaSQLRunning != yes) && (ss.LastIOErrno == 0 && ss.LastSQLErrno == 0): + return ReplicationStopped + default: + return ReplicationError + } +} + // GTIDExecuted contains SHOW MASTER STATUS response type GTIDExecuted struct { ExecutedGtidSet string `db:"Executed_Gtid_Set"` @@ -119,26 +239,44 @@ func (ev Event) String() string { } type Version struct { - MajorVersion string `db:"MajorVersion"` - FullVersion string `db:"FullVersion"` + MajorVersion int `db:"MajorVersion"` + MinorVersion int `db:"MinorVersion"` + PatchVersion int `db:"PatchVersion"` } const ( - Version80 = "8.0" - Version80ReplicaStatus = "8.0.22" - Version57 = "5.7" + Version80Major = 8 + Version80Minor = 0 + Version80PatchReplicaStatus = 22 + Version57Major = 5 ) -func (v *Version) GetSlaveStatusQuery() string { +func (v *Version) CheckIfVersionReplicaStatus() bool { switch v.MajorVersion { - case Version80: - if v.FullVersion >= Version80ReplicaStatus { - return queryReplicaStatus + case Version80Major: + if v.MinorVersion > Version80Minor || v.PatchVersion >= Version80PatchReplicaStatus { + return true } - return querySlaveStatus - case Version57: - return querySlaveStatus + return false + case Version57Major: + return false default: + return true + } +} + +func (v *Version) GetSlaveStatusQuery() string { + if v.CheckIfVersionReplicaStatus() { return queryReplicaStatus + } else { + return querySlaveStatus + } +} + +func (v *Version) GetSlaveOrReplicaStruct() ReplicaStatus { + if v.CheckIfVersionReplicaStatus() { + return new(ReplicaStatusStruct) + } else { + return new(SlaveStatusStruct) } } diff --git a/internal/mysql/node.go b/internal/mysql/node.go index c75b07e1..7001c4b1 100644 --- a/internal/mysql/node.go +++ b/internal/mysql/node.go @@ -482,17 +482,16 @@ func (n *Node) Ping() (bool, error) { return result.Ok > 0, err } -// SlaveStatus returns slave status or nil if node is master -func (n *Node) SlaveStatus() (*SlaveStatus, error) { - return n.SlaveStatusWithTimeout(n.config.DBTimeout) +// GetReplicaStatus returns slave/replica status or nil if node is master +func (n *Node) GetReplicaStatus() (ReplicaStatus, error) { + return n.ReplicaStatusWithTimeout(n.config.DBTimeout) } -func (n *Node) SlaveStatusWithTimeout(timeout time.Duration) (*SlaveStatus, error) { - query, err := n.GetVersionSlaveStatusQueryWithTimeout(timeout) +func (n *Node) ReplicaStatusWithTimeout(timeout time.Duration) (ReplicaStatus, error) { + query, status, err := n.GetVersionSlaveStatusQueryWithTimeout(timeout) if err != nil { return nil, nil } - status := new(SlaveStatus) err = n.queryRowMogrifyWithTimeout(query, map[string]interface{}{ "channel": n.config.ReplicationChannel, }, status, timeout) @@ -502,41 +501,38 @@ func (n *Node) SlaveStatusWithTimeout(timeout time.Duration) (*SlaveStatus, erro return status, err } -func (n *Node) GetVersionSlaveStatusQueryWithTimeout(timeout time.Duration) (string, error) { +func (n *Node) GetVersionSlaveStatusQueryWithTimeout(timeout time.Duration) (string, ReplicaStatus, error) { if n.version != nil { - return n.version.GetSlaveStatusQuery(), nil + return n.version.GetSlaveStatusQuery(), n.version.GetSlaveOrReplicaStruct(), nil } v := new(Version) err := n.queryRowWithTimeout(queryGetVersion, nil, v, timeout) if err != nil { - return "", err + return "", nil, err } n.version = v - return n.version.GetSlaveStatusQuery(), err + return n.version.GetSlaveStatusQuery(), n.version.GetSlaveOrReplicaStruct(), err } // ReplicationLag returns slave replication lag in seconds // ReplicationLag may return nil without error if lag is unknown (replication not running) -func (n *Node) ReplicationLag() (*float64, error) { +func (n *Node) ReplicationLag(sstatus ReplicaStatus) (*float64, error) { var err error - lag := new(replicationLag) if n.getQuery(queryReplicationLag) != "" { + lag := new(replicationLag) err = n.queryRow(queryReplicationLag, nil, lag) - } else { - query, err2 := n.GetVersionSlaveStatusQueryWithTimeout(n.config.DBTimeout) - if err2 != nil { - return nil, nil + if err == sql.ErrNoRows { + // looks like master + return new(float64), nil + } + if lag.Lag.Valid { + return &lag.Lag.Float64, nil + } + } else if sstatus != nil { + l := sstatus.GetReplicationLag() + if l.Valid { + return &l.Float64, nil } - err = n.queryRowMogrifyWithTimeout(query, map[string]interface{}{ - "channel": n.config.ReplicationChannel, - }, lag, n.config.DBTimeout) - } - if err == sql.ErrNoRows { - // looks like master - return new(float64), nil - } - if lag.Lag.Valid { - return &lag.Lag.Float64, nil } // replication not running, assume lag is huge return nil, err diff --git a/internal/mysql/queries.go b/internal/mysql/queries.go index c7d36a59..6875f9f8 100644 --- a/internal/mysql/queries.go +++ b/internal/mysql/queries.go @@ -42,7 +42,7 @@ var DefaultQueries = map[string]string{ queryPing: `SELECT 1 AS Ok`, querySlaveStatus: `SHOW SLAVE STATUS FOR CHANNEL :channel`, queryReplicaStatus: `SHOW REPLICA STATUS FOR CHANNEL :channel`, - queryGetVersion: `SELECT SUBSTRING(VERSION(), 1, 3) AS MajorVersion, SUBSTRING_INDEX(VERSION(), '-', 1) as FullVersion`, + queryGetVersion: `SELECT sys.version_major() AS MajorVersion, sys.version_minor() AS MinorVersion, sys.version_patch() AS PatchVersion`, queryGTIDExecuted: `SELECT @@GLOBAL.gtid_executed as Executed_Gtid_Set`, queryShowBinaryLogs: `SHOW BINARY LOGS`, querySlaveHosts: `SHOW SLAVE HOSTS`, diff --git a/tests/features/repair.feature b/tests/features/repair.feature index cb2acb2a..602716cc 100644 --- a/tests/features/repair.feature +++ b/tests/features/repair.feature @@ -84,7 +84,9 @@ Feature: repair hosts in cluster Given cluster is up and running Then mysql host "mysql1" should be master And mysql host "mysql1" should be writable - # jsut to have stable tests - turn on maintenance mode + And mysql host "mysql2" should be replica of "mysql1" + And mysql replication on host "mysql2" should run fine within "5" seconds + # just to have stable tests - turn on maintenance mode And I run command on host "mysql1" """ mysync maint on diff --git a/tests/images/base/Dockerfile b/tests/images/base/Dockerfile index db2462d7..fcb06ab1 100644 --- a/tests/images/base/Dockerfile +++ b/tests/images/base/Dockerfile @@ -2,7 +2,8 @@ FROM ubuntu:bionic ENV container docker ENV DEBIAN_FRONTEND noninteractive ENV ZK_VERSION=3.7.1 -ENV MYSQL_VERSION=5.7 +ARG MYSQL_VERSION="" +ENV MYSQL_VERSION="${MYSQL_VERSION}" COPY . /var/lib/dist/base RUN bash /var/lib/dist/base/setup.sh CMD ["/usr/local/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/tests/images/base/setup.sh b/tests/images/base/setup.sh index e788ecbc..9d1f7e4a 100644 --- a/tests/images/base/setup.sh +++ b/tests/images/base/setup.sh @@ -19,6 +19,7 @@ apt-get install \ apt-key add - < /var/lib/dist/base/percona.gpg add-apt-repository 'deb http://mirror.yandex.ru/mirrors/percona/percona/apt bionic main' +add-apt-repository 'deb http://mirror.yandex.ru/mirrors/percona/ps-80/apt bionic main' # common apt-get update @@ -53,11 +54,17 @@ cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys chmod 0600 /root/.ssh/* # mysql -apt-get install \ +if [[ "$MYSQL_VERSION" == "8.0" ]]; then + apt-get install \ + percona-server-server=8.0.\* \ + percona-xtrabackup-80 +else + apt-get install \ percona-xtradb-cluster-server-${MYSQL_VERSION} \ percona-xtradb-cluster-client-${MYSQL_VERSION} \ percona-xtradb-cluster-common-${MYSQL_VERSION} \ percona-xtrabackup-24 +fi rm -rf /var/lib/mysql/* # supervisor @@ -67,6 +74,5 @@ cp /var/lib/dist/base/supervisor.conf /etc/supervisor/supervisord.conf cp /var/lib/dist/base/supervisor_ssh.conf /etc/supervisor/conf.d # zookeeper -# wget -nc -O - --quiet https://www-eu.apache.org/dist/zookeeper/zookeeper-${ZK_VERSION}/apache-zookeeper-${ZK_VERSION}-bin.tar.gz | tar -xz -C /opt && \ wget -nc -O - --quiet https://downloads.apache.org/zookeeper/zookeeper-${ZK_VERSION}/apache-zookeeper-${ZK_VERSION}-bin.tar.gz | tar -xz -C /opt && \ mv /opt/apache-zookeeper* /opt/zookeeper diff --git a/tests/images/docker-compose.yaml b/tests/images/docker-compose.yaml index e0d8804a..0e866a04 100644 --- a/tests/images/docker-compose.yaml +++ b/tests/images/docker-compose.yaml @@ -4,6 +4,8 @@ services: zoo1: build: context: ./zookeeper + args: + - VERSION=$VERSION hostname: zoo1 ports: - 2181 @@ -24,6 +26,8 @@ services: zoo2: build: context: ./zookeeper + args: + - VERSION=$VERSION hostname: zoo2 ports: - 2181 @@ -38,6 +42,8 @@ services: zoo3: build: context: ./zookeeper + args: + - VERSION=$VERSION hostname: zoo3 ports: - 2181 @@ -52,6 +58,8 @@ services: mysql1: build: context: ./mysql + args: + - VERSION=$VERSION hostname: mysql1 ports: - 3306 @@ -92,6 +100,8 @@ services: mysql2: build: context: ./mysql + args: + - VERSION=$VERSION hostname: mysql2 ports: - 3306 @@ -126,6 +136,8 @@ services: mysql3: build: context: ./mysql + args: + - VERSION=$VERSION hostname: mysql3 ports: - 3306 diff --git a/tests/images/mysql/Dockerfile b/tests/images/mysql/Dockerfile index 72ef18a9..b57c98bc 100644 --- a/tests/images/mysql/Dockerfile +++ b/tests/images/mysql/Dockerfile @@ -1,4 +1,7 @@ -FROM mysync-test-base:latest +ARG VERSION="" +FROM mysync-test-base${VERSION}:latest +ARG VERSION="" +ENV VERSION="${VERSION}" COPY . /var/lib/dist/mysql COPY ./mysync /usr/bin/mysync RUN bash /var/lib/dist/mysql/setup.sh diff --git a/tests/images/mysql/my.cnf b/tests/images/mysql/my.cnf index 0f30fbf5..d574bd3d 100644 --- a/tests/images/mysql/my.cnf +++ b/tests/images/mysql/my.cnf @@ -10,6 +10,11 @@ timezone = Europe/Moscow log_timestamps = SYSTEM open_files_limit = 65535 +[mysqldump] +quick +quote-names +max_allowed_packet = 16M + [mysqld] plugin_load_add = 'rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so' user = mysql @@ -24,7 +29,7 @@ basedir = /usr datadir = /var/lib/mysql tmpdir = /tmp lc_messages_dir = /usr/share/mysql -max-connect-errors = 1000000 +max_connect_errors = 1000000 local_infile = OFF slave_net_timeout = 30 @@ -56,7 +61,6 @@ innodb_use_native_aio = OFF innodb_flush_log_at_trx_commit = 1 innodb_use_global_flush_log_at_trx_commit = OFF - # Replication # server_id = 2 # should set from env binlog_format = ROW @@ -83,8 +87,3 @@ rpl_semi_sync_master_wait_point = AFTER_SYNC read_only = ON super_read_only = ON offline_mode = ON - -[mysqldump] -quick -quote-names -max_allowed_packet = 16M diff --git a/tests/images/mysql/my.cnf.8.0 b/tests/images/mysql/my.cnf.8.0 new file mode 100644 index 00000000..b6f6c24e --- /dev/null +++ b/tests/images/mysql/my.cnf.8.0 @@ -0,0 +1,84 @@ +[client] +port = 3306 +socket = /tmp/mysqld.sock + +[mysqld_safe] +nice = 0 +socket = /tmp/mysqld.sock +log_error = /var/log/mysql/error.log +timezone = Europe/Moscow +log_timestamps = SYSTEM +open_files_limit = 65535 + +[mysqldump] +quick +quote-names +max_allowed_packet = 16M + +[mysqld] +plugin_load_add = 'rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so' +default_authentication_plugin=mysql_native_password # no ssl in docker +user = mysql +port = 3306 +pid_file = /tmp/mysqld.pid +socket = /tmp/mysqld.sock +log_error = /var/log/mysql/error.log +log_error_verbosity = 3 +general_log = ON +general_log_file = /var/log/mysql/query.log +basedir = /usr +datadir = /var/lib/mysql +tmpdir = /tmp +lc_messages_dir = /usr/share/mysql +max_connect_errors = 1000000 +local_infile = OFF +slave_net_timeout = 30 + +event_scheduler = ON +skip_external_locking = ON +default_storage_engine = InnoDB +disabled_storage_engines = "MyISAM,MRG_MyISAM,BLACKHOLE,FEDERATED,ARCHIVE,CSV" +explicit_defaults_for_timestamp = ON +log_timestamps = SYSTEM +max_allowed_packet = 16M +thread_stack = 192K + +max_connections = 200 +thread_handling = one-thread-per-connection +thread_cache_size = 5 + +# Slow log +max_slowlog_size = 1G + +# InnoDB settings +innodb_buffer_pool_size = 32M +innodb_file_per_table = ON +innodb_log_file_size = 8M +innodb_autoinc_lock_mode = 2 +innodb_use_native_aio = OFF +#innodb_flush_method = O_DIRECT +innodb_flush_log_at_trx_commit = 1 + + +# Replication +# server_id = 2 # should set from env +binlog_format = ROW +gtid_mode = ON +enforce_gtid_consistency = ON +log_slave_updates = ON +max_binlog_size = 100M +expire_logs_days = 3 +master_info_repository = TABLE +relay_log_info_repository = TABLE +relay_log_recovery = ON +log_bin = mysql-bin-log +relay_log = mysql-relay-log +sync_binlog = 1 + +# we should start in +# * read-only mode to avoid split brain after restart +# * offline_mode to avoid stale reads after restart +# mysync will bring us in writable mode +read_only = ON +super_read_only = ON +offline_mode = ON diff --git a/tests/images/mysql/setup.sh b/tests/images/mysql/setup.sh index 18b6f6df..dca5e9fd 100644 --- a/tests/images/mysql/setup.sh +++ b/tests/images/mysql/setup.sh @@ -2,7 +2,20 @@ set -e chown mysql:root /etc/mysql touch /etc/mysync.yaml -chown mysql:mysql /etc/mysync.yaml -cp /var/lib/dist/mysql/my.cnf /etc/mysql/my.cnf +chown mysql:mysql /etc/mysync.yaml +if [[ "$VERSION" == "8.0" ]]; then + cp /var/lib/dist/mysql/my.cnf.8.0 /etc/mysql/my.cnf + cp /var/lib/dist/mysql/my.cnf.8.0 /etc/mysql/init.cnf +cat <> /etc/mysql/my.cnf +rpl_semi_sync_master_timeout = 31536000000 +rpl_semi_sync_master_wait_for_slave_count = 1 +rpl_semi_sync_master_wait_no_slave = ON +rpl_semi_sync_master_wait_point = AFTER_SYNC +EOF +else + cp /var/lib/dist/mysql/my.cnf /etc/mysql/my.cnf + cp /var/lib/dist/mysql/my.cnf /etc/mysql/init.cnf +fi + cp /var/lib/dist/mysql/.my.cnf /root/.my.cnf cp /var/lib/dist/mysql/supervisor_mysql.conf /etc/supervisor/conf.d diff --git a/tests/images/mysql/start_mysql.sh b/tests/images/mysql/start_mysql.sh index ef8418b7..3f4953e6 100644 --- a/tests/images/mysql/start_mysql.sh +++ b/tests/images/mysql/start_mysql.sh @@ -32,7 +32,8 @@ else fi if [ ! -f /var/lib/mysql/auto.cnf ]; then - /usr/sbin/mysqld --initialize --datadir=/var/lib/mysql --init-file=/etc/mysql/init.sql --server-id=$MYSQL_SERVER_ID + /usr/sbin/mysqld --defaults-file=/etc/mysql/init.cnf \ + --initialize --datadir=/var/lib/mysql --init-file=/etc/mysql/init.sql --server-id=$MYSQL_SERVER_ID || true echo "==INITIALIZED==" fi diff --git a/tests/images/zookeeper/Dockerfile b/tests/images/zookeeper/Dockerfile index 3621e1d1..1554b290 100644 --- a/tests/images/zookeeper/Dockerfile +++ b/tests/images/zookeeper/Dockerfile @@ -1,3 +1,4 @@ -FROM mysync-test-base:latest +ARG VERSION="" +FROM mysync-test-base${VERSION}:latest COPY . /var/lib/dist/zookeeper RUN bash /var/lib/dist/zookeeper/setup.sh diff --git a/tests/mysync_test.go b/tests/mysync_test.go index 193d6b56..7866d06b 100644 --- a/tests/mysync_test.go +++ b/tests/mysync_test.go @@ -9,6 +9,7 @@ import ( "log" "os" "path/filepath" + "strconv" "strings" "sync" "testing" @@ -354,22 +355,46 @@ func (tctx *testContext) doMysqlQuery(db *sqlx.DB, query string, args interface{ return result, nil } -func (tctx *testContext) runSlaveStatusQuery(host string) ([]map[string]interface{}, error) { - query := "SELECT SUBSTRING(VERSION(), 1, 3) AS MajorVersion, SUBSTRING_INDEX(VERSION(), '-', 1) as FullVersion" +func (tctx *testContext) runSlaveStatusQuery(host string) (map[string]string, error) { + query := "SELECT sys.version_major() AS MajorVersion, sys.version_minor() AS MinorVersion, sys.version_patch() AS PatchVersion" res, err := tctx.queryMysql(host, query, nil) if err != nil { return nil, err } - v := mysql_internal.Version{MajorVersion: res[0]["MajorVersion"].(string), FullVersion: res[0]["FullVersion"].(string)} + MajorVersion, err := strconv.Atoi(res[0]["MajorVersion"].(string)) + if err != nil { + return nil, err + } + MinorVersion, err := strconv.Atoi(res[0]["MinorVersion"].(string)) + if err != nil { + return nil, err + } + PatchVersion, err := strconv.Atoi(res[0]["PatchVersion"].(string)) + if err != nil { + return nil, err + } + v := mysql_internal.Version{MajorVersion: MajorVersion, MinorVersion: MinorVersion, PatchVersion: PatchVersion} query = mysql_internal.DefaultQueries[v.GetSlaveStatusQuery()] query = mysql_internal.Mogrify(query, map[string]interface{}{ "channel": replicationChannel, }) res, err = tctx.queryMysql(host, query, nil) - if err != nil { + if len(res) == 0 || err != nil { return nil, err } - return res, nil + result := make(map[string]string) + result["Last_IO_Error"] = res[0]["Last_IO_Error"].(string) + result["Last_SQL_Error"] = res[0]["Last_SQL_Error"].(string) + if v.CheckIfVersionReplicaStatus() { + result["Master_Host"] = res[0]["Source_Host"].(string) + result["Slave_IO_Running"] = res[0]["Replica_IO_Running"].(string) + result["Slave_SQL_Running"] = res[0]["Replica_SQL_Running"].(string) + } else { + result["Master_Host"] = res[0]["Master_Host"].(string) + result["Slave_IO_Running"] = res[0]["Slave_IO_Running"].(string) + result["Slave_SQL_Running"] = res[0]["Slave_SQL_Running"].(string) + } + return result, nil } func (tctx *testContext) stepClusterEnvironmentIs(body *godog.DocString) error { @@ -382,6 +407,11 @@ func (tctx *testContext) stepClusterEnvironmentIs(body *godog.DocString) error { env = append(env, e) } } + version, _ := os.LookupEnv("VERSION") + if version != "" { + v := fmt.Sprintf("VERSION=%s", version) + env = append(env, v) + } tctx.composerEnv = env return nil } @@ -778,7 +808,7 @@ func (tctx *testContext) stepBreakReplicationOnHost(host string) error { func (tctx *testContext) stepBreakReplicationOnHostInARepairableWay(host string) error { // Kill Replication IO thread: - query := "SELECT id FROM information_schema.processlist WHERE state = 'Waiting for master to send event'" + query := "SELECT id FROM information_schema.processlist WHERE state = 'Waiting for master to send event' OR state = 'Waiting for source to send event'" queryReqult, err := tctx.queryMysql(host, query, struct{}{}) if err != nil { return err @@ -898,7 +928,7 @@ func (tctx *testContext) stepMysqlHostShouldBeMaster(host string) error { if err != nil { return err } - if len(res) != 0 { + if res != nil { return fmt.Errorf("host %s has not empty slave status", host) } return nil @@ -975,10 +1005,10 @@ func (tctx *testContext) stepMysqlHostShouldBeReplicaOf(host, master string) err if err != nil { return err } - if len(res) == 0 { + if res == nil { return fmt.Errorf("host %s has empty slave status", host) } - masterHostStr := res[0]["Master_Host"].(string) + masterHostStr := res["Master_Host"] if masterHostStr != master { return fmt.Errorf("host %s master is %s, when expected %s", host, masterHostStr, master) } @@ -999,16 +1029,16 @@ func (tctx *testContext) stepMysqlReplicationOnHostShouldRunFine(host string) er if err != nil { return err } - if len(res) == 0 { + if res == nil { return fmt.Errorf("host %s has empty slave status", host) } - ioRunning := res[0]["Slave_IO_Running"].(string) - ioError := res[0]["Last_IO_Error"].(string) + ioRunning := res["Slave_IO_Running"] + ioError := res["Last_IO_Error"] if ioRunning != yes { return fmt.Errorf("host %s replication io thread is not running: %s", host, ioError) } - sqlRunning := res[0]["Slave_SQL_Running"].(string) - sqlError := res[0]["Last_SQL_Error"].(string) + sqlRunning := res["Slave_SQL_Running"] + sqlError := res["Last_SQL_Error"] if sqlRunning != yes { return fmt.Errorf("host %s replication io thread is not running: %s", host, sqlError) } @@ -1029,11 +1059,11 @@ func (tctx *testContext) stepMysqlReplicationOnHostShouldNotRunFine(host string) if err != nil { return err } - if len(res) == 0 { + if res == nil { return fmt.Errorf("host %s has empty slave status", host) } - ioRunning := res[0]["Slave_IO_Running"].(string) - sqlRunning := res[0]["Slave_SQL_Running"].(string) + ioRunning := res["Slave_IO_Running"] + sqlRunning := res["Slave_SQL_Running"] if ioRunning != yes || sqlRunning != yes { return nil }