2016-05-01 18:36:36 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 GitHub Inc.
|
2016-05-16 09:09:17 +00:00
|
|
|
See https://github.com/github/gh-ost/blob/master/LICENSE
|
2016-05-01 18:36:36 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
package mysql
|
|
|
|
|
|
|
|
import (
|
|
|
|
gosql "database/sql"
|
|
|
|
"fmt"
|
2019-02-17 13:53:31 +00:00
|
|
|
"strconv"
|
2018-05-22 09:36:52 +00:00
|
|
|
"strings"
|
2017-12-14 23:53:29 +00:00
|
|
|
"sync"
|
2016-05-01 18:36:36 +00:00
|
|
|
"time"
|
|
|
|
|
2016-10-20 09:29:30 +00:00
|
|
|
"github.com/github/gh-ost/go/sql"
|
|
|
|
|
2016-05-01 18:36:36 +00:00
|
|
|
"github.com/outbrain/golib/log"
|
|
|
|
"github.com/outbrain/golib/sqlutils"
|
|
|
|
)
|
|
|
|
|
2017-02-22 00:34:49 +00:00
|
|
|
const MaxTableNameLength = 64
|
2017-09-03 07:27:04 +00:00
|
|
|
const MaxReplicationPasswordLength = 32
|
2017-02-22 00:34:49 +00:00
|
|
|
|
2016-07-27 07:59:46 +00:00
|
|
|
type ReplicationLagResult struct {
|
|
|
|
Key InstanceKey
|
|
|
|
Lag time.Duration
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
2017-01-29 08:18:39 +00:00
|
|
|
func NewNoReplicationLagResult() *ReplicationLagResult {
|
|
|
|
return &ReplicationLagResult{Lag: 0, Err: nil}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *ReplicationLagResult) HasLag() bool {
|
|
|
|
return this.Lag > 0
|
|
|
|
}
|
|
|
|
|
2017-12-14 23:53:29 +00:00
|
|
|
// knownDBs is a DB cache by uri
|
|
|
|
var knownDBs map[string]*gosql.DB = make(map[string]*gosql.DB)
|
|
|
|
var knownDBsMutex = &sync.Mutex{}
|
|
|
|
|
|
|
|
func GetDB(migrationUuid string, mysql_uri string) (*gosql.DB, bool, error) {
|
|
|
|
cacheKey := migrationUuid + ":" + mysql_uri
|
|
|
|
|
|
|
|
knownDBsMutex.Lock()
|
|
|
|
defer func() {
|
|
|
|
knownDBsMutex.Unlock()
|
|
|
|
}()
|
|
|
|
|
|
|
|
var exists bool
|
|
|
|
if _, exists = knownDBs[cacheKey]; !exists {
|
|
|
|
if db, err := gosql.Open("mysql", mysql_uri); err == nil {
|
|
|
|
knownDBs[cacheKey] = db
|
|
|
|
} else {
|
|
|
|
return db, exists, err
|
|
|
|
}
|
2017-11-10 23:41:37 +00:00
|
|
|
}
|
2017-12-14 23:53:29 +00:00
|
|
|
return knownDBs[cacheKey], exists, nil
|
2017-11-10 23:41:37 +00:00
|
|
|
}
|
|
|
|
|
2018-04-28 04:20:28 +00:00
|
|
|
// GetReplicationLagFromSlaveStatus returns replication lag for a given db; via SHOW SLAVE STATUS
|
|
|
|
func GetReplicationLagFromSlaveStatus(informationSchemaDb *gosql.DB) (replicationLag time.Duration, err error) {
|
2017-09-22 23:06:06 +00:00
|
|
|
err = sqlutils.QueryRowsMap(informationSchemaDb, `show slave status`, func(m sqlutils.RowMap) error {
|
2017-02-07 10:13:19 +00:00
|
|
|
slaveIORunning := m.GetString("Slave_IO_Running")
|
|
|
|
slaveSQLRunning := m.GetString("Slave_SQL_Running")
|
2016-05-01 18:36:36 +00:00
|
|
|
secondsBehindMaster := m.GetNullInt64("Seconds_Behind_Master")
|
|
|
|
if !secondsBehindMaster.Valid {
|
2017-02-08 10:24:44 +00:00
|
|
|
return fmt.Errorf("replication not running; Slave_IO_Running=%+v, Slave_SQL_Running=%+v", slaveIORunning, slaveSQLRunning)
|
2016-05-01 18:36:36 +00:00
|
|
|
}
|
|
|
|
replicationLag = time.Duration(secondsBehindMaster.Int64) * time.Second
|
|
|
|
return nil
|
|
|
|
})
|
2017-08-28 21:05:15 +00:00
|
|
|
|
2016-05-01 18:36:36 +00:00
|
|
|
return replicationLag, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetMasterKeyFromSlaveStatus(connectionConfig *ConnectionConfig) (masterKey *InstanceKey, err error) {
|
|
|
|
currentUri := connectionConfig.GetDBUri("information_schema")
|
2017-09-22 23:06:06 +00:00
|
|
|
// This function is only called once, okay to not have a cached connection pool
|
2017-12-14 23:53:29 +00:00
|
|
|
db, err := gosql.Open("mysql", currentUri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-09-22 23:06:06 +00:00
|
|
|
defer db.Close()
|
|
|
|
|
2016-05-01 18:36:36 +00:00
|
|
|
err = sqlutils.QueryRowsMap(db, `show slave status`, func(rowMap sqlutils.RowMap) error {
|
2016-12-05 12:41:49 +00:00
|
|
|
// We wish to recognize the case where the topology's master actually has replication configuration.
|
|
|
|
// This can happen when a DBA issues a `RESET SLAVE` instead of `RESET SLAVE ALL`.
|
|
|
|
|
|
|
|
// An empty log file indicates this is a master:
|
2016-12-05 11:42:16 +00:00
|
|
|
if rowMap.GetString("Master_Log_File") == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
slaveIORunning := rowMap.GetString("Slave_IO_Running")
|
|
|
|
slaveSQLRunning := rowMap.GetString("Slave_SQL_Running")
|
|
|
|
|
|
|
|
if slaveIORunning != "Yes" || slaveSQLRunning != "Yes" {
|
2016-12-05 12:41:49 +00:00
|
|
|
return fmt.Errorf("Replication on %+v is broken: Slave_IO_Running: %s, Slave_SQL_Running: %s. Please make sure replication runs before using gh-ost.",
|
|
|
|
connectionConfig.Key,
|
2016-12-05 11:42:16 +00:00
|
|
|
slaveIORunning,
|
|
|
|
slaveSQLRunning,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-05-01 18:36:36 +00:00
|
|
|
masterKey = &InstanceKey{
|
|
|
|
Hostname: rowMap.GetString("Master_Host"),
|
|
|
|
Port: rowMap.GetInt("Master_Port"),
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2016-12-05 11:42:16 +00:00
|
|
|
|
2016-05-01 18:36:36 +00:00
|
|
|
return masterKey, err
|
|
|
|
}
|
|
|
|
|
2016-06-22 08:38:13 +00:00
|
|
|
func GetMasterConnectionConfigSafe(connectionConfig *ConnectionConfig, visitedKeys *InstanceKeyMap, allowMasterMaster bool) (masterConfig *ConnectionConfig, err error) {
|
2016-05-01 18:36:36 +00:00
|
|
|
log.Debugf("Looking for master on %+v", connectionConfig.Key)
|
|
|
|
|
|
|
|
masterKey, err := GetMasterKeyFromSlaveStatus(connectionConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if masterKey == nil {
|
|
|
|
return connectionConfig, nil
|
|
|
|
}
|
|
|
|
if !masterKey.IsValid() {
|
|
|
|
return connectionConfig, nil
|
|
|
|
}
|
|
|
|
masterConfig = connectionConfig.Duplicate()
|
|
|
|
masterConfig.Key = *masterKey
|
|
|
|
|
|
|
|
log.Debugf("Master of %+v is %+v", connectionConfig.Key, masterConfig.Key)
|
|
|
|
if visitedKeys.HasKey(masterConfig.Key) {
|
2016-06-22 08:38:13 +00:00
|
|
|
if allowMasterMaster {
|
|
|
|
return connectionConfig, nil
|
|
|
|
}
|
2016-05-01 18:36:36 +00:00
|
|
|
return nil, fmt.Errorf("There seems to be a master-master setup at %+v. This is unsupported. Bailing out", masterConfig.Key)
|
|
|
|
}
|
|
|
|
visitedKeys.AddKey(masterConfig.Key)
|
2016-06-22 08:38:13 +00:00
|
|
|
return GetMasterConnectionConfigSafe(masterConfig, visitedKeys, allowMasterMaster)
|
2016-05-01 18:36:36 +00:00
|
|
|
}
|
2016-05-16 09:03:15 +00:00
|
|
|
|
2016-05-19 13:11:36 +00:00
|
|
|
func GetReplicationBinlogCoordinates(db *gosql.DB) (readBinlogCoordinates *BinlogCoordinates, executeBinlogCoordinates *BinlogCoordinates, err error) {
|
2016-05-16 09:03:15 +00:00
|
|
|
err = sqlutils.QueryRowsMap(db, `show slave status`, func(m sqlutils.RowMap) error {
|
|
|
|
readBinlogCoordinates = &BinlogCoordinates{
|
|
|
|
LogFile: m.GetString("Master_Log_File"),
|
|
|
|
LogPos: m.GetInt64("Read_Master_Log_Pos"),
|
|
|
|
}
|
2016-05-19 13:11:36 +00:00
|
|
|
executeBinlogCoordinates = &BinlogCoordinates{
|
|
|
|
LogFile: m.GetString("Relay_Master_Log_File"),
|
|
|
|
LogPos: m.GetInt64("Exec_Master_Log_Pos"),
|
|
|
|
}
|
2016-05-16 09:03:15 +00:00
|
|
|
return nil
|
|
|
|
})
|
2016-05-19 13:11:36 +00:00
|
|
|
return readBinlogCoordinates, executeBinlogCoordinates, err
|
2016-05-16 09:03:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetSelfBinlogCoordinates(db *gosql.DB) (selfBinlogCoordinates *BinlogCoordinates, err error) {
|
|
|
|
err = sqlutils.QueryRowsMap(db, `show master status`, func(m sqlutils.RowMap) error {
|
|
|
|
selfBinlogCoordinates = &BinlogCoordinates{
|
|
|
|
LogFile: m.GetString("File"),
|
|
|
|
LogPos: m.GetInt64("Position"),
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return selfBinlogCoordinates, err
|
|
|
|
}
|
2016-06-19 15:55:37 +00:00
|
|
|
|
|
|
|
// GetInstanceKey reads hostname and port on given DB
|
|
|
|
func GetInstanceKey(db *gosql.DB) (instanceKey *InstanceKey, err error) {
|
|
|
|
instanceKey = &InstanceKey{}
|
|
|
|
err = db.QueryRow(`select @@global.hostname, @@global.port`).Scan(&instanceKey.Hostname, &instanceKey.Port)
|
|
|
|
return instanceKey, err
|
|
|
|
}
|
2016-10-20 09:29:30 +00:00
|
|
|
|
|
|
|
// GetTableColumns reads column list from given table
|
2018-05-22 09:36:52 +00:00
|
|
|
func GetTableColumns(db *gosql.DB, databaseName, tableName string) (*sql.ColumnList, *sql.ColumnList, error) {
|
2016-10-20 09:29:30 +00:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
show columns from %s.%s
|
|
|
|
`,
|
|
|
|
sql.EscapeName(databaseName),
|
|
|
|
sql.EscapeName(tableName),
|
|
|
|
)
|
|
|
|
columnNames := []string{}
|
2018-05-22 09:36:52 +00:00
|
|
|
virtualColumnNames := []string{}
|
2016-10-20 09:29:30 +00:00
|
|
|
err := sqlutils.QueryRowsMap(db, query, func(rowMap sqlutils.RowMap) error {
|
2018-05-22 09:36:52 +00:00
|
|
|
columnName := rowMap.GetString("Field")
|
|
|
|
columnNames = append(columnNames, columnName)
|
|
|
|
if strings.Contains(rowMap.GetString("Extra"), " GENERATED") {
|
|
|
|
log.Debugf("%s is a generated column", columnName)
|
|
|
|
virtualColumnNames = append(virtualColumnNames, columnName)
|
|
|
|
}
|
2016-10-20 09:29:30 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
2018-05-22 09:36:52 +00:00
|
|
|
return nil, nil, err
|
2016-10-20 09:29:30 +00:00
|
|
|
}
|
|
|
|
if len(columnNames) == 0 {
|
2018-05-22 09:36:52 +00:00
|
|
|
return nil, nil, log.Errorf("Found 0 columns on %s.%s. Bailing out",
|
2016-10-20 09:29:30 +00:00
|
|
|
sql.EscapeName(databaseName),
|
|
|
|
sql.EscapeName(tableName),
|
|
|
|
)
|
|
|
|
}
|
2018-05-22 09:36:52 +00:00
|
|
|
return sql.NewColumnList(columnNames), sql.NewColumnList(virtualColumnNames), nil
|
2016-10-20 09:29:30 +00:00
|
|
|
}
|
2019-02-17 13:53:31 +00:00
|
|
|
|
|
|
|
// MajorVersion returns a MySQL major version number (e.g. given "5.5.36" it returns "5.5")
|
|
|
|
func majorVersion(version string) []string {
|
|
|
|
tokens := strings.Split(version, ".")
|
|
|
|
if len(tokens) < 2 {
|
|
|
|
return []string{"0", "0"}
|
|
|
|
}
|
|
|
|
return tokens[:2]
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsSmallerMajorVersion tests two versions against another and returns true if
|
|
|
|
// the former is a smaller "major" varsion than the latter.
|
|
|
|
// e.g. 5.5.36 is NOT a smaller major version as comapred to 5.5.40, but IS as compared to 5.6.9
|
|
|
|
func IsSmallerMajorVersion(version string, otherVersion string) bool {
|
|
|
|
thisMajorVersion := majorVersion(version)
|
|
|
|
otherMajorVersion := majorVersion(otherVersion)
|
|
|
|
for i := 0; i < len(thisMajorVersion); i++ {
|
|
|
|
thisToken, _ := strconv.Atoi(thisMajorVersion[i])
|
|
|
|
otherToken, _ := strconv.Atoi(otherMajorVersion[i])
|
|
|
|
if thisToken < otherToken {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if thisToken > otherToken {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|