class TestMySQLMaster(tests.utils.TestCase):
    """Unit test for the configuration file handling.
    """
    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(OPTIONS_MASTER["address"])
        OPTIONS_MASTER["uuid"] = _uuid.UUID(uuid)
        self.master = MySQLServer(**OPTIONS_MASTER)
        self.master.connect()
        reset_master(self.master)
        self.master.read_only = True
        self.master.read_only = False

    def tearDown(self):
        """Clean up the existing environment
        """
        cleanup_environment()
        self.master.disconnect()

    def test_master_binary_log(self):
        """Test the get_master_status() function.
        """
        # Note this is only being tested with the binary log.
        master = self.master

        # Get master status.
        check = re.compile('\w+-bin.000001')
        ret = get_master_status(master)
        self.assertNotEqual(check.match(ret[0][0]), None)

        # Reset Master.
        reset_master(master)
        ret = get_master_status(master)
        self.assertTrue(ret[0][1] in (151, 154)) # Format descriptor event.

    def test_master_health(self):
        """Test the check_master_issues() function.
        """
        # Note this is only being tested with the binary log.
        master = self.master

        # Check health as a master before calling connect.
        master.disconnect()
        error, result = check_master_issues(master)
        expected_result = {
            'is_gtid_not_enabled': False,
            'is_slave_updates_not_enabled': False,
            'is_not_running': True,
            'is_binlog_not_enabled': False,
            'no_rpl_user': False
        }
        self.assertEqual(error, True)
        self.assertEqual(result, expected_result)

        # Check health as a master after calling connect.
        master.connect()
        error, _ = check_master_issues(master)
        self.assertEqual(error, False)
 def test_wrong_uuid(self):
     """Test what happens when a server has a wrong uuid.
     """
     # Check wrong uuid.
     OPTIONS["uuid"] = _uuid.UUID("FD0AC9BB-1431-11E2-8137-11DEF124DCC5")
     server = MySQLServer(**OPTIONS)
     self.assertRaises(_errors.UuidError, server.connect)
     server.disconnect()
     ConnectionManager().purge_connections(server)
 def test_wrong_uuid(self):
     """Test what happens when a server has a wrong uuid.
     """
     # Check wrong uuid.
     OPTIONS["uuid"] = _uuid.UUID("FD0AC9BB-1431-11E2-8137-11DEF124DCC5")
     server = MySQLServer(**OPTIONS)
     self.assertRaises(_errors.UuidError, server.connect)
     server.disconnect()
     ConnectionPool().purge_connections(OPTIONS["uuid"])
class TestMySQLMaster(unittest.TestCase):
    """Unit test for the configuration file handling.
    """
    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(OPTIONS_MASTER["address"])
        OPTIONS_MASTER["uuid"] = _uuid.UUID(uuid)
        self.master = MySQLServer(**OPTIONS_MASTER)
        self.master.connect()
        reset_master(self.master)
        self.master.read_only = True
        self.master.read_only = False

    def tearDown(self):
        """Clean up the existing environment
        """
        cleanup_environment()
        self.master.disconnect()

    def test_master_binary_log(self):
        """Test the get_master_status() function.
        """
        # Note this is only being tested with the binary log.
        master = self.master

        # Get master status.
        check = re.compile('\w+-bin.000001')
        ret = get_master_status(master)
        self.assertNotEqual(check.match(ret[0][0]), None)

        # Reset Master.
        reset_master(master)
        ret = get_master_status(master)
        self.assertEqual(int(ret[0][1]), 151) # Format descriptor event.

    def test_master_health(self):
        """Test the check_master_issues() function.
        """
        # Note this is only being tested with the binary log.
        master = self.master

        # Check health as a master before calling connect.
        master.disconnect()
        ret = check_master_issues(master)
        self.assertEqual(ret, {'is_running': False})

        # Check health as a master after calling connect.
        master.connect()
        ret = check_master_issues(master)
        self.assertEqual(ret, {})
Exemple #5
0
def configure_servers(options):
    """Check if some MySQL's addresses were specified and the number is
    greater than NUMBER_OF_SERVERS.
    """
    import tests.utils as _test_utils
    from mysql.fabric.server import (
        MySQLServer,
        ConnectionPool,
    )
    try:
        servers = _test_utils.MySQLInstances()
        servers.state_store_address = "{host}:{port}".format(
            host=options.host, port=options.port
        )
        servers.user = options.db_user
        servers.passwd = None
        servers.root_user = options.user
        servers.root_passwd = options.password
        if options.servers:
            for address in options.servers.split():
                servers.add_address(address)
                uuid = MySQLServer.discover_uuid(
                    address=address, user=servers.root_user,
                    passwd=servers.root_passwd
                )
                server = MySQLServer(
                    _uuid.UUID(uuid), address=address, user=servers.root_user,
                    passwd=servers.root_passwd
                )
                server.connect()
                server.set_session_binlog(False)
                server.exec_stmt(
                    "GRANT {privileges} ON *.* TO '{user}'@'%%'".format(
                    privileges=", ".join(MySQLServer.ALL_PRIVILEGES),
                    user=servers.user)
                )
                server.exec_stmt("FLUSH PRIVILEGES")
                server.set_session_binlog(True)
                server.disconnect()
                ConnectionPool().purge_connections(server.uuid)
        if servers.get_number_addresses() < NUMBER_OF_SERVERS:
            print "<<<<<<<<<< Some unit tests need %s MySQL Instances. " \
              ">>>>>>>>>> " % (NUMBER_OF_SERVERS, )
            return False
    except Exception as error:
        print "Error configuring servers:", error
        return False

    return True
Exemple #6
0
def configure_servers(options, config):
    """Check if some MySQL's addresses were specified and the number is
    greater than NUMBER_OF_SERVERS.
    """
    import tests.utils as _test_utils
    from mysql.fabric.server import (
        MySQLServer,
        ConnectionManager,
    )
    from mysql.fabric.backup import (
        MySQLDump,
    )
    try:
        servers = _test_utils.MySQLInstances()

        # The administrative user as given in --user and --password options.
        # In simple use cases "root" is used.
        servers.user = options.user
        servers.passwd = options.password

        # Backing store - "fabric_store/storepw".
        servers.state_store_address = config.get("storage", "address")
        servers.store_user = config.get("storage", "user")
        servers.store_passwd = config.get("storage", "password")
        servers.store_db = config.get("storage", "database")

        # Server user - "fabric_server/serverpw".
        servers.server_user = config.get("servers", "user")
        servers.server_passwd = config.get("servers", "password")

        # Backup user - "fabric_backup/backuppw".
        servers.backup_user = config.get("servers", "backup_user")
        servers.backup_passwd = config.get("servers", "backup_password")

        # Restore user - "fabric_restore/restorepw".
        servers.restore_user = config.get("servers", "restore_user")
        servers.restore_passwd = config.get("servers", "restore_password")

        # Set up the backing store.
        from mysql.fabric import persistence
        uuid = MySQLServer.discover_uuid(
            address=servers.state_store_address,
            user=servers.user,
            passwd=servers.passwd
        )
        server = MySQLServer(
            _uuid.UUID(uuid), address=servers.state_store_address,
            user=servers.user, passwd=servers.passwd
        )
        server.connect()
        # Precautionary cleanup.
        server.exec_stmt("DROP DATABASE IF EXISTS %s" % (servers.store_db,))
        # Create store user.
        _test_utils.create_test_user(
            server,
            servers.store_user,
            servers.store_passwd,
            [(persistence.required_privileges(),
              "{db}.*".format(db=servers.store_db))]
        )

        # Set up managed servers.
        if options.servers:
            for address in options.servers.split():
                servers.add_address(address)
                uuid = MySQLServer.discover_uuid(
                    address=address,
                    user=servers.user,
                    passwd=servers.passwd
                )
                server = MySQLServer(
                    _uuid.UUID(uuid),
                    address=address,
                    user=servers.user,
                    passwd=servers.passwd
                )
                server.connect()
                server.set_session_binlog(False)
                server.read_only = False

                # Drop user databases
                server.set_foreign_key_checks(False)
                databases = server.exec_stmt("SHOW DATABASES")
                for database in databases:
                    if database[0] not in MySQLServer.NO_USER_DATABASES:
                        server.exec_stmt("DROP DATABASE IF EXISTS %s" %
                                         (database[0],))
                server.set_foreign_key_checks(True)

                # Create server user.
                _test_utils.create_test_user(
                    server,
                    servers.server_user,
                    servers.server_passwd,
                    [(MySQLServer.SERVER_PRIVILEGES, "*.*"),
                     (MySQLServer.SERVER_PRIVILEGES_DB, "mysql_fabric.*")]
                )

                # Create backup user.
                _test_utils.create_test_user(
                    server,
                    servers.backup_user,
                    servers.backup_passwd,
                    [(MySQLDump.BACKUP_PRIVILEGES, "*.*")]
                )

                # Create restore user.
                _test_utils.create_test_user(
                    server,
                    servers.restore_user,
                    servers.restore_passwd,
                    [(MySQLDump.RESTORE_PRIVILEGES, "*.*")]
                )

                server.set_session_binlog(True)
                server.disconnect()
                ConnectionManager().purge_connections(server)
        if servers.get_number_addresses() < NUMBER_OF_SERVERS:
            sys.stderr.write(
                "<<<<<<<<<< Some unit tests need {0} MySQL " \
                "Instances. >>>>>>>>>>\n".format(NUMBER_OF_SERVERS)
            )
            return False
    except Exception as error:
        sys.stderr.write(
            "Error configuring servers: {0}\n".format(str(error))
        )
        import traceback
        traceback.print_exc()
        return False

    return True
