/* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program 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, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "gcs_base_test.h" #include #include #include #include "app_data.h" #include "get_synode_app_data.h" #include "pax_msg.h" #include "xcom_base.h" #include "xcom_cache.h" #include "xcom_memory.h" #include "xcom_transport.h" namespace xcom_base_unittest { class XcomBase : public GcsBaseTest { protected: XcomBase() { ::init_cache(); } ~XcomBase() { ::deinit_cache(); } }; TEST_F(XcomBase, XcomSendClientAppDataUpgradeScenario) { app_data a; std::string address("127.0.0.1:12345"); char *names[] = {const_cast(address.c_str())}; node_address *na = new_node_address(1, names); a.body.c_t = add_node_type; a.body.app_u_u.nodes.node_list_len = 1; a.body.app_u_u.nodes.node_list_val = na; int result = 0; result = are_we_allowed_to_upgrade_to_v6(&a); ASSERT_EQ(result, 1); delete_node_address(1, na); } TEST_F(XcomBase, XcomSendClientAppDataUpgradeScenarioV6) { app_data a; std::string address("[::1]:12345"); char *names[] = {const_cast(address.c_str())}; node_address *na = new_node_address(1, names); a.body.c_t = add_node_type; a.body.app_u_u.nodes.node_list_len = 1; a.body.app_u_u.nodes.node_list_val = na; int result = 0; result = are_we_allowed_to_upgrade_to_v6(&a); ASSERT_EQ(result, 0); delete_node_address(1, na); } TEST_F(XcomBase, XcomSendClientAppDataUpgradeScenarioMalformed) { app_data a; std::string address("::1]:12345"); char *names[] = {const_cast(address.c_str())}; node_address *na = new_node_address(1, names); a.body.c_t = add_node_type; a.body.app_u_u.nodes.node_list_len = 1; a.body.app_u_u.nodes.node_list_val = na; int result = 0; result = are_we_allowed_to_upgrade_to_v6(&a); ASSERT_EQ(result, 0); delete_node_address(1, na); } TEST_F(XcomBase, XcomNewClientEligibleDowngradeScenario) { std::string address("127.0.0.1:12345"); char *names[] = {const_cast(address.c_str())}; node_address *na = new_node_address(1, names); site_def *sd = new_site_def(); init_site_def(1, na, sd); xcom_proto incoming = x_1_4; int result = is_new_node_eligible_for_ipv6(incoming, sd); ASSERT_EQ(result, 0); free_site_def(sd); delete_node_address(1, na); } TEST_F(XcomBase, XcomNewClientEligibleDowngradeScenarioFail) { std::string address("[::1]:12345"); char *names[] = {const_cast(address.c_str())}; node_address *na = new_node_address(1, names); site_def *sd = new_site_def(); init_site_def(1, na, sd); xcom_proto incoming = x_1_4; int result = is_new_node_eligible_for_ipv6(incoming, sd); ASSERT_EQ(result, 1); free_site_def(sd); delete_node_address(1, na); } TEST_F(XcomBase, XcomNewClientEligibleDowngradeScenarioNullSiteDef) { xcom_proto incoming = x_1_4; int result = is_new_node_eligible_for_ipv6(incoming, NULL); ASSERT_EQ(result, 0); } TEST_F(XcomBase, XcomNewClientEligibleDowngradeScenarioVersionSame) { xcom_proto incoming = my_xcom_version; int result = is_new_node_eligible_for_ipv6(incoming, NULL); ASSERT_EQ(result, 0); } TEST_F(XcomBase, GetSynodeAppDataNotCached) { synode_no synode; synode.group_id = 12345; synode.msgno = 0; synode.node = 0; synode_no_array synodes; synodes.synode_no_array_len = 1; synodes.synode_no_array_val = &synode; synode_app_data_array result; result.synode_app_data_array_len = 0; result.synode_app_data_array_val = NULL; auto error_code = ::xcom_get_synode_app_data(&synodes, &result); ASSERT_EQ(error_code, XCOM_GET_SYNODE_APP_DATA_NOT_CACHED); } TEST_F(XcomBase, GetSynodeAppDataNotDecided) { synode_no synode; synode.group_id = 12345; synode.msgno = 0; synode.node = 0; synode_no_array synodes; synodes.synode_no_array_len = 1; synodes.synode_no_array_val = &synode; synode_app_data_array result; result.synode_app_data_array_len = 0; result.synode_app_data_array_val = NULL; /* Add the synode to the cache, but it is undecided. */ get_cache(synode); auto error_code = ::xcom_get_synode_app_data(&synodes, &result); ASSERT_EQ(error_code, XCOM_GET_SYNODE_APP_DATA_NOT_DECIDED); } TEST_F(XcomBase, GetSynodeAppDataSuccessful) { synode_no synode; synode.group_id = 12345; synode.msgno = 0; synode.node = 0; synode_no_array synodes; synodes.synode_no_array_len = 1; synodes.synode_no_array_val = &synode; synode_app_data_array result; result.synode_app_data_array_len = 0; result.synode_app_data_array_val = NULL; /* Add the synode to the cache, and set it as decided. */ char const *const payload = "Message in a bottle"; app_data_ptr a = new_app_data(); a->body.c_t = app_type; a->body.app_u_u.data.data_len = std::strlen(payload) + 1; a->body.app_u_u.data.data_val = const_cast(payload); pax_msg *p = pax_msg_new_0(synode); p->op = learn_op; p->a = a; p->refcnt = 1; pax_machine *paxos = get_cache(synode); paxos->learner.msg = p; auto error_code = ::xcom_get_synode_app_data(&synodes, &result); ASSERT_EQ(error_code, XCOM_GET_SYNODE_APP_DATA_OK); ASSERT_EQ(result.synode_app_data_array_len, 1); ASSERT_EQ(synode_eq(result.synode_app_data_array_val[0].synode, synode), 1); ASSERT_EQ(result.synode_app_data_array_val[0].data.data_len, p->a->body.app_u_u.data.data_len); ASSERT_EQ(std::strcmp(result.synode_app_data_array_val[0].data.data_val, p->a->body.app_u_u.data.data_val), 0); /* Cleanup */ a->body.app_u_u.data.data_len = 0; a->body.app_u_u.data.data_val = nullptr; unchecked_replace_pax_msg(&paxos->learner.msg, nullptr); my_xdr_free(reinterpret_cast(xdr_synode_app_data_array), reinterpret_cast(&result)); } TEST_F(XcomBase, GetSynodeAppDataTooManySynodes) { /* Bypass protocol negotiation because we are not actually connected to anything. */ connection_descriptor con; con.connected_ = CON_PROTO; con.x_proto = get_latest_common_proto(); constexpr uint32_t group_id = 1; /* Unserializable message. Exceeds array size. */ u_int constexpr nr_synodes = MAX_SYNODE_ARRAY + 1; synode_no_array synodes; synodes.synode_no_array_len = nr_synodes; synodes.synode_no_array_val = static_cast(std::calloc(nr_synodes, sizeof(synode_no))); ASSERT_NE(synodes.synode_no_array_val, nullptr); synode_app_data_array reply; ASSERT_EQ(xcom_client_get_synode_app_data(&con, group_id, &synodes, &reply), 0); std::free(synodes.synode_no_array_val); } /* Disable on Windows. The test outcome varies wildly on our test environment, * likely due to different configurations of the stack size. */ #if !defined(_WIN32) TEST_F(XcomBase, ProposerBatchDeserialization) { pax_msg *p = nullptr; unchecked_replace_pax_msg(&p, pax_msg_new_0(null_synode)); for (auto i = 0; i < MAX_BATCH_APP_DATA; i++) { app_data_ptr a = new_app_data(); a->body.c_t = app_type; a->next = p->a; p->a = a; } char *buffer = nullptr; uint32_t buffer_len = 0; int const serialized = serialize_msg(p, get_latest_common_proto(), &buffer_len, &buffer); ASSERT_EQ(serialized, 1); unchecked_replace_pax_msg(&p, pax_msg_new_0(null_synode)); int const deserialized = deserialize_msg(p, get_latest_common_proto(), buffer, buffer_len); ASSERT_EQ(deserialized, 1); std::free(p); std::free(buffer); } #endif // !defined(_WIN32) // clang-format off /* This test validates the fix of Bug #28966455 APPLIER LOG MISSES A TRANSACTION IN GR. Situation --------- S0 is the leader, trying to get consensus on a transaction T for its own slot. S1 is the follower, trying to take over the slot and get consensus on a no_op. Legend ------ SX: Server X PX: Proposer of SX ALX: Acceptor/learner of SX O: Event on the respective server X->: Message sent from the server on X to the server on -> ###: Comment/observation E: The part where P1 is deviating from the Paxos protocol Diagram ------- S0 S1 S2 P0 AL0 P1 AL1 AL2 | O | O O AL{0,1,2}.promise = (0,0) | | | | | ############################ P0 starts trying consensus for T | | | | | O | | | | P0.ballot = (0,0); P0.value = T X--->|-------|--->|------->| accept_op[ballot=(0,0), | | | | | value=T (P0.value)] | | | | | | O | O O AL{0,1,2}.value = T |<---X-------|----X--------X ack_accept_op[ballot=(0,0)] | | | | | ############################ P0 got majority of accepts for (0,0) T | | | | | ############################ P1 starts trying consensus for no_op | | | | | | | O | | P1.ballot = (1,1); P1.value = no_op | |<------X--->|------->| prepare_op[ballot=(1,1)] | | | | | | O | O O AL{0,1,2}.promise = (1,1) | X------>|<---X--------X ack_prepare_op[ballot=(1,1), | | | | | accepted={(0,0) T}] | | | | | ############################ P1 got a majority of prepares for (1,1) | | | | | | | E | | P1.value should be set to T here. | | E | | According to the Paxos protocol, if any | | E | | acceptor replies with a previously | | E | | accepted value, one must use it. But | | E | | handle_ack_prepare did not do it because | | E | | handle_ack_prepare has the following code: | | E | | | | E | | if (gt_ballot(m->proposal, | | E | | p->proposer.msg->proposal)) | | E | | { | | E | | replace_pax_msg(&p->proposer.msg, m); | | E | | ... | | E | | } | | E | | | | E | | And p->proposer.msg->proposal was | | E | | initialized to (0,1) on P1, meaning that: | | E | | | | E | | if (0,0) > (0,1): P1.value = T | | E | | | | E | | Therefore, P1.value = no_op. | | E | | (see handle_ack_prepare) | | | | | | | ...--X--->|------->| accept_op[ballot=(1,1), | | | | | value=no_op (P1.value)] | | | | | | | | O O AL{1,2}.value = no_op | | |<---X--------X ack_accept_op[ballot=(1,1)] | | | | | ############################ P1 got majority of accepts for (1,1) no_op ############################ Values accepted for P{0,1} don't agree | | | | | | | ...--X--->|------->| tiny_learn_op[ballot=(1,1), no_op] | | | | | | | | O O AL{1,2} learn no_op | | | O O Executor task of S{1,2} delivers no_op | | | | | X--->|--... | | | tiny_learn_op[ballot=(0,0)] | | | | | | O | | | AL0 learns T | O | | | Executor task of S0 delivers T | | | | | ############################ S0 delivered T, S{1,2} delivered no_op | | | | | */ // clang-format on TEST_F(XcomBase, PaxosLearnSameValue) { // Synod (42, 0). synode_no synod; synod.group_id = 1; synod.msgno = 42; synod.node = 0; // pax_machine for each "server." auto *s0_paxos = static_cast(std::calloc(1, sizeof(pax_machine))); init_pax_machine(s0_paxos, nullptr, synod); auto *s1_paxos = static_cast(std::calloc(1, sizeof(pax_machine))); init_pax_machine(s1_paxos, nullptr, synod); auto *s2_paxos = static_cast(std::calloc(1, sizeof(pax_machine))); init_pax_machine(s2_paxos, nullptr, synod); // site_def for each "server." auto *s0_config = new_site_def(); s0_config->nodeno = 0; s0_config->nodes.node_list_len = 3; s0_config->global_node_set.node_set_len = 3; auto *s1_config = new_site_def(); s1_config->nodeno = 1; s1_config->nodes.node_list_len = 3; s1_config->global_node_set.node_set_len = 3; auto *s2_config = new_site_def(); s2_config->nodeno = 2; s2_config->nodes.node_list_len = 3; s2_config->global_node_set.node_set_len = 3; // clang-format off /**************************************************************************** S0 S1 S2 P0 AL0 P1 AL1 AL2 | | | | | O | | | | P0.ballot = (0,0); P0.value = T X--->|-------|--->|------->| accept_op[ballot=(0,0), | | | | | value=T (P0.value)] | | | | | | O | O O AL{0,1,2}.value = T |<---X-------|----X--------X ack_accept_op[ballot=(0,0)] | | | | | ****************************************************************************/ // clang-format on pax_msg *tx = pax_msg_new(synod, nullptr); tx->a = new_app_data(); tx->a->body.c_t = app_type; tx->a->body.app_u_u.data.data_len = 1; tx->a->body.app_u_u.data.data_val = static_cast(malloc(sizeof(char))); replace_pax_msg(&s0_paxos->proposer.msg, tx); prepare_push_2p(s0_config, s0_paxos); pax_msg *s0_accept_tx = s0_paxos->proposer.msg; init_propose_msg(s0_accept_tx); s0_accept_tx->from = 0; ballot ballot_tx; ballot_tx.cnt = 0; ballot_tx.node = 0; ASSERT_TRUE(eq_ballot(s0_paxos->proposer.msg->proposal, ballot_tx)); // S0 sends s0_accept_tx to AL{0,1,2} // AL{0,1,2} receive s0_accept_tx pax_msg *s0_accept_tx_s0 = clone_pax_msg(s0_accept_tx); pax_msg *s0_ack_accept_tx = handle_simple_accept(s0_paxos, s0_accept_tx_s0, s0_accept_tx_s0->synode); ASSERT_NE(nullptr, s0_ack_accept_tx); s0_ack_accept_tx->from = 0; pax_msg *s0_accept_tx_s1 = clone_pax_msg(s0_accept_tx); pax_msg *s1_ack_accept_tx = handle_simple_accept(s1_paxos, s0_accept_tx_s1, s0_accept_tx_s1->synode); ASSERT_NE(nullptr, s1_ack_accept_tx); s1_ack_accept_tx->from = 1; pax_msg *s0_accept_tx_s2 = clone_pax_msg(s0_accept_tx); pax_msg *s2_ack_accept_tx = handle_simple_accept(s2_paxos, s0_accept_tx_s2, s0_accept_tx_s2->synode); ASSERT_NE(nullptr, s2_ack_accept_tx); s2_ack_accept_tx->from = 2; // AL{0,1,2} send s{0,1,2}_ack_accept_tx to P0 // P0 receives s{0,1,2}_ack_accept_tx pax_msg *s0_ack_accept_tx_s0 = clone_pax_msg(s0_ack_accept_tx); ASSERT_EQ(nullptr, handle_simple_ack_accept(s0_config, s0_paxos, s0_ack_accept_tx_s0)); pax_msg *s1_ack_accept_tx_s0 = clone_pax_msg(s1_ack_accept_tx); pax_msg *s0_learn_tx = handle_simple_ack_accept(s0_config, s0_paxos, s1_ack_accept_tx_s0); ASSERT_NE(nullptr, s0_learn_tx); ASSERT_EQ(tiny_learn_op, s0_learn_tx->op); s0_learn_tx->from = 0; pax_msg *s2_ack_accept_tx_s0 = clone_pax_msg(s2_ack_accept_tx); ASSERT_EQ(nullptr, handle_simple_ack_accept(s0_config, s0_paxos, s2_ack_accept_tx_s0)); // clang-format off /**************************************************************************** S0 S1 S2 P0 AL0 P1 AL1 AL2 | | | | | | | O | | P1.ballot = (1,1); P1.value = no_op | |<------X--->|------->| prepare_op[ballot=(1,1)] | | | | | | | | | | | O | O O AL{0,1,2}.promise = (1,1) | X------>|<---X--------X ack_prepare_op[ballot=(1,1), | | | | | accepted={(0,0) T}] | | | | | ****************************************************************************/ // clang-format on replace_pax_msg(&s1_paxos->proposer.msg, pax_msg_new(synod, s1_config)); create_noop(s1_paxos->proposer.msg); pax_msg *s1_prepare_noop = clone_pax_msg(s1_paxos->proposer.msg); prepare_push_3p(s1_config, s1_paxos, s1_prepare_noop, synod, no_op); init_prepare_msg(s1_prepare_noop); s1_prepare_noop->from = 1; ballot ballot_noop; ballot_noop.cnt = 1; ballot_noop.node = 1; ASSERT_TRUE(eq_ballot(s1_prepare_noop->proposal, ballot_noop)); // P1 sends s1_prepare_noop to AL{0,1,2} // AL{0,1,2} receive s1_prepare_noop pax_msg *s1_prepare_noop_s0 = clone_pax_msg(s1_prepare_noop); pax_msg *s0_ack_prepare_noop = handle_simple_prepare(s0_paxos, s1_prepare_noop_s0, synod); ASSERT_NE(nullptr, s0_ack_prepare_noop); ASSERT_EQ(ack_prepare_op, s0_ack_prepare_noop->op); ASSERT_TRUE(eq_ballot(s0_ack_prepare_noop->proposal, ballot_tx)); ASSERT_EQ(normal, s0_ack_prepare_noop->msg_type); s0_ack_prepare_noop->from = 0; pax_msg *s1_prepare_noop_s1 = clone_pax_msg(s1_prepare_noop); pax_msg *s1_ack_prepare_noop = handle_simple_prepare(s1_paxos, s1_prepare_noop_s1, synod); ASSERT_NE(nullptr, s1_ack_prepare_noop); ASSERT_EQ(ack_prepare_op, s1_ack_prepare_noop->op); ASSERT_TRUE(eq_ballot(s1_ack_prepare_noop->proposal, ballot_tx)); ASSERT_EQ(normal, s1_ack_prepare_noop->msg_type); s1_ack_prepare_noop->from = 1; pax_msg *s1_prepare_noop_s2 = clone_pax_msg(s1_prepare_noop); pax_msg *s2_ack_prepare_noop = handle_simple_prepare(s2_paxos, s1_prepare_noop_s2, synod); ASSERT_NE(nullptr, s2_ack_prepare_noop); ASSERT_EQ(ack_prepare_op, s2_ack_prepare_noop->op); ASSERT_TRUE(eq_ballot(s2_ack_prepare_noop->proposal, ballot_tx)); ASSERT_EQ(normal, s2_ack_prepare_noop->msg_type); s2_ack_prepare_noop->from = 2; // AL{0,1,2} send s{0,1,2}_ack_prepare_noop to P1 // P1 receives s{0,1,2}_ack_prepare_noop pax_msg *s0_ack_prepare_noop_s1 = clone_pax_msg(s0_ack_prepare_noop); ASSERT_FALSE( handle_simple_ack_prepare(s1_config, s1_paxos, s0_ack_prepare_noop_s1)); pax_msg *s1_ack_prepare_noop_s1 = clone_pax_msg(s1_ack_prepare_noop); bool can_send_accept = handle_simple_ack_prepare(s1_config, s1_paxos, s1_ack_prepare_noop_s1); ASSERT_TRUE(can_send_accept); pax_msg *s1_accept_noop = s1_paxos->proposer.msg; ASSERT_NE(nullptr, s1_accept_noop); ASSERT_EQ(accept_op, s1_accept_noop->op); s1_accept_noop->from = 1; pax_msg *s2_ack_prepare_noop_s1 = clone_pax_msg(s2_ack_prepare_noop); ASSERT_FALSE( handle_simple_ack_prepare(s1_config, s1_paxos, s2_ack_prepare_noop_s1)); // clang-format off /**************************************************************************** S0 S1 S2 P0 AL0 P1 AL1 AL2 | | | | | | | E | | P1.value should be set to T here. | | E | | According to the Paxos protocol, if any | | E | | acceptor replies with a previously | | E | | accepted value, one must use it. But | | E | | handle_ack_prepare will not do it because | | E | | handle_ack_prepare has the following code: | | E | | | | E | | if (gt_ballot(m->proposal, | | E | | p->proposer.msg->proposal)) | | E | | { | | E | | replace_pax_msg(&p->proposer.msg, m); | | E | | ... | | E | | } | | E | | | | E | | However, p->proposer.msg->proposal is initialized | | E | | to (0,1) on P1, meaning that: | | E | | | | E | | if (0,0) > (0,1): P1.value = no_op | | E | | | | E | | Therefore, P1.value = no_op. | | E | | (see handle_ack_prepare) | | | | | | | ...--X--->|------->| accept_op[ballot=(1,1), | | | | | value=no_op (P1.value)] | | | | | | | | O O AL{0,1,2}.value = no_op | | |<---X--------X ack_accept_op[ballot=(1,1)] | | | | | | | | | | | | ...--X--->|------->| tiny_learn_op[ballot=(1,1), no_op] | | | | | | | | O O AL{1,2} learn no_op | | | O O Executor task of S{1,2} delivers no_op | | | | | ****************************************************************************/ // clang-format on /* Here was the problem. P1 should have inherited T from one of AL{0,1,2}. But it did not because, s1_paxos->proposer.msg->proposal was initialized to (0,1). This lead to gt_ballot(m->proposal, p->proposer.msg->proposal) being false: gt_ballot(m->proposal, p->proposer.msg->proposal) <=> gt_ballot((0,0), (0,1)) <=> false The assert below would fire on mysql-trunk before the fix for Bug #28966455 APPLIER LOG MISSES A TRANSACTION IN GR. */ // assert(s1_paxos->proposer.msg->msg_type != no_op); // P1 sends s1_accept_noop to AL{1,2} // P1 alsos sends s1_accept_noop to AL0, but it gets delayed // AL{1,2} receive s1_accept_noop pax_msg *s1_ack_accept_noop = handle_simple_accept(s1_paxos, clone_pax_msg(s1_accept_noop), synod); s1_ack_accept_noop->from = 1; pax_msg *s2_ack_accept_noop = handle_simple_accept(s2_paxos, clone_pax_msg(s1_accept_noop), synod); s2_ack_accept_noop->from = 2; // AL{1,2} send s{1,2}_ack_accept_noop to P1 // P1 receives s{1,2}_ack_accept_noop pax_msg *s1_ack_accept_noop_s1 = clone_pax_msg(s1_ack_accept_noop); ASSERT_EQ(nullptr, handle_simple_ack_accept(s1_config, s1_paxos, s1_ack_accept_noop_s1)); pax_msg *s2_ack_accept_noop_s1 = clone_pax_msg(s2_ack_accept_noop); pax_msg *s1_learn_noop = handle_simple_ack_accept(s1_config, s1_paxos, s2_ack_accept_noop_s1); ASSERT_NE(nullptr, s1_learn_noop); ASSERT_EQ(tiny_learn_op, s1_learn_noop->op); s1_learn_noop->from = 1; // P1 sends s1_learn_noop to AL{1,2} // P1 alsos sends s1_learn_noop to AL0, but it gets delayed // AL{1,2} receive s1_learn_noop pax_msg *s1_learn_noop_s1 = clone_pax_msg(s1_learn_noop); handle_learn(s1_config, s1_paxos, s1_learn_noop_s1); pax_msg *s1_learn_noop_s2 = clone_pax_msg(s1_learn_noop); handle_learn(s2_config, s2_paxos, s1_learn_noop_s2); ASSERT_TRUE(pm_finished(s1_paxos)); ASSERT_TRUE(pm_finished(s2_paxos)); // S1 and S2 would deliver no_op... (but deliver tx after the fix) // clang-format off /**************************************************************************** S0 S1 S2 P0 AL0 P1 AL1 AL2 | | | | | X--->|--... | | | tiny_learn_op[ballot=(0,0)] | | | | | | O | | | AL0 learns T | O | | | Executor task of S0 delivers T | | | | | ****************************************************************************/ // clang-format on // P0 sends s0_learn_tx to AL0 // P1 alsos sends s0_learn_tx to AL{1,2}, but it doesn't matter // AL0 receive s0_learn_tx pax_msg *s0_learn_tx_s0 = clone_pax_msg(s0_learn_tx); handle_tiny_learn(s0_config, s0_paxos, s0_learn_tx_s0); ASSERT_TRUE(pm_finished(s0_paxos)); // ...and S0 delivers tx bool const every_executor_delivered_same_value = (s0_paxos->learner.msg->msg_type == s1_paxos->learner.msg->msg_type && s1_paxos->learner.msg->msg_type == s2_paxos->learner.msg->msg_type); ASSERT_TRUE(every_executor_delivered_same_value); // Cleanup. auto free_pax_msg = [](pax_msg *p) { p->refcnt = 1; replace_pax_msg(&p, nullptr); }; init_pax_machine(s0_paxos, nullptr, synod); std::free(s0_paxos->proposer.prep_nodeset->bits.bits_val); std::free(s0_paxos->proposer.prep_nodeset); std::free(s0_paxos->proposer.prop_nodeset->bits.bits_val); std::free(s0_paxos->proposer.prop_nodeset); std::free(s0_paxos); std::free(s0_config); free_pax_msg(s0_ack_accept_tx); free_pax_msg(s0_ack_accept_tx_s0); free_pax_msg(s0_learn_tx); free_pax_msg(s1_prepare_noop_s0); free_pax_msg(s0_ack_prepare_noop); free_pax_msg(s0_learn_tx_s0); init_pax_machine(s1_paxos, nullptr, synod); std::free(s1_paxos->proposer.prep_nodeset->bits.bits_val); std::free(s1_paxos->proposer.prep_nodeset); std::free(s1_paxos->proposer.prop_nodeset->bits.bits_val); std::free(s1_paxos->proposer.prop_nodeset); std::free(s1_paxos); std::free(s1_config); free_pax_msg(s1_ack_accept_tx); free_pax_msg(s1_ack_accept_tx_s0); free_pax_msg(s1_prepare_noop); free_pax_msg(s1_prepare_noop_s1); free_pax_msg(s1_ack_prepare_noop); free_pax_msg(s1_ack_prepare_noop_s1); free_pax_msg(s2_ack_prepare_noop_s1); free_pax_msg(s1_ack_accept_noop); free_pax_msg(s1_ack_accept_noop_s1); free_pax_msg(s2_ack_accept_noop_s1); free_pax_msg(s1_learn_noop); init_pax_machine(s2_paxos, nullptr, synod); std::free(s2_paxos->proposer.prep_nodeset->bits.bits_val); std::free(s2_paxos->proposer.prep_nodeset); std::free(s2_paxos->proposer.prop_nodeset->bits.bits_val); std::free(s2_paxos->proposer.prop_nodeset); std::free(s2_paxos); std::free(s2_config); free_pax_msg(s2_ack_accept_tx); free_pax_msg(s2_ack_accept_tx_s0); free_pax_msg(s1_prepare_noop_s2); free_pax_msg(s2_ack_prepare_noop); free_pax_msg(s2_ack_accept_noop); } TEST_F(XcomBase, HandleBootWithoutIdentity) { // Synod (42, 0). synode_no synod; synod.group_id = 1; synod.msgno = 42; synod.node = 0; // Fake node information. char *name = const_cast("127.0.0.1:10001"); char *names[] = {name}; blob uuid; uuid.data.data_len = 1; uuid.data.data_val = const_cast("1"); blob uuids[] = {uuid}; // site_def for the "server." auto *config = new_site_def(); config->nodeno = 0; config->nodes.node_list_len = 1; config->nodes.node_list_val = ::new_node_address_uuid(1, names, uuids); pax_msg *need_boot = pax_msg_new(synod, nullptr); // need_boot_op without an identity. init_need_boot_op(need_boot, nullptr); ASSERT_TRUE(should_handle_boot(config, need_boot)); // Cleanup. need_boot->refcnt = 1; replace_pax_msg(&need_boot, nullptr); ::delete_node_address(1, config->nodes.node_list_val); std::free(config); } TEST_F(XcomBase, HandleBootWithIdentityOfExistingMember) { // Synod (42, 0). synode_no synod; synod.group_id = 1; synod.msgno = 42; synod.node = 0; // Fake node information. char *name = const_cast("127.0.0.1:10001"); char *names[] = {name}; blob uuid; uuid.data.data_len = 1; uuid.data.data_val = const_cast("1"); blob uuids[] = {uuid}; // site_def for the "server." auto *config = new_site_def(); config->nodeno = 0; config->nodes.node_list_len = 1; config->nodes.node_list_val = ::new_node_address_uuid(1, names, uuids); pax_msg *need_boot = pax_msg_new(synod, nullptr); // need_boot_op with an identity. node_address *identity = ::new_node_address_uuid(1, names, uuids); init_need_boot_op(need_boot, identity); ASSERT_TRUE(should_handle_boot(config, need_boot)); // Cleanup. need_boot->refcnt = 1; replace_pax_msg(&need_boot, nullptr); ::delete_node_address(1, config->nodes.node_list_val); ::delete_node_address(1, identity); std::free(config); } TEST_F(XcomBase, HandleBootWithIdentityOfNonExistingMember) { // Synod (42, 0). synode_no synod; synod.group_id = 1; synod.msgno = 42; synod.node = 0; // Fake node information. char *name = const_cast("127.0.0.1:10001"); char *names[] = {name}; blob uuid; uuid.data.data_len = 1; uuid.data.data_val = const_cast("1"); blob uuids[] = {uuid}; // site_def for the "server." auto *config = new_site_def(); config->nodeno = 0; config->nodes.node_list_len = 1; config->nodes.node_list_val = ::new_node_address_uuid(1, names, uuids); pax_msg *need_boot = pax_msg_new(synod, nullptr); // need_boot_op with an identity. blob unknown_uuid; unknown_uuid.data.data_len = 1; unknown_uuid.data.data_val = const_cast("2"); blob unknown_uuids[] = {unknown_uuid}; node_address *identity = ::new_node_address_uuid(1, names, unknown_uuids); init_need_boot_op(need_boot, identity); ASSERT_FALSE(should_handle_boot(config, need_boot)); // Cleanup. need_boot->refcnt = 1; replace_pax_msg(&need_boot, nullptr); ::delete_node_address(1, config->nodes.node_list_val); ::delete_node_address(1, identity); std::free(config); } TEST_F(XcomBase, HandleBootWithMoreThanOneIdentity) { // Synod (42, 0). synode_no synod; synod.group_id = 1; synod.msgno = 42; synod.node = 0; // Fake node information. char *name = const_cast("127.0.0.1:10001"); char *names[] = {name}; blob uuid; uuid.data.data_len = 1; uuid.data.data_val = const_cast("1"); blob uuids[] = {uuid}; // site_def for the "server." auto *config = new_site_def(); config->nodeno = 0; config->nodes.node_list_len = 1; config->nodes.node_list_val = ::new_node_address_uuid(1, names, uuids); pax_msg *need_boot = pax_msg_new(synod, nullptr); // need_boot_op with two identities. char *other_name = const_cast("127.0.0.1:10002"); char *two_names[] = {name, other_name}; blob two_uuids[] = {uuid, uuid}; node_address *identity = ::new_node_address_uuid(2, two_names, two_uuids); need_boot->op = need_boot_op; if (identity != NULL) { need_boot->a = new_app_data(); need_boot->a->body.c_t = xcom_boot_type; init_node_list(2, identity, &need_boot->a->body.app_u_u.nodes); } ASSERT_FALSE(should_handle_boot(config, need_boot)); // Cleanup. need_boot->refcnt = 1; replace_pax_msg(&need_boot, nullptr); ::delete_node_address(1, config->nodes.node_list_val); ::delete_node_address(2, identity); std::free(config); } } // namespace xcom_base_unittest