зеркало из https://github.com/github/vitess-gh.git
277 строки
8.7 KiB
Python
277 строки
8.7 KiB
Python
# Copyright 2019 The Vitess Authors.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Define abstractions for various MySQL flavors."""
|
|
|
|
import environment
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
|
|
|
|
class MysqlFlavor(object):
|
|
"""Base class with default SQL statements."""
|
|
|
|
def demote_master_commands(self):
|
|
"""Returns commands to stop the current master."""
|
|
return [
|
|
"SET GLOBAL read_only = ON",
|
|
"FLUSH TABLES WITH READ LOCK",
|
|
"UNLOCK TABLES",
|
|
]
|
|
|
|
def promote_slave_commands(self):
|
|
"""Returns commands to convert a slave to a master."""
|
|
return [
|
|
"STOP SLAVE",
|
|
"RESET SLAVE ALL",
|
|
"SET GLOBAL read_only = OFF",
|
|
]
|
|
|
|
def reset_replication_commands(self):
|
|
"""Returns commands to reset replication settings."""
|
|
return [
|
|
"STOP SLAVE",
|
|
"RESET SLAVE ALL",
|
|
"RESET MASTER",
|
|
]
|
|
|
|
def change_master_commands(self, host, port, pos):
|
|
raise NotImplementedError()
|
|
|
|
def set_semi_sync_enabled_commands(self, master=None, slave=None):
|
|
"""Returns commands to turn semi-sync on/off."""
|
|
cmds = []
|
|
if master is not None:
|
|
cmds.append("SET GLOBAL rpl_semi_sync_master_enabled = %d" % master)
|
|
if slave is not None:
|
|
cmds.append("SET GLOBAL rpl_semi_sync_slave_enabled = %d" % slave)
|
|
return cmds
|
|
|
|
def extra_my_cnf(self):
|
|
"""Returns the path to an extra my_cnf file, or None."""
|
|
return None
|
|
|
|
def master_position(self, tablet):
|
|
"""Returns the position from SHOW MASTER STATUS as a string."""
|
|
raise NotImplementedError()
|
|
|
|
def position_equal(self, a, b):
|
|
"""Returns true if position 'a' is equal to 'b'."""
|
|
raise NotImplementedError()
|
|
|
|
def position_at_least(self, a, b):
|
|
"""Returns true if position 'a' is at least as far along as 'b'."""
|
|
raise NotImplementedError()
|
|
|
|
def position_after(self, a, b):
|
|
"""Returns true if position 'a' is after 'b'."""
|
|
return self.position_at_least(a, b) and not self.position_equal(a, b)
|
|
|
|
def enable_binlog_checksum(self, tablet):
|
|
"""Enables binlog_checksum and returns True if the flavor supports it.
|
|
|
|
Args:
|
|
tablet: A tablet.Tablet object.
|
|
|
|
Returns:
|
|
False if the flavor doesn't support binlog_checksum.
|
|
"""
|
|
tablet.mquery("", "SET @@global.binlog_checksum=1")
|
|
return True
|
|
|
|
def disable_binlog_checksum(self, tablet):
|
|
"""Disables binlog_checksum if the flavor supports it."""
|
|
tablet.mquery("", "SET @@global.binlog_checksum=0")
|
|
|
|
def change_passwords(self, password_col):
|
|
"""set real passwords for all users"""
|
|
return '''
|
|
# Set real passwords for all users.
|
|
UPDATE mysql.user SET %s = PASSWORD('RootPass')
|
|
WHERE User = 'root' AND Host = 'localhost';
|
|
UPDATE mysql.user SET %s = PASSWORD('VtDbaPass')
|
|
WHERE User = 'vt_dba' AND Host = 'localhost';
|
|
UPDATE mysql.user SET %s = PASSWORD('VtAppPass')
|
|
WHERE User = 'vt_app' AND Host = 'localhost';
|
|
UPDATE mysql.user SET %s = PASSWORD('VtAllprivsPass')
|
|
WHERE User = 'vt_allprivs' AND Host = 'localhost';
|
|
UPDATE mysql.user SET %s = PASSWORD('VtReplPass')
|
|
WHERE User = 'vt_repl' AND Host = '%%';
|
|
UPDATE mysql.user SET %s = PASSWORD('VtFilteredPass')
|
|
WHERE User = 'vt_filtered' AND Host = 'localhost';
|
|
FLUSH PRIVILEGES;
|
|
''' % tuple([password_col] * 6)
|
|
|
|
class MariaDB(MysqlFlavor):
|
|
"""Overrides specific to MariaDB."""
|
|
|
|
def reset_replication_commands(self):
|
|
return [
|
|
"STOP SLAVE",
|
|
"RESET SLAVE ALL",
|
|
"RESET MASTER",
|
|
"SET GLOBAL gtid_slave_pos = ''",
|
|
]
|
|
|
|
def extra_my_cnf(self):
|
|
return environment.vttop + "/config/mycnf/master_mariadb100.cnf"
|
|
|
|
def master_position(self, tablet):
|
|
gtid = tablet.mquery("", "SELECT @@GLOBAL.gtid_binlog_pos")[0][0]
|
|
return "MariaDB/" + gtid
|
|
|
|
def position_equal(self, a, b):
|
|
return a == b
|
|
|
|
def position_at_least(self, a, b):
|
|
# positions are MariaDB/A-B-C and we only compare C
|
|
return int(a.split("-")[2]) >= int(b.split("-")[2])
|
|
|
|
def change_master_commands(self, host, port, pos):
|
|
gtid = pos.split("/")[1]
|
|
return [
|
|
"SET GLOBAL gtid_slave_pos = '%s'" % gtid,
|
|
"CHANGE MASTER TO MASTER_HOST='%s', MASTER_PORT=%d, "
|
|
"MASTER_USER='vt_repl', MASTER_USE_GTID = slave_pos" %
|
|
(host, port)]
|
|
|
|
|
|
class MariaDB103(MariaDB):
|
|
"""Overrides specific to MariaDB 10.3+."""
|
|
|
|
def extra_my_cnf(self):
|
|
return environment.vttop + "/config/mycnf/master_mariadb103.cnf"
|
|
|
|
class MySQL56(MysqlFlavor):
|
|
"""Overrides specific to MySQL 5.6/5.7"""
|
|
|
|
def master_position(self, tablet):
|
|
gtid = tablet.mquery("", "SELECT @@GLOBAL.gtid_executed")[0][0]
|
|
return "MySQL56/" + gtid
|
|
|
|
def position_equal(self, a, b):
|
|
return subprocess.check_output([
|
|
"mysqlctl", "position", "equal", a, b,
|
|
]).strip() == "true"
|
|
|
|
def position_at_least(self, a, b):
|
|
return subprocess.check_output([
|
|
"mysqlctl", "position", "at_least", a, b,
|
|
]).strip() == "true"
|
|
|
|
def extra_my_cnf(self):
|
|
return environment.vttop + "/config/mycnf/master_mysql56.cnf"
|
|
|
|
def change_master_commands(self, host, port, pos):
|
|
gtid = pos.split("/")[1]
|
|
return [
|
|
"RESET MASTER",
|
|
"SET GLOBAL gtid_purged = '%s'" % gtid,
|
|
"CHANGE MASTER TO MASTER_HOST='%s', MASTER_PORT=%d, "
|
|
"MASTER_USER='vt_repl', MASTER_AUTO_POSITION = 1" %
|
|
(host, port)]
|
|
|
|
class MySQL80(MySQL56):
|
|
"""Overrides specific to MySQL 8.0."""
|
|
def extra_my_cnf(self):
|
|
return environment.vttop + "/config/mycnf/master_mysql80.cnf"
|
|
def change_passwords(self, password_col):
|
|
"""set real passwords for all users"""
|
|
return '''
|
|
# Set real passwords for all users.
|
|
ALTER USER 'root'@'localhost' IDENTIFIED BY 'RootPass';
|
|
ALTER USER 'vt_dba'@'localhost' IDENTIFIED BY 'VtDbaPass';
|
|
ALTER USER 'vt_app'@'localhost' IDENTIFIED BY 'VtAppPass';
|
|
ALTER USER 'vt_allprivs'@'localhost' IDENTIFIED BY 'VtAllPrivsPass';
|
|
ALTER USER 'vt_repl'@'%' IDENTIFIED BY 'VtReplPass';
|
|
ALTER USER 'vt_filtered'@'localhost' IDENTIFIED BY 'VtFilteredPass';
|
|
FLUSH PRIVILEGES;
|
|
'''
|
|
|
|
|
|
# Map of registered MysqlFlavor classes (keyed by an identifier).
|
|
flavor_map = {}
|
|
|
|
MYSQL_FLAVOR = None
|
|
|
|
|
|
# mysql_flavor is a function because we need something to import before the
|
|
# variable MYSQL_FLAVOR is initialized, since that doesn't happen until after
|
|
# the command-line options are parsed. If we make mysql_flavor a variable and
|
|
# import it before it's initialized, the module that imported it won't get the
|
|
# updated value when it's later initialized.
|
|
def mysql_flavor():
|
|
return MYSQL_FLAVOR
|
|
|
|
|
|
def set_mysql_flavor(flavor):
|
|
"""Set the object that will be returned by mysql_flavor().
|
|
|
|
If flavor is not specified, set it based on MYSQL_FLAVOR environment variable.
|
|
|
|
Args:
|
|
flavor: String of the MySQL flavor e.g. "MariaDB" or "MySQL56".
|
|
"""
|
|
|
|
global MYSQL_FLAVOR
|
|
|
|
if not flavor:
|
|
flavor = os.environ.get("MYSQL_FLAVOR", "MySQL56")
|
|
# The environment variable might be set, but equal to "".
|
|
if not flavor:
|
|
flavor = "MySQL56"
|
|
|
|
v = flavor_map.get(flavor, None)
|
|
if not v:
|
|
logging.error("Unknown MYSQL_FLAVOR '%s'", flavor)
|
|
exit(1)
|
|
|
|
cls = v["cls"]
|
|
env = v["env"]
|
|
MYSQL_FLAVOR = cls()
|
|
# Set the environment variable explicitly in case we're overriding it via
|
|
# command-line flag.
|
|
os.environ["MYSQL_FLAVOR"] = env
|
|
|
|
logging.debug("Using MySQL flavor: %s, setting MYSQL_FLAVOR=%s (%s)",
|
|
str(flavor), env, cls)
|
|
|
|
|
|
def register_flavor(flavor, cls, env):
|
|
"""Register the available MySQL flavors.
|
|
|
|
Note: We need the 'env' argument because our internal implementation is
|
|
similar to 'MariaDB' (and hence requires MYSQL_FLAVOR=MariaDB) but has its own
|
|
flavor class.
|
|
|
|
Args:
|
|
flavor: Name of the flavor (must be passed to test flag --mysql-flavor).
|
|
cls: Class which inherits MysqlFlavor and provides the implementation.
|
|
env: Value which will be used for the environment variable MYSQL_FLAVOR.
|
|
"""
|
|
if flavor in flavor_map:
|
|
old_cls = flavor_map[flavor]["cls"]
|
|
old_env = flavor_map[flavor]["env"]
|
|
logging.error("Cannot register MySQL flavor %s because class %s (env: %s)"
|
|
" is already registered for it.", flavor, old_cls, old_env)
|
|
exit(1)
|
|
|
|
flavor_map[flavor] = {"cls": cls, "env": env}
|
|
|
|
register_flavor("MariaDB", MariaDB, "MariaDB")
|
|
register_flavor("MariaDB103", MariaDB103, "MariaDB103")
|
|
register_flavor("MySQL56", MySQL56, "MySQL56")
|
|
register_flavor("MySQL80", MySQL80, "MySQL80")
|