/**
 * @file rwsplit_readonly.cpp Test of the read-only mode for readwritesplit when master fails
 * - check INSERTs via RWSplit
 * - block Master
 * - check SELECT and INSERT with -- fail_instantly, -- error_on_write, -- fail_on_write
 */


#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include "testconnections.h"
#include "maxadmin_operations.h"

void test_all_ok(TestConnections *Test)
{
    /** Insert should work */
    Test->set_timeout(30);
    Test->tprintf("Testing that writes and reads to all services work\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_rwsplit[0],
                                          "INSERT INTO test.readonly VALUES (1) -- fail_instantly"),
                     "Query to service with 'fail_instantly' should succeed\n");
    Test->set_timeout(30);
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "INSERT INTO test.readonly VALUES (1) -- fail_on_write"),
                     "Query to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "INSERT INTO test.readonly VALUES (1) -- error_on_write"),
                     "Query to service with 'error_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->add_result(execute_query_silent(Test->maxscales->conn_rwsplit[0],
                                          "SELECT * FROM test.readonly -- fail_instantly"),
                     "Query to service with 'fail_instantly' should succeed\n");
    Test->set_timeout(30);
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "SELECT * FROM test.readonly -- fail_on_write"),
                     "Query to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "Query to service with 'error_on_write' should succeed\n");
}

void test_basic(TestConnections *Test)
{
    /** Check that everything is OK before blocking the master */
    Test->maxscales->connect_maxscale(0);
    test_all_ok(Test);

    /** Block master */
    Test->stop_timeout();
    Test->repl->block_node(0);
    sleep(10);

    /** Select to service with 'fail_instantly' should close the connection */
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_instantly'\n");
    Test->add_result(!execute_query_silent(Test->maxscales->conn_rwsplit[0],
                                           "SELECT * FROM test.readonly -- fail_instantly"),
                     "SELECT to service with 'fail_instantly' should fail\n");

    /** Other services should still work */
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'error_on_write' should succeed\n");

    /** Insert to 'fail_on_write' should fail and close the connection */
    Test->set_timeout(30);
    Test->tprintf("INSERT to 'fail_on_write'\n");
    Test->add_result(!execute_query_silent(Test->maxscales->conn_master[0],
                                           "INSERT INTO test.readonly VALUES (1) -- fail_on_write"),
                     "INSERT to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(!execute_query_silent(Test->maxscales->conn_master[0],
                                           "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should fail after an INSERT\n");

    /** Insert to 'error_on_write' should fail but subsequent SELECTs should work */
    Test->set_timeout(30);
    Test->tprintf("INSERT to 'error_on_write'\n");
    Test->add_result(!execute_query_silent(Test->maxscales->conn_slave[0],
                                           "INSERT INTO test.readonly VALUES (1) -- error_on_write"),
                     "INSERT to service with 'error_on_write' should fail\n");
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'fail_on_write' should succeed after an INSERT\n");

    /** Close connections and try to create new ones */
    Test->set_timeout(30);
    Test->maxscales->close_maxscale_connections(0);
    Test->tprintf("Opening connections while master is blocked\n");
    Test->add_result(Test->maxscales->connect_rwsplit(0) == 0,
                     "Connection to 'fail_instantly' service should fail\n");
    Test->add_result(Test->maxscales->connect_readconn_master(0) != 0,
                     "Connection to 'fail_on_write' service should succeed\n");
    Test->add_result(Test->maxscales->connect_readconn_slave(0) != 0,
                     "Connection to 'error_on_write' service should succeed\n");


    /** The {fail|error}_on_write services should work and allow reads */
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'error_on_write' should succeed\n");

    Test->maxscales->close_maxscale_connections(0);
    Test->stop_timeout();
    Test->repl->unblock_node(0);
    sleep(10);

    /** Check that everything is OK after unblocking */
    Test->maxscales->connect_maxscale(0);
    test_all_ok(Test);
    Test->maxscales->close_maxscale_connections(0);
}

void test_complex(TestConnections *Test)
{
    /** Check that everything works before test */
    Test->maxscales->connect_maxscale(0);
    test_all_ok(Test);

    /** Block master */
    Test->stop_timeout();
    Test->repl->block_node(0);
    sleep(10);

    /** Select to service with 'fail_instantly' should close the connection */
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_instantly'\n");
    Test->add_result(!execute_query_silent(Test->maxscales->conn_rwsplit[0],
                                           "SELECT * FROM test.readonly -- fail_instantly"),
                     "SELECT to service with 'fail_instantly' should fail\n");

    /** The {fail|error}_on_write services should allow reads */
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'error_on_write' should succeed\n");

    /** Unblock node and try to read */
    Test->stop_timeout();
    Test->repl->unblock_node(0);
    sleep(10);

    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'error_on_write' should succeed\n");

    /** Block slaves */
    Test->stop_timeout();
    Test->maxscales->close_maxscale_connections(0);
    Test->repl->block_node(1);
    Test->repl->block_node(2);
    Test->repl->block_node(3);
    sleep(20);

    /** Reconnect to MaxScale */
    Test->set_timeout(30);
    Test->maxscales->connect_maxscale(0);

    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'error_on_write' should succeed\n");

    Test->stop_timeout();
    Test->repl->unblock_node(1);
    Test->repl->unblock_node(2);
    Test->repl->unblock_node(3);
    sleep(10);


    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_master[0],
                                          "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should succeed\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(execute_query_silent(Test->maxscales->conn_slave[0],
                                          "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'error_on_write' should succeed\n");

    /** Block all nodes */
    Test->stop_timeout();
    Test->repl->block_node(0);
    Test->repl->block_node(1);
    Test->repl->block_node(2);
    Test->repl->block_node(3);
    sleep(10);

    /** SELECTs should fail*/
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'fail_on_write'\n");
    Test->add_result(!execute_query_silent(Test->maxscales->conn_master[0],
                                           "SELECT * FROM test.readonly -- fail_on_write"),
                     "SELECT to service with 'fail_on_write' should fail\n");
    Test->set_timeout(30);
    Test->tprintf("SELECT to 'error_on_write'\n");
    Test->add_result(!execute_query_silent(Test->maxscales->conn_slave[0],
                                           "SELECT * FROM test.readonly -- error_on_write"),
                     "SELECT to service with 'error_on_write' should fail\n");
    Test->stop_timeout();
    Test->repl->unblock_node(0);
    Test->repl->unblock_node(1);
    Test->repl->unblock_node(2);
    Test->repl->unblock_node(3);
    sleep(10);

    /** Reconnect and check that everything works after the test */
    Test->maxscales->close_maxscale_connections(0);
    Test->maxscales->connect_maxscale(0);
    test_all_ok(Test);
    Test->maxscales->close_maxscale_connections(0);
}

int main(int argc, char *argv[])
{

    TestConnections *Test = new TestConnections(argc, argv);

    /** Prepare for tests */
    Test->stop_timeout();
    Test->maxscales->connect_maxscale(0);
    execute_query_silent(Test->maxscales->conn_rwsplit[0], "DROP TABLE IF EXISTS test.readonly");
    execute_query_silent(Test->maxscales->conn_rwsplit[0], "CREATE TABLE test.readonly(id int)");
    Test->maxscales->close_maxscale_connections(0);

    Test->repl->connect();
    Test->repl->sync_slaves();
    Test->repl->disconnect();

    /** Basic tests */
    test_basic(Test);

    /** More complex tests */
    test_complex(Test);

    /** Clean up test environment */
    Test->repl->flush_hosts();
    int rval = Test->global_result;
    delete Test;
    return rval;
}