class TestErrorLog(unittest.TestCase):
    """Unit test for testing ErrorLog.
    """
    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(
            tests.utils.MySQLInstances().get_address(0)
        )
        self.server = MySQLServer(_uuid.UUID(uuid),
            tests.utils.MySQLInstances().get_address(0)
        )
        MySQLServer.add(self.server)

    def tearDown(self):
        """Clean up the existing environment
        """
        tests.utils.cleanup_environment()
        self.server.disconnect()
        MySQLServer.remove(self.server)

    def test_init(self):
        """Test basic properties/methods in the ErrorLog.
        """
        # Check that the input parameters are not changed.
        interval = get_time_delta(1)
        now = get_time()
        input_whens = [ 30, 40 ]
        input_reporters = [ "reporter", "reporter" ]
        st = ErrorLog(self.server, interval, now, input_whens,
                               input_reporters)
        self.assertEqual(st.server_uuid, self.server.uuid)
        self.assertEqual(st.interval, interval)
        self.assertEqual(st.now, now)
        self.assertEqual(st.whens, input_whens)
        self.assertEqual(st.reporters, input_reporters)

        # If whens and reporters don't have the same length, an exception is
        # raised
        interval = get_time_delta(1)
        now = get_time()
        input_whens = [ 0, 0, 0, 0 ]
        input_reporters = []
        self.assertRaises(AssertionError, ErrorLog,
                          self.server, interval, now, input_whens,
                          input_reporters)

    def test_persistence(self):
        """Test ErrorLog.
        """
        # Update/Notify and fetch, they should match.
        interval = get_time_delta(1)
        now = get_time()
        input_whens = [ now, now ]
        input_reporters = [ "client:1000", "client:2000" ]
        st = ErrorLog(self.server, interval, now, input_whens,
                               input_reporters)
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now, "client:2000", "error")
        new_st = ErrorLog.fetch(self.server, interval, now)
        self.assertEqual(st.reporters, new_st.reporters)
        self.assertEqual(st.whens, new_st.whens)

        # Call remove, they should be empty and match.
        interval = get_time_delta(1)
        now = get_time()
        input_whens = [ ]
        input_reporters = [ ]
        ErrorLog.remove(self.server)
        st = ErrorLog(self.server, interval, now, input_whens,
                               input_reporters)
        new_st = ErrorLog.fetch(self.server, interval, now)
        self.assertEqual(st.reporters, new_st.reporters)
        self.assertEqual(st.whens, new_st.whens)

        # Update/Notify and refresh, they should match.
        interval = get_time_delta(10)
        now = get_time()
        input_whens = [ now, now - get_time_delta(5) ]
        input_reporters = [ "client:1000", "client:2000" ]
        st = ErrorLog(self.server, interval, now, [], [])
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now - get_time_delta(5),
                            "client:2000", "error")
        ErrorLog.add(self.server, now - get_time_delta(11),
                            "client:3000", "error")
        st.refresh()
        self.assertEqual(set(st.reporters), set(input_reporters))
        self.assertEqual(set(st.whens), set(input_whens))

        # Check whether a statement similar to the one used in the
        # event is fine.
        ErrorLog.remove(self.server)
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now, "client:2000", "error")
        persister = _persistence.current_persister()
        out = persister.exec_stmt(
            "SELECT reported, UTC_TIMESTAMP() as now, "
            "TIMEDIFF(UTC_TIMESTAMP(), reported - MAKETIME(2,0,0)) as diff "
            "FROM error_log"
        )
        _LOGGER.debug("Output test persistence %s.", out)
        self.assertEqual(len(out), 2)
        res = persister.exec_stmt(
            "DELETE FROM error_log WHERE "
            "TIMEDIFF(UTC_TIMESTAMP(), reported - MAKETIME(2,0,0)) > "
            "MAKETIME(1,0,0)"
        )
        _LOGGER.debug("Output test persistence %s.", res)
        out = persister.exec_stmt(
            "SELECT reported, UTC_TIMESTAMP() as now, "
            "TIMEDIFF(UTC_TIMESTAMP(), reported - MAKETIME(2,0,0)) as diff "
            "FROM error_log"
        )
        _LOGGER.debug("Output test persistence %s.", out)
        self.assertEqual(len(out), 0)

    def test_check_instability(self):
        """Test whether a server can be considered unstable or not.
        """
        # Update/Notify and refresh, they should match.
        interval = get_time_delta(10)
        now = get_time()
        input_whens = [ now, now - get_time_delta(5) ]
        input_reporters = [ "client:1000", "client:2000" ]
        st = ErrorLog(self.server, interval, now, [], [])
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now - get_time_delta(5),
                            "client:2000", "error")
        ErrorLog.add(self.server, now - get_time_delta(11),
                            "client:3000", "error")
        st.refresh()
        self.assertEqual(
            st.is_unstable(n_notifications=1, n_reporters=1,
                           filter_reporter=None),
            True
        )
        self.assertEqual(
            st.is_unstable(n_notifications=2, n_reporters=2,
                           filter_reporter=None),
            True
        )
        self.assertEqual(
            st.is_unstable(n_notifications=3, n_reporters=2,
                           filter_reporter=None),
            False
        )
        self.assertEqual(
            st.is_unstable(n_notifications=2, n_reporters=3,
                           filter_reporter=None),
            False
        )
        self.assertEqual(
            st.is_unstable(n_notifications=1, n_reporters=1,
                           filter_reporter=["client:2000"]),
            True
        )
