diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:24:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:24:36 +0000 |
commit | 06eaf7232e9a920468c0f8d74dcf2fe8b555501c (patch) | |
tree | e2c7b5777f728320e5b5542b6213fd3591ba51e2 /wsrep-lib/test | |
parent | Initial commit. (diff) | |
download | mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.tar.xz mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.zip |
Adding upstream version 1:10.11.6.upstream/1%10.11.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'wsrep-lib/test')
26 files changed, 6638 insertions, 0 deletions
diff --git a/wsrep-lib/test/CMakeLists.txt b/wsrep-lib/test/CMakeLists.txt new file mode 100644 index 00000000..366cc478 --- /dev/null +++ b/wsrep-lib/test/CMakeLists.txt @@ -0,0 +1,47 @@ +# +# Copyright (C) 2018 Codership Oy <info@codership.com> +# + + +set(TEST_SOURCES + mock_client_state.cpp + mock_high_priority_service.cpp + mock_storage_service.cpp + test_utils.cpp + buffer_test.cpp + gtid_test.cpp + id_test.cpp + nbo_test.cpp + rsu_test.cpp + server_context_test.cpp + toi_test.cpp + transaction_test.cpp + transaction_test_2pc.cpp + transaction_test_xa.cpp + view_test.cpp + xid_test.cpp + wsrep-lib_test.cpp + ) + +if (WSREP_LIB_WITH_UNIT_TESTS_EXTRA) + set(TEST_SOURCES ${TEST_SOURCES} + reporter_test.cpp + ) +endif() + +add_executable(wsrep-lib_test ${TEST_SOURCES}) + +target_link_libraries(wsrep-lib_test wsrep-lib) + +add_test(NAME wsrep-lib_test + COMMAND wsrep-lib_test) + +if (WSREP_LIB_WITH_AUTO_TEST) + set(UNIT_TEST wsrep-lib_test) + add_custom_command( + TARGET ${UNIT_TEST} + COMMENT "Run tests" + POST_BUILD + COMMAND ${UNIT_TEST} + ) +endif() diff --git a/wsrep-lib/test/buffer_test.cpp b/wsrep-lib/test/buffer_test.cpp new file mode 100644 index 00000000..84d4a483 --- /dev/null +++ b/wsrep-lib/test/buffer_test.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/buffer.hpp" +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_CASE(buffer_test_empty_access) +{ + wsrep::mutable_buffer buf; + BOOST_REQUIRE(buf.size() == 0); + (void)buf.data(); +} diff --git a/wsrep-lib/test/client_state_fixture.hpp b/wsrep-lib/test/client_state_fixture.hpp new file mode 100644 index 00000000..ab784f28 --- /dev/null +++ b/wsrep-lib/test/client_state_fixture.hpp @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WSREP_TEST_CLIENT_CONTEXT_FIXTURE_HPP +#define WSREP_TEST_CLIENT_CONTEXT_FIXTURE_HPP + +#include "mock_server_state.hpp" +#include "mock_client_state.hpp" + + +#include <boost/test/unit_test.hpp> + +namespace +{ + struct replicating_client_fixture_sync_rm + { + replicating_client_fixture_sync_rm() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_sync, server_service) + , cc(sc, wsrep::client_id(1), + wsrep::client_state::m_local) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct replicating_two_clients_fixture_sync_rm + { + replicating_two_clients_fixture_sync_rm() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_sync, server_service) + , cc1(sc, wsrep::client_id(1), + wsrep::client_state::m_local) + , cc2(sc, wsrep::client_id(2), + wsrep::client_state::m_local) + , tc(cc1.transaction()) + { + sc.mock_connect(); + cc1.open(cc1.id()); + BOOST_REQUIRE(cc1.before_command() == 0); + BOOST_REQUIRE(cc1.before_statement() == 0); + cc2.open(cc2.id()); + BOOST_REQUIRE(cc2.before_command() == 0); + BOOST_REQUIRE(cc2.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc1; + wsrep::mock_client cc2; + const wsrep::transaction& tc; + }; + + struct replicating_client_fixture_async_rm + { + replicating_client_fixture_async_rm() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_async, server_service) + , cc(sc, wsrep::client_id(1), + wsrep::client_state::m_local) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct replicating_client_fixture_2pc + { + replicating_client_fixture_2pc() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_sync, server_service) + , cc(sc, wsrep::client_id(1), + wsrep::client_state::m_local) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + cc.do_2pc_ = true; + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct replicating_client_fixture_autocommit + { + replicating_client_fixture_autocommit() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_sync, server_service) + , cc(sc, wsrep::client_id(1), + wsrep::client_state::m_local) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + cc.is_autocommit_ = true; + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct applying_client_fixture + { + applying_client_fixture() + : server_service(&sc) + , sc("s1", + wsrep::server_state::rm_async, server_service) + , cc(sc, + wsrep::client_id(1), + wsrep::client_state::m_high_priority) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + } + void start_transaction(wsrep::transaction_id id, + wsrep::seqno seqno) + { + wsrep::ws_handle ws_handle(id, (void*)1); + wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("1"), seqno), + wsrep::stid(sc.id(), + wsrep::transaction_id(1), + cc.id()), + wsrep::seqno(0), + wsrep::provider::flag::start_transaction | + wsrep::provider::flag::commit); + BOOST_REQUIRE(cc.start_transaction(ws_handle, ws_meta) == 0); + BOOST_REQUIRE(tc.active() == true); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + } + + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct applying_client_fixture_2pc + { + applying_client_fixture_2pc() + : server_service(&sc) + , sc("s1", + wsrep::server_state::rm_async, server_service) + , cc(sc, + wsrep::client_id(1), + wsrep::client_state::m_high_priority) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + cc.do_2pc_ = true; + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + wsrep::ws_handle ws_handle(wsrep::transaction_id(1), (void*)1); + wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(1)), + wsrep::stid(sc.id(), + wsrep::transaction_id(1), + cc.id()), + wsrep::seqno(0), + wsrep::provider::flag::start_transaction | + wsrep::provider::flag::commit); + BOOST_REQUIRE(cc.start_transaction(ws_handle, ws_meta) == 0); + BOOST_REQUIRE(tc.active() == true); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct streaming_client_fixture_row + { + streaming_client_fixture_row() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_sync, server_service) + , cc(sc, + wsrep::client_id(1), + wsrep::client_state::m_local) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + cc.enable_streaming(wsrep::streaming_context::row, 1); + } + + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct streaming_client_fixture_byte + { + streaming_client_fixture_byte() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_sync, server_service) + , cc(sc, + wsrep::client_id(1), + wsrep::client_state::m_local) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + cc.enable_streaming(wsrep::streaming_context::bytes, 1); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; + + struct streaming_client_fixture_statement + { + streaming_client_fixture_statement() + : server_service(&sc) + , sc("s1", wsrep::server_state::rm_sync, server_service) + , cc(sc, + wsrep::client_id(1), + wsrep::client_state::m_local) + , tc(cc.transaction()) + { + sc.mock_connect(); + cc.open(cc.id()); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Verify initial state + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + cc.enable_streaming(wsrep::streaming_context::statement, 1); + } + + wsrep::mock_server_service server_service; + wsrep::mock_server_state sc; + wsrep::mock_client cc; + const wsrep::transaction& tc; + }; +} +#endif // WSREP_TEST_CLIENT_CONTEXT_FIXTURE_HPP diff --git a/wsrep-lib/test/gtid_test.cpp b/wsrep-lib/test/gtid_test.cpp new file mode 100644 index 00000000..4f4d644b --- /dev/null +++ b/wsrep-lib/test/gtid_test.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/gtid.hpp" +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_CASE(gtid_test_scan_from_string_uuid) +{ + std::string gtid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdad:123456"); + wsrep::gtid gtid; + ssize_t ret(wsrep::scan_from_c_str( + gtid_str.c_str(), + gtid_str.size(), gtid)); + BOOST_REQUIRE_MESSAGE(ret == ssize_t(gtid_str.size()), + "Expected " << gtid_str.size() << " got " << ret); + BOOST_REQUIRE(gtid.seqno().get() == 123456); +} + +BOOST_AUTO_TEST_CASE(gtid_test_scan_from_string_uuid_too_long) +{ + std::string gtid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdadx:123456"); + wsrep::gtid gtid; + ssize_t ret(wsrep::scan_from_c_str( + gtid_str.c_str(), + gtid_str.size(), gtid)); + BOOST_REQUIRE_MESSAGE(ret == -EINVAL, + "Expected " << -EINVAL << " got " << ret); +} + +BOOST_AUTO_TEST_CASE(gtid_test_scan_from_string_seqno_out_of_range) +{ + std::string gtid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdad:9223372036854775808"); + wsrep::gtid gtid; + ssize_t ret(wsrep::scan_from_c_str( + gtid_str.c_str(), + gtid_str.size(), gtid)); + BOOST_REQUIRE_MESSAGE(ret == -EINVAL, + "Expected " << -EINVAL << " got " << ret); + + gtid_str = "6a20d44a-6e17-11e8-b1e2-9061aec0cdad:-9223372036854775809"; + ret = wsrep::scan_from_c_str( + gtid_str.c_str(), + gtid_str.size(), gtid); + BOOST_REQUIRE_MESSAGE(ret == -EINVAL, + "Expected " << -EINVAL << " got " << ret); +} diff --git a/wsrep-lib/test/id_test.cpp b/wsrep-lib/test/id_test.cpp new file mode 100644 index 00000000..5a87ba16 --- /dev/null +++ b/wsrep-lib/test/id_test.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/id.hpp" +#include <boost/test/unit_test.hpp> + +#include <sstream> + +namespace +{ + bool exception_check(const wsrep::runtime_error&) { return true; } +} + +BOOST_AUTO_TEST_CASE(id_test_uuid) +{ + std::string uuid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdad"); + wsrep::id id(uuid_str); + std::ostringstream os; + os << id; + BOOST_REQUIRE(uuid_str == os.str()); +} + +BOOST_AUTO_TEST_CASE(id_test_string) +{ + std::string id_str("1234567890123456"); + wsrep::id id(id_str); + std::ostringstream os; + os << id; + BOOST_REQUIRE(id_str == os.str()); +} + +BOOST_AUTO_TEST_CASE(id_test_string_too_long) +{ + std::string id_str("12345678901234567"); + BOOST_REQUIRE_EXCEPTION(wsrep::id id(id_str), wsrep::runtime_error, + exception_check); +} + +BOOST_AUTO_TEST_CASE(id_test_binary) +{ + char data[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 ,5 ,6}; + wsrep::id id(data, sizeof(data)); + std::ostringstream os; + os << id; + BOOST_REQUIRE(os.str() == "01020304-0506-0708-0900-010203040506"); +} + +BOOST_AUTO_TEST_CASE(id_test_binary_too_long) +{ + char data[17] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 ,5 ,6, 7}; + BOOST_REQUIRE_EXCEPTION(wsrep::id id(data, sizeof(data)), + wsrep::runtime_error, exception_check);; +} diff --git a/wsrep-lib/test/mock_client_state.cpp b/wsrep-lib/test/mock_client_state.cpp new file mode 100644 index 00000000..f98b512e --- /dev/null +++ b/wsrep-lib/test/mock_client_state.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/transaction.hpp" +#include "mock_client_state.hpp" +#include "mock_high_priority_service.hpp" + +int wsrep::mock_client_service::bf_rollback() +{ + int ret(0); + if (client_state_->before_rollback()) + { + ret = 1; + } + else if (client_state_->after_rollback()) + { + ret = 1; + } + return ret; +} + +struct replayer_context +{ + wsrep::mock_client_state state; + wsrep::mock_client_service service; + replayer_context(wsrep::server_state& server_state, + const wsrep::transaction& transaction, + const wsrep::client_id& id) + : state{server_state, service, id, wsrep::client_state::m_high_priority} + , service{&state} + { + state.open(id); + state.before_command(); + state.clone_transaction_for_replay(transaction); + } + + ~replayer_context() { + state.after_applying(); + state.after_command_before_result(); + state.after_command_after_result(); + state.close(); + } +}; + +enum wsrep::provider::status +wsrep::mock_client_service::replay() +{ + /* Mimic application and allocate separate client state for replaying. */ + wsrep::client_id replayer_id{ 1001 }; + replayer_context replayer(client_state_->server_state(), + client_state_->transaction(), replayer_id); + wsrep::mock_high_priority_service hps{ client_state_->server_state(), + &replayer.state, true }; + + enum wsrep::provider::status ret( + client_state_->provider().replay( + replayer.state.transaction().ws_handle(), + &hps)); + ++replays_; + return ret; +} diff --git a/wsrep-lib/test/mock_client_state.hpp b/wsrep-lib/test/mock_client_state.hpp new file mode 100644 index 00000000..73b27755 --- /dev/null +++ b/wsrep-lib/test/mock_client_state.hpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WSREP_MOCK_CLIENT_CONTEXT_HPP +#define WSREP_MOCK_CLIENT_CONTEXT_HPP + +#include "wsrep/client_state.hpp" +#include "wsrep/mutex.hpp" +#include "wsrep/compiler.hpp" +#include "wsrep/client_service.hpp" +#include "wsrep/condition_variable.hpp" + +#include "test_utils.hpp" + +namespace wsrep +{ + class mock_client_state : public wsrep::client_state + { + public: + mock_client_state(wsrep::server_state& server_state, + wsrep::client_service& client_service, + const wsrep::client_id& id, + enum wsrep::client_state::mode mode) + : wsrep::client_state(mutex_, cond_, server_state, client_service, + id, mode) + , mutex_() + , cond_() + { } + ~mock_client_state() WSREP_OVERRIDE + { + if (transaction().active()) + { + (void)client_service().bf_rollback(); + } + } + private: + wsrep::default_mutex mutex_; + wsrep::default_condition_variable cond_; + public: + private: + }; + + + class mock_client_service : public wsrep::client_service + { + public: + mock_client_service(wsrep::mock_client_state* client_state) + : wsrep::client_service() + , is_autocommit_() + , do_2pc_() + // , fail_next_applying_() + // , fail_next_toi_() + , bf_abort_during_wait_() + , bf_abort_during_fragment_removal_() + , error_during_prepare_data_() + , killed_before_certify_() + , sync_point_enabled_() + , sync_point_action_() + , bytes_generated_() + , client_state_(client_state) + , will_replay_called_() + , replays_() + , unordered_replays_() + , aborts_() + { } + mock_client_service(const mock_client_service&) = delete; + mock_client_service& operator=(const mock_client_service&) = delete; + + int bf_rollback() WSREP_OVERRIDE; + + bool interrupted(wsrep::unique_lock<wsrep::mutex>&) + const WSREP_OVERRIDE + { return killed_before_certify_; } + + + void emergency_shutdown() WSREP_OVERRIDE { ++aborts_; } + + int remove_fragments() WSREP_OVERRIDE + { + if (bf_abort_during_fragment_removal_) + { + client_state_->before_rollback(); + client_state_->after_rollback(); + return 1; + } + else + { + return 0; + } + } + + void will_replay() WSREP_OVERRIDE { will_replay_called_ = true; } + + void signal_replayed() WSREP_OVERRIDE { } + + enum wsrep::provider::status replay() WSREP_OVERRIDE; + + enum wsrep::provider::status replay_unordered() WSREP_OVERRIDE + { + unordered_replays_++; + return wsrep::provider::success; + } + + void wait_for_replayers( + wsrep::unique_lock<wsrep::mutex>& lock) + WSREP_OVERRIDE + { + lock.unlock(); + if (bf_abort_during_wait_) + { + wsrep_test::bf_abort_unordered(*client_state_); + } + lock.lock(); + } + + int prepare_data_for_replication() WSREP_OVERRIDE + { + if (error_during_prepare_data_) + { + return 1; + } + static const char buf[1] = { 1 }; + wsrep::const_buffer data = wsrep::const_buffer(buf, 1); + return client_state_->append_data(data); + } + + void cleanup_transaction() WSREP_OVERRIDE { } + + size_t bytes_generated() const WSREP_OVERRIDE + { + return bytes_generated_; + } + + bool statement_allowed_for_streaming() const WSREP_OVERRIDE + { return true; } + int prepare_fragment_for_replication(wsrep::mutable_buffer& buffer, size_t& position) + WSREP_OVERRIDE + { + if (error_during_prepare_data_) + { + return 1; + } + static const char buf[1] = { 1 }; + buffer.push_back(&buf[0], &buf[1]); + wsrep::const_buffer data(buffer.data(), buffer.size()); + position = buffer.size(); + return client_state_->append_data(data); + } + + void store_globals() WSREP_OVERRIDE { } + void reset_globals() WSREP_OVERRIDE { } + + enum wsrep::provider::status commit_by_xid() WSREP_OVERRIDE + { + return wsrep::provider::success; + } + + bool is_explicit_xa() WSREP_OVERRIDE + { + return false; + } + + bool is_xa_rollback() WSREP_OVERRIDE + { + return false; + } + + void debug_sync(const char* sync_point) WSREP_OVERRIDE + { + if (sync_point_enabled_ == sync_point) + { + switch (sync_point_action_) + { + case spa_bf_abort_unordered: + wsrep_test::bf_abort_unordered(*client_state_); + break; + case spa_bf_abort_ordered: + wsrep_test::bf_abort_ordered(*client_state_); + break; + } + } + } + + void debug_crash(const char*) WSREP_OVERRIDE + { + // Not going to do this while unit testing + } + + + // + // Knobs to tune the behavior + // + bool is_autocommit_; + bool do_2pc_; + // bool fail_next_applying_; + // bool fail_next_toi_; + bool bf_abort_during_wait_; + bool bf_abort_during_fragment_removal_; + bool error_during_prepare_data_; + bool killed_before_certify_; + std::string sync_point_enabled_; + enum sync_point_action + { + spa_bf_abort_unordered, + spa_bf_abort_ordered + } sync_point_action_; + size_t bytes_generated_; + + // + // Verifying the state + // + bool will_replay_called() const { return will_replay_called_; } + size_t replays() const { return replays_; } + size_t unordered_replays() const { return unordered_replays_; } + size_t aborts() const { return aborts_; } + private: + wsrep::mock_client_state* client_state_; + bool will_replay_called_; + size_t replays_; + size_t unordered_replays_; + size_t aborts_; + }; + + class mock_client + : public mock_client_state + , public mock_client_service + { + public: + mock_client(wsrep::server_state& server_state, + const wsrep::client_id& id, + enum wsrep::client_state::mode mode) + : mock_client_state(server_state, *this, id, mode) + , mock_client_service(static_cast<mock_client_state*>(this)) + { } + + int after_row() + { + bytes_generated_++; + return wsrep::client_state::after_row(); + } + }; +} + +#endif // WSREP_MOCK_CLIENT_CONTEXT_HPP diff --git a/wsrep-lib/test/mock_high_priority_service.cpp b/wsrep-lib/test/mock_high_priority_service.cpp new file mode 100644 index 00000000..bb67c9a9 --- /dev/null +++ b/wsrep-lib/test/mock_high_priority_service.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2018-2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "mock_high_priority_service.hpp" +#include "mock_server_state.hpp" +#include <sstream> + +int wsrep::mock_high_priority_service::start_transaction( + const wsrep::ws_handle& ws_handle, const wsrep::ws_meta& ws_meta) +{ + return client_state_->start_transaction(ws_handle, ws_meta); +} + +int wsrep::mock_high_priority_service::next_fragment( + const wsrep::ws_meta& ws_meta) +{ + return client_state_->next_fragment(ws_meta); +} + +int wsrep::mock_high_priority_service::adopt_transaction( + const wsrep::transaction& transaction) +{ + client_state_->adopt_transaction(transaction); + if (transaction.state() == wsrep::transaction::s_prepared) + { + client_state_->restore_xid(transaction.xid()); + } + return 0; +} + +int wsrep::mock_high_priority_service::apply_write_set( + const wsrep::ws_meta& meta, + const wsrep::const_buffer&, + wsrep::mutable_buffer& err) +{ + assert(client_state_->toi_meta().seqno().is_undefined()); + assert(client_state_->transaction().state() == wsrep::transaction::s_executing || + client_state_->transaction().state() == wsrep::transaction::s_prepared || + client_state_->transaction().state() == wsrep::transaction::s_replaying); + if (fail_next_applying_) + { + std::ostringstream os; + os << "failed " << meta; + err.push_back(os.str()); + assert(err.size() > 0); + return 1; + } + else + { + int ret(0); + if (!(meta.flags() & wsrep::provider::flag::commit)) + { + client_state_->fragment_applied(meta.seqno()); + } + if ((meta.flags() & wsrep::provider::flag::prepare)) + { + client_state_->assign_xid(wsrep::xid(1, 3, 1, "xid")); + ret = client_state_->before_prepare() || + client_state_->after_prepare(); + } + return ret; + }; +} + +int wsrep::mock_high_priority_service::commit( + const wsrep::ws_handle& ws_handle, + const wsrep::ws_meta& ws_meta) +{ + int ret(0); + client_state_->prepare_for_ordering(ws_handle, ws_meta, true); + if (do_2pc_) + { + ret = client_state_->before_prepare() || + client_state_->after_prepare(); + } + const bool is_ordered= !ws_meta.seqno().is_undefined(); + if (!is_ordered) + { + client_state_->before_rollback(); + client_state_->after_rollback(); + return 0; + } + else + { + return (ret || client_state_->before_commit() || + client_state_->ordered_commit() || + client_state_->after_commit()); + } +} + +int wsrep::mock_high_priority_service::rollback( + const wsrep::ws_handle& ws_handle, + const wsrep::ws_meta& ws_meta) +{ + client_state_->prepare_for_ordering(ws_handle, ws_meta, false); + return (client_state_->before_rollback() || + client_state_->after_rollback()); +} + +int wsrep::mock_high_priority_service::apply_toi(const wsrep::ws_meta&, + const wsrep::const_buffer&, + wsrep::mutable_buffer&) +{ + assert(client_state_->transaction().active() == false); + assert(client_state_->toi_meta().seqno().is_undefined() == false); + return (fail_next_toi_ ? 1 : 0); +} + +int wsrep::mock_high_priority_service::apply_nbo_begin( + const wsrep::ws_meta& ws_meta, + const wsrep::const_buffer&, + wsrep::mutable_buffer&) +{ + const int nbo_begin_flags __attribute__((unused)) + (wsrep::provider::flag::isolation | + wsrep::provider::flag::start_transaction); + assert(ws_meta.flags() & nbo_begin_flags); + assert((ws_meta.flags() & ~nbo_begin_flags) == 0); + + if (fail_next_toi_) + { + return 1; + } + else + { + nbo_cs_ = std::unique_ptr<wsrep::mock_client>( + new wsrep::mock_client(client_state_->server_state(), + wsrep::client_id(1), + wsrep::client_state::m_local)); + nbo_cs_->open(wsrep::client_id(1)); + nbo_cs_->before_command(); + nbo_cs_->before_statement(); + return nbo_cs_->enter_nbo_mode(ws_meta); + } +} + +void wsrep::mock_high_priority_service::adopt_apply_error( + wsrep::mutable_buffer& err) +{ + client_state_->adopt_apply_error(err); +} + +void wsrep::mock_high_priority_service::after_apply() +{ + client_state_->after_applying(); +} + +int wsrep::mock_high_priority_service::log_dummy_write_set( + const wsrep::ws_handle&, + const wsrep::ws_meta&, + wsrep::mutable_buffer& err) +{ + return err.size() > 0; +} diff --git a/wsrep-lib/test/mock_high_priority_service.hpp b/wsrep-lib/test/mock_high_priority_service.hpp new file mode 100644 index 00000000..615ba9db --- /dev/null +++ b/wsrep-lib/test/mock_high_priority_service.hpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018-2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WSREP_MOCK_HIGH_PRIORITY_SERVICE_HPP +#define WSREP_MOCK_HIGH_PRIORITY_SERVICE_HPP + +#include "wsrep/high_priority_service.hpp" +#include "mock_client_state.hpp" + +#include <memory> + +namespace wsrep +{ + class mock_high_priority_service : public wsrep::high_priority_service + { + public: + mock_high_priority_service( + wsrep::server_state& server_state, + wsrep::mock_client_state* client_state, + bool replaying) + : wsrep::high_priority_service(server_state) + , do_2pc_() + , fail_next_applying_() + , fail_next_toi_() + , client_state_(client_state) + , replaying_(replaying) + , nbo_cs_() + { } + + ~mock_high_priority_service() WSREP_OVERRIDE + { } + int start_transaction(const wsrep::ws_handle&, const wsrep::ws_meta&) + WSREP_OVERRIDE; + + int next_fragment(const wsrep::ws_meta&) WSREP_OVERRIDE; + + const wsrep::transaction& transaction() const WSREP_OVERRIDE + { return client_state_->transaction(); } + int adopt_transaction(const wsrep::transaction&) WSREP_OVERRIDE; + int apply_write_set(const wsrep::ws_meta&, + const wsrep::const_buffer&, + wsrep::mutable_buffer&) WSREP_OVERRIDE; + int append_fragment_and_commit( + const wsrep::ws_handle&, + const wsrep::ws_meta&, + const wsrep::const_buffer&, + const wsrep::xid&) WSREP_OVERRIDE + { return 0; } + int remove_fragments(const wsrep::ws_meta&) WSREP_OVERRIDE + { return 0; } + int commit(const wsrep::ws_handle&, const wsrep::ws_meta&) + WSREP_OVERRIDE; + int rollback(const wsrep::ws_handle&, const wsrep::ws_meta&) WSREP_OVERRIDE; + int apply_toi(const wsrep::ws_meta&, + const wsrep::const_buffer&, + wsrep::mutable_buffer&) WSREP_OVERRIDE; + int apply_nbo_begin(const wsrep::ws_meta&, + const wsrep::const_buffer&, + wsrep::mutable_buffer&) WSREP_OVERRIDE; + void adopt_apply_error(wsrep::mutable_buffer& err) WSREP_OVERRIDE; + void after_apply() WSREP_OVERRIDE; + void store_globals() WSREP_OVERRIDE { } + void reset_globals() WSREP_OVERRIDE { } + void switch_execution_context(wsrep::high_priority_service&) + WSREP_OVERRIDE { } + int log_dummy_write_set(const wsrep::ws_handle&, + const wsrep::ws_meta&, + wsrep::mutable_buffer&) WSREP_OVERRIDE; + bool is_replaying() const WSREP_OVERRIDE { return replaying_; } + void debug_crash(const char*) WSREP_OVERRIDE { /* Not in unit tests*/} + + wsrep::client_state& client_state() + { + return *client_state_; + } + bool do_2pc_; + bool fail_next_applying_; + bool fail_next_toi_; + + wsrep::mock_client* nbo_cs() const { return nbo_cs_.get(); } + + private: + mock_high_priority_service(const mock_high_priority_service&); + mock_high_priority_service& operator=(const mock_high_priority_service&); + wsrep::mock_client_state* client_state_; + bool replaying_; + + /* Client state associated to NBO processing. This should be + * stored elsewhere (like mock_server_state), but is kept + * here for convenience. */ + std::unique_ptr<wsrep::mock_client> nbo_cs_; + }; +} + +#endif // WSREP_MOCK_HIGH_PRIORITY_SERVICE_HPP diff --git a/wsrep-lib/test/mock_provider.hpp b/wsrep-lib/test/mock_provider.hpp new file mode 100644 index 00000000..bcfd2e45 --- /dev/null +++ b/wsrep-lib/test/mock_provider.hpp @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2018-2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WSREP_MOCK_PROVIDER_HPP +#define WSREP_MOCK_PROVIDER_HPP + +#include "wsrep/provider.hpp" +#include "wsrep/logger.hpp" +#include "wsrep/buffer.hpp" +#include "wsrep/high_priority_service.hpp" + +#include <cstring> +#include <map> +#include <iostream> // todo: proper logging + +#include <boost/test/unit_test.hpp> + +namespace wsrep +{ + class mock_provider : public wsrep::provider + { + public: + typedef std::map<wsrep::transaction_id, wsrep::seqno> bf_abort_map; + + mock_provider(wsrep::server_state& server_state) + : provider(server_state) + , certify_result_() + , commit_order_enter_result_() + , commit_order_leave_result_() + , release_result_() + , replay_result_() + , group_id_("1") + , server_id_("1") + , group_seqno_(0) + , bf_abort_map_() + , start_fragments_() + , fragments_() + , commit_fragments_() + , rollback_fragments_() + , toi_write_sets_() + , toi_start_transaction_() + , toi_commit_() + { } + + enum wsrep::provider::status + connect(const std::string&, const std::string&, const std::string&, + bool) WSREP_OVERRIDE + { return wsrep::provider::success; } + int disconnect() WSREP_OVERRIDE { return 0; } + int capabilities() const WSREP_OVERRIDE { return 0; } + int desync() WSREP_OVERRIDE { return 0; } + int resync() WSREP_OVERRIDE { return 0; } + wsrep::seqno pause() WSREP_OVERRIDE { return wsrep::seqno(0); } + int resume() WSREP_OVERRIDE { return 0; } + enum wsrep::provider::status run_applier(wsrep::high_priority_service*) + WSREP_OVERRIDE + { + return wsrep::provider::success; + } + // Provider implemenatation interface + int start_transaction(wsrep::ws_handle&) WSREP_OVERRIDE { return 0; } + enum wsrep::provider::status + certify(wsrep::client_id client_id, + wsrep::ws_handle& ws_handle, + int flags, + wsrep::ws_meta& ws_meta) + WSREP_OVERRIDE + { + ws_handle = wsrep::ws_handle(ws_handle.transaction_id(), (void*)1); + wsrep::log_debug() << "provider certify: " + << "client: " << client_id.get() + << " flags: " << std::hex << flags + << std::dec + << " certify_status: " << certify_result_; + if (certify_result_) + { + return certify_result_; + } + + ++fragments_; + if (starts_transaction(flags)) + { + ++start_fragments_; + } + if (commits_transaction(flags)) + { + ++commit_fragments_; + } + if (rolls_back_transaction(flags)) + { + ++rollback_fragments_; + } + + wsrep::stid stid(server_id_, + ws_handle.transaction_id(), + client_id); + bf_abort_map::iterator it(bf_abort_map_.find( + ws_handle.transaction_id())); + if (it == bf_abort_map_.end()) + { + ++group_seqno_; + wsrep::gtid gtid(group_id_, wsrep::seqno(group_seqno_)); + ws_meta = wsrep::ws_meta(gtid, stid, + wsrep::seqno(group_seqno_ - 1), + flags); + return wsrep::provider::success; + } + else + { + enum wsrep::provider::status ret; + if (it->second.is_undefined()) + { + ws_meta = wsrep::ws_meta(wsrep::gtid(), wsrep::stid(), + wsrep::seqno::undefined(), 0); + ret = wsrep::provider::error_certification_failed; + } + else + { + ++group_seqno_; + wsrep::gtid gtid(group_id_, wsrep::seqno(group_seqno_)); + ws_meta = wsrep::ws_meta(gtid, stid, + wsrep::seqno(group_seqno_ - 1), + flags); + ret = wsrep::provider::error_bf_abort; + } + bf_abort_map_.erase(it); + return ret; + } + } + + enum wsrep::provider::status + assign_read_view(wsrep::ws_handle&, const wsrep::gtid*) + WSREP_OVERRIDE + { return wsrep::provider::success; } + int append_key(wsrep::ws_handle&, const wsrep::key&) + WSREP_OVERRIDE + { return 0; } + enum wsrep::provider::status + append_data(wsrep::ws_handle&, const wsrep::const_buffer&) + WSREP_OVERRIDE + { return wsrep::provider::success; } + enum wsrep::provider::status rollback(const wsrep::transaction_id) + WSREP_OVERRIDE + { + ++fragments_; + ++rollback_fragments_; + return wsrep::provider::success; + } + enum wsrep::provider::status + commit_order_enter(const wsrep::ws_handle& ws_handle, + const wsrep::ws_meta& ws_meta) + WSREP_OVERRIDE + { + BOOST_REQUIRE(ws_handle.opaque()); + BOOST_REQUIRE(ws_meta.seqno().is_undefined() == false); + return commit_order_enter_result_; + } + + int commit_order_leave(const wsrep::ws_handle& ws_handle, + const wsrep::ws_meta& ws_meta, + const wsrep::mutable_buffer& err) + WSREP_OVERRIDE + { + BOOST_REQUIRE(ws_handle.opaque()); + BOOST_REQUIRE(ws_meta.seqno().is_undefined() == false); + return err.size() > 0 ? + wsrep::provider::error_fatal : + commit_order_leave_result_; + } + + int release(wsrep::ws_handle& ) + WSREP_OVERRIDE + { + // BOOST_REQUIRE(ws_handle.opaque()); + return release_result_; + } + + enum wsrep::provider::status replay( + const wsrep::ws_handle& ws_handle, + wsrep::high_priority_service* hps) + WSREP_OVERRIDE + { + wsrep::mock_high_priority_service& high_priority_service( + *static_cast<wsrep::mock_high_priority_service*>(hps)); + wsrep::mock_client_state& cc( + static_cast<wsrep::mock_client_state&>( + high_priority_service.client_state())); + const wsrep::transaction& tc(cc.transaction()); + wsrep::ws_meta ws_meta; + if (replay_result_ == wsrep::provider::success) + { + // If the ws_meta was not assigned yet, the certify + // returned early due to BF abort. + if (tc.ws_meta().seqno().is_undefined()) + { + ++group_seqno_; + ws_meta = wsrep::ws_meta( + wsrep::gtid(group_id_, wsrep::seqno(group_seqno_)), + wsrep::stid(server_id_, tc.id(), cc.id()), + wsrep::seqno(group_seqno_ - 1), + wsrep::provider::flag::start_transaction | + wsrep::provider::flag::commit); + } + else + { + ws_meta = tc.ws_meta(); + } + } + else + { + return replay_result_; + } + + if (high_priority_service.apply(ws_handle, ws_meta, + wsrep::const_buffer())) + { + return wsrep::provider::error_fatal; + } + return wsrep::provider::success; + } + + enum wsrep::provider::status enter_toi(wsrep::client_id client_id, + const wsrep::key_array&, + const wsrep::const_buffer&, + wsrep::ws_meta& toi_meta, + int flags) + WSREP_OVERRIDE + { + ++group_seqno_; + wsrep::gtid gtid(group_id_, wsrep::seqno(group_seqno_)); + wsrep::stid stid(server_id_, + wsrep::transaction_id::undefined(), + client_id); + toi_meta = wsrep::ws_meta(gtid, stid, + wsrep::seqno(group_seqno_ - 1), + flags); + ++toi_write_sets_; + if (flags & wsrep::provider::flag::start_transaction) + ++toi_start_transaction_; + if (flags & wsrep::provider::flag::commit) + ++toi_commit_; + return certify_result_; + } + + enum wsrep::provider::status leave_toi(wsrep::client_id, + const wsrep::mutable_buffer&) + WSREP_OVERRIDE + { return wsrep::provider::success; } + + std::pair<wsrep::gtid, enum wsrep::provider::status> + causal_read(int) const WSREP_OVERRIDE + { + return std::make_pair(wsrep::gtid::undefined(), + wsrep::provider::error_not_implemented); + } + enum wsrep::provider::status wait_for_gtid(const wsrep::gtid&, + int) const WSREP_OVERRIDE + { return wsrep::provider::success; } + wsrep::gtid last_committed_gtid() const WSREP_OVERRIDE + { return wsrep::gtid(); } + enum wsrep::provider::status sst_sent(const wsrep::gtid&, int) + WSREP_OVERRIDE + { return wsrep::provider::success; } + enum wsrep::provider::status sst_received(const wsrep::gtid&, int) + WSREP_OVERRIDE + { return wsrep::provider::success; } + + enum wsrep::provider::status enc_set_key(const wsrep::const_buffer&) + WSREP_OVERRIDE + { return wsrep::provider::success; } + + std::vector<status_variable> status() const WSREP_OVERRIDE + { + return std::vector<status_variable>(); + } + void reset_status() WSREP_OVERRIDE { } + std::string options() const WSREP_OVERRIDE { return ""; } + enum wsrep::provider::status options(const std::string&) + WSREP_OVERRIDE + { return wsrep::provider::success; } + std::string name() const WSREP_OVERRIDE { return "mock"; } + std::string version() const WSREP_OVERRIDE { return "0.0"; } + std::string vendor() const WSREP_OVERRIDE { return "mock"; } + void* native() const WSREP_OVERRIDE { return 0; } + + // + // Methods to modify mock state + // + /** Inject BF abort event into the provider. + * + * @param bf_seqno Aborter sequence number + * @param trx_id Trx id to be aborted + * @param[out] victim_seqno + */ + enum wsrep::provider::status + bf_abort(wsrep::seqno bf_seqno, + wsrep::transaction_id trx_id, + wsrep::seqno& victim_seqno) + WSREP_OVERRIDE + { + bf_abort_map_.insert(std::make_pair(trx_id, bf_seqno)); + if (bf_seqno.is_undefined() == false) + { + group_seqno_ = bf_seqno.get(); + } + victim_seqno = wsrep::seqno::undefined(); + return wsrep::provider::success; + } + + // Parameters to control return value from the call + enum wsrep::provider::status certify_result_; + enum wsrep::provider::status commit_order_enter_result_; + enum wsrep::provider::status commit_order_leave_result_; + enum wsrep::provider::status release_result_; + enum wsrep::provider::status replay_result_; + + size_t start_fragments() const { return start_fragments_; } + size_t fragments() const { return fragments_; } + size_t commit_fragments() const { return commit_fragments_; } + size_t rollback_fragments() const { return rollback_fragments_; } + size_t toi_write_sets() const { return toi_write_sets_; } + size_t toi_start_transaction() const { return toi_start_transaction_; } + size_t toi_commit() const { return toi_commit_; } + private: + wsrep::id group_id_; + wsrep::id server_id_; + long long group_seqno_; + bf_abort_map bf_abort_map_; + size_t start_fragments_; + size_t fragments_; + size_t commit_fragments_; + size_t rollback_fragments_; + size_t toi_write_sets_; + size_t toi_start_transaction_; + size_t toi_commit_; + }; +} + + +#endif // WSREP_MOCK_PROVIDER_HPP diff --git a/wsrep-lib/test/mock_server_state.hpp b/wsrep-lib/test/mock_server_state.hpp new file mode 100644 index 00000000..093a620a --- /dev/null +++ b/wsrep-lib/test/mock_server_state.hpp @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2018-2013 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WSREP_MOCK_SERVER_STATE_HPP +#define WSREP_MOCK_SERVER_STATE_HPP + +#include "wsrep/server_state.hpp" +#include "wsrep/server_service.hpp" +#include "mock_client_state.hpp" +#include "mock_high_priority_service.hpp" +#include "mock_storage_service.hpp" +#include "mock_provider.hpp" + +#include "wsrep/compiler.hpp" + +namespace wsrep +{ + class mock_server_service : public wsrep::server_service + { + public: + mock_server_service(wsrep::server_state* server_state) + : sync_point_enabled_() + , sync_point_action_() + , sst_before_init_() + , server_state_(server_state) + , last_client_id_(0) + , last_transaction_id_(0) + , logged_view_() + , position_() + { } + mock_server_service(const mock_server_service&) = delete; + mock_server_service& operator=(const mock_server_service&) = delete; + + wsrep::storage_service* storage_service(wsrep::client_service&) + WSREP_OVERRIDE + { + return new wsrep::mock_storage_service(*server_state_, + wsrep::client_id(++last_client_id_)); + } + + wsrep::storage_service* storage_service(wsrep::high_priority_service&) + WSREP_OVERRIDE + { + return new wsrep::mock_storage_service(*server_state_, + wsrep::client_id(++last_client_id_)); + } + + void release_storage_service(wsrep::storage_service* storage_service) + WSREP_OVERRIDE + { + delete storage_service; + } + + wsrep::high_priority_service* streaming_applier_service( + wsrep::client_service&) + WSREP_OVERRIDE + { + wsrep::mock_client* cs(new wsrep::mock_client( + *server_state_, + wsrep::client_id(++last_client_id_), + wsrep::client_state::m_high_priority)); + wsrep::mock_high_priority_service* ret( + new wsrep::mock_high_priority_service(*server_state_, + cs, false)); + cs->open(cs->id()); + cs->before_command(); + return ret; + } + + wsrep::high_priority_service* streaming_applier_service( + wsrep::high_priority_service&) WSREP_OVERRIDE + { + wsrep::mock_client* cs(new wsrep::mock_client( + *server_state_, + wsrep::client_id(++last_client_id_), + wsrep::client_state::m_high_priority)); + wsrep::mock_high_priority_service* ret( + new wsrep::mock_high_priority_service(*server_state_, + cs, false)); + cs->open(cs->id()); + cs->before_command(); + return ret; + } + + void release_high_priority_service( + wsrep::high_priority_service *high_priority_service) + WSREP_OVERRIDE + { + mock_high_priority_service* mhps( + static_cast<mock_high_priority_service*>(high_priority_service)); + wsrep::mock_client* cs(&static_cast<wsrep::mock_client&>( + mhps->client_state())); + cs->after_command_before_result(); + cs->after_command_after_result(); + cs->close(); + cs->cleanup(); + delete cs; + delete mhps; + } + void bootstrap() WSREP_OVERRIDE { } + void log_message(enum wsrep::log::level level, const char* message) + WSREP_OVERRIDE + { + wsrep::log(level, server_state_->name().c_str()) << message; + } + void log_dummy_write_set(wsrep::client_state&, + const wsrep::ws_meta&) + WSREP_OVERRIDE + { + } + void log_view(wsrep::high_priority_service*, const wsrep::view& view) + WSREP_OVERRIDE + { + logged_view_ = view; + } + + void recover_streaming_appliers(wsrep::client_service&) + WSREP_OVERRIDE + { } + + void recover_streaming_appliers(wsrep::high_priority_service&) + WSREP_OVERRIDE + { } + + wsrep::view get_view(wsrep::client_service&, const wsrep::id& own_id) + WSREP_OVERRIDE + { + int const my_idx(logged_view_.member_index(own_id)); + wsrep::view my_view( + logged_view_.state_id(), + logged_view_.view_seqno(), + logged_view_.status(), + logged_view_.capabilities(), + my_idx, + logged_view_.protocol_version(), + logged_view_.members() + ); + return my_view; + } + + wsrep::gtid get_position(wsrep::client_service&) WSREP_OVERRIDE + { + return position_; + } + + void set_position(wsrep::client_service&, + const wsrep::gtid& gtid) WSREP_OVERRIDE + { + position_ = gtid; + } + + void log_state_change(enum wsrep::server_state::state, + enum wsrep::server_state::state) + WSREP_OVERRIDE + { } + bool sst_before_init() const WSREP_OVERRIDE + { return sst_before_init_; } + std::string sst_request() WSREP_OVERRIDE { return ""; } + + // Action to take when start_sst() method is called. + // This can be overriden by test case to inject custom + // behavior. + std::function<int()> start_sst_action{[](){ return 0; }}; + int start_sst(const std::string&, const wsrep::gtid&, + bool) WSREP_OVERRIDE + { + return start_sst_action(); + } + + void + background_rollback(wsrep::unique_lock<wsrep::mutex>& lock, + wsrep::client_state& client_state) WSREP_OVERRIDE + { + lock.unlock(); + client_state.before_rollback(); + client_state.after_rollback(); + lock.lock(); + } + + int wait_committing_transactions(int) WSREP_OVERRIDE { return 0; } + + wsrep::transaction_id next_transaction_id() + { + return wsrep::transaction_id(++last_transaction_id_); + } + + void debug_sync(const char* sync_point) WSREP_OVERRIDE + { + if (sync_point_enabled_ == sync_point) + { + switch (sync_point_action_) + { + case spa_none: + break; + case spa_initialize: + server_state_->initialized(); + break; + case spa_initialize_error: + throw wsrep::runtime_error("Inject initialization error"); + break; + } + } + } + + std::string sync_point_enabled_; + enum sync_point_action + { + spa_none, + spa_initialize, + spa_initialize_error + + } sync_point_action_; + bool sst_before_init_; + + void logged_view(const wsrep::view& view) + { + logged_view_ = view; + } + void position(const wsrep::gtid& position) + { + position_ = position; + } + private: + wsrep::server_state* server_state_; + unsigned long long last_client_id_; + unsigned long long last_transaction_id_; + wsrep::view logged_view_; + wsrep::gtid position_; + }; + + + class mock_server_state : public wsrep::server_state + { + public: + mock_server_state(const std::string& name, + enum wsrep::server_state::rollback_mode rollback_mode, + wsrep::server_service& server_service) + : wsrep::server_state(mutex_, cond_, server_service, NULL, + name, "", "", "./", + wsrep::gtid::undefined(), + 1, + rollback_mode) + , mutex_() + , cond_() + , provider_(*this) + { } + + wsrep::mock_provider& provider() const WSREP_OVERRIDE + { return provider_; } + + // mock connected state for tests without overriding the connect() + // method. + int mock_connect(const std::string& own_id, + const std::string& cluster_name, + const std::string& cluster_address, + const std::string& state_donor, + bool bootstrap) + { + int const ret(server_state::connect(cluster_name, + cluster_address, + state_donor, + bootstrap)); + if (0 == ret) + { + wsrep::id cluster_id("1"); + wsrep::gtid state_id(cluster_id, wsrep::seqno(0)); + std::vector<wsrep::view::member> members; + members.push_back(wsrep::view::member(wsrep::id(own_id), + "name", "")); + wsrep::view bootstrap_view(state_id, + wsrep::seqno(1), + wsrep::view::primary, + 0, + 0, + 1, + members); + server_state::on_connect(bootstrap_view); + } + else + { + assert(0); + } + + return ret; + } + + int mock_connect() + { + return mock_connect(name(), "cluster", "local", "0", false); + } + + private: + wsrep::default_mutex mutex_; + wsrep::default_condition_variable cond_; + mutable wsrep::mock_provider provider_; + }; +} + +#endif // WSREP_MOCK_SERVER_STATE_HPP diff --git a/wsrep-lib/test/mock_storage_service.cpp b/wsrep-lib/test/mock_storage_service.cpp new file mode 100644 index 00000000..db513bc4 --- /dev/null +++ b/wsrep-lib/test/mock_storage_service.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018-2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "mock_storage_service.hpp" +#include "mock_server_state.hpp" + +#include "wsrep/client_state.hpp" + +wsrep::mock_storage_service::mock_storage_service( + wsrep::server_state& server_state, + wsrep::client_id client_id) + : client_service_(&client_state_) + , client_state_(server_state, client_service_, client_id, + wsrep::client_state::m_high_priority) +{ + client_state_.open(client_id); + client_state_.before_command(); +} + + +wsrep::mock_storage_service::~mock_storage_service() +{ + client_state_.after_command_before_result(); + client_state_.after_command_after_result(); + client_state_.close(); + client_state_.cleanup(); +} + +int wsrep::mock_storage_service::start_transaction( + const wsrep::ws_handle& ws_handle) +{ + return client_state_.start_transaction(ws_handle.transaction_id()); +} + +void wsrep::mock_storage_service::adopt_transaction( + const wsrep::transaction& transaction) +{ + client_state_.adopt_transaction(transaction); +} + +int wsrep::mock_storage_service::commit(const wsrep::ws_handle& ws_handle, + const wsrep::ws_meta& ws_meta) +{ + // the logic here matches mariadb's wsrep_storage_service::commit + bool ret = 0; + const bool is_ordered = !ws_meta.seqno().is_undefined(); + if (is_ordered) + { + ret = ret || client_state_.prepare_for_ordering(ws_handle, ws_meta, true); + ret = ret || client_state_.before_commit(); + ret = ret || client_state_.ordered_commit(); + ret = ret || client_state_.after_commit(); + } + + if (!is_ordered) + { + client_state_.before_rollback(); + client_state_.after_rollback(); + } + else if (ret) + { + client_state_.prepare_for_ordering(wsrep::ws_handle(), wsrep::ws_meta(), false); + } + + client_state_.after_applying(); + return ret; +} + +int wsrep::mock_storage_service::rollback(const wsrep::ws_handle& ws_handle, + const wsrep::ws_meta& ws_meta) +{ + int ret(client_state_.prepare_for_ordering( + ws_handle, ws_meta, false) || + client_state_.before_rollback() || + client_state_.after_rollback()); + client_state_.after_applying(); + return ret; +} diff --git a/wsrep-lib/test/mock_storage_service.hpp b/wsrep-lib/test/mock_storage_service.hpp new file mode 100644 index 00000000..18d2b98d --- /dev/null +++ b/wsrep-lib/test/mock_storage_service.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef WSREP_MOCK_STORAGE_SERVICE_HPP +#define WSREP_MOCK_STORAGE_SERVICE_HPP + +#include "wsrep/storage_service.hpp" +#include "mock_client_state.hpp" + +namespace wsrep +{ +class mock_server_state; + class mock_storage_service : public wsrep::storage_service + { + public: + mock_storage_service(wsrep::server_state&, wsrep::client_id); + ~mock_storage_service() WSREP_OVERRIDE; + + int start_transaction(const wsrep::ws_handle&) WSREP_OVERRIDE; + + void adopt_transaction(const wsrep::transaction&) WSREP_OVERRIDE; + + int append_fragment(const wsrep::id&, + wsrep::transaction_id, + int, + const wsrep::const_buffer&, + const wsrep::xid&) WSREP_OVERRIDE + { return 0; } + + int update_fragment_meta(const wsrep::ws_meta&) WSREP_OVERRIDE + { return 0; } + int remove_fragments() WSREP_OVERRIDE { return 0; } + int commit(const wsrep::ws_handle&, const wsrep::ws_meta&) + WSREP_OVERRIDE; + + int rollback(const wsrep::ws_handle&, const wsrep::ws_meta&) + WSREP_OVERRIDE; + + void store_globals() WSREP_OVERRIDE { } + void reset_globals() WSREP_OVERRIDE { } + private: + wsrep::mock_client_service client_service_; + wsrep::mock_client_state client_state_; + }; +} + +#endif // WSREP_MOCK_STORAGE_SERVICE_HPP diff --git a/wsrep-lib/test/nbo_test.cpp b/wsrep-lib/test/nbo_test.cpp new file mode 100644 index 00000000..238a4da5 --- /dev/null +++ b/wsrep-lib/test/nbo_test.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/client_state.hpp" + +#include "client_state_fixture.hpp" + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_CASE(test_local_nbo, + replicating_client_fixture_sync_rm) +{ + // NBO is executed in two consecutive TOI operations + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local); + // First phase certifies the write set and enters TOI + wsrep::key key(wsrep::key::exclusive); + key.append_key_part("k1", 2); + key.append_key_part("k2", 2); + wsrep::key_array keys{key}; + std::string data("data"); + BOOST_REQUIRE(cc.begin_nbo_phase_one( + keys, + wsrep::const_buffer(data.data(), + data.size())) == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_nbo); + BOOST_REQUIRE(cc.in_toi()); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local); + // After required resoureces have been grabbed, NBO leave should + // end TOI but leave the client state in m_nbo. + const wsrep::mutable_buffer err; + BOOST_REQUIRE(cc.end_nbo_phase_one(err) == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_nbo); + BOOST_REQUIRE(cc.in_toi() == false); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); + // Second phase replicates the NBO end event and grabs TOI + // again for finalizing the NBO. + BOOST_REQUIRE(cc.begin_nbo_phase_two(keys) == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_nbo); + BOOST_REQUIRE(cc.in_toi()); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local); + // End of NBO phase will leave TOI and put the client state back to + // m_local + BOOST_REQUIRE(cc.end_nbo_phase_two(err) == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local); + BOOST_REQUIRE(cc.in_toi() == false); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); + + // There must have been two toi write sets, one with + // start transaction flag, another with commit flag. + BOOST_REQUIRE(sc.provider().toi_write_sets() == 2); + BOOST_REQUIRE(sc.provider().toi_start_transaction() == 1); + BOOST_REQUIRE(sc.provider().toi_commit() == 1); +} + +BOOST_FIXTURE_TEST_CASE(test_local_nbo_cert_failure, + replicating_client_fixture_sync_rm) +{ + // NBO is executed in two consecutive TOI operations + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local); + // First phase certifies the write set and enters TOI + wsrep::key key(wsrep::key::exclusive); + key.append_key_part("k1", 2); + key.append_key_part("k2", 2); + wsrep::key_array keys{key}; + std::string data("data"); + sc.provider().certify_result_ = wsrep::provider::error_certification_failed; + BOOST_REQUIRE(cc.begin_nbo_phase_one( + keys, + wsrep::const_buffer(data.data(), + data.size())) == 1); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + BOOST_REQUIRE(cc.current_error_status() == + wsrep::provider::error_certification_failed); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local); + BOOST_REQUIRE(cc.in_toi() == false); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); +} + +// This test case operates through server_state object in order to +// verify that the high priority service is called with appropriate +// arguments. +BOOST_FIXTURE_TEST_CASE(test_applying_nbo, + applying_client_fixture) +{ + + wsrep::mock_high_priority_service hps(sc, &cc, false); + wsrep::ws_handle ws_handle(wsrep::transaction_id::undefined(), (void*)(1)); + const int nbo_begin_flags(wsrep::provider::flag::start_transaction | + wsrep::provider::flag::isolation); + wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("cluster1"), + wsrep::seqno(1)), + wsrep::stid(wsrep::id("s1"), + wsrep::transaction_id::undefined(), + wsrep::client_id(1)), + wsrep::seqno(0), + nbo_begin_flags); + std::string nbo_begin("nbo_begin"); + BOOST_REQUIRE(sc.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer(nbo_begin.data(), + nbo_begin.size())) == 0); + wsrep::mock_client* nbo_cs(hps.nbo_cs()); + BOOST_REQUIRE(nbo_cs); + BOOST_REQUIRE(nbo_cs->toi_mode() == wsrep::client_state::m_undefined); + BOOST_REQUIRE(nbo_cs->mode() == wsrep::client_state::m_nbo); + + // After this point the control is on local process and applier + // has released toi critical section. + wsrep::key key(wsrep::key::exclusive); + key.append_key_part("k1", 2); + key.append_key_part("k2", 2); + wsrep::key_array keys{key}; + // Starting phase two should put nbo_cs in toi mode. + BOOST_REQUIRE(nbo_cs->begin_nbo_phase_two(keys) == 0); + BOOST_REQUIRE(nbo_cs->mode() == wsrep::client_state::m_nbo); + BOOST_REQUIRE(nbo_cs->in_toi()); + BOOST_REQUIRE(nbo_cs->toi_mode() == wsrep::client_state::m_local); + // Ending phase two should make nbo_cs leave TOI mode and + // return to m_local mode. + const wsrep::mutable_buffer err; + BOOST_REQUIRE(nbo_cs->end_nbo_phase_two(err) == 0); + BOOST_REQUIRE(nbo_cs->mode() == wsrep::client_state::m_local); + BOOST_REQUIRE(nbo_cs->in_toi() == false); + BOOST_REQUIRE(nbo_cs->toi_mode() == wsrep::client_state::m_undefined); + + // There must have been one toi write set with commit flag. + BOOST_REQUIRE(sc.provider().toi_write_sets() == 1); + BOOST_REQUIRE(sc.provider().toi_start_transaction() == 0); + BOOST_REQUIRE(sc.provider().toi_commit() == 1); +} + +// This test case operates through server_state object in order to +// verify that the high priority service is called with appropriate +// arguments. The test checks that error is returned in the case if +// launching asynchronous process for NBO fails. +BOOST_FIXTURE_TEST_CASE(test_applying_nbo_fail, + applying_client_fixture) +{ + + wsrep::mock_high_priority_service hps(sc, &cc, false); + wsrep::ws_handle ws_handle(wsrep::transaction_id::undefined(), (void*)(1)); + const int nbo_begin_flags(wsrep::provider::flag::start_transaction | + wsrep::provider::flag::isolation); + wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("cluster1"), + wsrep::seqno(1)), + wsrep::stid(wsrep::id("s1"), + wsrep::transaction_id::undefined(), + wsrep::client_id(1)), + wsrep::seqno(0), + nbo_begin_flags); + std::string nbo_begin("nbo_begin"); + hps.fail_next_toi_ = true; + BOOST_REQUIRE(sc.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer(nbo_begin.data(), + nbo_begin.size())) == 1); +} diff --git a/wsrep-lib/test/reporter_test.cpp b/wsrep-lib/test/reporter_test.cpp new file mode 100644 index 00000000..9ee381e1 --- /dev/null +++ b/wsrep-lib/test/reporter_test.cpp @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2021 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/reporter.hpp" + +#include <boost/test/unit_test.hpp> + +#include <boost/json/src.hpp> + +#include <fstream> +#include <deque> +#include <vector> +#include <unistd.h> // unlink() for cleanup + +namespace json = boost::json; + +//////// HELPERS /////// + +static inline double +timestamp() +{ + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + return (double(time.tv_sec) + double(time.tv_nsec)*1.0e-9); +} + +static std::string make_progress_string(int const from, int const to, + int const total,int const done, + int const indefinite) +{ + std::ostringstream os; + + os << "{ \"from\": " << from << ", " + << "\"to\": " << to << ", " + << "\"total\": " << total << ", " + << "\"done\": " << done << ", " + << "\"indefinite\": " << indefinite << " }"; + + return os.str(); +} + + +static json::value +read_file(const char* const filename) +{ + std::ifstream input(filename, std::ios::binary); + std::vector<char> buffer(std::istreambuf_iterator<char>(input), {}); + json::stream_parser parser; + json::error_code err; + + parser.write(buffer.data(), buffer.size(), err); + if (err) + { + assert(0); + return nullptr; + } + + parser.finish(err); + if (err) + { + assert(0); + return nullptr; + } + + return parser.release(); +} + +struct logs +{ + std::deque<double> tstamp_; + std::deque<std::string> msg_; +}; + +struct progress +{ + int from_; + int to_; + int total_; + int done_; + int indefinite_; + + bool indefinite() const { return total_ == indefinite_; } + bool steady() const { return total_ == 0; } + progress(int const from, int const to, int const total, int const done, + int const indefinite) + : from_(from) + , to_(to) + , total_(total) + , done_(done) + , indefinite_(indefinite) + {} +}; + +static bool +operator==(const progress& left, const progress& right) +{ + if (left.indefinite() && right.indefinite()) return true; + if (left.steady() && right.steady()) return true; + + return (left.from_ == right.from_ && + left.to_ == right.to_ && + left.total_ == right.total_ && + left.done_ == right.done_ && + left.indefinite_ == right.indefinite_); +} + +static bool +operator!=(const progress& left, const progress& right) +{ + return !(left == right); +} + +static std::ostream& +operator<<(std::ostream& os, const progress& p) +{ + os << make_progress_string(p.from_, p.to_, p.total_, p.done_,p.indefinite_); + return os; +} + +struct result +{ + logs errors_; + logs warnings_; + struct + { + std::string state_; + std::string comment_; + progress progress_; + } status_; +}; + +static void +parse_result(const json::value& value, struct result& res, + const std::string& path = "") +{ + //std::cout << "Parsing " << path << ": " << value << ": " << value.kind() << std::endl; + switch (value.kind()) + { + case json::kind::object: + { + auto const obj(value.get_object()); + if (!obj.empty()) + { + for (auto it = obj.begin(); it != obj.end(); ++it) + { + std::string const key(it->key().data(), it->key().length()); + parse_result(it->value(), res, path + "." + key); + } + } + return; + } + case json::kind::array: + { + auto const arr(value.get_array()); + if (!arr.empty()) + { + for (auto it = arr.begin(); it != arr.end(); ++it) + { + parse_result(*it, res, path + ".[]"); + } + } + return; + } + case json::kind::string: + { + auto const val(value.get_string().c_str()); + if (path == ".errors.[].msg") + { + res.errors_.msg_.push_back(val); + } + else if (path == ".warnings.[].msg") + { + res.warnings_.msg_.push_back(val); + } + else if (path == ".status.state") + { + res.status_.state_ = val; + } + else if (path == ".status.comment") + { + res.status_.comment_ = val; + } + return; + } + case json::kind::uint64: + WSREP_FALLTHROUGH; + case json::kind::int64: + if (path == ".status.progress.from") + { + res.status_.progress_.from_ = int(value.get_int64()); + } + else if (path == ".status.progress.to") + { + res.status_.progress_.to_ = int(value.get_int64()); + } + else if (path == ".status.progress.total") + { + res.status_.progress_.total_ = int(value.get_int64()); + } + else if (path == ".status.progress.done") + { + res.status_.progress_.done_ = int(value.get_int64()); + } + else if (path == ".status.progress.indefinite") + { + res.status_.progress_.indefinite_ = int(value.get_int64()); + } + return; + case json::kind::double_: + if (path == ".errors.[].timestamp") + { + res.errors_.tstamp_.push_back(value.get_double()); + } + else if (path == ".warnings.[].timestamp") + { + res.warnings_.tstamp_.push_back(value.get_double()); + } + + return; + case json::kind::bool_: + return; + case json::kind::null: + return; + } + + assert(0); +} + +static bool +equal(const std::string& left, const std::string& right) +{ + return left == right; +} + +static bool +equal(double const left, double const right) +{ + return ::fabs(left - right) < 0.0001; // we are looking for ms precision +} + +template <typename T> +static bool +operator!=(const std::deque<T>& left, const std::deque<T>& right) +{ + if (left.size() != right.size()) return true; + + for (size_t i(0); i < left.size(); ++i) + if (!equal(left[i], right[i])) return true; + + return false; +} + +static bool +operator!=(const logs& left, const logs& right) +{ + if (left.tstamp_ != right.tstamp_) return true; + return (left.msg_ != right.msg_); +} + +static bool +operator==(const result& left, const result& right) +{ + if (left.errors_ != right.errors_) return false; + if (left.warnings_ != right.warnings_) return false; + + if (left.status_.state_ != right.status_.state_) return false; + if (left.status_.comment_ != right.status_.comment_) return false; + if (left.status_.progress_ != right.status_.progress_) return false; + + return true; +} + +template <typename T> +static void +print_deque(std::ostream& os,const std::deque<T> left,const std::deque<T> right) +{ + auto const max(std::max(left.size(), right.size())); + for (size_t i(0); i < max; ++i) + { + os << "|\t'"; + + if (i < left.size()) + os << left[i] << "'"; + else + os << "'\t"; + + if (i < right.size()) + os << "\t'" << right[i] << "'"; + else + os << "\t''"; + + if (!equal(left[i], right[i])) os << "\t!!!"; + + os << "\n"; + } +} + +static void +print_logs(std::ostream& os, const logs& left, const logs& right) +{ + os << "|\t" << left.msg_.size() << "\t" << right.msg_.size() << "\n"; + print_deque(os, left.tstamp_, right.tstamp_); + print_deque(os, left.msg_, right.msg_); +} + +// print two results against each other +template <typename Iteration> +static std::string +print(const result& left, const result& right, Iteration it) +{ + std::ostringstream os; + + os << std::showpoint << std::setprecision(18); + + os << "Iteration " << it << "\nerrors:\n"; + print_logs(os, left.errors_, right.errors_); + os << "warnings:\n"; + print_logs(os, left.warnings_, right.warnings_); + os << "state:\n"; + os << "\t" << left.status_.state_ << "\t" << right.status_.state_ + << "\n"; + os << "\t" << left.status_.comment_ << "\t" << right.status_.comment_ + << "\n"; + os << "\t" << left.status_.progress_ << "\t" << right.status_.progress_ + << "\n"; + + return os.str(); +} + +#define VERIFY_RESULT(left, right, it) \ + BOOST_CHECK_MESSAGE(left == right, print(left, right, it)); + +static const char* +const REPORT = "report.json"; + +static progress +const indefinite(-1, -1, -1, -1, -1); + +static progress +const steady(-1, -1, 0, 0, -1); + +static struct logs +const LOGS_INIT = { std::deque<double>(), std::deque<std::string>() }; +static struct result +const RES_INIT = { LOGS_INIT, LOGS_INIT, + { "DISCONNECTED", "Disconnected", indefinite } }; + +template <typename Iteration> +static void +test_log(const char* const fname, + wsrep::reporter& rep, + result& check, + wsrep::reporter::log_level const lvl, + double const tstamp, + const std::string& msg, + Iteration const iteration) +{ + // this is implementaiton detail, if it changes in the code, it needs + // to be changed here + size_t const MAX_ERROR(4); + + logs& log(lvl == wsrep::reporter::error ? check.errors_ : check.warnings_); + log.tstamp_.push_back(tstamp); + if (log.tstamp_.size() > MAX_ERROR) log.tstamp_.pop_front(); + log.msg_.push_back(msg); + if (log.msg_.size() > MAX_ERROR) log.msg_.pop_front(); + + rep.report_log_msg(lvl, msg, tstamp); + + auto value = read_file(fname); + auto res = RES_INIT; + parse_result(value, res); + VERIFY_RESULT(res, check, iteration); +} + +static size_t const MAX_MSG = 4; + +struct reporter_fixture +{ + wsrep::default_mutex mutex{}; + wsrep::reporter rep{mutex, REPORT, MAX_MSG}; +}; + +BOOST_FIXTURE_TEST_CASE(log_msg_test, reporter_fixture) +{ + auto value = read_file(REPORT); + BOOST_REQUIRE(value != nullptr); + + struct result res(RES_INIT), check(RES_INIT); + parse_result(value, res); + VERIFY_RESULT(res, check, -1); + + struct entry + { + double tstamp_; + std::string msg_; + }; + std::vector<entry> msgs = + { + { 0.1, "a" }, + { 0.2, "bb" }, + { 0.3, "ccc" }, + { 0.4, "dddd" }, + { 0.5, "eeeee" }, + { 0.6, "ffffff" } + }; + for (size_t i(0); i < msgs.size(); ++i) + { + test_log(REPORT, rep, check, + wsrep::reporter::error, msgs[i].tstamp_, msgs[i].msg_, i); + test_log(REPORT, rep, check, + wsrep::reporter::warning, msgs[i].tstamp_, msgs[i].msg_, i); + } + + // test indefinite timestmap + std::string const msg("err"); + rep.report_log_msg(wsrep::reporter::error, msg); + value = read_file(REPORT); + res = RES_INIT; + parse_result(value, res); + BOOST_REQUIRE(res.errors_.tstamp_.back() > 0); + BOOST_REQUIRE(res.errors_.msg_.back() == msg); + + ::unlink(REPORT); +} + +BOOST_FIXTURE_TEST_CASE(state_test, reporter_fixture) +{ + using wsrep::server_state; + + double const err_tstamp(timestamp()); + std::string const err_msg("Error!"); + + struct test + { + struct + { + enum wsrep::server_state::state state; + float progress; + } input; + struct result output; + }; + + logs const ERRS_INIT = { {err_tstamp}, {err_msg} }; + + std::vector<test> tests = + { + {{ server_state::s_disconnected, 0 }, + { ERRS_INIT, LOGS_INIT, + { "DISCONNECTED", "Disconnected", indefinite }}}, + {{ server_state::s_initializing, 0 }, + { ERRS_INIT, LOGS_INIT, + { "DISCONNECTED", "Initializing", indefinite }}}, + {{ server_state::s_initialized, 0 }, + { ERRS_INIT, LOGS_INIT, + { "DISCONNECTED", "Connecting", indefinite }}}, + {{ server_state::s_connected, 0 }, + { ERRS_INIT, LOGS_INIT, + { "CONNECTED", "Waiting", indefinite }}}, + {{ server_state::s_joiner, 0 }, + { ERRS_INIT, LOGS_INIT, + { "JOINING", "Receiving state", indefinite }}}, + {{ server_state::s_disconnecting, 0 }, + { ERRS_INIT, LOGS_INIT, + { "DISCONNECTING", "Disconnecting", indefinite }}}, + {{ server_state::s_disconnected, 0 }, + { ERRS_INIT, LOGS_INIT, + { "DISCONNECTED", "Disconnected", indefinite }}}, + {{ server_state::s_connected, 0 }, + { ERRS_INIT, LOGS_INIT, + { "CONNECTED", "Waiting", indefinite }}}, + {{ server_state::s_joiner, 0 }, + { ERRS_INIT, LOGS_INIT, + { "JOINING", "Receiving SST", indefinite }}}, + {{ server_state::s_initializing, 0 }, + { ERRS_INIT, LOGS_INIT, + { "JOINING", "Initializing", indefinite }}}, + {{ server_state::s_initialized, 0 }, + { ERRS_INIT, LOGS_INIT, + { "JOINING", "Receiving IST", indefinite }}}, + {{ server_state::s_joined, 0 }, + { ERRS_INIT, LOGS_INIT, + { "JOINED", "Syncing", indefinite }}}, + {{ server_state::s_synced, 0 }, + { ERRS_INIT, LOGS_INIT, + { "SYNCED", "Operational", steady }}}, + {{ server_state::s_donor, 0 }, + { ERRS_INIT, LOGS_INIT, + { "DONOR", "Donating SST", indefinite }}}, + {{ server_state::s_joined, 0 }, + { ERRS_INIT, LOGS_INIT, + { "JOINED", "Syncing", indefinite }}}, + {{ server_state::s_synced, 0 }, + { ERRS_INIT, LOGS_INIT, + { "SYNCED", "Operational", steady }}}, + {{ server_state::s_disconnecting, 0 }, + { ERRS_INIT, LOGS_INIT, + { "DISCONNECTING", "Disconnecting", indefinite }}}, + }; + + rep.report_log_msg(wsrep::reporter::error, err_msg, err_tstamp); + + for (auto i(tests.begin()); i != tests.end(); ++i) + { + rep.report_state(i->input.state); + auto value = read_file(REPORT); + result res(RES_INIT); + parse_result(value, res); + auto check(i->output); + VERIFY_RESULT(res, check, i - tests.begin()); + } + + ::unlink(REPORT); +} + +BOOST_FIXTURE_TEST_CASE(progress_test, reporter_fixture) +{ + using wsrep::server_state; + + wsrep::default_mutex m; + wsrep::reporter rep(m, REPORT, MAX_MSG); + double const warn_tstamp(timestamp()); + std::string const warn_msg("Warn!"); + + static progress const progress5_0(-1, -1, 5, 0, -1); + static progress const progress5_2(-1, -1, 5, 2, -1); + static progress const progress5_5(-1, -1, 5, 5, -1); + + struct test + { + struct + { + enum wsrep::server_state::state state; + int total; + int done; + } input; + struct result output; + }; + + logs const WARN_INIT = { {warn_tstamp}, {warn_msg} }; + + std::vector<test> tests = + { + {{ server_state::s_initialized, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "DISCONNECTED", "Connecting", indefinite }}}, + {{ server_state::s_connected, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "CONNECTED", "Waiting", indefinite }}}, + {{ server_state::s_joiner, 5, 0 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Receiving state", progress5_0 }}}, + {{ server_state::s_joiner, 5, 2 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Receiving state", progress5_2 }}}, + {{ server_state::s_disconnected, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "DISCONNECTED", "Disconnected", indefinite }}}, + {{ server_state::s_connected, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "CONNECTED", "Waiting", indefinite }}}, + {{ server_state::s_joiner, 5, 0 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Receiving SST", progress5_0 }}}, + {{ server_state::s_joiner, 5, 2 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Receiving SST", progress5_2 }}}, + {{ server_state::s_joiner, 5, 5 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Receiving SST", progress5_5 }}}, + {{ server_state::s_initializing, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Initializing", indefinite }}}, + {{ server_state::s_initializing, 5, 2 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Initializing", progress5_2 }}}, + {{ server_state::s_initialized, 5, 0 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Receiving IST", progress5_0 }}}, + {{ server_state::s_initialized, 5, 5 }, + { LOGS_INIT, WARN_INIT, + { "JOINING", "Receiving IST", progress5_5 }}}, + {{ server_state::s_joined, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "JOINED", "Syncing", indefinite }}}, + {{ server_state::s_joined, 5, 2 }, + { LOGS_INIT, WARN_INIT, + { "JOINED", "Syncing", progress5_2 }}}, + {{ server_state::s_synced, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "SYNCED", "Operational", steady }}}, + {{ server_state::s_donor, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "DONOR", "Donating SST", indefinite }}}, + {{ server_state::s_donor, 5, 0 }, + { LOGS_INIT, WARN_INIT, + { "DONOR", "Donating SST", progress5_0 }}}, + {{ server_state::s_joined, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "JOINED", "Syncing", indefinite }}}, + {{ server_state::s_synced, -1, -1 }, + { LOGS_INIT, WARN_INIT, + { "SYNCED", "Operational", steady }}}, + }; + + rep.report_log_msg(wsrep::reporter::warning, warn_msg, warn_tstamp); + + for (auto i(tests.begin()); i != tests.end(); ++i) + { + rep.report_state(i->input.state); + if (i->input.total >= 0) + rep.report_progress(make_progress_string(-1, -1, + i->input.total, i->input.done, + -1)); + auto value = read_file(REPORT); + result res(RES_INIT); + parse_result(value, res); + auto check(i->output); + VERIFY_RESULT(res, check, i - tests.begin()); + } + + ::unlink(REPORT); +} + +BOOST_FIXTURE_TEST_CASE(event_test, reporter_fixture) +{ + rep.report_event("{\"msg\": \"message\"}"); + auto value = read_file(REPORT); + BOOST_REQUIRE(value.at("events").is_array()); + auto event_array = value.at("events").as_array(); + BOOST_REQUIRE(event_array.size() == 1); + auto event = event_array[0]; + BOOST_REQUIRE(event.is_object()); + BOOST_REQUIRE(event.at("timestamp").is_double()); + BOOST_REQUIRE(event.at("event").is_object()); + BOOST_REQUIRE(event.at("event").at("msg").is_string()); + BOOST_REQUIRE(event.at("event").at("msg").as_string() == "message"); + ::unlink(REPORT); +} diff --git a/wsrep-lib/test/rsu_test.cpp b/wsrep-lib/test/rsu_test.cpp new file mode 100644 index 00000000..b032ef8c --- /dev/null +++ b/wsrep-lib/test/rsu_test.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/client_state.hpp" + +#include "client_state_fixture.hpp" + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_CASE(test_rsu, + replicating_client_fixture_sync_rm) +{ + BOOST_REQUIRE(cc.begin_rsu(1) == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_rsu); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local); + + BOOST_REQUIRE(cc.end_rsu() == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); +} diff --git a/wsrep-lib/test/server_context_test.cpp b/wsrep-lib/test/server_context_test.cpp new file mode 100644 index 00000000..42b3055d --- /dev/null +++ b/wsrep-lib/test/server_context_test.cpp @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2018-2023 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "mock_server_state.hpp" + +#include <boost/test/unit_test.hpp> + +namespace +{ + struct server_fixture_base + { + server_fixture_base() + : server_service(&ss) + , ss("s1", + wsrep::server_state::rm_sync, server_service) + , cc(ss, + wsrep::client_id(1), + wsrep::client_state::m_high_priority) + , hps(ss, &cc, false) + , ws_handle(wsrep::transaction_id(1), (void*)1) + , ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(1)), + wsrep::stid(wsrep::id("1"), wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(0), + wsrep::provider::flag::start_transaction | + wsrep::provider::flag::commit) + , cluster_id("1") + , bootstrap_view() + , second_view() + , third_view() + { + wsrep::gtid state_id(cluster_id, wsrep::seqno(0)); + std::vector<wsrep::view::member> members; + members.push_back(wsrep::view::member( + wsrep::id("s1"), "s1", "")); + bootstrap_view = wsrep::view(state_id, + wsrep::seqno(1), + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members); + + members.push_back(wsrep::view::member( + wsrep::id("s2"), "s2", "")); + second_view = wsrep::view(wsrep::gtid(cluster_id, wsrep::seqno(1)), + wsrep::seqno(2), + wsrep::view::primary, + 0, // capabilities + 1, // own index + 1, // protocol version + members); + + members.push_back(wsrep::view::member( + wsrep::id("s3"), "s3", "")); + + third_view = wsrep::view(wsrep::gtid(cluster_id, wsrep::seqno(2)), + wsrep::seqno(3), + wsrep::view::primary, + 0, // capabilities + 1, // own index + 1, // protocol version + members); + + cc.open(cc.id()); + BOOST_REQUIRE(cc.before_command() == 0); + } + wsrep::mock_server_service server_service; + wsrep::mock_server_state ss; + wsrep::mock_client cc; + wsrep::mock_high_priority_service hps; + wsrep::ws_handle ws_handle; + wsrep::ws_meta ws_meta; + wsrep::id cluster_id; + wsrep::view bootstrap_view; + wsrep::view second_view; + wsrep::view third_view; + + void connect_in_view(const wsrep::view& view) + { + BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0); + ss.on_connect(view); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + } + + void prepare_for_sst() + { + ss.prepare_for_sst(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner); + } + + void non_prim() + { + BOOST_REQUIRE(ss.state() != wsrep::server_state::s_disconnected); + std::vector<wsrep::view::member> members; + members.push_back(wsrep::view::member( + ss.id(), "s1", "")); + + wsrep::view view(wsrep::gtid(), // state_id + wsrep::seqno::undefined(), // view seqno + wsrep::view::non_primary, // status + 0, // capabilities + 0, // own_index + 0, // protocol ver + members // members + ); + ss.on_view(view, &hps); + } + + void final_view() + { + BOOST_REQUIRE(ss.state() != wsrep::server_state::s_disconnected); + wsrep::view view(wsrep::gtid(), // state_id + wsrep::seqno::undefined(), // view seqno + wsrep::view::disconnected, // status + 0, // capabilities + -1, // own_index + 0, // protocol ver + std::vector<wsrep::view::member>() // members + ); + ss.on_view(view, &hps); + } + + void disconnect() + { + BOOST_REQUIRE(ss.state() != wsrep::server_state::s_disconnecting); + ss.disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting); + final_view(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); + } + + }; + + struct applying_server_fixture : server_fixture_base + { + applying_server_fixture() + : server_fixture_base() + { + ss.mock_connect(); + } + }; + + struct sst_first_server_fixture : server_fixture_base + { + sst_first_server_fixture() + : server_fixture_base() + { + server_service.sst_before_init_ = true; + } + + void sst_received_action() + { + server_service.sync_point_enabled_ = "on_view_wait_initialized"; + server_service.sync_point_action_ = server_service.spa_initialize; + } + + void initialization_failure_action() + { + server_service.sync_point_enabled_ = "on_view_wait_initialized"; + server_service.sync_point_action_ = + server_service.spa_initialize_error; + } + + void clear_sync_point_action() + { + server_service.sync_point_enabled_ = ""; + server_service.sync_point_action_ = server_service.spa_none; + } + + // Helper method to bootstrap the server with bootstrap view + void bootstrap() + { + connect_in_view(bootstrap_view); + + sst_received_action(); + ss.on_view(bootstrap_view, &hps); + clear_sync_point_action(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); + } + + }; + + struct init_first_server_fixture : server_fixture_base + { + init_first_server_fixture() + : server_fixture_base() + { + server_service.sst_before_init_ = false; + } + + // Helper method to bootstrap the server with bootstrap view + void bootstrap() + { + ss.initialized(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initialized); + BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0); + ss.on_connect(bootstrap_view); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + ss.on_view(bootstrap_view, &hps); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); + } + }; + + // Helper to pass to BOOST_REQUIRE_EXCEPTION. Always returns true. + bool exception_check(const wsrep::runtime_error&) { return true; } +} + +// Test on_apply() method for 1pc +BOOST_FIXTURE_TEST_CASE(server_state_applying_1pc, + applying_server_fixture) +{ + char buf[1] = { 1 }; + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer(buf, 1)) == 0); + const wsrep::transaction& txc(cc.transaction()); + // ::abort(); + BOOST_REQUIRE_MESSAGE( + txc.state() == wsrep::transaction::s_committed, + "Transaction state " << txc.state() << " not committed"); +} + +// Test on_apply() method for 2pc +BOOST_FIXTURE_TEST_CASE(server_state_applying_2pc, + applying_server_fixture) +{ + hps.do_2pc_ = true; + char buf[1] = { 1 }; + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer(buf, 1)) == 0); + const wsrep::transaction& txc(cc.transaction()); + BOOST_REQUIRE(txc.state() == wsrep::transaction::s_committed); +} + +// Test on_apply() method for 1pc transaction which +// fails applying and rolls back +BOOST_FIXTURE_TEST_CASE(server_state_applying_1pc_rollback, + applying_server_fixture) +{ + /* make sure default success result is flipped to error_fatal */ + ss.provider().commit_order_leave_result_ = wsrep::provider::success; + hps.fail_next_applying_ = true; + char buf[1] = { 1 }; + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer(buf, 1)) == 1); + const wsrep::transaction& txc(cc.transaction()); + BOOST_REQUIRE(txc.state() == wsrep::transaction::s_aborted); +} + +// Test on_apply() method for 2pc transaction which +// fails applying and rolls back +BOOST_FIXTURE_TEST_CASE(server_state_applying_2pc_rollback, + applying_server_fixture) +{ + /* make sure default success result is flipped to error_fatal */ + ss.provider().commit_order_leave_result_ = wsrep::provider::success; + hps.do_2pc_ = true; + hps.fail_next_applying_ = true; + char buf[1] = { 1 }; + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer(buf, 1)) == 1); + const wsrep::transaction& txc(cc.transaction()); + BOOST_REQUIRE(txc.state() == wsrep::transaction::s_aborted); +} + +BOOST_FIXTURE_TEST_CASE(server_state_streaming, applying_server_fixture) +{ + ws_meta = wsrep::ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(1)), + wsrep::stid(wsrep::id("1"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(0), + wsrep::provider::flag::start_transaction); + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(ss.find_streaming_applier( + ws_meta.server_id(), ws_meta.transaction_id())); + ws_meta = wsrep::ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(2)), + wsrep::stid(wsrep::id("1"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(1), + 0); + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer("1", 1)) == 0); + ws_meta = wsrep::ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(2)), + wsrep::stid(wsrep::id("1"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(1), + wsrep::provider::flag::commit); + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(ss.find_streaming_applier( + ws_meta.server_id(), ws_meta.transaction_id()) == 0); +} + + +BOOST_AUTO_TEST_CASE(server_state_state_strings) +{ + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_disconnected) == "disconnected"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_initializing) == "initializing"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_initialized) == "initialized"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_connected) == "connected"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_joiner) == "joiner"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_joined) == "joined"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_donor) == "donor"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_synced) == "synced"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::server_state::s_disconnecting) == "disconnecting"); +} + +/////////////////////////////////////////////////////////////////////////////// +// Test cases for SST first // +/////////////////////////////////////////////////////////////////////////////// + +BOOST_FIXTURE_TEST_CASE(server_state_sst_first_boostrap, + sst_first_server_fixture) +{ + bootstrap(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + + +BOOST_FIXTURE_TEST_CASE(server_state_sst_first_join_with_sst, + sst_first_server_fixture) +{ + connect_in_view(second_view); + prepare_for_sst(); + sst_received_action(); + // Mock server service get_view() gets view from logged_view_. + // Get_view() is called from sst_received(). This emulates the + // case where SST contains the view in which SST happens. + server_service.logged_view(second_view); + server_service.position(wsrep::gtid(cluster_id, wsrep::seqno(2))); + BOOST_REQUIRE(ss.sst_received(cc, 0) == 0); + clear_sync_point_action(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +BOOST_FIXTURE_TEST_CASE(server_state_sst_first_join_with_ist, + sst_first_server_fixture) +{ + connect_in_view(second_view); + // Mock server service get_view() gets view from logged_view_. + // Get_view() is called from sst_received(). This emulates the + // case where the view is stored in stable storage. + server_service.logged_view(second_view); + sst_received_action(); + ss.on_view(second_view, &hps); + clear_sync_point_action(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_view(third_view, &hps); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + + +// Cycle from synced state to disconnected and back to synced. Server +// storage engines remain initialized. +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_synced_disconnected_synced_no_sst, + sst_first_server_fixture) +{ + bootstrap(); + ss.disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting); + final_view(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); + + // Connect back as a sole member in the cluster + BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0); + // @todo: s_connecting state would be good to have + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); + // Server state must keep the initialized state + BOOST_REQUIRE(ss.is_initialized() == true); + std::vector<wsrep::view::member> members; + members.push_back(wsrep::view::member(wsrep::id("s1"), "name", "")); + wsrep::view view(wsrep::gtid(cluster_id, wsrep::seqno(1)), + wsrep::seqno(2), + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members); + ss.on_connect(view); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + // As storage engines have been initialized, there should not be + // any reason to wait for initialization. State should jump directly + // to s_joined after handling the view. + ss.on_view(view, &hps); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +// +// Error after connecting to cluster. This scenario may happen if SST +// request preparation fails. +// +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_error_on_connect, + sst_first_server_fixture) +{ + connect_in_view(second_view); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + disconnect(); +} + +// Error during SST. +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_error_on_joiner, + sst_first_server_fixture) +{ + connect_in_view(second_view); + ss.prepare_for_sst(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner); + server_service.position(wsrep::gtid::undefined()); + BOOST_REQUIRE(ss.sst_received(cc, 1) == 0); + disconnect(); +} + +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_error_on_initializing, + sst_first_server_fixture) +{ + connect_in_view(second_view); + ss.prepare_for_sst(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner); + initialization_failure_action(); + server_service.position(wsrep::gtid(second_view.state_id())); + BOOST_REQUIRE(ss.sst_received(cc, 0) != 0); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initializing); + BOOST_REQUIRE_EXCEPTION(ss.on_view(second_view, &hps), + wsrep::runtime_error, exception_check); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initializing); + disconnect(); +} + +// Error or shutdown happens during catchup phase after receiving +// SST successfully. +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_error_on_joined, + sst_first_server_fixture) +{ + connect_in_view(second_view); + ss.prepare_for_sst(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner); + sst_received_action(); + // Mock server service get_view() gets view from logged_view_. + // Get_view() is called from sst_received(). This emulates the + // case where SST contains the view in which SST happens. + server_service.logged_view(second_view); + server_service.position(wsrep::gtid(cluster_id, wsrep::seqno(2))); + BOOST_REQUIRE(ss.sst_received(cc, 0) == 0); + clear_sync_point_action(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); +} + +// Error or shutdown happens when donating a snapshot. +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_error_on_donor, + sst_first_server_fixture) +{ + bootstrap(); + ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_donor); + disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); +} + +/////////////////////////////////////////////////////////////////////////////// +// Test cases for init first // +/////////////////////////////////////////////////////////////////////////////// + +BOOST_FIXTURE_TEST_CASE(server_state_init_first_boostrap, + init_first_server_fixture) +{ + bootstrap(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +BOOST_FIXTURE_TEST_CASE(server_state_init_first_join_with_sst, + init_first_server_fixture) +{ + ss.initialized(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initialized); + connect_in_view(second_view); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + prepare_for_sst(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner); + server_service.logged_view(second_view); + server_service.position(wsrep::gtid(cluster_id, wsrep::seqno(2))); + BOOST_REQUIRE(ss.sst_received(cc, 0) == 0); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +BOOST_FIXTURE_TEST_CASE(server_state_init_first_join_with_ist, + init_first_server_fixture) +{ + ss.initialized(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initialized); + connect_in_view(second_view); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + server_service.logged_view(second_view); + ss.on_view(second_view, &hps); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_view(third_view, &hps); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + + +// Cycle from synced state to disconnected and back to synced. Server +// storage engines remain initialized. +BOOST_FIXTURE_TEST_CASE( + server_state_init_first_synced_disconnected_synced_no_sst, + init_first_server_fixture) +{ + bootstrap(); + ss.disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting); + final_view(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); + + // Connect back as a sole member in the cluster + BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0); + // @todo: s_connecting state would be good to have + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); + // Server state must keep the initialized state + BOOST_REQUIRE(ss.is_initialized() == true); + std::vector<wsrep::view::member> members; + members.push_back(wsrep::view::member(wsrep::id("s1"), "name", "")); + wsrep::view view(wsrep::gtid(cluster_id, wsrep::seqno(1)), + wsrep::seqno(2), + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members); + ss.on_connect(view); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + // As storage engines have been initialized, there should not be + // any reason to wait for initialization. State should jump directly + // to s_joined after handling the view. + ss.on_view(view, &hps); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +///////////////////////////////////////////////////////////////////////////// +// Donor state transitions // +///////////////////////////////////////////////////////////////////////////// + +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_donate_success, + sst_first_server_fixture) +{ + bootstrap(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); + ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_donor); + ss.sst_sent(wsrep::gtid(cluster_id, wsrep::seqno(2)), 0); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_donate_error, + sst_first_server_fixture) +{ + bootstrap(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); + ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_donor); + ss.sst_sent(wsrep::gtid(cluster_id, wsrep::seqno(2)), 1); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined); + ss.on_sync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_donor_start_sst_error_in_non_prim, + sst_first_server_fixture) +{ + bootstrap(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); + server_service.start_sst_action = [&]() { + non_prim(); + return 1; + }; + ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); +} + +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_donor_sst_sent_in_non_prim, + sst_first_server_fixture) +{ + bootstrap(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); + ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_donor); + non_prim(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); + ss.sst_sent(wsrep::gtid(cluster_id, wsrep::seqno(2)), 0); + // Must stay in connected state + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected); +} + +///////////////////////////////////////////////////////////////////////////// +// Pause/Resume and Desync/Resync // +///////////////////////////////////////////////////////////////////////////// + +BOOST_FIXTURE_TEST_CASE( + server_state_sst_first_desync_and_pause_resync_and_resume, + sst_first_server_fixture) +{ + bootstrap(); + ss.desync_and_pause(); + // @todo: Should we have here different state than synced + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); + ss.resume_and_resync(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced); +} + +///////////////////////////////////////////////////////////////////////////// +// Disconnect // +///////////////////////////////////////////////////////////////////////////// + +BOOST_FIXTURE_TEST_CASE( + server_state_disconnect, + sst_first_server_fixture) +{ + bootstrap(); + ss.disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting); + final_view(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); +} + +// This test case verifies that the disconnect can be initiated +// concurrently by several callers. This may happen in failure situations +// where provider shutdown causes cascading failures and the failing operations +// try to disconnect the provider. +BOOST_FIXTURE_TEST_CASE( + server_state_disconnect_twice, + sst_first_server_fixture) +{ + bootstrap(); + ss.disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting); + ss.disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting); + final_view(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); + ss.disconnect(); + BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected); +} + +///////////////////////////////////////////////////////////////////////////// +// Orphaned SR // +///////////////////////////////////////////////////////////////////////////// + +// Test the behavior of server_state::close_orphaned_sr_transactions(). +// In this test we check the scenario where we initially have 3 nodes in +// the cluster (s1, s2, s3), and this server_state delivers one streaming +// fragment from s2 and s3 each, followed by view changes: +// +// view 1: primary (s1, s2, s3) +// view 2: primary (s1, s2) +// view 3: non-primary (s1) +// view 4: non-primary (s1, s3) +// view 5: primary (s1, s2, s3) +// +// We expect that on view 2, transaction originated from s3 is considered +// orphaned, so it should be rolled back. +// Transaction from s2 should never be considered orphaned in this scenario, +// we expect it to survive until the end of the test. That's because +// transactions are rolled back in primary views only, and because s2 +// is member of all primary views in this scenario. +BOOST_FIXTURE_TEST_CASE(server_state_close_orphaned_transactions, + sst_first_server_fixture) +{ + connect_in_view(third_view); + server_service.logged_view(third_view); + sst_received_action(); + ss.on_view(third_view, &hps); + + // initially we have members (s1, s2, s3) + std::vector<wsrep::view::member> members(ss.current_view().members()); + + // apply a fragment coming from s2 + wsrep::ws_meta meta_s2(wsrep::gtid(wsrep::id("s2"), wsrep::seqno(1)), + wsrep::stid(wsrep::id("s2"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(1), + wsrep::provider::flag::start_transaction); + + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s2, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s2.server_id(), meta_s2.transaction_id())); + + // apply a fragment coming from s3 + wsrep::ws_meta meta_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(2)), + wsrep::stid(wsrep::id("s3"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(2), + wsrep::provider::flag::start_transaction); + + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s3, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); + + // s3 drops out of the cluster, deliver primary view (s1, s2) + wsrep::view::member s3(members.back()); + members.pop_back(); + ss.on_view(wsrep::view(ss.current_view().state_id(), + ss.current_view().view_seqno() + 1, + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members), &hps); + + // transaction from s2 is still present + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s2.server_id(), meta_s2.transaction_id())); + // transaction from s3 is gone + BOOST_REQUIRE(not ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); + + // s2 drops out of the cluster, deliver non-primary view (s1) + wsrep::view::member s2(members.back()); + members.pop_back(); + ss.on_view(wsrep::view(ss.current_view().state_id(), + ss.current_view().view_seqno(), + wsrep::view::non_primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members), &hps); + + // no streaming appliers are closed on non-primary view, + // so transaction from s2 is still present + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s2.server_id(), meta_s2.transaction_id())); + + // s3 comes back, deliver non-primary view (s1, s3) + members.push_back(s3); + ss.on_view(wsrep::view(ss.current_view().state_id(), + ss.current_view().view_seqno() + 1, + wsrep::view::non_primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members), &hps); + + // transaction s2 is still present after non-primary view + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s2.server_id(), meta_s2.transaction_id())); + + // s2 comes back, deliver primary-view (s1, s2, s3) + members.push_back(s2); + ss.on_view(wsrep::view(ss.current_view().state_id(), + ss.current_view().view_seqno() + 1, + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members), &hps); + + // finally, transaction from s2 is still present (part of primary view) + // and transaction from s3 is gone + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s2.server_id(), meta_s2.transaction_id())); + BOOST_REQUIRE(not ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); + + // cleanup + wsrep::ws_meta meta_commit_s2(wsrep::gtid(wsrep::id("s2"), wsrep::seqno(3)), + wsrep::stid(wsrep::id("s2"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(3), + wsrep::provider::flag::commit); + + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_commit_s2, + wsrep::const_buffer("1", 1)) == 0); + + BOOST_REQUIRE(not ss.find_streaming_applier( + meta_commit_s2.server_id(), meta_commit_s2.transaction_id())); +} + + +// Test the case where two consecutive primary views with the +// same members are delivered (provider may do so). +// Expect SR transactions to be rolled back on equal consecutive views +BOOST_FIXTURE_TEST_CASE(server_state_equal_consecutive_views, + sst_first_server_fixture) +{ + connect_in_view(third_view); + server_service.logged_view(third_view); + sst_received_action(); + ss.on_view(third_view, &hps); + + // apply a fragment coming from s2 + wsrep::ws_meta meta_s2(wsrep::gtid(wsrep::id("s2"), wsrep::seqno(1)), + wsrep::stid(wsrep::id("s2"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(1), + wsrep::provider::flag::start_transaction); + + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s2, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s2.server_id(), meta_s2.transaction_id())); + + // apply a fragment coming from s3 + wsrep::ws_meta meta_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(2)), + wsrep::stid(wsrep::id("s3"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(2), + wsrep::provider::flag::start_transaction); + + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s3, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); + + // deliver primary view with the same members (s1, s2, s3) + ss.on_view(wsrep::view(ss.current_view().state_id(), + ss.current_view().view_seqno() + 1, + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + ss.current_view().members()), &hps); + + // transaction from s2 and s3 are gone + BOOST_REQUIRE(not ss.find_streaming_applier( + meta_s2.server_id(), meta_s2.transaction_id())); + BOOST_REQUIRE(not ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); +} + +// Verify that prepared XA transactions are not rolled back +// by close_orphaned_transactions() +BOOST_FIXTURE_TEST_CASE(server_state_xa_not_orphaned, + sst_first_server_fixture) +{ + connect_in_view(third_view); + server_service.logged_view(third_view); + sst_received_action(); + ss.on_view(third_view, &hps); + + // initially we have members (s1, s2, s3) + std::vector<wsrep::view::member> members(ss.current_view().members()); + + + wsrep::ws_meta meta_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(1)), + wsrep::stid(wsrep::id("s3"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(1), + wsrep::provider::flag::start_transaction | + wsrep::provider::flag::prepare); + + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s3, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); + + + // s3 drops out of the cluster, deliver primary view (s1, s2) + wsrep::view::member s3(members.back()); + members.pop_back(); + ss.on_view(wsrep::view(ss.current_view().state_id(), + ss.current_view().view_seqno() + 1, + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members), &hps); + + // transaction from s3 is still present + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); + + // s3 comes back, deliver primary view (s1, s2, s3) + members.push_back(s3); + ss.on_view(wsrep::view(ss.current_view().state_id(), + ss.current_view().view_seqno() + 1, + wsrep::view::primary, + 0, // capabilities + 0, // own index + 1, // protocol version + members), &hps); + + // transaction from s3 is still present + BOOST_REQUIRE(ss.find_streaming_applier( + meta_s3.server_id(), meta_s3.transaction_id())); + + // cleanup + wsrep::ws_meta meta_commit_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(3)), + wsrep::stid(wsrep::id("s3"), + wsrep::transaction_id(1), + wsrep::client_id(1)), + wsrep::seqno(3), + wsrep::provider::flag::commit); + + BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_commit_s3, + wsrep::const_buffer("1", 1)) == 0); + BOOST_REQUIRE(not ss.find_streaming_applier( + meta_commit_s3.server_id(), meta_commit_s3.transaction_id())); +} diff --git a/wsrep-lib/test/test_utils.cpp b/wsrep-lib/test/test_utils.cpp new file mode 100644 index 00000000..02e88cf0 --- /dev/null +++ b/wsrep-lib/test/test_utils.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "test_utils.hpp" +#include "wsrep/client_state.hpp" +#include "mock_server_state.hpp" + + +// Simple BF abort method to BF abort unordered transasctions +void wsrep_test::bf_abort_unordered(wsrep::client_state& cc) +{ + assert(cc.transaction().ordered() == false); + cc.bf_abort(wsrep::seqno(1)); +} + +void wsrep_test::bf_abort_ordered(wsrep::client_state& cc) +{ + assert(cc.transaction().ordered()); + cc.bf_abort(wsrep::seqno(0)); +} + +void wsrep_test::bf_abort_in_total_order(wsrep::client_state& cc) +{ + cc.total_order_bf_abort(wsrep::seqno(0)); +} +// BF abort method to abort transactions via provider +void wsrep_test::bf_abort_provider(wsrep::mock_server_state& sc, + const wsrep::transaction& tc, + wsrep::seqno bf_seqno) +{ + wsrep::seqno victim_seqno; + sc.provider().bf_abort(bf_seqno, tc.id(), victim_seqno); + (void)victim_seqno; +} + +void wsrep_test::terminate_streaming_applier( + wsrep::mock_server_state& sc, + const wsrep::id& server_id, + wsrep::transaction_id transaction_id) +{ + // Note that all other arguments than server_id and + // transaction_id are chosen arbitrarily and it is hoped + // that the mock implementation does not freak out about it. + wsrep::mock_client mc(sc, wsrep::client_id(10), + wsrep::client_state::m_high_priority); + mc.open(wsrep::client_id(10)); + mc.before_command(); + wsrep::mock_high_priority_service hps(sc, &mc, false); + wsrep::ws_handle ws_handle(transaction_id, (void*)(1)); + wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("cluster1"), + wsrep::seqno(100)), + wsrep::stid(server_id, + transaction_id, + wsrep::client_id(1)), + wsrep::seqno(0), + wsrep::provider::flag::rollback); + wsrep::const_buffer data(0, 0); + sc.on_apply(hps, ws_handle, ws_meta, data); +} diff --git a/wsrep-lib/test/test_utils.hpp b/wsrep-lib/test/test_utils.hpp new file mode 100644 index 00000000..7e4c896d --- /dev/null +++ b/wsrep-lib/test/test_utils.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +// Forward declarations +namespace wsrep +{ + class client_state; + class mock_server_state; +} + +#include "wsrep/transaction.hpp" +#include "wsrep/provider.hpp" + +// +// Utility functions +// +namespace wsrep_test +{ + + // Simple BF abort method to BF abort unordered transasctions + void bf_abort_unordered(wsrep::client_state& cc); + + // Simple BF abort method to BF abort unordered transasctions + void bf_abort_ordered(wsrep::client_state& cc); + + // BF abort method to abort transactions via provider + void bf_abort_provider(wsrep::mock_server_state& sc, + const wsrep::transaction& tc, + wsrep::seqno bf_seqno); + + // BF abort in total order + void bf_abort_in_total_order(wsrep::client_state&); + + // Terminate streaming applier by applying rollback fragment. + void terminate_streaming_applier( + wsrep::mock_server_state& sc, + const wsrep::id& server_id, + wsrep::transaction_id transaction_id); +} diff --git a/wsrep-lib/test/toi_test.cpp b/wsrep-lib/test/toi_test.cpp new file mode 100644 index 00000000..e6370170 --- /dev/null +++ b/wsrep-lib/test/toi_test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/client_state.hpp" + +#include "client_state_fixture.hpp" + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_CASE(test_toi_mode, + replicating_client_fixture_sync_rm) +{ + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); + wsrep::key key(wsrep::key::exclusive); + key.append_key_part("k1", 2); + key.append_key_part("k2", 2); + wsrep::key_array keys{key}; + wsrep::const_buffer buf("toi", 3); + BOOST_REQUIRE(cc.enter_toi_local(keys, buf) == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_toi); + BOOST_REQUIRE(cc.in_toi()); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local); + wsrep::mutable_buffer err; + BOOST_REQUIRE(cc.leave_toi_local(err) == 0); + BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); + BOOST_REQUIRE(sc.provider().toi_write_sets() == 1); + BOOST_REQUIRE(sc.provider().toi_start_transaction() == 1); + BOOST_REQUIRE(sc.provider().toi_commit() == 1); +} + +BOOST_FIXTURE_TEST_CASE(test_toi_applying, + applying_client_fixture) +{ + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); + wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(2)), + wsrep::stid(sc.id(), + wsrep::transaction_id::undefined(), + cc.id()), + wsrep::seqno(1), + wsrep::provider::flag::start_transaction | + wsrep::provider::flag::commit); + cc.enter_toi_mode(ws_meta); + BOOST_REQUIRE(cc.in_toi()); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_high_priority); + cc.leave_toi_mode(); + BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined); + cc.after_applying(); +} diff --git a/wsrep-lib/test/transaction_test.cpp b/wsrep-lib/test/transaction_test.cpp new file mode 100644 index 00000000..3efd038d --- /dev/null +++ b/wsrep-lib/test/transaction_test.cpp @@ -0,0 +1,1763 @@ +/* + * Copyright (C) 2018-2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/transaction.hpp" +#include "wsrep/provider.hpp" + +#include "test_utils.hpp" +#include "client_state_fixture.hpp" + +#include <boost/mpl/vector.hpp> + +namespace +{ + typedef + boost::mpl::vector<replicating_client_fixture_sync_rm, + replicating_client_fixture_async_rm> + replicating_fixtures; +} + +BOOST_FIXTURE_TEST_CASE(transaction_append_key_data, + replicating_client_fixture_sync_rm) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.is_empty()); + int vals[3] = {1, 2, 3}; + wsrep::key key(wsrep::key::exclusive); + for (int i(0); i < 3; ++i) + { + key.append_key_part(&vals[i], sizeof(vals[i])); + } + BOOST_REQUIRE(cc.append_key(key) == 0); + BOOST_REQUIRE(tc.is_empty() == false); + wsrep::const_buffer data(&vals[2], sizeof(vals[2])); + BOOST_REQUIRE(cc.append_data(data) == 0); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + cc.after_statement(); +} +// +// Test a succesful 1PC transaction lifecycle +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_1pc, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + // Establish default read view + BOOST_REQUIRE(0 == cc.assign_read_view(NULL)); + + // Verify that the commit can be successfully executed in separate command + BOOST_REQUIRE(cc.after_statement() == 0); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + // Run before commit + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + + // Run ordered commit + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit); + + // Run after commit + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + + +// +// Test a voluntary rollback +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_rollback, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + // Run before commit + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + + // Run after commit + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test a 1PC transaction which gets BF aborted before before_commit +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_before_before_commit, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + wsrep_test::bf_abort_unordered(cc); + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error()); +} + + + +// +// Test a 1PC transaction which gets BF aborted during before_commit via +// provider before the write set was ordered and certified. +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_during_before_commit_uncertified, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + wsrep_test::bf_abort_provider(sc, tc, wsrep::seqno::undefined()); + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_cert_failed); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error()); +} + +// +// Test a 1PC transaction which gets BF aborted during before_commit +// when waiting for replayers +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_during_commit_wait_for_replayers, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + cc.bf_abort_during_wait_ = true; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error()); +} + +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_during_commit_order_enter, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + auto& sc(T::sc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().commit_order_enter_result_ = wsrep::provider::error_bf_abort; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + + sc.provider().commit_order_enter_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + + // Replay from after_statement() + cc.after_statement(); + BOOST_REQUIRE(cc.replays() > 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(not cc.current_error()); +} + +BOOST_FIXTURE_TEST_CASE( + transaction_1pc_bf_after_before_commit, + replicating_client_fixture_async_rm) +{ + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + // Run before commit + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE_EQUAL(tc.state(), wsrep::transaction::s_committing); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + + wsrep_test::bf_abort_ordered(cc); + BOOST_REQUIRE_EQUAL(tc.state(), wsrep::transaction::s_committing); + + // Clean up + cc.ordered_commit(); + cc.after_commit(); + cc.after_statement(); +} + + +// +// Test a 1PC transaction for which prepare data fails +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_error_during_prepare_data, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + cc.error_during_prepare_data_ = true; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(cc.current_error() == wsrep::e_size_exceeded_error); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error()); +} + +// +// Test a 1PC transaction which gets killed by DBMS before certification +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_killed_before_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + cc.killed_before_certify_ = true; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(cc.current_error() == wsrep::e_interrupted_error); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error()); +} + +// +// Test a transaction which gets BF aborted inside provider before +// certification result is known. Replaying will be successful +// +BOOST_FIXTURE_TEST_CASE( + transaction_bf_before_cert_result_replay_success, + replicating_client_fixture_sync_rm) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + sc.provider().certify_result_ = wsrep::provider::error_bf_abort; + sc.provider().replay_result_ = wsrep::provider::success; + + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test a transaction which gets BF aborted inside provider before +// certification result is known. Replaying will fail because of +// certification failure. +// +BOOST_FIXTURE_TEST_CASE( + transaction_bf_before_cert_result_replay_cert_fail, + replicating_client_fixture_sync_rm) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + sc.provider().certify_result_ = wsrep::provider::error_bf_abort; + sc.provider().replay_result_ = wsrep::provider::error_certification_failed; + + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(cc.after_statement() ); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + BOOST_REQUIRE(tc.active() == false); +} + +// +// Test a 1PC transaction which gets BF aborted during before_commit via +// provider after the write set was ordered and certified. This must +// result replaying of transaction. +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_during_before_commit_certified, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + wsrep_test::bf_abort_provider(sc, tc, wsrep::seqno(1)); + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == true); + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(cc.replays() == 1); +} + +// +// Test a 1PC transaction which gets BF aborted simultaneously with +// certification failure. BF abort should not succeed as the +// transaction is going to roll back anyway. Certification failure +// should not generate seqno for write set meta. +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_before_unordered_cert_failure, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + cc.sync_point_enabled_ = "wsrep_before_certification"; + cc.sync_point_action_ = wsrep::mock_client_service::spa_bf_abort_unordered; + sc.provider().certify_result_ = wsrep::provider::error_certification_failed; + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_cert_failed); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.after_statement() ); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); +} + +// +// Test a 1PC transaction which gets "warning error" from certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_warning_error_from_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().certify_result_ = wsrep::provider::error_warning; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + sc.provider().certify_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit); +} + +// +// Test a 1PC transaction which gets transaction missing from certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_transaction_missing_from_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().certify_result_ = wsrep::provider::error_transaction_missing; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + sc.provider().certify_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit); +} + +// +// Test a 1PC transaction which gets size exceeded error from certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_size_exceeded_from_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().certify_result_ = wsrep::provider::error_size_exceeded; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + sc.provider().certify_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit); +} + +// +// Test a 1PC transaction which gets connection failed error from certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_connection_failed_from_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().certify_result_ = wsrep::provider::error_connection_failed; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + sc.provider().certify_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit); +} + +// +// Test a 1PC transaction which gets not allowed error from certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_no_allowed_from_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().certify_result_ = wsrep::provider::error_not_allowed; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + sc.provider().certify_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit); +} + +// +// Test a 1PC transaction which gets fatal error from certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_fatal_from_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().certify_result_ = wsrep::provider::error_fatal; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + sc.provider().certify_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit); + BOOST_REQUIRE(cc.aborts() == 1); +} + +// +// Test a 1PC transaction which gets unknown from certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_unknown_from_certify, T, + replicating_fixtures, T) +{ + wsrep::mock_server_state& sc(T::sc); + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + sc.provider().certify_result_ = wsrep::provider::error_unknown; + + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + + sc.provider().certify_result_ = wsrep::provider::success; + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit); +} + +// +// Test a 1PC transaction which gets BF aborted before grabbing lock +// after certify call +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_abort_before_certify_regain_lock, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + cc.sync_point_enabled_ = "wsrep_after_certification"; + cc.sync_point_action_ = wsrep::mock_client_service::spa_bf_abort_ordered; + // Run before commit + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + + // Rollback sequence + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + + // Cleanup after statement + cc.after_statement(); + BOOST_REQUIRE(cc.replays() == 1); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test a transaction which gets BF aborted before before_statement. +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_before_before_statement, T, + replicating_fixtures, T) +{ + wsrep::client_state& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + cc.after_statement(); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.before_command() == 0); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(cc.before_statement() == 1); + BOOST_REQUIRE(tc.active()); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_after_result(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); +} + +// +// Test a transaction which gets BF aborted before after_statement. +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_before_after_statement, T, + replicating_fixtures, T) +{ + wsrep::client_state& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + // Start a new transaction with ID 1 + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + wsrep_test::bf_abort_unordered(cc); + + cc.after_statement(); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error()); +} + +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_abort_after_after_statement, T, + replicating_fixtures, T) +{ + wsrep::client_state& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + cc.after_statement(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); +} + +BOOST_FIXTURE_TEST_CASE( + transaction_1pc_autocommit_retry_bf_aborted, + replicating_client_fixture_autocommit) +{ + + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(cc.after_statement()); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); +} + +BOOST_FIXTURE_TEST_CASE_TEMPLATE( + transaction_1pc_bf_abort_after_after_command_before_result, T, + replicating_fixtures, T) +{ + wsrep::client_state& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + cc.after_statement(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + wsrep_test::bf_abort_unordered(cc); + // The result is being sent to client. We need to mark transaction + // as must_abort but not override error yet as this might cause + // a race condition resulting incorrect result returned to the DBMS client. + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + // After the result has been sent to the DBMS client, the after result + // processing should roll back the transaction and set the error. + cc.after_command_after_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + BOOST_REQUIRE(tc.active() == true); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + cc.sync_rollback_complete(); + BOOST_REQUIRE(cc.before_command() == 1); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +BOOST_FIXTURE_TEST_CASE( + transaction_1pc_bf_abort_after_after_command_after_result_sync_rm, + replicating_client_fixture_sync_rm) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + cc.after_statement(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(tc.active()); + cc.sync_rollback_complete(); + BOOST_REQUIRE(cc.before_command() == 1); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(tc.active() == false); +} + +// Check the case where client program calls wait_rollback_complete() to +// gain control before before_command(). +BOOST_FIXTURE_TEST_CASE( + transaction_1pc_bf_abort_after_after_command_after_result_sync_rm_wait_rollback, + replicating_client_fixture_sync_rm) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + cc.after_statement(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(tc.active()); + cc.sync_rollback_complete(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + cc.wait_rollback_complete_and_acquire_ownership(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + // Idempotent + cc.wait_rollback_complete_and_acquire_ownership(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + BOOST_REQUIRE(cc.before_command() == 1); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(tc.active() == false); +} + +// Check the case where BF abort happens between client calls to +// wait_rollback_complete_and_acquire_ownership() +// and before before_command(). +BOOST_FIXTURE_TEST_CASE( + transaction_1pc_bf_abort_after_acquire_before_before_command_sync_rm, + replicating_client_fixture_sync_rm) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + cc.after_statement(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + cc.wait_rollback_complete_and_acquire_ownership(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + // As the control is now on client, the BF abort must just change + // the state to s_must_abort. + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(cc.before_command() == 1); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(tc.active() == false); +} + +BOOST_FIXTURE_TEST_CASE( + transaction_1pc_bf_abort_after_after_command_after_result_async_rm, + replicating_client_fixture_async_rm) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + cc.after_statement(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(cc.before_command() == 1); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + BOOST_REQUIRE(tc.active() == false); +} + +// +// Test before_command() with keep_command_error param +// Failure free case is not affected by keep_command_error +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + cc.after_statement(); + cc.after_command_before_result(); + cc.after_command_after_result(); + + bool keep_command_error(true); + BOOST_REQUIRE(cc.before_command(keep_command_error) == 0); + BOOST_REQUIRE(tc.active()); + cc.after_command_before_result(); + cc.after_command_after_result(); + + keep_command_error = false; + BOOST_REQUIRE(cc.before_command(keep_command_error) == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + cc.after_statement(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test before_command() with keep_command_error param +// BF abort while idle +// +BOOST_FIXTURE_TEST_CASE(transaction_keep_error_bf_idle_sync_rm, + replicating_client_fixture_sync_rm) +{ + cc.start_transaction(wsrep::transaction_id(1)); + cc.after_statement(); + cc.after_command_before_result(); + cc.after_command_after_result(); + + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + wsrep_test::bf_abort_unordered(cc); + cc.sync_rollback_complete(); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + + bool keep_command_error(true); + BOOST_REQUIRE(cc.before_command(keep_command_error) == 0); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + + keep_command_error = false; + BOOST_REQUIRE(cc.before_command(keep_command_error) == 1); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test before_command() with keep_command_error param +// BF abort after ownership is acquired and before before_command() +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error_bf_after_ownership, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + cc.start_transaction(wsrep::transaction_id(1)); + cc.after_statement(); + cc.after_command_before_result(); + cc.after_command_after_result(); + + cc.wait_rollback_complete_and_acquire_ownership(); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + + bool keep_command_error(true); + BOOST_REQUIRE(cc.before_command(keep_command_error) == 0); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + + keep_command_error = false; + BOOST_REQUIRE(cc.before_command(keep_command_error) == 1); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test before_command() with keep_command_error param +// BF abort right after before_command() +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error_bf_after_before_command, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + cc.start_transaction(wsrep::transaction_id(1)); + cc.after_statement(); + cc.after_command_before_result(); + cc.after_command_after_result(); + + bool keep_command_error(true); + BOOST_REQUIRE(cc.before_command(keep_command_error) == 0); + BOOST_REQUIRE(tc.active()); + + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + + keep_command_error = false; + BOOST_REQUIRE(cc.before_command(keep_command_error) == 1); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test before_command() with keep_command_error param +// BF abort right after after_command_before_result() +// +BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error_bf_after_after_command_before_result, T, + replicating_fixtures, T) +{ + wsrep::mock_client& cc(T::cc); + const wsrep::transaction& tc(T::tc); + + cc.start_transaction(wsrep::transaction_id(1)); + cc.after_statement(); + cc.after_command_before_result(); + cc.after_command_after_result(); + + bool keep_command_error(true); + BOOST_REQUIRE(cc.before_command(keep_command_error) == 0); + BOOST_REQUIRE(tc.active()); + + cc.after_command_before_result(); + + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + + cc.after_command_after_result(); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + + keep_command_error = false; + BOOST_REQUIRE(cc.before_command(keep_command_error) == 1); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +BOOST_FIXTURE_TEST_CASE(transaction_1pc_applying, + applying_client_fixture) +{ + start_transaction(wsrep::transaction_id(1), + wsrep::seqno(1)); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + cc.after_applying(); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + + +BOOST_FIXTURE_TEST_CASE(transaction_applying_rollback, + applying_client_fixture) +{ + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + cc.after_applying(); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +/////////////////////////////////////////////////////////////////////////////// +// STREAMING REPLICATION // +/////////////////////////////////////////////////////////////////////////////// + +// +// Test 1PC with row streaming with one row +// +BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_1pc_commit, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +// +// Test 1PC with row streaming with one row +// +BOOST_FIXTURE_TEST_CASE(transaction_row_batch_streaming_1pc_commit, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.enable_streaming( + wsrep::streaming_context::row, 2) == 0); + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +// +// Test 1PC row streaming with two separate statements +// +BOOST_FIXTURE_TEST_CASE( + transaction_row_streaming_1pc_commit_two_statements, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 2); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 3); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +// +// Fragments are removed in before_prepare in running transaction context. +// In 1pc the before_prepare() is called from before_commit(). +// However, the BF abort may arrive during this removal and the +// client_service::remove_fragments() may roll back the transaction +// internally. This will cause the transaction to leave before_prepare() +// in aborted state. +// +BOOST_FIXTURE_TEST_CASE(transaction_streaming_1pc_bf_abort_during_fragment_removal, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + cc.bf_abort_during_fragment_removal_ = true; + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.after_statement()); + BOOST_REQUIRE(tc.active() == false); + wsrep_test::terminate_streaming_applier(sc, sc.id(), + wsrep::transaction_id(1)); +} + +// +// Test streaming rollback +// +BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_rollback, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().rollback_fragments() == 1); + + wsrep::high_priority_service* hps( + sc.find_streaming_applier( + sc.id(), wsrep::transaction_id(1))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +// +// Test streaming BF abort in executing state. +// +BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_bf_abort_executing, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.streaming_context().rolled_back()); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(cc.after_statement()); + wsrep_test::terminate_streaming_applier(sc, sc.id(), + wsrep::transaction_id(1)); + +} + +BOOST_FIXTURE_TEST_CASE( + transaction_row_streaming_total_order_bf_abort_executing, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + wsrep_test::bf_abort_in_total_order(cc); + BOOST_REQUIRE(tc.bf_aborted_in_total_order()); + // TO BF abort must not replicate rollback fragment, + // rollback must complete before TO is allowed to + // continue. + BOOST_REQUIRE(sc.provider().rollback_fragments() == 0); + BOOST_REQUIRE(tc.streaming_context().rolled_back()); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(cc.after_statement()); + wsrep_test::terminate_streaming_applier(sc, sc.id(), + wsrep::transaction_id(1)); +} + +// +// Test streaming certification failure during fragment replication +// +BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_cert_fail_non_commit, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + sc.provider().certify_result_ = wsrep::provider::error_certification_failed; + BOOST_REQUIRE(cc.after_row() == 1); + sc.provider().certify_result_ = wsrep::provider::success; + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(cc.after_statement() == 1); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().rollback_fragments() == 1); + + wsrep::high_priority_service* hps( + sc.find_streaming_applier( + sc.id(), wsrep::transaction_id(1))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +// +// Test streaming certification failure during commit +// +BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_cert_fail_commit, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + sc.provider().certify_result_ = wsrep::provider::error_certification_failed; + BOOST_REQUIRE(cc.before_commit() == 1); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_cert_failed); + sc.provider().certify_result_ = wsrep::provider::success; + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(cc.after_statement() ); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().rollback_fragments() == 1); + + wsrep::high_priority_service* hps( + sc.find_streaming_applier( + sc.id(), wsrep::transaction_id(1))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +// +// Test streaming BF abort after succesful certification +// +BOOST_FIXTURE_TEST_CASE( + transaction_row_streaming_bf_abort_during_commit_order_enter, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + sc.provider().commit_order_enter_result_ = wsrep::provider::error_bf_abort; + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE_EQUAL(tc.state(), wsrep::transaction::s_must_replay); + sc.provider().commit_order_enter_result_ = wsrep::provider::success; + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +// +// Test a streaming transaction which gets BF aborted inside provider before +// certification result is known. Replaying will be successful +// +BOOST_FIXTURE_TEST_CASE( + transaction_row_streaming_bf_before_cert_result_replay_success, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + + sc.provider().certify_result_ = wsrep::provider::error_bf_abort; + sc.provider().replay_result_ = wsrep::provider::success; + + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test a streaming transaction which gets BF aborted inside provider before +// certification result is known. Replaying will fail because of +// certification failure. +// +BOOST_FIXTURE_TEST_CASE( + transaction_row_streaming_bf_before_cert_result_replay_cert_fail, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + + sc.provider().certify_result_ = wsrep::provider::error_bf_abort; + sc.provider().replay_result_ = wsrep::provider::error_certification_failed; + + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(cc.after_statement() ); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + BOOST_REQUIRE(tc.active() == false); +} + + +BOOST_FIXTURE_TEST_CASE(transaction_byte_streaming_1pc_commit, + streaming_client_fixture_byte) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + cc.bytes_generated_ = 1; + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +BOOST_FIXTURE_TEST_CASE(transaction_byte_batch_streaming_1pc_commit, + streaming_client_fixture_byte) +{ + BOOST_REQUIRE( + cc.enable_streaming( + wsrep::streaming_context::bytes, 2) == 0); + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + + +BOOST_FIXTURE_TEST_CASE(transaction_statement_streaming_statement_with_no_effect, + streaming_client_fixture_statement) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +BOOST_FIXTURE_TEST_CASE(transaction_statement_streaming_1pc_commit, + streaming_client_fixture_statement) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +BOOST_FIXTURE_TEST_CASE(transaction_statement_batch_streaming_1pc_commit, + streaming_client_fixture_statement) +{ + BOOST_REQUIRE( + cc.enable_streaming( + wsrep::streaming_context::statement, 2) == 0); + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +BOOST_FIXTURE_TEST_CASE(transaction_statement_streaming_cert_fail, + streaming_client_fixture_statement) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0); + sc.provider().certify_result_ = wsrep::provider::error_certification_failed; + BOOST_REQUIRE(cc.after_statement()); + BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error); + // Note: Due to possible limitation in wsrep-API error codes + // or a bug in current Galera provider, rollback fragment may be + // replicated even in case of certification failure. + // If the limitation is lifted later on or the provider is fixed, + // the above check should be change for fragments == 0, + // rollback_fragments == 0. + BOOST_REQUIRE(sc.provider().fragments() == 1); + BOOST_REQUIRE(sc.provider().start_fragments() == 0); + BOOST_REQUIRE(sc.provider().rollback_fragments() == 1); + + wsrep::high_priority_service* hps( + sc.find_streaming_applier( + sc.id(), wsrep::transaction_id(1))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +/////////////////////////////////////////////////////////////////////////////// +// misc // +/////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE(transaction_state_strings) +{ + BOOST_REQUIRE(wsrep::to_string( + wsrep::transaction::s_executing) == "executing"); + BOOST_REQUIRE(wsrep::to_string( + wsrep::transaction::s_preparing) == "preparing"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_certifying) == "certifying"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_committing) == "committing"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_ordered_commit) == "ordered_commit"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_committed) == "committed"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_cert_failed) == "cert_failed"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_must_abort) == "must_abort"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_aborting) == "aborting"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_aborted) == "aborted"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_must_replay) == "must_replay"); + BOOST_REQUIRE( + wsrep::to_string( + wsrep::transaction::s_replaying) == "replaying"); +} diff --git a/wsrep-lib/test/transaction_test_2pc.cpp b/wsrep-lib/test/transaction_test_2pc.cpp new file mode 100644 index 00000000..8f95828d --- /dev/null +++ b/wsrep-lib/test/transaction_test_2pc.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2018-2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "client_state_fixture.hpp" + +// +// Test a succesful 2PC transaction lifecycle +// +BOOST_FIXTURE_TEST_CASE(transaction_2pc, + replicating_client_fixture_2pc) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing); + BOOST_REQUIRE(tc.ordered()); + BOOST_REQUIRE(tc.certified()); + BOOST_REQUIRE(tc.ws_meta().gtid().is_undefined() == false); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test a 2PC transaction which gets BF aborted before before_prepare +// +BOOST_FIXTURE_TEST_CASE( + transaction_2pc_bf_before_before_prepare, + replicating_client_fixture_2pc) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(cc.before_prepare()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.after_statement() ); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error()); +} + +// +// Test a 2PC transaction which gets BF aborted before before_prepare +// +BOOST_FIXTURE_TEST_CASE( + transaction_2pc_bf_before_after_prepare, + replicating_client_fixture_2pc) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing); + wsrep_test::bf_abort_ordered(cc); + BOOST_REQUIRE(cc.after_prepare()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test a 2PC transaction which gets BF aborted after_prepare() and +// the rollback takes place before entering before_commit(). +// +BOOST_FIXTURE_TEST_CASE( + transaction_2pc_bf_after_after_prepare, + replicating_client_fixture_2pc) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + wsrep_test::bf_abort_ordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +// +// Test a 2PC transaction which gets BF aborted between after_prepare() +// and before_commit() +// +BOOST_FIXTURE_TEST_CASE( + transaction_2pc_bf_before_before_commit, + replicating_client_fixture_2pc) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + wsrep_test::bf_abort_ordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort); + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + + +// +// Test a 2PC transaction which gets BF aborted when trying to grab +// commit order. +// +BOOST_FIXTURE_TEST_CASE( + transaction_2pc_bf_during_commit_order_enter, + replicating_client_fixture_2pc) +{ + cc.start_transaction(wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(cc.after_prepare() == 0); + sc.provider().commit_order_enter_result_ = wsrep::provider::error_bf_abort; + BOOST_REQUIRE(cc.before_commit()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.will_replay_called() == true); + BOOST_REQUIRE(tc.certified() == true); + BOOST_REQUIRE(tc.ordered() == true); + sc.provider().commit_order_enter_result_ = wsrep::provider::success; + BOOST_REQUIRE(cc.before_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_rollback() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +/////////////////////////////////////////////////////////////////////////////// +// STREAMING REPLICATION // +/////////////////////////////////////////////////////////////////////////////// + + +BOOST_FIXTURE_TEST_CASE(transaction_streaming_2pc_commit, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +BOOST_FIXTURE_TEST_CASE(transaction_streaming_2pc_commit_two_statements, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(cc.before_statement() == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 2); + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(sc.provider().fragments() == 3); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} + +// +// Fragments are removed in before_prepare in running transaction context. +// However, the BF abort may arrive during this removal and the +// client_service::remove_fragments() may roll back the transaction +// internally. This will cause the transaction to leave before_prepare() +// in aborted state. +// +BOOST_FIXTURE_TEST_CASE(transaction_streaming_2pc_bf_abort_during_fragment_removal, + streaming_client_fixture_row) +{ + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + cc.bf_abort_during_fragment_removal_ = true; + BOOST_REQUIRE(cc.before_prepare()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc.after_statement()); + BOOST_REQUIRE(tc.active() == false); + wsrep_test::terminate_streaming_applier(sc, sc.id(), + wsrep::transaction_id(1)); +} + +/////////////////////////////////////////////////////////////////////////////// +// APPLYING // +/////////////////////////////////////////////////////////////////////////////// + +BOOST_FIXTURE_TEST_CASE(transaction_2pc_applying, + applying_client_fixture_2pc) +{ + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + cc.after_applying(); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} diff --git a/wsrep-lib/test/transaction_test_xa.cpp b/wsrep-lib/test/transaction_test_xa.cpp new file mode 100644 index 00000000..d9fbad27 --- /dev/null +++ b/wsrep-lib/test/transaction_test_xa.cpp @@ -0,0 +1,296 @@ +#include "client_state_fixture.hpp" +#include <iostream> + +// +// Test a successful XA transaction lifecycle +// +BOOST_FIXTURE_TEST_CASE(transaction_xa, + replicating_client_fixture_sync_rm) +{ + wsrep::xid xid(1, 9, 0, "test xid"); + + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + cc.assign_xid(xid); + + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1)); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing); + BOOST_REQUIRE(tc.ordered() == false); + // certified() only after the last fragment + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + // XA START + PREPARE fragment + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().fragments() == 1); + + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit); + BOOST_REQUIRE(tc.ordered()); + BOOST_REQUIRE(tc.certified()); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + // XA PREPARE and XA COMMIT fragments + BOOST_REQUIRE(sc.provider().fragments() == 2); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); + + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + + +// +// Test detaching of XA transactions +// +BOOST_FIXTURE_TEST_CASE(transaction_xa_detach_commit_by_xid, + replicating_two_clients_fixture_sync_rm) +{ + wsrep::xid xid(1, 1, 1, "id"); + + cc1.start_transaction(wsrep::transaction_id(1)); + cc1.assign_xid(xid); + cc1.before_prepare(); + cc1.after_prepare(); + BOOST_REQUIRE(sc.provider().fragments() == 1); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + + cc1.xa_detach(); + + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc1.after_statement() == 0); + + cc2.start_transaction(wsrep::transaction_id(2)); + cc2.assign_xid(xid); + BOOST_REQUIRE(cc2.client_state::commit_by_xid(xid) == 0); + BOOST_REQUIRE(cc2.after_statement() == 0); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); + + // xa_detach() creates a streaming applier, clean it up + wsrep::mock_high_priority_service* hps( + static_cast<wsrep::mock_high_priority_service*>( + sc.find_streaming_applier(xid))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +BOOST_FIXTURE_TEST_CASE(transaction_xa_detach_rollback_by_xid, + replicating_two_clients_fixture_sync_rm) +{ + wsrep::xid xid(1, 1, 1, "id"); + + cc1.start_transaction(wsrep::transaction_id(1)); + cc1.assign_xid(xid); + cc1.before_prepare(); + cc1.after_prepare(); + BOOST_REQUIRE(sc.provider().fragments() == 1); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + + cc1.xa_detach(); + + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted); + BOOST_REQUIRE(cc1.after_statement() == 0); + + cc2.start_transaction(wsrep::transaction_id(2)); + cc2.assign_xid(xid); + BOOST_REQUIRE(cc2.rollback_by_xid(xid) == 0); + BOOST_REQUIRE(cc2.after_statement() == 0); + BOOST_REQUIRE(sc.provider().rollback_fragments() == 1); + + // xa_detach() creates a streaming applier, clean it up + wsrep::mock_high_priority_service* hps( + static_cast<wsrep::mock_high_priority_service*>( + sc.find_streaming_applier(xid))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + + +// +// Test XA replay +// +BOOST_FIXTURE_TEST_CASE(transaction_xa_replay, + replicating_client_fixture_sync_rm) +{ + wsrep::xid xid(1, 1, 1, "id"); + + cc.start_transaction(wsrep::transaction_id(1)); + cc.assign_xid(xid); + cc.before_prepare(); + cc.after_prepare(); + cc.after_command_before_result(); + cc.after_command_after_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared); + wsrep_test::bf_abort_unordered(cc); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay); + + // this is normally done by rollbacker + cc.xa_replay(); + cc.sync_rollback_complete(); + + BOOST_REQUIRE(cc.unordered_replays() == 1); + + // xa_replay() creates a streaming applier, clean it up + wsrep::mock_high_priority_service* hps( + static_cast<wsrep::mock_high_priority_service*>( + sc.find_streaming_applier(sc.id(), wsrep::transaction_id(1)))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +BOOST_FIXTURE_TEST_CASE(transaction_xa_replay_after_command_before_result, + replicating_client_fixture_sync_rm) +{ + wsrep::xid xid(1, 1, 1, "id"); + + cc.start_transaction(wsrep::transaction_id(1)); + cc.assign_xid(xid); + cc.before_prepare(); + cc.after_prepare(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec); + wsrep_test::bf_abort_unordered(cc); + cc.after_command_before_result(); + cc.after_command_after_result(); + + BOOST_REQUIRE(cc.unordered_replays() == 1); + + // xa_replay() creates a streaming applier, clean it up + wsrep::mock_high_priority_service* hps( + static_cast<wsrep::mock_high_priority_service*>( + sc.find_streaming_applier(sc.id(), wsrep::transaction_id(1)))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +BOOST_FIXTURE_TEST_CASE(transaction_xa_replay_after_command_after_result, + replicating_client_fixture_sync_rm) +{ + wsrep::xid xid(1, 1, 1, "id"); + + cc.start_transaction(wsrep::transaction_id(1)); + cc.assign_xid(xid); + cc.before_prepare(); + cc.after_prepare(); + cc.after_command_before_result(); + BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result); + wsrep_test::bf_abort_unordered(cc); + + cc.after_command_after_result(); + + BOOST_REQUIRE(cc.unordered_replays() == 1); + + // xa_replay() creates a a streaming applier, clean it up + wsrep::mock_high_priority_service* hps( + static_cast<wsrep::mock_high_priority_service*>( + sc.find_streaming_applier(sc.id(), wsrep::transaction_id(1)))); + BOOST_REQUIRE(hps); + hps->rollback(wsrep::ws_handle(), wsrep::ws_meta()); + hps->after_apply(); + sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1)); + server_service.release_high_priority_service(hps); +} + +// +// Test a successful XA transaction lifecycle (applying side) +// +BOOST_FIXTURE_TEST_CASE(transaction_xa_applying, + applying_client_fixture) +{ + wsrep::xid xid(1, 9, 0, "test xid"); + + start_transaction(wsrep::transaction_id(1), wsrep::seqno(1)); + cc.assign_xid(xid); + + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing); + BOOST_REQUIRE(tc.ordered()); + BOOST_REQUIRE(tc.certified()); + BOOST_REQUIRE(tc.ws_meta().gtid().is_undefined() == false); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared); + + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + cc.after_applying(); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); +} + +/////////////////////////////////////////////////////////////////////////////// +// STREAMING REPLICATION // +/////////////////////////////////////////////////////////////////////////////// + +// +// Test a successful XA transaction lifecycle +// +BOOST_FIXTURE_TEST_CASE(transaction_xa_sr, + streaming_client_fixture_byte) +{ + wsrep::xid xid(1, 9, 0, "test xid"); + + BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0); + cc.assign_xid(xid); + + cc.bytes_generated_ = 1; + BOOST_REQUIRE(cc.after_row() == 0); + BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1); + // XA START fragment with data + BOOST_REQUIRE(sc.provider().fragments() == 1); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + + BOOST_REQUIRE(tc.active()); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing); + + BOOST_REQUIRE(cc.before_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.after_prepare() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared); + // XA PREPARE fragment + BOOST_REQUIRE(sc.provider().fragments() == 2); + + BOOST_REQUIRE(cc.before_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing); + BOOST_REQUIRE(cc.ordered_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit); + BOOST_REQUIRE(tc.ordered()); + BOOST_REQUIRE(tc.certified()); + BOOST_REQUIRE(cc.after_commit() == 0); + BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed); + BOOST_REQUIRE(cc.after_statement() == 0); + BOOST_REQUIRE(tc.active() == false); + BOOST_REQUIRE(tc.ordered() == false); + BOOST_REQUIRE(tc.certified() == false); + BOOST_REQUIRE(cc.current_error() == wsrep::e_success); + // XA START fragment (with data), XA PREPARE fragment and XA COMMIT fragment + BOOST_REQUIRE(sc.provider().fragments() == 3); + BOOST_REQUIRE(sc.provider().start_fragments() == 1); + BOOST_REQUIRE(sc.provider().commit_fragments() == 1); +} diff --git a/wsrep-lib/test/view_test.cpp b/wsrep-lib/test/view_test.cpp new file mode 100644 index 00000000..1cab1e7c --- /dev/null +++ b/wsrep-lib/test/view_test.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2018 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/view.hpp" +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_CASE(view_test_member_index) +{ + std::vector<wsrep::view::member> members; + members.push_back(wsrep::view::member(wsrep::id("1"), "", "")); + members.push_back(wsrep::view::member(wsrep::id("2"), "", "")); + members.push_back(wsrep::view::member(wsrep::id("3"), "", "")); + + wsrep::view view(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)), + wsrep::seqno(1), + wsrep::view::primary, + 0, + 1, + 0, + members); + BOOST_REQUIRE(view.member_index(wsrep::id("1")) == 0); + BOOST_REQUIRE(view.member_index(wsrep::id("2")) == 1); + BOOST_REQUIRE(view.member_index(wsrep::id("3")) == 2); + BOOST_REQUIRE(view.member_index(wsrep::id("4")) == -1); +} + +BOOST_AUTO_TEST_CASE(view_test_equal_membership) +{ + std::vector<wsrep::view::member> m1; + m1.push_back(wsrep::view::member(wsrep::id("1"), "", "")); + m1.push_back(wsrep::view::member(wsrep::id("2"), "", "")); + m1.push_back(wsrep::view::member(wsrep::id("3"), "", "")); + + std::vector<wsrep::view::member> m2; + m2.push_back(wsrep::view::member(wsrep::id("2"), "", "")); + m2.push_back(wsrep::view::member(wsrep::id("3"), "", "")); + m2.push_back(wsrep::view::member(wsrep::id("1"), "", "")); + + std::vector<wsrep::view::member> m3; + m3.push_back(wsrep::view::member(wsrep::id("1"), "", "")); + m3.push_back(wsrep::view::member(wsrep::id("2"), "", "")); + m3.push_back(wsrep::view::member(wsrep::id("3"), "", "")); + m3.push_back(wsrep::view::member(wsrep::id("4"), "", "")); + + wsrep::view v1(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)), + wsrep::seqno(1), + wsrep::view::primary, + 0, + 1, + 0, + m1); + + wsrep::view v2(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)), + wsrep::seqno(1), + wsrep::view::primary, + 0, + 1, + 0, + m2); + + wsrep::view v3(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)), + wsrep::seqno(1), + wsrep::view::primary, + 0, + 1, + 0, + m3); + + BOOST_REQUIRE(v1.equal_membership(v2)); + BOOST_REQUIRE(v2.equal_membership(v1)); + BOOST_REQUIRE(!v1.equal_membership(v3)); + BOOST_REQUIRE(!v3.equal_membership(v1)); +} + +BOOST_AUTO_TEST_CASE(view_test_is_member) +{ + wsrep::view view(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)), + wsrep::seqno(1), + wsrep::view::primary, + 0, + 1, + 0, + { wsrep::view::member(wsrep::id("1"), "", ""), + wsrep::view::member(wsrep::id("2"), "", "") }); + + BOOST_REQUIRE(view.is_member(wsrep::id("2"))); + BOOST_REQUIRE(view.is_member(wsrep::id("1"))); + BOOST_REQUIRE(not view.is_member(wsrep::id("0"))); +} diff --git a/wsrep-lib/test/wsrep-lib_test.cpp b/wsrep-lib/test/wsrep-lib_test.cpp new file mode 100644 index 00000000..4eedaa30 --- /dev/null +++ b/wsrep-lib/test/wsrep-lib_test.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2018-2019 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +/** @file wsrep-lib_test.cpp + * + * Run wsrep-lib unit tests. + * + * Commandline arguments: + * + * --wsrep-log-file=<file> Write log from wsrep-lib logging facility + * into <file>. If <file> is left empty, the + * log is written into stdout. + * --wsrep-debug-level=<int> Set debug level + * See wsrep::log::debug_level for valid values + */ + +#include "wsrep/logger.hpp" +#include <fstream> + +#define BOOST_TEST_ALTERNATIVE_INIT_API +#include <boost/test/included/unit_test.hpp> + +// Log file to write messages logged via wsrep-lib logging facility. +static std::string log_file_name("wsrep-lib_test.log"); +static std::ofstream log_file; +// Debug log level for wsrep-lib logging +static std::string debug_log_level; + + +static void log_fn(wsrep::log::level level, + const char* pfx, + const char* msg) +{ + log_file << wsrep::log::to_c_string(level) << " " << pfx << msg << std::endl; +} + +static bool parse_arg(const std::string& arg) +{ + const std::string delim("="); + auto delim_pos(arg.find(delim)); + const auto parm(arg.substr(0, delim_pos)); + std::string val; + if (delim_pos != std::string::npos) + { + val = arg.substr(delim_pos + 1); + } + + if (parm == "--wsrep-log-file") + { + log_file_name = val; + } + else if (parm == "--wsrep-debug-level") + { + debug_log_level = val; + } + else + { + std::cerr << "Error: Unknown argument " << arg << std::endl; + return false; + } + return true; +} + +static bool setup_env(int argc, char* argv[]) +{ + for (int i(1); i < argc; ++i) + { + if (parse_arg(argv[i]) == false) + { + return false; + } + } + + if (log_file_name.size()) + { + log_file.open(log_file_name); + if (!log_file) + { + int err(errno); + std::cerr << "Failed to open '" << log_file_name + << "': '" << ::strerror(err) << "'" << std::endl; + return false; + } + std::cout << "Writing wsrep-lib log into '" << log_file_name << "'" + << std::endl; + wsrep::log::logger_fn(log_fn); + } + + if (debug_log_level.size()) + { + int level = std::stoi(debug_log_level); + std::cout << "Setting debug level '" << level << "'" << std::endl; + wsrep::log::debug_log_level(level); + } + + return true; +} + +bool init_unit_test() +{ + return setup_env(boost::unit_test::framework::master_test_suite().argc, + boost::unit_test::framework::master_test_suite().argv); +} diff --git a/wsrep-lib/test/xid_test.cpp b/wsrep-lib/test/xid_test.cpp new file mode 100644 index 00000000..ce2ffab4 --- /dev/null +++ b/wsrep-lib/test/xid_test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 Codership Oy <info@codership.com> + * + * This file is part of wsrep-lib. + * + * Wsrep-lib is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Wsrep-lib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "wsrep/xid.hpp" +#include <boost/test/unit_test.hpp> + +BOOST_AUTO_TEST_CASE(xid_test_is_null) +{ + wsrep::xid null_xid; + BOOST_REQUIRE(null_xid.is_null()); + wsrep::xid test_xid(1,0,0,nullptr); + BOOST_REQUIRE(!test_xid.is_null()); +} + +BOOST_AUTO_TEST_CASE(xid_test_equal) +{ + wsrep::xid a(1,1,1,"ab"); + wsrep::xid b(1,1,1,"ab"); + BOOST_REQUIRE(a == b); +} + +BOOST_AUTO_TEST_CASE(xid_test_null_equal) +{ + wsrep::xid a; + wsrep::xid b; + BOOST_REQUIRE(a == b); + BOOST_REQUIRE(a.is_null()); +} + +BOOST_AUTO_TEST_CASE(xid_test_not_equal) +{ + wsrep::xid a(1,1,0,"a"); + wsrep::xid b(1,0,1,"a"); + wsrep::xid c(-1,1,0,"a"); + wsrep::xid d(1,1,0,"b"); + BOOST_REQUIRE(!(a == b)); + BOOST_REQUIRE(!(a == c)); + BOOST_REQUIRE(!(a == d)); +} + +BOOST_AUTO_TEST_CASE(xid_clear) +{ + wsrep::xid null_xid; + wsrep::xid to_clear(1, 1, 0, "a"); + to_clear.clear(); + BOOST_REQUIRE(to_clear.is_null()); + BOOST_REQUIRE(null_xid == to_clear); +} + +BOOST_AUTO_TEST_CASE(xid_to_string) +{ + wsrep::xid null_xid; + std::stringstream null_xid_str; + null_xid_str << null_xid; + BOOST_REQUIRE(null_xid_str.str() == ""); + + wsrep::xid test_xid(1,4,0,"test"); + std::string xid_str(to_string(test_xid)); + BOOST_REQUIRE(xid_str == "test"); +} + +static bool exception_check(const wsrep::runtime_error&) +{ + return true; +} + +BOOST_AUTO_TEST_CASE(xid_too_big) +{ + std::string s(65,'a'); + BOOST_REQUIRE_EXCEPTION(wsrep::xid a(1, 65, 0, s.c_str()), + wsrep::runtime_error, exception_check); + BOOST_REQUIRE_EXCEPTION(wsrep::xid b(1, 0, 65, s.c_str()), + wsrep::runtime_error, exception_check); +} |