Example #1
0
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
Example #2
0
    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
Example #3
0
    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 _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()
Example #6
0
    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()
Example #9
0
    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 _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 _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