def get_server(name, values, quiet): """Connect to a server and return Server instance If the name is 'master' or 'slave', the connection will be made via the Master or Slave class else a normal Server class shall be used. name[in] name of the server values[in] dictionary of connection values quiet[in] if True, do not print messages Returns Server class instance """ from mysql.utilities.common.replication import Master from mysql.utilities.common.replication import Slave server_conn = None # Try to connect to the MySQL database server. if not quiet: _print_connection(name, values) server_options = { 'conn_info' : values, 'role' : name, } if name.lower() == 'master': server_conn = Master(server_options) elif name.lower() == 'slave': server_conn = Slave(server_options) else: server_conn = Server(server_options) server_conn.connect() return server_conn
def setup(self): self.res_fname = "result.txt" result = replicate.test.setup(self) # Note: server1 is master, server2, server3 are slaves. # server3 is a new slave with nothing on it. self.server3 = self.servers.spawn_server( "new_slave", kill=True, mysqld='"--log-bin=mysql-bin"') self._drop_all() self.server1.exec_query("STOP SLAVE") self.server1.exec_query("RESET SLAVE") self.server2.exec_query("STOP SLAVE") self.server2.exec_query("RESET SLAVE") try: for cmd in _MASTER_DB_CMDS: self.server1.exec_query(cmd) except MUTLibError: raise data_file = os.path.normpath("./std_data/basic_data.sql") try: self.server1.read_and_exec_SQL(data_file, self.debug) self.server2.read_and_exec_SQL(data_file, self.debug) except MUTLibError as err: raise MUTLibError("Failed to read commands from file {0}: " "{1}".format(data_file, err.errmsg)) master_str = "--master={0}".format( self.build_connection_string(self.server1)) slave_str = " --slave={0}".format( self.build_connection_string(self.server2)) conn_str = master_str + slave_str cmd = "mysqlreplicate.py --rpl-user=rpl:rpl {0}".format(conn_str) try: self.exec_util(cmd, self.res_fname) except MUTLibError: raise # server1 is now a master server, lets treat it accordingly self.server1 = Master.fromServer(self.server1) try: self.server1.connect() except UtilError as err: raise MUTLibError("Cannot connect to spawned " "server: {0}".format(err.errmsg)) # server2 is now a slave, lets treat it accordingly self.server2 = Slave.fromServer(self.server2) try: self.server2.connect() except UtilError as err: raise MUTLibError("Cannot connect to spawned " "server: {0}".format(err.errmsg)) return result
def _check_server_versions(self): """Checks the server versions. """ if self.verbosity > 0: print("# Checking server versions.\n#") # Connection dictionary conn_dict = { "conn_info": None, "quiet": True, "verbose": self.verbosity > 0, } # Check masters version for master_vals in self.masters_vals: conn_dict["conn_info"] = master_vals master = Master(conn_dict) master.connect() if not master.check_version_compat(*_MIN_SERVER_VERSION): raise UtilRplError( ERROR_MIN_SERVER_VERSIONS.format( utility="mysqlrplms", min_version=".".join([str(val) for val in _MIN_SERVER_VERSION]), host=master.host, port=master.port ) ) master.disconnect() # Check slave version conn_dict["conn_info"] = self.slave_vals slave = Slave(conn_dict) slave.connect() if not slave.check_version_compat(*_MIN_SERVER_VERSION): raise UtilRplError( ERROR_MIN_SERVER_VERSIONS.format( utility="mysqlrplms", min_version=".".join([str(val) for val in _MIN_SERVER_VERSION]), host=slave.host, port=slave.port ) ) slave.disconnect()
def run_test_case(self, actual_result, test_num, master, source, destination, cmd_list, db_list, cmd_opts, comment, expected_results, restart_replication=False, skip_wait=False): results = [comment] # Drop all databases and reestablish replication if restart_replication: # Rollback here to avoid active transaction error for STOP SLAVE # with 5.5 servers (versions > 5.5.0). if self.servers.get_server(0).check_version_compat(5, 5, 0): destination.rollback() destination.exec_query("STOP SLAVE") destination.exec_query("RESET SLAVE") for db in db_list: self.drop_db(destination, db) master_str = "--master={0}".format( self.build_connection_string(master)) slave_str = " --slave={0}".format( self.build_connection_string(destination)) conn_str = master_str + slave_str cmd = "mysqlreplicate.py --rpl-user=rpl:rpl {0}".format(conn_str) try: self.exec_util(cmd, self.res_fname) except MUTLibError: raise # Convert object instance of master server to Master, if needed if not isinstance(master, Master): master = Master.fromServer(master) try: master.connect() except UtilError as err: raise MUTLibError("Cannot connect to spawned " "server: {0}".format(err.errmsg)) # Convert object instance of destination server to Slave, if needed if not isinstance(destination, Slave): destination = Slave.fromServer(destination) try: destination.connect() except UtilError as err: raise MUTLibError("Cannot connect to spawned " "server: {0}".format(err.errmsg)) # Check databases on slave and save results for 'BEFORE' check results.append(self._check_result(destination, "SHOW DATABASES " "LIKE 'util_test'")) results.append(self._check_result(destination, "SELECT COUNT(*) " "FROM util_test.t1")) results.append(self._check_result(destination, "SHOW DATABASES " "LIKE 'master_db1'")) results.append(self._check_result(destination, "SELECT COUNT(*) " "FROM master_db1.t1")) # Run the commands for cmd_str in cmd_list: try: res = self.exec_util(cmd_str + cmd_opts, self.res_fname) results.insert(1, res) # save result at front of list if res != actual_result: return False except MUTLibError: raise # Wait for slave to connect to master if not skip_wait: if self.debug: print "# Waiting for slave to connect to master", try: self.wait_for_slave_connection(destination, _MAX_ATTEMPTS) except MUTLibError: raise if self.debug: print "done." # Check databases on slave and save results for 'AFTER' check results.append(self._check_result(destination, "SHOW DATABASES " "LIKE 'util_test'")) results.append(self._check_result(destination, "SELECT COUNT(*) " "FROM util_test.t1")) results.append(self._check_result(destination, "SHOW DATABASES " "LIKE 'master_db1'")) results.append(self._check_result(destination, "SELECT COUNT(*) " "FROM master_db1.t1")) # Add something to master and check slave master.exec_query("INSERT INTO master_db1.t1 VALUES (10), (11)") # Wait for slave to catch up if not skip_wait: if self.debug: print "# Waiting for slave to sync", bin_info = master.get_binlog_info() if bin_info is None: # server is no longer acting as a master raise MUTLibError("The server '{0}' is no longer a master" "server".format(master.role)) # pylint: disable=W0633 binlog_file, binlog_pos = bin_info # Wait for slave to catch up with master, using the binlog # Note: This test requires servers without GTIDs (prior to 5.6.5) synced = destination.wait_for_slave(binlog_file, binlog_pos, _SYNC_TIMEOUT) if not synced: raise MUTLibError("Slave did not catch up with master") if self.debug: print "done." # ROLLBACK to close any active transaction leading to wrong values for # the next SELECT COUNT(*) with 5.5 servers (versions > 5.5.0). if self.servers.get_server(0).check_version_compat(5, 5, 0): destination.rollback() results.append(self._check_result(destination, "SELECT COUNT(*) " "FROM master_db1.t1")) if self.debug: print comment print "Expected Results:", expected_results[test_num - 1] print " Actual Results:", results[1:] self.results.append(results) return True
def _get_slaves(self, max_depth, seed_conn=None, masters_found=None): """Find the attached slaves for a list of server connections. This method connects to each server in the list and retrieves its slaves. It can be called recursively if the recurse option is True. max_depth[in] Maximum depth of recursive search seed_conn[in] Current master connection dictionary. Initially, this is the seed server (original master defined in constructor) masters_found[in] a list of all servers in master roles - used to detect a circular replication topology. Initially, this is an empty list as the master detection must occur as the topology is traversed. Returns list - list of slaves connected to each server in list """ if not masters_found: masters_found = [] topology = [] if seed_conn is None: seed_conn = self.seed_server master, master_info = self._connect(seed_conn) if master is None: return [] # Check user permissions self._check_permissions(master, "REPLICATION SLAVE") # Save the master for circular replication identification masters_found.append(master_info) if not self.quiet: print "# Finding slaves for master: %s" % master_info # See if the user wants us to discover slaves. discover = self.options.get("discover", None) if discover is None: return # Get user and password (supports login-paths) try: user, password = parse_user_password(discover, options=self.options) except FormatError: raise UtilError(USER_PASSWORD_FORMAT.format("--discover-slaves")) # Get replication topology slaves = master.get_slaves(user, password) slave_list = [] depth = 0 if len(slaves) > 0: for slave in slaves: if slave.find(":") > 0: host, port = slave.split(":", 1) else: host = slave port = _START_PORT # Use the default slave_conn = self.seed_server.copy() slave_conn['host'] = host slave_conn['port'] = port io_sql_running = None # If verbose then get slave threads (IO and SQL) status if self.verbose: # Create slave instance conn_dict = { 'conn_info': { 'user': user, 'passwd': password, 'host': host, 'port': port, 'socket': None }, 'role': slave, 'verbose': self.verbose } slave_obj = Slave(conn_dict) # Get IO and SQL status try: slave_obj.connect() thread_status = slave_obj.get_thread_status() if thread_status: io_sql_running = (thread_status[1], thread_status[2]) except UtilError: # Connection error io_sql_running = ('ERROR', 'ERROR') # Now check for circular replication topology - do not recurse # if slave is also a master. if self.recurse and slave not in masters_found and \ ((max_depth is None) or (depth < max_depth)): new_list = self._get_slaves(max_depth, slave_conn, masters_found) if new_list == []: slave_list.append((slave, [], io_sql_running)) else: # Add IO and SQL state to slave from recursion if io_sql_running: new_list = [(new_list[0][0], new_list[0][1], io_sql_running)] slave_list.append(new_list) depth += 1 else: slave_list.append((slave, [], io_sql_running)) topology.append((master_info, slave_list)) return topology
def _check_privileges(self): """Check required privileges to perform the multi-source replication. This method check if the used users for the slave and masters have the required privileges to perform the multi-source replication. The following privileges are required: - on slave: SUPER, SELECT, INSERT, UPDATE, REPLICATION SLAVE AND GRANT OPTION; - on the master: SUPER, SELECT, INSERT, UPDATE, REPLICATION SLAVE AND GRANT OPTION. An exception is thrown if users doesn't have enough privileges. """ if self.verbosity > 0: print("# Checking users privileges for replication.\n#") # Connection dictionary conn_dict = { "conn_info": None, "quiet": True, "verbose": self.verbosity > 0, } # Check privileges for master. master_priv = [('SUPER',), ('SELECT',), ('INSERT',), ('UPDATE',), ('REPLICATION SLAVE',), ('GRANT OPTION',)] master_priv_str = ("SUPER, SELECT, INSERT, UPDATE, REPLICATION SLAVE " "AND GRANT OPTION") for master_vals in self.masters_vals: conn_dict["conn_info"] = master_vals master = Master(conn_dict) master.connect() user_obj = User(master, "{0}@{1}".format(master.user, master.host)) for any_priv_tuple in master_priv: has_privilege = any( [user_obj.has_privilege('*', '*', priv) for priv in any_priv_tuple] ) if not has_privilege: msg = ERROR_USER_WITHOUT_PRIVILEGES.format( user=master.user, host=master.host, port=master.port, operation='perform replication', req_privileges=master_priv_str ) self._report(msg, logging.CRITICAL, False) raise UtilRplError(msg) master.disconnect() # Check privileges for slave slave_priv = [('SUPER',), ('SELECT',), ('INSERT',), ('UPDATE',), ('REPLICATION SLAVE',), ('GRANT OPTION',)] slave_priv_str = ("SUPER, SELECT, INSERT, UPDATE, REPLICATION SLAVE " "AND GRANT OPTION") conn_dict["conn_info"] = self.slave_vals slave = Slave(conn_dict) slave.connect() user_obj = User(slave, "{0}@{1}".format(slave.user, slave.host)) for any_priv_tuple in slave_priv: has_privilege = any( [user_obj.has_privilege('*', '*', priv) for priv in any_priv_tuple] ) if not has_privilege: msg = ("User '{0}' on '{1}@{2}' does not have sufficient " "privileges to perform replication (required: {3})." "".format(slave.user, slave.host, slave.port, slave_priv_str)) self._report(msg, logging.CRITICAL, False) raise UtilRplError(msg) slave.disconnect()
def run_test_case(self, actual_result, test_num, master, source, destination, cmd_list, db_list, cmd_opts, comment, expected_results, restart_replication=False, skip_wait=False): results = [comment] # Drop all databases and reestablish replication if restart_replication: # Rollback here to avoid active transaction error for STOP SLAVE # with 5.5 servers (versions > 5.5.0). if self.servers.get_server(0).check_version_compat(5, 5, 0): destination.rollback() destination.exec_query("STOP SLAVE") destination.exec_query("RESET SLAVE") for db in db_list: self.drop_db(destination, db) master_str = "--master={0}".format( self.build_connection_string(master)) slave_str = " --slave={0}".format( self.build_connection_string(destination)) conn_str = master_str + slave_str cmd = "mysqlreplicate.py --rpl-user=rpl:rpl {0}".format(conn_str) try: self.exec_util(cmd, self.res_fname) except MUTLibError: raise # Convert object instance of master server to Master, if needed if not isinstance(master, Master): master = Master.fromServer(master) try: master.connect() except UtilError as err: raise MUTLibError("Cannot connect to spawned " "server: {0}".format(err.errmsg)) # Convert object instance of destination server to Slave, if needed if not isinstance(destination, Slave): destination = Slave.fromServer(destination) try: destination.connect() except UtilError as err: raise MUTLibError("Cannot connect to spawned " "server: {0}".format(err.errmsg)) # Check databases on slave and save results for 'BEFORE' check results.append( self._check_result(destination, "SHOW DATABASES " "LIKE 'util_test'")) results.append( self._check_result(destination, "SELECT COUNT(*) " "FROM util_test.t1")) results.append( self._check_result(destination, "SHOW DATABASES " "LIKE 'master_db1'")) results.append( self._check_result(destination, "SELECT COUNT(*) " "FROM master_db1.t1")) # Run the commands for cmd_str in cmd_list: try: res = self.exec_util(cmd_str + cmd_opts, self.res_fname) results.insert(1, res) # save result at front of list if res != actual_result: return False except MUTLibError: raise # Wait for slave to connect to master if not skip_wait: if self.debug: print "# Waiting for slave to connect to master", try: self.wait_for_slave_connection(destination, _MAX_ATTEMPTS) except MUTLibError: raise if self.debug: print "done." # Check databases on slave and save results for 'AFTER' check results.append( self._check_result(destination, "SHOW DATABASES " "LIKE 'util_test'")) results.append( self._check_result(destination, "SELECT COUNT(*) " "FROM util_test.t1")) results.append( self._check_result(destination, "SHOW DATABASES " "LIKE 'master_db1'")) results.append( self._check_result(destination, "SELECT COUNT(*) " "FROM master_db1.t1")) # Add something to master and check slave master.exec_query("INSERT INTO master_db1.t1 VALUES (10), (11)") # Wait for slave to catch up if not skip_wait: if self.debug: print "# Waiting for slave to sync", bin_info = master.get_binlog_info() if bin_info is None: # server is no longer acting as a master raise MUTLibError("The server '{0}' is no longer a master" "server".format(master.role)) # pylint: disable=W0633 binlog_file, binlog_pos = bin_info # Wait for slave to catch up with master, using the binlog # Note: This test requires servers without GTIDs (prior to 5.6.5) synced = destination.wait_for_slave(binlog_file, binlog_pos, _SYNC_TIMEOUT) if not synced: raise MUTLibError("Slave did not catch up with master") if self.debug: print "done." # ROLLBACK to close any active transaction leading to wrong values for # the next SELECT COUNT(*) with 5.5 servers (versions > 5.5.0). if self.servers.get_server(0).check_version_compat(5, 5, 0): destination.rollback() results.append( self._check_result(destination, "SELECT COUNT(*) " "FROM master_db1.t1")) if self.debug: print comment print "Expected Results:", expected_results[test_num - 1] print " Actual Results:", results[1:] self.results.append(results) return True
def _get_slaves(self, max_depth, seed_conn=None, masters_found=None): """Find the attached slaves for a list of server connections. This method connects to each server in the list and retrieves its slaves. It can be called recursively if the recurse option is True. max_depth[in] Maximum depth of recursive search seed_conn[in] Current master connection dictionary. Initially, this is the seed server (original master defined in constructor) masters_found[in] a list of all servers in master roles - used to detect a circular replication topology. Initially, this is an empty list as the master detection must occur as the topology is traversed. Returns list - list of slaves connected to each server in list """ if not masters_found: masters_found = [] topology = [] if seed_conn is None: seed_conn = self.seed_server master, master_info = self._connect(seed_conn) if master is None: return [] # Check user permissions self._check_permissions(master, "REPLICATION SLAVE") # Save the master for circular replication identification masters_found.append(master_info) if not self.quiet: print "# Finding slaves for master: %s" % master_info # See if the user wants us to discover slaves. discover = self.options.get("discover", None) if discover is None: return # Get user and password (supports login-paths) user, password = parse_user_password(discover, options=self.options) # Get replication topology slaves = master.get_slaves(user, password) slave_list = [] depth = 0 if len(slaves) > 0: for slave in slaves: if slave.find(":") > 0: host, port = slave.split(":", 1) else: host = slave port = _START_PORT # Use the default slave_conn = self.seed_server.copy() slave_conn['host'] = host slave_conn['port'] = port io_sql_running = None # If verbose then get slave threads (IO and SQL) status if self.verbose: # Create slave instance conn_dict = { 'conn_info': {'user': user, 'passwd': password, 'host': host, 'port': port, 'socket': None}, 'role': slave, 'verbose': self.verbose } slave_obj = Slave(conn_dict) # Get IO and SQL status try: slave_obj.connect() thread_status = slave_obj.get_thread_status() if thread_status: io_sql_running = (thread_status[1], thread_status[2]) except UtilError: # Connection error io_sql_running = ('ERROR', 'ERROR') # Now check for circular replication topology - do not recurse # if slave is also a master. if self.recurse and not slave in masters_found and \ ((max_depth is None) or (depth < max_depth)): new_list = self._get_slaves(max_depth, slave_conn, masters_found) if new_list == []: slave_list.append((slave, [], io_sql_running)) else: # Add IO and SQL state to slave from recursion if io_sql_running: new_list = [(new_list[0][0], new_list[0][1], io_sql_running)] slave_list.append(new_list) depth += 1 else: slave_list.append((slave, [], io_sql_running)) topology.append((master_info, slave_list)) return topology