vitess-gh/go/vt/mysqlctl/mysql_flavor.go

156 строки
5.6 KiB
Go

// Copyright 2014, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mysqlctl
import (
"fmt"
"os"
"time"
log "github.com/golang/glog"
"github.com/youtube/vitess/go/sqldb"
blproto "github.com/youtube/vitess/go/vt/binlog/proto"
"github.com/youtube/vitess/go/vt/mysqlctl/proto"
)
/*
This file handles the differences between flavors of mysql.
*/
// MysqlFlavor is the abstract interface for a flavor.
type MysqlFlavor interface {
// VersionMatch returns true if the version string (from
// SELECT VERSION()) represents a server that this flavor
// knows how to talk to.
VersionMatch(version string) bool
// MasterPosition returns the ReplicationPosition of a master.
MasterPosition(mysqld *Mysqld) (proto.ReplicationPosition, error)
// SlaveStatus returns the ReplicationStatus of a slave.
SlaveStatus(mysqld *Mysqld) (proto.ReplicationStatus, error)
// ResetReplicationCommands returns the commands to completely reset
// replication on the host.
ResetReplicationCommands() []string
// PromoteSlaveCommands returns the commands to run to change
// a slave into a master.
PromoteSlaveCommands() []string
// SetSlavePositionCommands returns the commands to set the
// replication position at which the slave will resume
// when it is later reparented with SetMasterCommands.
SetSlavePositionCommands(pos proto.ReplicationPosition) ([]string, error)
// SetMasterCommands returns the commands to use the provided master
// as the new master (without changing any GTID position).
// It is guaranteed to be called with replication stopped.
// It should not start or stop replication.
SetMasterCommands(params *sqldb.ConnParams, masterHost string, masterPort int, masterConnectRetry int) ([]string, error)
// ParseGTID parses a GTID in the canonical format of this
// MySQL flavor into a proto.GTID interface value.
ParseGTID(string) (proto.GTID, error)
// ParseReplicationPosition parses a replication position in
// the canonical format of this MySQL flavor into a
// proto.ReplicationPosition struct.
ParseReplicationPosition(string) (proto.ReplicationPosition, error)
// SendBinlogDumpCommand sends the flavor-specific version of
// the COM_BINLOG_DUMP command to start dumping raw binlog
// events over a slave connection, starting at a given GTID.
SendBinlogDumpCommand(conn *SlaveConnection, startPos proto.ReplicationPosition) error
// MakeBinlogEvent takes a raw packet from the MySQL binlog
// stream connection and returns a BinlogEvent through which
// the packet can be examined.
MakeBinlogEvent(buf []byte) blproto.BinlogEvent
// WaitMasterPos waits until slave replication reaches at
// least targetPos.
WaitMasterPos(mysqld *Mysqld, targetPos proto.ReplicationPosition, waitTimeout time.Duration) error
// EnableBinlogPlayback prepares the server to play back
// events from a binlog stream. Whatever it does for a given
// flavor, it must be idempotent.
EnableBinlogPlayback(mysqld *Mysqld) error
// DisableBinlogPlayback returns the server to the normal
// state after playback is done. Whatever it does for a given
// flavor, it must be idempotent.
DisableBinlogPlayback(mysqld *Mysqld) error
}
var mysqlFlavors = make(map[string]MysqlFlavor)
// registerFlavorBuiltin adds a flavor to the map only if the name is unused.
// The flavor implementation passed to this function will only be used if there
// are no calls to registerFlavorOverride with the same flavor name.
// This should only be called from the init() goroutine.
func registerFlavorBuiltin(name string, flavor MysqlFlavor) {
if _, ok := mysqlFlavors[name]; !ok {
mysqlFlavors[name] = flavor
}
}
// registerFlavorOverride adds a flavor to the map, overriding any built-in
// implementations registered with registerFlavorBuiltin. There should only
// be one override per name, or else there's no guarantee which will win.
// This should only be called from the init() goroutine.
func registerFlavorOverride(name string, flavor MysqlFlavor) {
mysqlFlavors[name] = flavor
}
// detectFlavor decides which flavor to assume, based on the MYSQL_FLAVOR
// environment variable. If that variable is empty or unset, we will try to
// auto-detect the flavor.
func (mysqld *Mysqld) detectFlavor() (MysqlFlavor, error) {
// Check environment variable, which overrides auto-detect.
if env := os.Getenv("MYSQL_FLAVOR"); env != "" {
if flavor, ok := mysqlFlavors[env]; ok {
log.Infof("Using MySQL flavor %v (set by MYSQL_FLAVOR)", env)
return flavor, nil
}
return nil, fmt.Errorf("Unknown flavor (MYSQL_FLAVOR=%v)", env)
}
// If no environment variable set, fall back to auto-detect.
log.Infof("MYSQL_FLAVOR empty or unset, attempting to auto-detect...")
qr, err := mysqld.FetchSuperQuery("SELECT VERSION()")
if err != nil {
return nil, fmt.Errorf("couldn't SELECT VERSION(): %v", err)
}
if len(qr.Rows) != 1 || len(qr.Rows[0]) != 1 {
return nil, fmt.Errorf("unexpected result for SELECT VERSION(): %#v", qr)
}
version := qr.Rows[0][0].String()
log.Infof("SELECT VERSION() = %s", version)
for name, flavor := range mysqlFlavors {
if flavor.VersionMatch(version) {
log.Infof("Using MySQL flavor %v (auto-detect match)", name)
return flavor, nil
}
}
return nil, fmt.Errorf("MYSQL_FLAVOR empty or unset, no auto-detect match found for VERSION() = %v", version)
}
func (mysqld *Mysqld) flavor() (MysqlFlavor, error) {
mysqld.mutex.Lock()
defer mysqld.mutex.Unlock()
if mysqld.mysqlFlavor == nil {
flavor, err := mysqld.detectFlavor()
if err != nil {
return nil, fmt.Errorf("couldn't detect MySQL flavor: %v", err)
}
mysqld.mysqlFlavor = flavor
}
return mysqld.mysqlFlavor, nil
}