class TestMySQLServer(unittest.TestCase):
    """Unit test for testing MySQLServer.
    """
    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        OPTIONS["uuid"] = _uuid.UUID(uuid)
        self.server = MySQLServer(**OPTIONS)
        MySQLServer.add(self.server)

    def tearDown(self):
        """Clean up the existing environment
        """
        tests.utils.cleanup_environment()
        self.server.disconnect()
        MySQLServer.remove(self.server)

    def test_wrong_uuid(self):
        """Test what happens when a server has a wrong uuid.
        """
        # Check wrong uuid.
        OPTIONS["uuid"] = _uuid.UUID("FD0AC9BB-1431-11E2-8137-11DEF124DCC5")
        server = MySQLServer(**OPTIONS)
        self.assertRaises(_errors.UuidError, server.connect)
        server.disconnect()
        ConnectionManager().purge_connections(server)

    def test_properties(self):
        """Test setting MySQLServer's properties.
        """
        server = self.server

        # Check property user.
        self.assertEqual(server.user, tests.utils.MySQLInstances().user)
        server.user = "******"
        self.assertEqual(server.user, "user")
        server.user = tests.utils.MySQLInstances().user

        # Check property passwd.
        self.assertEqual(server.passwd, tests.utils.MySQLInstances().passwd)
        server.passwd = "passwd"
        self.assertEqual(server.passwd, "passwd")
        server.passwd = tests.utils.MySQLInstances().passwd

        # Check property status.
        self.assertEqual(server.status, MySQLServer.SECONDARY)
        server.status = MySQLServer.FAULTY
        self.assertEqual(server.status, MySQLServer.FAULTY)
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.status, fetched_server.status)
        server.status = MySQLServer.SECONDARY
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.status, fetched_server.status)

        # Check property mode.
        self.assertEqual(server.mode, MySQLServer.READ_ONLY)
        server.mode = MySQLServer.OFFLINE
        self.assertEqual(server.mode, MySQLServer.OFFLINE)
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.mode, fetched_server.mode)
        server.mode = MySQLServer.READ_ONLY
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.mode, fetched_server.mode)

        # Check property weight.
        self.assertEqual(server.weight, MySQLServer.DEFAULT_WEIGHT)
        server.weight = 0.1
        self.assertEqual(server.weight, 0.1)
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.weight, fetched_server.weight)
        server.weight = MySQLServer.DEFAULT_WEIGHT
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.weight, fetched_server.weight)

        # Create instance without connecting it with a server.
        self.assertEqual(server.read_only, None)
        self.assertEqual(server.server_id, None)
        self.assertEqual(server.gtid_enabled, None)
        self.assertEqual(server.binlog_enabled, None)
        self.assertEqual(server.version, None)

        # Bind instance to a server.
        server.connect()
        self.assertNotEqual(server.read_only, None)
        self.assertNotEqual(server.server_id, 0)
        self.assertEqual(server.gtid_enabled, True)
        self.assertEqual(server.binlog_enabled, True)

        # Check read_only property.
        server.read_only = True
        self.assertEqual(server.read_only, True)
        server.read_only = False
        self.assertEqual(server.read_only, False)

    def test_address(self):
        """Check whether the address is automatically converted to
        the format host:port where port is the MySQL default port
        if a port is not provided.
        """
        server = MySQLServer(_uuid.uuid4(), "address")
        default_port = _server_utils.MYSQL_DEFAULT_PORT
        self.assertEqual(server.address,
                         "{0}:{1}".format("address", default_port))

        server = MySQLServer(_uuid.uuid4(), "address:port")
        self.assertEqual(server.address, "{0}:{1}".format("address", "port"))

    def test_version(self):
        """Check MySQLServer's version.
        """
        server = self.server
        server.connect()

        # Check version.
        self.assertFalse(server.check_version_compat((7, 0, 0)))
        self.assertFalse(server.check_version_compat((6, 0, 0)))
        # Note this function needs to be updated to update every
        # time a new release is created.

    def test_gtid(self):
        """Check MySQLServer's GTIDs.
        """
        server = self.server
        server.connect()

        for record in server.get_gtid_status():
            self.assertEqual(record.GTID_EXECUTED, "")
            self.assertEqual(record.GTID_PURGED, "")
            self.assertEqual(record.GTID_OWNED, "")
        # Note this is only being tested with GTIDs.

    def test_kill_connections(self):
        """Check that the kill method successfully kills existing connections
            on the server.
        """
        #check that the current connection is not terminated.
        server = self.server
        server.connect()
        server.kill_processes(server.processes())
        self.assertTrue(server.has_storage_engine("Innodb"))
        #check that the only connection remaining is the current
        #connection
        server.kill_processes(server.processes(True, False))
        self.assertEqual(len(server.processes(True, True)), 1)

    def test_storage(self):
        """Check MySQLServer's storage.
        """
        server = self.server
        server.connect()
        self.assertTrue(server.has_storage_engine("Innodb"))
        self.assertTrue(server.has_storage_engine(""))
        self.assertFalse(server.has_storage_engine("Unknown"))

    def test_session_properties(self):
        """Test some session's properties.
        """
        server = self.server
        server.connect()

        server.set_session_binlog(False)
        self.assertFalse(server.session_binlog_enabled())
        server.set_session_binlog(True)
        self.assertTrue(server.session_binlog_enabled())

        server.set_foreign_key_checks(False)
        self.assertFalse(server.foreign_key_checks_enabled())
        server.set_foreign_key_checks(True)
        self.assertTrue(server.foreign_key_checks_enabled())

    def test_binlog(self):
        """Test binary logging is supported.
        """
        server = self.server
        server.connect()

        check = re.compile('\w+-bin.000001')
        for record in server.get_binary_logs():
            self.assertNotEqual(check.match(record.Log_name), None)
        # Note this is only being tested with the binary log.

    def test_exec_stmt_options(self):
        """Test statement's execution.
        """
        server = self.server
        server.connect()

        # Populate testing tables.
        server.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        server.exec_stmt("USE test")
        server.exec_stmt("DROP TABLE IF EXISTS test_1")
        server.exec_stmt("CREATE TABLE test_1(id INTEGER)")
        server.exec_stmt("DROP TABLE IF EXISTS test_2")
        server.exec_stmt("CREATE TABLE test_2(id INTEGER)")
        for cont in range(1, 10):
            server.exec_stmt("INSERT INTO test_1 VALUES(%s)",
                             {"params": (cont, )})

        # Test raw: True fetch : True
        ret = server.exec_stmt("SELECT COUNT(*) FROM test_1", {
            "raw": True,
            "fetch": True
        })
        self.assertEqual(int(ret[0][0]), 9)

        # Test raw: False fetch : True
        ret = server.exec_stmt("SELECT COUNT(*) FROM test_1", {
            "raw": False,
            "fetch": True
        })
        self.assertEqual(ret[0][0], 9)

        # Test raw: False fetch : False
        cursor = server.exec_stmt("SELECT COUNT(*) FROM test_1", {
            "raw": False,
            "fetch": False
        })
        ret = cursor.fetchone()
        self.assertEqual(ret[0], 9)

        # Test raw: True fetch : False
        cursor = server.exec_stmt("SELECT COUNT(*) FROM test_1", {
            "raw": False,
            "fetch": False
        })
        ret = cursor.fetchone()
        self.assertEqual(int(ret[0]), 9)

        # Nothing to be fetched.
        ret = server.exec_stmt("SELECT * FROM test_2")
        self.assertEqual(ret, [])

        # Unknown table.
        self.assertRaises(_errors.DatabaseError, server.exec_stmt,
                          "SELECT * FROM test_3")

        # Test option columns
        ret = server.exec_stmt("SELECT COUNT(*) COUNT FROM test_1",
                               {"columns": True})
        self.assertEqual(int(ret[0][0]), 9)
        self.assertEqual(int(ret[0].COUNT), 9)

    def test_is_connected(self):
        """Check whether MySQLServer is alive or not.
        """
        # Check if server is alive.
        server = self.server
        self.assertTrue(MySQLServer.is_alive(server))
        self.assertFalse(server.is_connected())
        server.connect()
        self.assertTrue(server.is_connected())

    def test_utilities(self):
        """Check MySQLServer's utilities module.
        """
        # Test a function that gets host and port and returns
        # host:port
        address = _server_utils.combine_host_port(None, None, 3306)
        self.assertEqual(address, "unknown host:3306")

        address = _server_utils.combine_host_port("", None, 3306)
        self.assertEqual(address, "unknown host:3306")

        address = _server_utils.combine_host_port(None, "", 3306)
        self.assertEqual(address, "unknown host:3306")

        address = _server_utils.combine_host_port("host", "port", 3306)
        self.assertEqual(address, "host:port")

        address = _server_utils.combine_host_port("host", 1500, 3306)
        self.assertEqual(address, "host:1500")

        address = _server_utils.combine_host_port("127.0.0.1", 1500, 3306)
        self.assertEqual(address, "localhost:1500")

        # Test a function that gets host:port and returns (host, port)
        host_port = _server_utils.split_host_port("", 3306)
        self.assertEqual(host_port, ("", 3306))

        host_port = _server_utils.split_host_port(":", 3306)
        self.assertEqual(host_port, ("", ""))

        host_port = _server_utils.split_host_port("host:", 3306)
        self.assertEqual(host_port, ("host", ""))

        host_port = _server_utils.split_host_port(":port", 3306)
        self.assertEqual(host_port, ("", "port"))

        host_port = _server_utils.split_host_port("host:port", 3306)
        self.assertEqual(host_port, ("host", "port"))

    def test_server_id(self):
        """Test MySQLServer's uuid.
        """
        # Configure
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        OPTIONS["uuid"] = _uuid.UUID(uuid)
        server_1 = MySQLServer(**OPTIONS)
        server_2 = MySQLServer(**OPTIONS)

        # Check that two different objects represent the same server.
        self.assertEqual(server_1, server_2)

        # Check that a dictionary with server_1 and server_2 has in
        # fact only one entry.
        hash_info = {}
        hash_info[server_1] = server_1
        hash_info[server_2] = server_2
        self.assertEqual(len(hash_info), 1)

    def test_persister_id(self):
        """Test Persister's uuid.
        """
        # Get persister'a address.
        instances = tests.utils.MySQLInstances()
        address = instances.state_store_address
        user = instances.store_user
        passwd = instances.store_passwd

        # Try to manage the MySQLPersister.
        uuid = MySQLServer.discover_uuid(address=address,
                                         user=user,
                                         passwd=passwd)
        server = MySQLServer(_uuid.UUID(uuid), address, user, passwd)
        self.assertRaises(_errors.ServerError, MySQLServer.add, server)

    def test_privileges(self):
        """Test whether user's have the appropriate privileges.
        """
        # Some privileges
        MINIMUM_PRIVILEGES = [
            "REPLICATION SLAVE", "REPLICATION CLIENT", "SUPER",
            "SHOW DATABASES", "RELOAD"
        ]

        # Connect to server as admin and create temporary user.
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        server = MySQLServer(_uuid.UUID(uuid), OPTIONS["address"],
                             tests.utils.MySQLInstances().user,
                             tests.utils.MySQLInstances().passwd)
        ConnectionManager().purge_connections(server)
        server.connect()
        server.set_session_binlog(False)
        server.exec_stmt("CREATE USER 'jeffrey'@'%%' IDENTIFIED BY 'mypass'")

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # There is not privilege associate to jeffrey.
        new_server = MySQLServer(_uuid.UUID(uuid), OPTIONS["address"],
                                 "jeffrey", "mypass")
        new_server.connect()
        self.assertFalse(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Grant required privileges except RELOAD
        # There is no RELOAD on a global level.
        privileges = ", ".join(
            [priv for priv in MINIMUM_PRIVILEGES if priv != "RELOAD"])
        server.exec_stmt("GRANT {privileges} ON *.* TO 'jeffrey'@'%%'".format(
            privileges=privileges))
        self.assertFalse(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # The RELOAD on a global level was granted.
        server.exec_stmt("GRANT RELOAD ON *.* TO 'jeffrey'@'%%'")
        self.assertTrue(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Revoke privilegs from temporary user.
        # There is no ALL on a global level.
        server.exec_stmt("REVOKE ALL PRIVILEGES, GRANT OPTION FROM "
                         "'jeffrey'@'%%'")
        server.exec_stmt("GRANT ALL ON fabric.* TO 'jeffrey'@'%%'")
        self.assertFalse(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Grant all privileges, which the administrative user has to have.
        server.exec_stmt("GRANT"
                         " ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE,"
                         " CREATE TEMPORARY TABLES, CREATE USER,"
                         " CREATE VIEW, DELETE, DROP, EVENT, EXECUTE,"
                         " GRANT OPTION, INDEX, INSERT, LOCK TABLES, PROCESS, "
                         " RELOAD, REPLICATION CLIENT, REPLICATION SLAVE,"
                         " SELECT, SHOW DATABASES, SHOW VIEW, SHUTDOWN,"
                         " SUPER, TRIGGER, UPDATE"
                         " ON *.* TO 'jeffrey'@'%%'")
        self.assertTrue(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Drop temporary user.
        server.exec_stmt("DROP USER 'jeffrey'@'%%'")
        server.set_session_binlog(True)
        server.disconnect()
        new_server.disconnect()
        ConnectionManager().purge_connections(server)

    def test_max_connections(self):
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        server = MySQLServer(
            _uuid.UUID(uuid),
            OPTIONS["address"],
        )
        server.connect()
        res = server.get_variable("max_connections")
        self.assertNotEqual(int(res), 0)
    def test_privileges(self):
        """Test whether user's have the appropriate privileges.
        """
        # Some privileges
        MINIMUM_PRIVILEGES = [
            "REPLICATION SLAVE", "REPLICATION CLIENT", "SUPER",
            "SHOW DATABASES", "RELOAD"
        ]

        # Connect to server as admin and create temporary user.
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        server = MySQLServer(_uuid.UUID(uuid), OPTIONS["address"],
                             tests.utils.MySQLInstances().user,
                             tests.utils.MySQLInstances().passwd)
        ConnectionManager().purge_connections(server)
        server.connect()
        server.set_session_binlog(False)
        server.exec_stmt("CREATE USER 'jeffrey'@'%%' IDENTIFIED BY 'mypass'")

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # There is not privilege associate to jeffrey.
        new_server = MySQLServer(_uuid.UUID(uuid), OPTIONS["address"],
                                 "jeffrey", "mypass")
        new_server.connect()
        self.assertFalse(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Grant required privileges except RELOAD
        # There is no RELOAD on a global level.
        privileges = ", ".join(
            [priv for priv in MINIMUM_PRIVILEGES if priv != "RELOAD"])
        server.exec_stmt("GRANT {privileges} ON *.* TO 'jeffrey'@'%%'".format(
            privileges=privileges))
        self.assertFalse(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # The RELOAD on a global level was granted.
        server.exec_stmt("GRANT RELOAD ON *.* TO 'jeffrey'@'%%'")
        self.assertTrue(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Revoke privilegs from temporary user.
        # There is no ALL on a global level.
        server.exec_stmt("REVOKE ALL PRIVILEGES, GRANT OPTION FROM "
                         "'jeffrey'@'%%'")
        server.exec_stmt("GRANT ALL ON fabric.* TO 'jeffrey'@'%%'")
        self.assertFalse(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Grant all privileges, which the administrative user has to have.
        server.exec_stmt("GRANT"
                         " ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE,"
                         " CREATE TEMPORARY TABLES, CREATE USER,"
                         " CREATE VIEW, DELETE, DROP, EVENT, EXECUTE,"
                         " GRANT OPTION, INDEX, INSERT, LOCK TABLES, PROCESS, "
                         " RELOAD, REPLICATION CLIENT, REPLICATION SLAVE,"
                         " SELECT, SHOW DATABASES, SHOW VIEW, SHUTDOWN,"
                         " SUPER, TRIGGER, UPDATE"
                         " ON *.* TO 'jeffrey'@'%%'")
        self.assertTrue(new_server.has_privileges(MINIMUM_PRIVILEGES))

        # Drop temporary user.
        server.exec_stmt("DROP USER 'jeffrey'@'%%'")
        server.set_session_binlog(True)
        server.disconnect()
        new_server.disconnect()
        ConnectionManager().purge_connections(server)
class TestMySQLServer(unittest.TestCase):
    """Unit test for testing MySQLServer.
    """
    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        OPTIONS["uuid"] = _uuid.UUID(uuid)
        self.server = MySQLServer(**OPTIONS)
        MySQLServer.add(self.server)

    def tearDown(self):
        """Clean up the existing environment
        """
        tests.utils.cleanup_environment()
        self.server.disconnect()
        MySQLServer.remove(self.server)

    def test_wrong_uuid(self):
        """Test what happens when a server has a wrong uuid.
        """
        # Check wrong uuid.
        OPTIONS["uuid"] = _uuid.UUID("FD0AC9BB-1431-11E2-8137-11DEF124DCC5")
        server = MySQLServer(**OPTIONS)
        self.assertRaises(_errors.UuidError, server.connect)
        server.disconnect()
        ConnectionPool().purge_connections(OPTIONS["uuid"])

    def test_properties(self):
        """Test setting MySQLServer's properties.
        """
        server = self.server

        # Check property user.
        self.assertEqual(server.user, tests.utils.MySQLInstances().user)
        server.user = "******"
        self.assertEqual(server.user, "user")
        server.user = tests.utils.MySQLInstances().user

        # Check property passwd.
        self.assertEqual(server.passwd, tests.utils.MySQLInstances().passwd)
        server.passwd = "passwd"
        self.assertEqual(server.passwd, "passwd")
        server.passwd = tests.utils.MySQLInstances().passwd

        # Check property status.
        self.assertEqual(server.status, MySQLServer.SECONDARY)
        server.status = MySQLServer.FAULTY
        self.assertEqual(server.status, MySQLServer.FAULTY)
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.status, fetched_server.status)
        server.status = MySQLServer.SECONDARY
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.status, fetched_server.status)

        # Check property mode.
        self.assertEqual(server.mode, MySQLServer.READ_ONLY)
        server.mode = MySQLServer.OFFLINE
        self.assertEqual(server.mode, MySQLServer.OFFLINE)
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.mode, fetched_server.mode)
        server.mode = MySQLServer.READ_ONLY
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.mode, fetched_server.mode)

        # Check property weight.
        self.assertEqual(server.weight, MySQLServer.DEFAULT_WEIGHT)
        server.weight = 0.1
        self.assertEqual(server.weight, 0.1)
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.weight, fetched_server.weight)
        server.weight = MySQLServer.DEFAULT_WEIGHT
        fetched_server = MySQLServer.fetch(server.uuid)
        self.assertEqual(server.weight, fetched_server.weight)

        # Create instance without connecting it with a server.
        self.assertEqual(server.read_only, None)
        self.assertEqual(server.server_id, None)
        self.assertEqual(server.gtid_enabled, None)
        self.assertEqual(server.binlog_enabled, None)
        self.assertEqual(server.version, None)

        # Bind instance to a server.
        server.connect()
        self.assertNotEqual(server.read_only, None)
        self.assertNotEqual(server.server_id, 0)
        self.assertEqual(server.gtid_enabled, True)
        self.assertEqual(server.binlog_enabled, True)

        # Check read_only property.
        server.read_only = True
        self.assertEqual(server.read_only, True)
        server.read_only = False
        self.assertEqual(server.read_only, False)

    def test_version(self):
        """Check MySQLServer's version.
        """
        server = self.server
        server.connect()

        # Check version.
        self.assertFalse(server.check_version_compat((7, 0, 0)))
        self.assertFalse(server.check_version_compat((6, 0, 0)))
        # Note this function needs to be updated to update every
        # time a new release is created.

    def test_gtid(self):
        """Check MySQLServer's GTIDs.
        """
        server = self.server
        server.connect()

        for record in server.get_gtid_status():
            self.assertEqual(record.GTID_EXECUTED, "")
            self.assertEqual(record.GTID_PURGED, "")
            self.assertEqual(record.GTID_OWNED, "")
        # Note this is only being tested with GTIDs.


    def test_storage(self):
        """Check MySQLServer's storage.
        """
        server = self.server
        server.connect()
        self.assertTrue(server.has_storage_engine("Innodb"))
        self.assertTrue(server.has_storage_engine(""))
        self.assertFalse(server.has_storage_engine("Unknown"))

    def test_session_properties(self):
        """Test some session's properties.
        """
        server = self.server
        server.connect()

        server.set_session_binlog(False)
        self.assertFalse(server.session_binlog_enabled())
        server.set_session_binlog(True)
        self.assertTrue(server.session_binlog_enabled())

        server.set_foreign_key_checks(False)
        self.assertFalse(server.foreign_key_checks_enabled())
        server.set_foreign_key_checks(True)
        self.assertTrue(server.foreign_key_checks_enabled())

    def test_binlog(self):
        """Test binary logging is supported.
        """
        server = self.server
        server.connect()

        check = re.compile('\w+-bin.000001')
        for record in server.get_binary_logs():
            self.assertNotEqual(check.match(record.Log_name), None)
        # Note this is only being tested with the binary log.

    def test_exec_stmt_options(self):
        """Test statement's execution.
        """
        server = self.server
        server.connect()

        # Populate testing tables.
        server.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        server.exec_stmt("USE test")
        server.exec_stmt("DROP TABLE IF EXISTS test_1")
        server.exec_stmt("CREATE TABLE test_1(id INTEGER)")
        server.exec_stmt("DROP TABLE IF EXISTS test_2")
        server.exec_stmt("CREATE TABLE test_2(id INTEGER)")
        for cont in range(1, 10):
            server.exec_stmt("INSERT INTO test_1 VALUES(%s)",
                             {"params" : (cont,)})

        # Test raw: True fetch : True
        ret = server.exec_stmt("SELECT COUNT(*) FROM test_1",
                               {"raw" : True, "fetch" : True})
        self.assertEqual(int(ret[0][0]), 9)

        # Test raw: False fetch : True
        ret = server.exec_stmt("SELECT COUNT(*) FROM test_1",
                               {"raw" : False, "fetch" : True})
        self.assertEqual(ret[0][0], 9)

        # Test raw: False fetch : False
        cursor = server.exec_stmt("SELECT COUNT(*) FROM test_1",
                                  {"raw" : False, "fetch" : False})
        ret = cursor.fetchone()
        self.assertEqual(ret[0], 9)

        # Test raw: True fetch : False
        cursor = server.exec_stmt("SELECT COUNT(*) FROM test_1",
                                  {"raw" : False, "fetch" : False})
        ret = cursor.fetchone()
        self.assertEqual(int(ret[0]), 9)

        # Nothing to be fetched.
        ret = server.exec_stmt("SELECT * FROM test_2")
        self.assertEqual(ret, [])

        # Unknown table.
        self.assertRaises(_errors.DatabaseError, server.exec_stmt,
                          "SELECT * FROM test_3")

        # Test option columns
        ret = server.exec_stmt("SELECT COUNT(*) COUNT FROM test_1",
                               {"columns" : True})
        self.assertEqual(int(ret[0][0]), 9)
        self.assertEqual(int(ret[0].COUNT), 9)

    def test_is_connected(self):
        """Check whether MySQLServer is alive or not.
        """
        # Check if server is alive.
        server = self.server
        self.assertTrue(server.is_alive())
        self.assertFalse(server.is_connected())
        server.connect()
        self.assertTrue(server.is_connected())

    def test_utilities(self):
        """Check MySQLServer's utilities module.
        """
        # Test a function that gets host and port and returns
        # host:port
        address = _server_utils.combine_host_port(None, None, 3306)
        self.assertEqual(address, "unknown host:3306")

        address = _server_utils.combine_host_port("", None, 3306)
        self.assertEqual(address, "unknown host:3306")

        address = _server_utils.combine_host_port(None, "", 3306)
        self.assertEqual(address, "unknown host:3306")

        address = _server_utils.combine_host_port("host", "port", 3306)
        self.assertEqual(address, "host:port")

        address = _server_utils.combine_host_port("host", 1500, 3306)
        self.assertEqual(address, "host:1500")

        address = _server_utils.combine_host_port("127.0.0.1", 1500, 3306)
        self.assertEqual(address, "localhost:1500")

        # Test a function that gets host:port and returns (host, port)
        host_port = _server_utils.split_host_port("", 3306)
        self.assertEqual(host_port, ("", 3306))

        host_port = _server_utils.split_host_port(":", 3306)
        self.assertEqual(host_port, ("", ""))

        host_port = _server_utils.split_host_port("host:", 3306)
        self.assertEqual(host_port, ("host", ""))

        host_port = _server_utils.split_host_port(":port", 3306)
        self.assertEqual(host_port, ("", "port"))

        host_port = _server_utils.split_host_port("host:port", 3306)
        self.assertEqual(host_port, ("host", "port"))

    def test_server_id(self):
        """Test MySQLServer's uuid.
        """
        # Configure
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        OPTIONS["uuid"] = _uuid.UUID(uuid)
        server_1 = MySQLServer(**OPTIONS)
        server_2 = MySQLServer(**OPTIONS)

        # Check that two different objects represent the same server.
        self.assertEqual(server_1, server_2)

        # Check that a dictionary with server_1 and server_2 has in
        # fact only one entry.
        hash_info = {}
        hash_info[server_1] = server_1
        hash_info[server_2] = server_2
        self.assertEqual(len(hash_info), 1)

    def test_persister_id(self):
        """Test Persister's uuid.
        """
        # Get persister'a address.
        instances = tests.utils.MySQLInstances()
        address = instances.state_store_address
        user = instances.root_user
        passwd = instances.root_passwd

        # Try to manage the MySQLPersister.
        uuid = MySQLServer.discover_uuid(
            address=address, user=user, passwd=passwd
        )
        server = MySQLServer(_uuid.UUID(uuid), address, user, passwd)
        self.assertRaises(_errors.ServerError, MySQLServer.add, server)

    def test_privileges(self):
        """Test whether user's have the appropriate privileges.
        """
        # Some privileges
        MINIMUM_PRIVILEGES = [
            "REPLICATION SLAVE", "REPLICATION CLIENT", "SUPER",
            "SHOW DATABASES", "RELOAD"
        ]

        # Connect to server as root and create temporary user.
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        server = MySQLServer(
            _uuid.UUID(uuid), OPTIONS["address"],
            tests.utils.MySQLInstances().root_user,
            tests.utils.MySQLInstances().root_passwd
        )
        ConnectionPool().purge_connections(_uuid.UUID(uuid))
        server.connect()
        server.set_session_binlog(False)
        server.exec_stmt(
            "CREATE USER 'jeffrey'@'%%' IDENTIFIED BY 'mypass'"
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # There is not privilege associate to jeffrey.
        new_server = MySQLServer(
            _uuid.UUID(uuid), OPTIONS["address"], "jeffrey", "mypass"
        )
        new_server.connect()
        self.assertFalse(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Grant required privileges except RELOAD
        # There is no RELOAD on a global level.
        privileges=", ".join([priv for priv in MINIMUM_PRIVILEGES
             if priv != "RELOAD"]
        )
        server.exec_stmt(
            "GRANT {privileges} ON *.* TO 'jeffrey'@'%%'".format(
            privileges=privileges)
        )
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertFalse(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # The RELOAD on a global level was granted.
        server.exec_stmt("GRANT RELOAD ON *.* TO 'jeffrey'@'%%'")
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertTrue(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Revoke privilegs from temporary user.
        # There is no ALL on a global level.
        server.exec_stmt("REVOKE ALL PRIVILEGES, GRANT OPTION FROM "
                         "'jeffrey'@'%%'"
        )
        server.exec_stmt("GRANT ALL ON fabric.* TO 'jeffrey'@'%%'")
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertFalse(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # The ALL on a global level was granted.
        server.exec_stmt("GRANT ALL ON *.* TO 'jeffrey'@'%%'")
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertTrue(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Drop temporary user.
        server.exec_stmt("DROP USER 'jeffrey'@'%%'")
        server.set_session_binlog(True)
        server.disconnect()
        new_server.disconnect()
        ConnectionPool().purge_connections(_uuid.UUID(uuid))

    def test_max_connections(self):
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        server = MySQLServer(
            _uuid.UUID(uuid), OPTIONS["address"],
        )
        server.connect()
        res = server.get_variable("max_connections")
        self.assertNotEqual(int(res), 0)
    def test_privileges(self):
        """Test whether user's have the appropriate privileges.
        """
        # Some privileges
        MINIMUM_PRIVILEGES = [
            "REPLICATION SLAVE", "REPLICATION CLIENT", "SUPER",
            "SHOW DATABASES", "RELOAD"
        ]

        # Connect to server as root and create temporary user.
        uuid = MySQLServer.discover_uuid(OPTIONS["address"])
        server = MySQLServer(
            _uuid.UUID(uuid), OPTIONS["address"],
            tests.utils.MySQLInstances().root_user,
            tests.utils.MySQLInstances().root_passwd
        )
        ConnectionPool().purge_connections(_uuid.UUID(uuid))
        server.connect()
        server.set_session_binlog(False)
        server.exec_stmt(
            "CREATE USER 'jeffrey'@'%%' IDENTIFIED BY 'mypass'"
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # There is not privilege associate to jeffrey.
        new_server = MySQLServer(
            _uuid.UUID(uuid), OPTIONS["address"], "jeffrey", "mypass"
        )
        new_server.connect()
        self.assertFalse(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Grant required privileges except RELOAD
        # There is no RELOAD on a global level.
        privileges=", ".join([priv for priv in MINIMUM_PRIVILEGES
             if priv != "RELOAD"]
        )
        server.exec_stmt(
            "GRANT {privileges} ON *.* TO 'jeffrey'@'%%'".format(
            privileges=privileges)
        )
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertFalse(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # The RELOAD on a global level was granted.
        server.exec_stmt("GRANT RELOAD ON *.* TO 'jeffrey'@'%%'")
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertTrue(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # Revoke privilegs from temporary user.
        # There is no ALL on a global level.
        server.exec_stmt("REVOKE ALL PRIVILEGES, GRANT OPTION FROM "
                         "'jeffrey'@'%%'"
        )
        server.exec_stmt("GRANT ALL ON fabric.* TO 'jeffrey'@'%%'")
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertFalse(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Check if jeffrey (temporary user) has the appropriate privileges.
        # The ALL on a global level was granted.
        server.exec_stmt("GRANT ALL ON *.* TO 'jeffrey'@'%%'")
        server.exec_stmt("FLUSH PRIVILEGES")
        self.assertTrue(
            new_server.has_privileges(MINIMUM_PRIVILEGES)
        )

        # Drop temporary user.
        server.exec_stmt("DROP USER 'jeffrey'@'%%'")
        server.set_session_binlog(True)
        server.disconnect()
        new_server.disconnect()
        ConnectionPool().purge_connections(_uuid.UUID(uuid))
class TestMySQLSlave(tests.utils.TestCase):
    """Unit test for the configuration file handling.
    """

    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(OPTIONS_MASTER["address"])
        OPTIONS_MASTER["uuid"] = _uuid.UUID(uuid)
        self.master = MySQLServer(**OPTIONS_MASTER)
        self.master.connect()
        reset_master(self.master)

        uuid = MySQLServer.discover_uuid(OPTIONS_SLAVE["address"])
        OPTIONS_SLAVE["uuid"] = _uuid.UUID(uuid)
        self.slave = MySQLServer(**OPTIONS_SLAVE)
        self.slave.connect()
        stop_slave(self.slave, wait=True)
        reset_master(self.slave)
        reset_slave(self.slave, clean=True)

    def tearDown(self):
        """Clean up the existing environment
        """
        cleanup_environment()
        stop_slave(self.slave, wait=True)
        self.slave.disconnect()
        self.master.disconnect()

    def test_switch_master_(self):
        """Test the switch_master() function.
        """
        # Note this is only being tested with gtids.

        # Set up replication.
        master = self.master
        slave = self.slave

        # Check that is slave is not connected to any master.
        self.assertFalse(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertNotEqual(slave_has_master(slave), str(master.uuid))

        # Switch to a master.
        switch_master(slave, master, MySQLInstances().user,
                      MySQLInstances().passwd)
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        # The IO_THREAD status and the UUID are not atomically updated
        # for that reason master_uuid can be None.
        master_uuid = slave_has_master(slave)
        self.assertTrue(
            master_uuid == None or master_uuid == str(master.uuid)
            )

        # It is not possible to switch when replication is running.
        self.assertRaises(_errors.DatabaseError, switch_master, slave,
            master, MySQLInstances().user, MySQLInstances().passwd
        )

        # Reset and try to reconnect master and slave.
        stop_slave(slave, wait=True)
        reset_slave(slave, clean=True)
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertEqual(slave_has_master(slave), str(master.uuid))

        # Change master's password, reset and try to reconnect master
        # and slave.
        stop_slave(slave, wait=True)
        master.set_session_binlog(False)
        master.exec_stmt(
            "SET PASSWORD = PASSWORD('foobar')"
        )
        master.set_session_binlog(True)
        switch_master(slave, master, MySQLInstances().user, "foobar")
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertEqual(slave_has_master(slave), str(master.uuid))

        # Reset master's password, reset and try to reconnect master
        # and slave.
        stop_slave(slave, wait=True)
        master.set_session_binlog(False)
        master.exec_stmt(
            "SET PASSWORD = PASSWORD('{passwd}')".format(
           passwd=MySQLInstances().passwd or "")
        )
        master.set_session_binlog(True)
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertEqual(slave_has_master(slave), str(master.uuid))

    def test_start_stop(self):
        """Test start/stop slave functions.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )

        # Start SQL Thread.
        start_slave(slave, wait=True, threads=(SQL_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "NO")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "YES")

        # Start IO Thread.
        start_slave(slave, wait=True, threads=(IO_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "YES")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "YES")

        # Stop IO Thread
        stop_slave(slave, wait=True, threads=(IO_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "NO")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "YES")

        # Stop IO Thread
        stop_slave(slave, wait=True, threads=(SQL_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "NO")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "NO")

    def test_wait_for_slave(self):
        """Test wait_for_slave_thread function.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )

        # Wait for SQL Thread and IO Thread to stop. This times out.
        start_slave(slave, wait=True)
        self.assertRaises(_errors.TimeoutError, wait_for_slave_thread, slave,
                          timeout=1, wait_for_running=False)

        # Wait for SQL Thread and IO Thread to start. This times out.
        stop_slave(slave, wait=True)
        self.assertRaises(_errors.TimeoutError, wait_for_slave_thread, slave,
                          timeout=1, wait_for_running=True)

        master.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        master.exec_stmt("USE test")
        master.exec_stmt("DROP TABLE IF EXISTS test")
        master.exec_stmt("CREATE TABLE test(id INTEGER)")
        binlog = get_master_status(master)
        binlog_file = binlog[0][0]
        binlog_pos = binlog[0][1]

        # Wait until the slave catches up. It returns False because
        # the threads are stopped.
        self.assertRaises(_errors.DatabaseError, wait_for_slave, slave,
                          binlog_file, binlog_pos, timeout=0)

        # Wait until the slave catches up. It returns True because
        # the threads are running.
        start_slave(slave, wait=True)
        wait_for_slave(slave, binlog_file, binlog_pos, timeout=0)

        # This times out because there are no new events.
        self.assertRaises(_errors.TimeoutError, wait_for_slave, slave,
                          binlog_file, 2 * binlog_pos, timeout=3)
        stop_slave(slave, wait=True)

        master.exec_stmt("DROP TABLE IF EXISTS test")
        master.exec_stmt("CREATE TABLE test(id INTEGER)")

        # It throws an error because the threads are stopped.
        self.assertRaises(_errors.DatabaseError, sync_slave_with_master,
                          slave, master, timeout=0)

        # Wait until the slave catches up. It does not throw an error
        # because the threads are running.
        start_slave(slave, wait=True)
        sync_slave_with_master(slave, master, timeout=0)

        # This times out because there are no new events.
        gtid_executed = "%s:%s" % (str(master.uuid), "1-20")
        self.assertRaises(_errors.TimeoutError, wait_for_slave_gtid, slave,
                          gtid_executed, timeout=3)

    def test_check_rpl_health(self):
        """Test check_slave_issues() function.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        slave.disconnect()

        # Try to check the health when one cannot connect to the server.
        error, ret = check_slave_issues(slave)
        self.assertEqual(error, True)
        self.assertEqual(ret['is_not_running'], True)

        # Try to check the health when change master has not been executed.
        slave.connect()
        error, ret = check_slave_issues(slave)
        self.assertEqual(error, True)
        self.assertEqual(ret['is_not_configured'], True)

        # Try to check the health after executing change master.
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )
        error, ret = check_slave_issues(slave)
        self.assertEqual(error, True)
        self.assertEqual(ret['io_not_running'], True)
        self.assertEqual(ret['sql_not_running'], True)

        # Try to check the health after starting one thread.
        start_slave(slave, wait=True, threads=(SQL_THREAD, ))
        error, ret = check_slave_issues(slave)
        self.assertEqual(error, True)
        self.assertEqual(ret['io_not_running'], True)

        # Create data and synchronize to show there is no gtid behind.
        master.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        master.exec_stmt("USE test")
        master.exec_stmt("DROP TABLE IF EXISTS test")
        master.exec_stmt("CREATE TABLE test(id INTEGER)")
        start_slave(slave, wait=True, threads=(IO_THREAD, ))
        sync_slave_with_master(slave, master, timeout=0)
        result = check_slave_delay(slave, master)
        expected_result = {
            'seconds_behind': 0,
            'is_not_configured': False,
            'gtids_behind': 0,
            'is_not_running': False,
            'sql_delay': 0
        }
        self.assertEqual(result, expected_result)

    def test_get_gtid_behind(self):
        """Test get_gtid_behind() function.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )

        # Check gtid that has no information on server_uuid.
        self.assertRaises(_errors.ProgrammingError, get_num_gtid, "1")

        sid_1 = "80139491-08ed-11e2-b7bd-f0def124dcc5"
        sid_2 = "99939491-08ed-11e2-b7bd-f0def124dcc5"

        # Check the pattern sid:trx_id.
        ret = get_num_gtid("%s:%s" % (sid_1, "5"))
        self.assertEqual(ret, 1)

        # Check the pattern sid:trx_id-trx_id.
        ret = get_num_gtid("%s:%s" % (sid_1, "5-10"))
        self.assertEqual(ret, 6)

        # Check the pattern sid:trx_id-trx_id, trx_id, trx_id-trx-id.
        ret = get_num_gtid("%s:%s,%s,%s" % (sid_1, "5-10", "20", "25-30"))
        self.assertEqual(ret, 13)

        # Check the pattern sid:trx_id-trx_id, sid:trx_id-trx-id.
        ret = get_num_gtid("%s:%s,%s:%s" % (sid_1, "5-10", sid_2, "5-6"))
        self.assertEqual(ret, 8)

        # Check the pattern sid:trx_id-trx_id, sid:trx_id-trx-id but filtering
        # server_uuids that are different from sid_2.
        ret = get_num_gtid("%s:%s,%s:%s" %
                           (sid_1, "5-10", sid_2, "5-6"), sid_2)
        self.assertEqual(ret, 2)

        # Check empty master_gtid_status and empty slave_gtid_status.
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        ret = get_slave_num_gtid_behind(slave, master_gtid_status)
        self.assertEqual(ret, 0)
        self.assertEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertEqual(master_gtid_status[0].GTID_EXECUTED, "")

        # It is not possible to do any comparison if the master_gtid_status
        # is empty.
        slave.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        slave.exec_stmt("USE test")
        slave.exec_stmt("DROP TABLE IF EXISTS test")
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        self.assertRaises(_errors.InvalidGtidError, get_slave_num_gtid_behind,
                          slave, master_gtid_status)
        self.assertNotEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertEqual(master_gtid_status[0].GTID_EXECUTED, "")

        # Check what happens if there are different sets of transactions.
        master.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        ret = get_slave_num_gtid_behind(slave, master_gtid_status)
        self.assertEqual(ret, 1)
        self.assertNotEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertNotEqual(master_gtid_status[0].GTID_EXECUTED, "")

        # Check what happens if the slave_gtid_status is empty.
        reset_master(slave)
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        ret = get_slave_num_gtid_behind(slave, master_gtid_status)
        self.assertEqual(ret, 1)
        self.assertEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertNotEqual(master_gtid_status[0].GTID_EXECUTED, "")
class TestMySQLSlave(unittest.TestCase):
    """Unit test for the configuration file handling.
    """

    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(OPTIONS_MASTER["address"])
        OPTIONS_MASTER["uuid"] = _uuid.UUID(uuid)
        self.master = MySQLServer(**OPTIONS_MASTER)
        self.master.connect()
        reset_master(self.master)

        uuid = MySQLServer.discover_uuid(OPTIONS_SLAVE["address"])
        OPTIONS_SLAVE["uuid"] = _uuid.UUID(uuid)
        self.slave = MySQLServer(**OPTIONS_SLAVE)
        self.slave.connect()
        stop_slave(self.slave, wait=True)
        reset_master(self.slave)
        reset_slave(self.slave, clean=True)

    def tearDown(self):
        """Clean up the existing environment
        """
        cleanup_environment()
        stop_slave(self.slave, wait=True)
        self.slave.disconnect()
        self.master.disconnect()

    def test_switch_master_(self):
        """Test the switch_master() function.
        """
        # Note this is only being tested with gtids.

        # Set up replication.
        master = self.master
        slave = self.slave

        # Check that is slave is not connected to any master.
        self.assertFalse(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertNotEqual(slave_has_master(slave), str(master.uuid))

        # Switch to a master.
        switch_master(slave, master, MySQLInstances().user,
                      MySQLInstances().passwd)
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        # The IO_THREAD status and the UUID are not atomically updated
        # for that reason master_uuid can be None.
        master_uuid = slave_has_master(slave)
        self.assertTrue(
            master_uuid == None or master_uuid == str(master.uuid)
            )

        # It is not possible to switch when replication is running.
        self.assertRaises(_errors.DatabaseError, switch_master, slave,
            master, MySQLInstances().user, MySQLInstances().passwd
        )

        # Reset and try to reconnect master and slave.
        stop_slave(slave, wait=True)
        reset_slave(slave, clean=True)
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertEqual(slave_has_master(slave), str(master.uuid))

        # Change master's password, reset and try to reconnect master
        # and slave.
        stop_slave(slave, wait=True)
        master.set_session_binlog(False)
        master.exec_stmt(
            "SET PASSWORD FOR '{user}'@'%%' = PASSWORD('foobar')".format(
            user=MySQLInstances().user)
        )
        master.exec_stmt("FLUSH PRIVILEGES")
        master.set_session_binlog(True)
        switch_master(slave, master, MySQLInstances().user, "foobar")
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertEqual(slave_has_master(slave), str(master.uuid))

        # Reset master's password, reset and try to reconnect master
        # and slave.
        stop_slave(slave, wait=True)
        master.set_session_binlog(False)
        master.exec_stmt(
            "SET PASSWORD FOR '{user}'@'%%' = "
            "PASSWORD('{passwd}')".format(user=MySQLInstances().user,
           passwd=MySQLInstances().passwd or "")
        )
        master.exec_stmt("FLUSH PRIVILEGES")
        master.set_session_binlog(True)
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )
        start_slave(slave, wait=True)
        self.assertTrue(is_slave_thread_running(slave, (IO_THREAD, )))
        self.assertEqual(slave_has_master(slave), str(master.uuid))

    def test_start_stop(self):
        """Test start/stop slave functions.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )

        # Start SQL Thread.
        start_slave(slave, wait=True, threads=(SQL_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "NO")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "YES")

        # Start IO Thread.
        start_slave(slave, wait=True, threads=(IO_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "YES")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "YES")

        # Stop IO Thread
        stop_slave(slave, wait=True, threads=(IO_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "NO")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "YES")

        # Stop IO Thread
        stop_slave(slave, wait=True, threads=(SQL_THREAD, ))
        status = get_slave_status(slave)
        self.assertEqual(status[0].Slave_IO_Running.upper(), "NO")
        self.assertEqual(status[0].Slave_SQL_Running.upper(), "NO")

    def test_wait_for_slave(self):
        """Test wait_for_slave_thread function.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )

        # Wait for SQL Thread and IO Thread to stop. This times out.
        start_slave(slave, wait=True)
        self.assertRaises(_errors.TimeoutError, wait_for_slave_thread, slave,
                          timeout=1, wait_for_running=False)

        # Wait for SQL Thread and IO Thread to start. This times out.
        stop_slave(slave, wait=True)
        self.assertRaises(_errors.TimeoutError, wait_for_slave_thread, slave,
                          timeout=1, wait_for_running=True)

        master.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        master.exec_stmt("USE test")
        master.exec_stmt("DROP TABLE IF EXISTS test")
        master.exec_stmt("CREATE TABLE test(id INTEGER)")
        binlog = get_master_status(master)
        binlog_file = binlog[0][0]
        binlog_pos = int(binlog[0][1])

        # Wait until the slave catches up. It returns False because
        # the threads are stopped.
        self.assertRaises(_errors.DatabaseError, wait_for_slave, slave,
                          binlog_file, binlog_pos, timeout=0)

        # Wait until the slave catches up. It returns True because
        # the threads are running.
        start_slave(slave, wait=True)
        wait_for_slave(slave, binlog_file, binlog_pos, timeout=0)

        # This times out because there are no new events.
        self.assertRaises(_errors.TimeoutError, wait_for_slave, slave,
                          binlog_file, 2 * binlog_pos, timeout=3)
        stop_slave(slave, wait=True)

        master.exec_stmt("DROP TABLE IF EXISTS test")
        master.exec_stmt("CREATE TABLE test(id INTEGER)")

        # It throws an error because the threads are stopped.
        self.assertRaises(_errors.DatabaseError, sync_slave_with_master,
                          slave, master, timeout=0)

        # Wait until the slave catches up. It does not throw an error
        # because the threads are running.
        start_slave(slave, wait=True)
        sync_slave_with_master(slave, master, timeout=0)

        # This times out because there are no new events.
        gtid_executed = "%s:%s" % (str(master.uuid), "1-20")
        self.assertRaises(_errors.TimeoutError, wait_for_slave_gtid, slave,
                          gtid_executed, timeout=3)

    def test_check_rpl_health(self):
        """Test check_slave_issues() function.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        slave.disconnect()

        # Try to check the health when one cannot connect to the server.
        ret = check_slave_issues(slave)
        self.assertEqual(ret, {'is_running': False})

        # Try to check the health when change master has not been executed.
        slave.connect()
        ret = check_slave_issues(slave)
        self.assertEqual(ret, {'is_configured': False})

        # Try to check the health after executing change master.
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )
        ret = check_slave_issues(slave)
        self.assertEqual(ret, {'io_running': False, 'sql_running': False})

        # Try to check the health after starting one thread.
        start_slave(slave, wait=True, threads=(SQL_THREAD, ))
        ret = check_slave_issues(slave)
        self.assertEqual(ret, {'io_running': False})

        # Create data and synchronize to show there is no gtid behind.
        master.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        master.exec_stmt("USE test")
        master.exec_stmt("DROP TABLE IF EXISTS test")
        master.exec_stmt("CREATE TABLE test(id INTEGER)")
        start_slave(slave, wait=True, threads=(IO_THREAD, ))
        sync_slave_with_master(slave, master, timeout=0)
        ret = check_slave_delay(slave, master)
        self.assertEqual(ret, {})

    def test_get_gtid_behind(self):
        """Test get_gtid_behind() function.
        """
        # Set up replication.
        master = self.master
        slave = self.slave
        switch_master(slave, master, MySQLInstances().user,
            MySQLInstances().passwd
        )

        # Check gtid that has no information on server_uuid.
        self.assertRaises(_errors.ProgrammingError, get_num_gtid, "1")

        sid_1 = "80139491-08ed-11e2-b7bd-f0def124dcc5"
        sid_2 = "99939491-08ed-11e2-b7bd-f0def124dcc5"

        # Check the pattern sid:trx_id.
        ret = get_num_gtid("%s:%s" % (sid_1, "5"))
        self.assertEqual(ret, 1)

        # Check the pattern sid:trx_id-trx_id.
        ret = get_num_gtid("%s:%s" % (sid_1, "5-10"))
        self.assertEqual(ret, 6)

        # Check the pattern sid:trx_id-trx_id, trx_id, trx_id-trx-id.
        ret = get_num_gtid("%s:%s,%s,%s" % (sid_1, "5-10", "20", "25-30"))
        self.assertEqual(ret, 13)

        # Check the pattern sid:trx_id-trx_id, sid:trx_id-trx-id.
        ret = get_num_gtid("%s:%s,%s:%s" % (sid_1, "5-10", sid_2, "5-6"))
        self.assertEqual(ret, 8)

        # Check the pattern sid:trx_id-trx_id, sid:trx_id-trx-id but filtering
        # server_uuids that are different from sid_2.
        ret = get_num_gtid("%s:%s,%s:%s" %
                           (sid_1, "5-10", sid_2, "5-6"), sid_2)
        self.assertEqual(ret, 2)

        # Check empty master_gtid_status and empty slave_gtid_status.
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        ret = get_slave_num_gtid_behind(slave, master_gtid_status)
        self.assertEqual(ret, 0)
        self.assertEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertEqual(master_gtid_status[0].GTID_EXECUTED, "")

        # It is not possible to do any comparison if the master_gtid_status
        # is empty.
        slave.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        slave.exec_stmt("USE test")
        slave.exec_stmt("DROP TABLE IF EXISTS test")
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        self.assertRaises(_errors.InvalidGtidError, get_slave_num_gtid_behind,
                          slave, master_gtid_status)
        self.assertNotEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertEqual(master_gtid_status[0].GTID_EXECUTED, "")

        # Check what happens if there are different sets of transactions.
        master.exec_stmt("CREATE DATABASE IF NOT EXISTS test")
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        ret = get_slave_num_gtid_behind(slave, master_gtid_status)
        self.assertEqual(ret, 1)
        self.assertNotEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertNotEqual(master_gtid_status[0].GTID_EXECUTED, "")

        # Check what happens if the slave_gtid_status is empty.
        reset_master(slave)
        master_gtid_status = master.get_gtid_status()
        slave_gtid_status = slave.get_gtid_status()
        ret = get_slave_num_gtid_behind(slave, master_gtid_status)
        self.assertEqual(ret, 1)
        self.assertEqual(slave_gtid_status[0].GTID_EXECUTED, "")
        self.assertNotEqual(master_gtid_status[0].GTID_EXECUTED, "")
class TestErrorLog(unittest.TestCase):
    """Unit test for testing ErrorLog.
    """
    def setUp(self):
        """Configure the existing environment
        """
        uuid = MySQLServer.discover_uuid(
            tests.utils.MySQLInstances().get_address(0))
        self.server = MySQLServer(_uuid.UUID(uuid),
                                  tests.utils.MySQLInstances().get_address(0))
        MySQLServer.add(self.server)

    def tearDown(self):
        """Clean up the existing environment
        """
        tests.utils.cleanup_environment()
        self.server.disconnect()
        MySQLServer.remove(self.server)

    def test_init(self):
        """Test basic properties/methods in the ErrorLog.
        """
        # Check that the input parameters are not changed.
        interval = get_time_delta(1)
        now = get_time()
        input_whens = [30, 40]
        input_reporters = ["reporter", "reporter"]
        st = ErrorLog(self.server, interval, now, input_whens, input_reporters)
        self.assertEqual(st.server_uuid, self.server.uuid)
        self.assertEqual(st.interval, interval)
        self.assertEqual(st.now, now)
        self.assertEqual(st.whens, input_whens)
        self.assertEqual(st.reporters, input_reporters)

        # If whens and reporters don't have the same length, an exception is
        # raised
        interval = get_time_delta(1)
        now = get_time()
        input_whens = [0, 0, 0, 0]
        input_reporters = []
        self.assertRaises(AssertionError, ErrorLog, self.server, interval, now,
                          input_whens, input_reporters)

    def test_persistence(self):
        """Test ErrorLog.
        """
        # Update/Notify and fetch, they should match.
        interval = get_time_delta(1)
        now = get_time()
        input_whens = [now, now]
        input_reporters = ["client:1000", "client:2000"]
        st = ErrorLog(self.server, interval, now, input_whens, input_reporters)
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now, "client:2000", "error")
        new_st = ErrorLog.fetch(self.server, interval, now)
        self.assertEqual(st.reporters, new_st.reporters)
        self.assertEqual(st.whens, new_st.whens)

        # Call remove, they should be empty and match.
        interval = get_time_delta(1)
        now = get_time()
        input_whens = []
        input_reporters = []
        ErrorLog.remove(self.server)
        st = ErrorLog(self.server, interval, now, input_whens, input_reporters)
        new_st = ErrorLog.fetch(self.server, interval, now)
        self.assertEqual(st.reporters, new_st.reporters)
        self.assertEqual(st.whens, new_st.whens)

        # Update/Notify and refresh, they should match.
        interval = get_time_delta(10)
        now = get_time()
        input_whens = [now, now - get_time_delta(5)]
        input_reporters = ["client:1000", "client:2000"]
        st = ErrorLog(self.server, interval, now, [], [])
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now - get_time_delta(5), "client:2000",
                     "error")
        ErrorLog.add(self.server, now - get_time_delta(11), "client:3000",
                     "error")
        st.refresh()
        self.assertEqual(set(st.reporters), set(input_reporters))
        self.assertEqual(set(st.whens), set(input_whens))

        # Check whether a statement similar to the one used in the
        # event is fine.
        ErrorLog.remove(self.server)
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now, "client:2000", "error")
        persister = _persistence.current_persister()
        out = persister.exec_stmt(
            "SELECT reported, UTC_TIMESTAMP() as now, "
            "TIMEDIFF(UTC_TIMESTAMP(), reported - MAKETIME(2,0,0)) as diff "
            "FROM error_log")
        _LOGGER.debug("Output test persistence %s.", out)
        self.assertEqual(len(out), 2)
        res = persister.exec_stmt(
            "DELETE FROM error_log WHERE "
            "TIMEDIFF(UTC_TIMESTAMP(), reported - MAKETIME(2,0,0)) > "
            "MAKETIME(1,0,0)")
        _LOGGER.debug("Output test persistence %s.", res)
        out = persister.exec_stmt(
            "SELECT reported, UTC_TIMESTAMP() as now, "
            "TIMEDIFF(UTC_TIMESTAMP(), reported - MAKETIME(2,0,0)) as diff "
            "FROM error_log")
        _LOGGER.debug("Output test persistence %s.", out)
        self.assertEqual(len(out), 0)

    def test_check_instability(self):
        """Test whether a server can be considered unstable or not.
        """
        # Update/Notify and refresh, they should match.
        interval = get_time_delta(10)
        now = get_time()
        input_whens = [now, now - get_time_delta(5)]
        input_reporters = ["client:1000", "client:2000"]
        st = ErrorLog(self.server, interval, now, [], [])
        ErrorLog.add(self.server, now, "client:1000", "error")
        ErrorLog.add(self.server, now - get_time_delta(5), "client:2000",
                     "error")
        ErrorLog.add(self.server, now - get_time_delta(11), "client:3000",
                     "error")
        st.refresh()
        self.assertEqual(
            st.is_unstable(n_notifications=1,
                           n_reporters=1,
                           filter_reporter=None), True)
        self.assertEqual(
            st.is_unstable(n_notifications=2,
                           n_reporters=2,
                           filter_reporter=None), True)
        self.assertEqual(
            st.is_unstable(n_notifications=3,
                           n_reporters=2,
                           filter_reporter=None), False)
        self.assertEqual(
            st.is_unstable(n_notifications=2,
                           n_reporters=3,
                           filter_reporter=None), False)
        self.assertEqual(
            st.is_unstable(n_notifications=1,
                           n_reporters=1,
                           filter_reporter=["client:2000"]), True)