From 0328f32f361498b00e1d6517693488f19770b2c4 Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Mon, 28 Oct 2019 13:30:41 -0400 Subject: [PATCH 01/42] CPP-827 Port future callback tests to Google test framework (#311) --- .../src/integration/objects/cluster.hpp | 17 ++ .../src/integration/objects/session.hpp | 7 + .../src/integration/tests/test_basics.cpp | 88 ++++++++++ .../src/test_future_callbacks.cpp | 154 ------------------ 4 files changed, 112 insertions(+), 154 deletions(-) delete mode 100644 cpp-driver/test/integration_tests/src/test_future_callbacks.cpp diff --git a/cpp-driver/gtests/src/integration/objects/cluster.hpp b/cpp-driver/gtests/src/integration/objects/cluster.hpp index 07949e300..52b5efc57 100644 --- a/cpp-driver/gtests/src/integration/objects/cluster.hpp +++ b/cpp-driver/gtests/src/integration/objects/cluster.hpp @@ -362,6 +362,23 @@ class Cluster : public Object { Session connect(const std::string& keyspace = "", bool assert_ok = true) { return Session::connect(get(), keyspace, assert_ok); } + + /** + * Asynchronously connect the provided session with the settings of this cluster object. + * + * @param session The session to connect. + * @param keyspace Keyspace to use (default: None) + * @return A future to track the connection process of the session. + */ + Future connect_async(Session& session, const std::string& keyspace = "") { + Future future; + if (keyspace.empty()) { + future = cass_session_connect(session.get(), get()); + } else { + future = cass_session_connect_keyspace(session.get(), get(), keyspace.c_str()); + } + return future; + } }; }} // namespace test::driver diff --git a/cpp-driver/gtests/src/integration/objects/session.hpp b/cpp-driver/gtests/src/integration/objects/session.hpp index f15e3e2f6..27bc5246b 100644 --- a/cpp-driver/gtests/src/integration/objects/session.hpp +++ b/cpp-driver/gtests/src/integration/objects/session.hpp @@ -77,6 +77,13 @@ class Session : public Object { close_future.wait(assert_ok); } + /** + * Asynchronously close the session. + * + * @return A future to track the closing process of the session. + */ + Future close_async() { return cass_session_close(get()); } + /** * Get the error code that occurred during the connection * diff --git a/cpp-driver/gtests/src/integration/tests/test_basics.cpp b/cpp-driver/gtests/src/integration/tests/test_basics.cpp index c8cf42d0d..edb755c98 100644 --- a/cpp-driver/gtests/src/integration/tests/test_basics.cpp +++ b/cpp-driver/gtests/src/integration/tests/test_basics.cpp @@ -396,3 +396,91 @@ CASSANDRA_INTEGRATION_TEST_F(BasicsTests, NoCompactEnabledConnection) { ASSERT_TRUE(row.next().as().is_null()); } } + +static void on_future_callback_connect_close(CassFuture* future, void* data) { + bool* is_success = static_cast(data); + *is_success = cass_future_error_code(future) == CASS_OK; +} + +/** + * Verify a future callback is called when connecting a session. + * + * @expected_result The flag is set correctly inside the connect future callback. + */ +CASSANDRA_INTEGRATION_TEST_F(BasicsTests, FutureCallbackConnect) { + CHECK_FAILURE; + + Session session; + Future future = default_cluster().connect_async(session); + + bool is_success = false; + cass_future_set_callback(future.get(), on_future_callback_connect_close, &is_success); + + future.wait(); + + EXPECT_TRUE(is_success); +} + +/** + * Verify a future callback is called when closing a session. + * + * @expected_result The flag is set correctly inside the close future callback. + */ +CASSANDRA_INTEGRATION_TEST_F(BasicsTests, FutureCallbackClose) { + CHECK_FAILURE; + + Session session = default_cluster().connect(); + + Future future = session.close_async(); + + bool is_success = false; + cass_future_set_callback(future.get(), on_future_callback_connect_close, &is_success); + + future.wait(); + + EXPECT_TRUE(is_success); +} + +static void on_future_callback_result(CassFuture* future, void* data) { + const CassResult** result = static_cast(data); + *result = cass_future_get_result(future); +} + +/** + * Verify a future callback is called with query results. + * + * @expected_result Result correctly returned in the future callback. + */ +CASSANDRA_INTEGRATION_TEST_F(BasicsTests, FutureCallbackResult) { + CHECK_FAILURE; + + Future future = session_.execute_async(SELECT_ALL_SYSTEM_LOCAL_CQL); + + const CassResult* result = NULL; + cass_future_set_callback(future.get(), on_future_callback_result, &result); + + future.wait(); + + ASSERT_TRUE(result != NULL); + EXPECT_EQ(1u, Result(result).row_count()); +} + +/** + * Verify a future callback is called correctly after the query results have been set. + * + * @expected_result Result correctly returned in the future callback. + */ +CASSANDRA_INTEGRATION_TEST_F(BasicsTests, FutureCallbackAfterSet) { + CHECK_FAILURE; + + Future future = session_.execute_async(SELECT_ALL_SYSTEM_LOCAL_CQL); + + future.wait(); // Wait for result before setting the callback + + const CassResult* result = NULL; + // Callback should be called immediately with the already retrieved result. + cass_future_set_callback(future.get(), on_future_callback_result, &result); + + ASSERT_TRUE(result != NULL); + EXPECT_EQ(1u, Result(result).row_count()); +} diff --git a/cpp-driver/test/integration_tests/src/test_future_callbacks.cpp b/cpp-driver/test/integration_tests/src/test_future_callbacks.cpp deleted file mode 100644 index 41fa5f6ac..000000000 --- a/cpp-driver/test/integration_tests/src/test_future_callbacks.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cassandra.h" -#include "test_utils.hpp" - -#include - -namespace { - -struct CallbackData { - CallbackData(CassSession* session = NULL) - : was_called(false) - , row_count(0) - , cass_session(session) {} - - void wait() { - boost::unique_lock lock(mutex); - while (!was_called) { - cond.wait(lock); - } - } - - void notify() { - boost::unique_lock lock(mutex); - was_called = true; - cond.notify_one(); - } - - boost::mutex mutex; - boost::condition cond; - bool was_called; - size_t row_count; - CassSession* cass_session; -}; - -void check_callback(CassFuture* future, void* data) { - CallbackData* callback_data = reinterpret_cast(data); - callback_data->notify(); -} - -void check_result_callback(CassFuture* future, void* data) { - CallbackData* callback_data = reinterpret_cast(data); - - test_utils::CassResultPtr result(cass_future_get_result(future)); - - if (result) { - boost::unique_lock lock(callback_data->mutex); - callback_data->row_count = cass_result_row_count(result.get()); - } - - callback_data->notify(); -} - -} // namespace - -struct FutureCallbacksTests : public test_utils::MultipleNodesTest { - FutureCallbacksTests() - : test_utils::MultipleNodesTest(1, 0) {} -}; - -BOOST_FIXTURE_TEST_SUITE(future_callbacks, FutureCallbacksTests) - -BOOST_AUTO_TEST_CASE(connect) { - boost::scoped_ptr callback_data(new CallbackData()); - - test_utils::CassSessionPtr session(cass_session_new()); - test_utils::CassFuturePtr future(cass_session_connect(session.get(), cluster)); - cass_future_set_callback(future.get(), check_callback, callback_data.get()); - - callback_data->wait(); - - BOOST_CHECK(callback_data->was_called); -} - -BOOST_AUTO_TEST_CASE(close) { - boost::scoped_ptr callback_data(new CallbackData()); - - test_utils::CassSessionPtr session(cass_session_new()); - - test_utils::CassFuturePtr connect_future(cass_session_connect(session.get(), cluster)); - test_utils::wait_and_check_error(connect_future.get()); - - test_utils::CassFuturePtr close_future(cass_session_close(session.get())); - cass_future_set_callback(close_future.get(), check_callback, callback_data.get()); - - callback_data->wait(); - - BOOST_CHECK(callback_data->was_called); -} - -BOOST_AUTO_TEST_CASE(result) { - boost::scoped_ptr callback_data(new CallbackData()); - - test_utils::CassSessionPtr session(cass_session_new()); - - test_utils::CassFuturePtr connect_future(cass_session_connect(session.get(), cluster)); - test_utils::wait_and_check_error(connect_future.get()); - - test_utils::CassResultPtr result; - - std::stringstream query; - query << "SELECT * FROM " - << (version >= "3.0.0" ? "system_schema.keyspaces" : "system.schema_keyspaces"); - test_utils::CassStatementPtr statement(cass_statement_new(query.str().c_str(), 0)); - test_utils::CassFuturePtr future(cass_session_execute(session.get(), statement.get())); - - cass_future_set_callback(future.get(), check_result_callback, callback_data.get()); - - callback_data->wait(); - - BOOST_CHECK(callback_data->was_called); - BOOST_CHECK(callback_data->row_count > 0); -} - -BOOST_AUTO_TEST_CASE(after_set) { - boost::scoped_ptr callback_data(new CallbackData()); - - test_utils::CassSessionPtr session(cass_session_new()); - - test_utils::CassFuturePtr future(cass_session_connect(session.get(), cluster)); - test_utils::wait_and_check_error(future.get()); - - cass_future_set_callback(future.get(), check_callback, callback_data.get()); - - callback_data->wait(); - - BOOST_CHECK(callback_data->was_called); -} - -BOOST_AUTO_TEST_SUITE_END() From 732f7eb94086afe64663b22ad055f6bafc39d382 Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Mon, 28 Oct 2019 14:11:13 -0400 Subject: [PATCH 02/42] CPP-832 Port paging tests to Google test framework (#312) --- .../gtests/src/integration/integration.hpp | 7 + .../gtests/src/integration/objects/result.hpp | 19 +++ .../src/integration/objects/statement.cpp | 5 + .../src/integration/objects/statement.hpp | 26 +++ .../src/integration/tests/test_basics.cpp | 87 ++++++++++ .../integration_tests/src/test_paging.cpp | 158 ------------------ 6 files changed, 144 insertions(+), 158 deletions(-) delete mode 100644 cpp-driver/test/integration_tests/src/test_paging.cpp diff --git a/cpp-driver/gtests/src/integration/integration.hpp b/cpp-driver/gtests/src/integration/integration.hpp index a4f695d33..665d592c4 100644 --- a/cpp-driver/gtests/src/integration/integration.hpp +++ b/cpp-driver/gtests/src/integration/integration.hpp @@ -122,6 +122,13 @@ #define SELECT_ALL_SYSTEM_LOCAL_CQL "SELECT * FROM system.local" #define SELECT_COUNT_FORMAT "SELECT COUNT(*) FROM %s LIMIT 1000000" +#define CASSANDRA_COMPOSITE_KEY_VALUE_TABLE_FORMAT \ + "CREATE TABLE IF NOT EXISTS %s (primary_key %s, column_key timeuuid, value %s, PRIMARY " \ + "KEY(primary_key, column_key))" +#define CASSANDRA_COMPOSITE_KEY_VALUE_INSERT_FORMAT \ + "INSERT INTO %s (primary_key, column_key, value) VALUES(%s, %s, %s)" +#define CASSANDRA_COMPOSITE_SELECT_VALUE_FORMAT "SELECT value FROM %s WHERE primary_key=%s" + using namespace test; using namespace test::driver; diff --git a/cpp-driver/gtests/src/integration/objects/result.hpp b/cpp-driver/gtests/src/integration/objects/result.hpp index 0ac69a649..aae9a0188 100644 --- a/cpp-driver/gtests/src/integration/objects/result.hpp +++ b/cpp-driver/gtests/src/integration/objects/result.hpp @@ -193,6 +193,25 @@ class Result : public Object { return Uuid(); } + /** + * Determine if a follow up query would return more results. + * + * @return true if there are more pages; otherwise false + */ + bool has_more_pages() const { return cass_result_has_more_pages(get()) == cass_true; } + + /** + * Get the paging state token. + * + * @return The paging state token. + */ + std::string paging_state_token() const { + const char* token = NULL; + size_t token_length = 0; + cass_result_paging_state_token(get(), &token, &token_length); + return std::string(token, token_length); + } + private: /** * Future wrapped object diff --git a/cpp-driver/gtests/src/integration/objects/statement.cpp b/cpp-driver/gtests/src/integration/objects/statement.cpp index b00a663b0..d04112641 100644 --- a/cpp-driver/gtests/src/integration/objects/statement.cpp +++ b/cpp-driver/gtests/src/integration/objects/statement.cpp @@ -17,7 +17,12 @@ #include "statement.hpp" #include "objects/custom_payload.hpp" +#include "objects/result.hpp" void test::driver::Statement::set_custom_payload(CustomPayload custom_payload) { ASSERT_EQ(CASS_OK, cass_statement_set_custom_payload(get(), custom_payload.get())); } + +void test::driver::Statement::set_paging_state(const test::driver::Result& result) { + ASSERT_EQ(CASS_OK, cass_statement_set_paging_state(get(), result.get())); +} diff --git a/cpp-driver/gtests/src/integration/objects/statement.hpp b/cpp-driver/gtests/src/integration/objects/statement.hpp index 4a80e3402..f2bf036ff 100644 --- a/cpp-driver/gtests/src/integration/objects/statement.hpp +++ b/cpp-driver/gtests/src/integration/objects/statement.hpp @@ -32,6 +32,7 @@ namespace test { namespace driver { // Forward declaration for circular dependency class CustomPayload; +class Result; /** * Wrapped statement object @@ -215,6 +216,31 @@ class Statement : public Object { void set_host(const CassInet* host, int port) { ASSERT_EQ(CASS_OK, cass_statement_set_host_inet(get(), host, port)); } + + /** + * Set the paging size for the statement. + * + * @param page_size + */ + void set_paging_size(int page_size) { + ASSERT_EQ(CASS_OK, cass_statement_set_paging_size(get(), page_size)); + } + + /** + * Set the paging state for the statement. + * + * @param result + */ + void set_paging_state(const Result& result); + + /** + * Set the raw paging state token for the statement. + * + * @param token + */ + void set_paging_state_token(const std::string& token) { + ASSERT_EQ(CASS_OK, cass_statement_set_paging_state_token(get(), token.c_str(), token.size())); + } }; /** diff --git a/cpp-driver/gtests/src/integration/tests/test_basics.cpp b/cpp-driver/gtests/src/integration/tests/test_basics.cpp index edb755c98..132d056a0 100644 --- a/cpp-driver/gtests/src/integration/tests/test_basics.cpp +++ b/cpp-driver/gtests/src/integration/tests/test_basics.cpp @@ -484,3 +484,90 @@ CASSANDRA_INTEGRATION_TEST_F(BasicsTests, FutureCallbackAfterSet) { ASSERT_TRUE(result != NULL); EXPECT_EQ(1u, Result(result).row_count()); } + +/** + * Verify that paging and paging using the token properly returns rows. + * + * @expected_result Expect 10 pages of 10 rows using both paging methods. + */ +CASSANDRA_INTEGRATION_TEST_F(BasicsTests, Paging) { + CHECK_FAILURE; + + session_.execute( + format_string(CASSANDRA_COMPOSITE_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "int")); + + { // Insert rows + Statement insert_statement(format_string(CASSANDRA_COMPOSITE_KEY_VALUE_INSERT_FORMAT, + table_name_.c_str(), "0", "?", "?"), + 2); + + for (int i = 0; i < 100; ++i) { + insert_statement.bind(0, uuid_generator_.generate_timeuuid()); + insert_statement.bind(1, Integer(i)); + session_.execute(insert_statement); + } + } + + { // Page through inserted rows + Statement select_statement( + format_string(CASSANDRA_COMPOSITE_SELECT_VALUE_FORMAT, table_name_.c_str(), "0")); + select_statement.set_paging_size(10); + + size_t num_pages = 0; + + while (true) { + Result result = session_.execute(select_statement); + if (!result.has_more_pages()) break; + EXPECT_EQ(10u, result.row_count()); + num_pages++; + select_statement.set_paging_state(result); + } + + EXPECT_EQ(10u, num_pages); + } + + { // Page through inserted rows using page state token + Statement select_statement( + format_string(CASSANDRA_COMPOSITE_SELECT_VALUE_FORMAT, table_name_.c_str(), "0")); + select_statement.set_paging_size(10); + + size_t num_pages = 0; + + while (true) { + Result result = session_.execute(select_statement); + if (!result.has_more_pages()) break; + EXPECT_EQ(10u, result.row_count()); + num_pages++; + std::string token = result.paging_state_token(); + EXPECT_FALSE(token.empty()); + select_statement.set_paging_state_token(token); + } + + EXPECT_EQ(10u, num_pages); + } +} + +/** + * Verify that a query of an empty table returns the correct paging state. + * + * @expected_result The result should signal no more pages and have an empty paging token. + */ +CASSANDRA_INTEGRATION_TEST_F(BasicsTests, PagingEmpty) { + CHECK_FAILURE; + + session_.execute( + format_string(CASSANDRA_COMPOSITE_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "int")); + + // No rows inserted + + Statement select_statement( + format_string(CASSANDRA_COMPOSITE_SELECT_VALUE_FORMAT, table_name_.c_str(), "0")); + select_statement.set_paging_size(10); + + Result result = session_.execute(select_statement); + + EXPECT_FALSE(result.has_more_pages()); + + std::string token = result.paging_state_token(); + EXPECT_TRUE(token.empty()); +} diff --git a/cpp-driver/test/integration_tests/src/test_paging.cpp b/cpp-driver/test/integration_tests/src/test_paging.cpp deleted file mode 100644 index 51184d279..000000000 --- a/cpp-driver/test/integration_tests/src/test_paging.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "cassandra.h" -#include "test_utils.hpp" - -struct PagingTests : public test_utils::SingleSessionTest { - PagingTests() - : SingleSessionTest(1, 0) { - test_utils::execute_query(session, - str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % - test_utils::SIMPLE_KEYSPACE % "1")); - test_utils::execute_query(session, str(boost::format("USE %s") % test_utils::SIMPLE_KEYSPACE)); - test_utils::execute_query( - session, "CREATE TABLE test (part int, key timeuuid, value int, PRIMARY KEY(part, key));"); - } - - ~PagingTests() { - // Drop the keyspace (ignore any and all errors) - test_utils::execute_query_with_error( - session, - str(boost::format(test_utils::DROP_KEYSPACE_FORMAT) % test_utils::SIMPLE_KEYSPACE)); - } - - void insert_rows(int num_rows) { - std::string insert_query = "INSERT INTO test (part, key, value) VALUES (?, ?, ?);"; - test_utils::CassStatementPtr statement(cass_statement_new(insert_query.c_str(), 3)); - - // Determine if bound parameters can be used based on C* version - if (version.major_version == 1) { - test_utils::CassPreparedPtr prepared = test_utils::prepare(session, insert_query.c_str()); - statement = test_utils::CassStatementPtr(cass_prepared_bind(prepared.get())); - } - - const cass_int32_t part_key = 0; - - for (int i = 0; i < num_rows; ++i) { - cass_statement_bind_int32(statement.get(), 0, part_key); - cass_statement_bind_uuid(statement.get(), 1, test_utils::generate_time_uuid(uuid_gen)); - cass_statement_bind_int32(statement.get(), 2, i); - test_utils::CassFuturePtr future(cass_session_execute(session, statement.get())); - test_utils::wait_and_check_error(future.get()); - } - } -}; - -BOOST_FIXTURE_TEST_SUITE(paging, PagingTests) - -BOOST_AUTO_TEST_CASE(paging_simple) { - const int num_rows = 100; - const int page_size = 5; - - std::string select_query = "SELECT value FROM test"; - - insert_rows(num_rows); - - test_utils::CassResultPtr result; - test_utils::CassStatementPtr statement( - test_utils::CassStatementPtr(cass_statement_new(select_query.c_str(), 0))); - cass_statement_set_paging_size(statement.get(), page_size); - - cass_int32_t count = 0; - do { - test_utils::CassFuturePtr future(cass_session_execute(session, statement.get())); - test_utils::wait_and_check_error(future.get()); - result = test_utils::CassResultPtr(cass_future_get_result(future.get())); - - test_utils::CassIteratorPtr iterator(cass_iterator_from_result(result.get())); - - while (cass_iterator_next(iterator.get())) { - const CassRow* row = cass_iterator_get_row(iterator.get()); - cass_int32_t value; - cass_value_get_int32(cass_row_get_column(row, 0), &value); - BOOST_REQUIRE(value == count++); - } - - if (cass_result_has_more_pages(result.get())) { - cass_statement_set_paging_state(statement.get(), result.get()); - } - } while (cass_result_has_more_pages(result.get())); -} - -BOOST_AUTO_TEST_CASE(paging_raw) { - const int num_rows = 100; - const int page_size = 5; - - std::string select_query = "SELECT value FROM test"; - - insert_rows(num_rows); - - test_utils::CassResultPtr result; - test_utils::CassStatementPtr statement( - test_utils::CassStatementPtr(cass_statement_new(select_query.c_str(), 0))); - cass_statement_set_paging_size(statement.get(), page_size); - - cass_int32_t count = 0; - do { - test_utils::CassFuturePtr future(cass_session_execute(session, statement.get())); - test_utils::wait_and_check_error(future.get()); - result = test_utils::CassResultPtr(cass_future_get_result(future.get())); - - test_utils::CassIteratorPtr iterator(cass_iterator_from_result(result.get())); - - while (cass_iterator_next(iterator.get())) { - const CassRow* row = cass_iterator_get_row(iterator.get()); - cass_int32_t value; - cass_value_get_int32(cass_row_get_column(row, 0), &value); - BOOST_REQUIRE(value == count++); - } - - if (cass_result_has_more_pages(result.get())) { - const char* paging_state; - size_t paging_state_size; - BOOST_CHECK(cass_result_paging_state_token(result.get(), &paging_state, &paging_state_size) == - CASS_OK); - cass_statement_set_paging_state_token(statement.get(), paging_state, paging_state_size); - } - } while (cass_result_has_more_pages(result.get())); -} - -BOOST_AUTO_TEST_CASE(paging_empty) { - const int page_size = 5; - - const char* select_query = "SELECT value FROM test"; - - test_utils::CassStatementPtr statement(cass_statement_new(select_query, 0)); - cass_statement_set_paging_size(statement.get(), page_size); - - test_utils::CassFuturePtr future(cass_session_execute(session, statement.get())); - test_utils::wait_and_check_error(future.get()); - test_utils::CassResultPtr result(cass_future_get_result(future.get())); - BOOST_REQUIRE(cass_result_has_more_pages(result.get()) == cass_false); -} - -BOOST_AUTO_TEST_SUITE_END() From e76390f5b5815f86af4ba1e7008c4afb49ed6b8f Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Tue, 29 Oct 2019 14:01:28 -0400 Subject: [PATCH 03/42] CPP-834 Port schema agreement tests to Google test framework (#313) --- .../tests/test_schema_agreement.cpp | 37 +++ cpp-driver/gtests/src/unit/mockssandra.cpp | 48 ++-- cpp-driver/gtests/src/unit/mockssandra.hpp | 6 +- .../src/unit/tests/test_schema_agreement.cpp | 238 ++++++++++++++++++ .../src/test_schema_agreement.cpp | 188 -------------- 5 files changed, 303 insertions(+), 214 deletions(-) create mode 100644 cpp-driver/gtests/src/integration/tests/test_schema_agreement.cpp create mode 100644 cpp-driver/gtests/src/unit/tests/test_schema_agreement.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_schema_agreement.cpp diff --git a/cpp-driver/gtests/src/integration/tests/test_schema_agreement.cpp b/cpp-driver/gtests/src/integration/tests/test_schema_agreement.cpp new file mode 100644 index 000000000..02cdfb55a --- /dev/null +++ b/cpp-driver/gtests/src/integration/tests/test_schema_agreement.cpp @@ -0,0 +1,37 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "atomic.hpp" +#include "integration.hpp" + +/** + * Schema changes should wait for the schema to propagate before returning. + */ +class SchemaAgreementTests : public Integration { +public: + SchemaAgreementTests() { number_dc1_nodes_ = 3; } +}; + +CASSANDRA_INTEGRATION_TEST_F(SchemaAgreementTests, Simple) { + CHECK_FAILURE; + + logger_.add_critera("Found schema agreement in"); + + session_.execute( + format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "int")); + + EXPECT_EQ(1u, logger_.count()); +} diff --git a/cpp-driver/gtests/src/unit/mockssandra.cpp b/cpp-driver/gtests/src/unit/mockssandra.cpp index f174f1481..64af801c2 100644 --- a/cpp-driver/gtests/src/unit/mockssandra.cpp +++ b/cpp-driver/gtests/src/unit/mockssandra.cpp @@ -748,27 +748,27 @@ int ServerConnection::on_password(char* buf, int size, int rwflag, void* passwor } \ } while (0) -inline const char* decode_int8(const char* input, const char* end, int8_t* value) { +const char* decode_int8(const char* input, const char* end, int8_t* value) { CHECK(input + 1, "Unable to decode byte"); *value = static_cast(input[0]); return input + sizeof(int8_t); } -inline const char* decode_int16(const char* input, const char* end, int16_t* value) { +const char* decode_int16(const char* input, const char* end, int16_t* value) { CHECK(input + 2, "Unable to decode signed short"); *value = (static_cast(static_cast(input[1])) << 0) | (static_cast(static_cast(input[0])) << 8); return input + sizeof(int16_t); } -inline const char* decode_uint16(const char* input, const char* end, uint16_t* value) { +const char* decode_uint16(const char* input, const char* end, uint16_t* value) { CHECK(input + 2, "Unable to decode unsigned short"); *value = (static_cast(static_cast(input[1])) << 0) | (static_cast(static_cast(input[0])) << 8); return input + sizeof(uint16_t); } -inline const char* decode_int32(const char* input, const char* end, int32_t* value) { +const char* decode_int32(const char* input, const char* end, int32_t* value) { CHECK(input + 4, "Unable to decode integer"); *value = (static_cast(static_cast(input[3])) << 0) | (static_cast(static_cast(input[2])) << 8) | @@ -777,7 +777,7 @@ inline const char* decode_int32(const char* input, const char* end, int32_t* val return input + sizeof(int32_t); } -inline const char* decode_int64(const char* input, const char* end, int64_t* value) { +const char* decode_int64(const char* input, const char* end, int64_t* value) { CHECK(input + 8, "Unable to decode long"); *value = (static_cast(static_cast(input[7])) << 0) | (static_cast(static_cast(input[6])) << 8) | @@ -790,7 +790,7 @@ inline const char* decode_int64(const char* input, const char* end, int64_t* val return input + sizeof(int64_t); } -inline const char* decode_string(const char* input, const char* end, String* output) { +const char* decode_string(const char* input, const char* end, String* output) { uint16_t len = 0; const char* pos = decode_uint16(input, end, &len); CHECK(pos + len, "Unable to decode string"); @@ -798,7 +798,7 @@ inline const char* decode_string(const char* input, const char* end, String* out return pos + len; } -inline const char* decode_long_string(const char* input, const char* end, String* output) { +const char* decode_long_string(const char* input, const char* end, String* output) { int32_t len = 0; const char* pos = decode_int32(input, end, &len); CHECK(pos + len, "Unable to decode long string"); @@ -807,7 +807,7 @@ inline const char* decode_long_string(const char* input, const char* end, String return pos + len; } -inline const char* decode_bytes(const char* input, const char* end, String* output) { +const char* decode_bytes(const char* input, const char* end, String* output) { int32_t len = 0; const char* pos = decode_int32(input, end, &len); if (len > 0) { @@ -817,7 +817,7 @@ inline const char* decode_bytes(const char* input, const char* end, String* outp return pos + len; } -inline const char* decode_uuid(const char* input, CassUuid* output) { +const char* decode_uuid(const char* input, CassUuid* output) { output->time_and_version = static_cast(static_cast(input[3])); output->time_and_version |= static_cast(static_cast(input[2])) << 8; output->time_and_version |= static_cast(static_cast(input[1])) << 16; @@ -837,8 +837,8 @@ inline const char* decode_uuid(const char* input, CassUuid* output) { return input + 16; } -inline const char* decode_string_map(const char* input, const char* end, - Vector >* output) { +const char* decode_string_map(const char* input, const char* end, + Vector >* output) { uint16_t len = 0; const char* pos = decode_uint16(input, end, &len); @@ -853,7 +853,7 @@ inline const char* decode_string_map(const char* input, const char* end, return pos; } -inline const char* decode_stringlist(const char* input, const char* end, Vector* output) { +const char* decode_stringlist(const char* input, const char* end, Vector* output) { uint16_t len = 0; const char* pos = decode_uint16(input, end, &len); output->reserve(len); @@ -865,7 +865,7 @@ inline const char* decode_stringlist(const char* input, const char* end, Vector< return pos; } -inline const char* decode_values(const char* input, const char* end, Vector* output) { +const char* decode_values(const char* input, const char* end, Vector* output) { uint16_t len = 0; const char* pos = decode_uint16(input, end, &len); output->reserve(len); @@ -877,8 +877,8 @@ inline const char* decode_values(const char* input, const char* end, Vector* names, Vector* values) { +const char* decode_values_with_names(const char* input, const char* end, Vector* names, + Vector* values) { uint16_t len = 0; const char* pos = decode_uint16(input, end, &len); names->reserve(len); @@ -1011,24 +1011,24 @@ const char* decode_prepare_params(int version, const char* input, const char* en return pos; } -inline int32_t encode_int8(int8_t value, String* output) { +int32_t encode_int8(int8_t value, String* output) { output->push_back(static_cast(value)); return 1; } -inline int32_t encode_int16(int16_t value, String* output) { +int32_t encode_int16(int16_t value, String* output) { output->push_back(static_cast(value >> 8)); output->push_back(static_cast(value >> 0)); return 2; } -inline int32_t encode_uint16(uint16_t value, String* output) { +int32_t encode_uint16(uint16_t value, String* output) { output->push_back(static_cast(value >> 8)); output->push_back(static_cast(value >> 0)); return 2; } -inline int32_t encode_int32(int32_t value, String* output) { +int32_t encode_int32(int32_t value, String* output) { output->push_back(static_cast(value >> 24)); output->push_back(static_cast(value >> 16)); output->push_back(static_cast(value >> 8)); @@ -1036,13 +1036,13 @@ inline int32_t encode_int32(int32_t value, String* output) { return 4; } -inline int32_t encode_string(const String& value, String* output) { +int32_t encode_string(const String& value, String* output) { int32_t size = encode_uint16(value.size(), output) + value.size(); output->append(value); return size + value.size(); } -inline int32_t encode_string_list(const Vector& value, String* output) { +int32_t encode_string_list(const Vector& value, String* output) { int32_t size = encode_int16(value.size(), output); for (Vector::const_iterator it = value.begin(), end = value.end(); it != end; ++it) { size += encode_string(*it, output); @@ -1050,13 +1050,13 @@ inline int32_t encode_string_list(const Vector& value, String* output) { return size; } -inline int32_t encode_bytes(const String& value, String* output) { +int32_t encode_bytes(const String& value, String* output) { int32_t size = encode_int32(value.size(), output) + value.size(); output->append(value); return size + value.size(); } -inline int32_t encode_inet(const Address& value, String* output) { +int32_t encode_inet(const Address& value, String* output) { uint8_t buf[16]; uint8_t len = value.to_inet(buf); encode_int8(len, output); @@ -1067,7 +1067,7 @@ inline int32_t encode_inet(const Address& value, String* output) { return 1 + len + 4; } -inline int32_t encode_uuid(CassUuid uuid, String* output) { +int32_t encode_uuid(CassUuid uuid, String* output) { uint64_t time_and_version = uuid.time_and_version; char buf[16]; buf[3] = static_cast(time_and_version & 0x00000000000000FFLL); diff --git a/cpp-driver/gtests/src/unit/mockssandra.hpp b/cpp-driver/gtests/src/unit/mockssandra.hpp index eb011ce4d..a6fb9807d 100644 --- a/cpp-driver/gtests/src/unit/mockssandra.hpp +++ b/cpp-driver/gtests/src/unit/mockssandra.hpp @@ -296,8 +296,8 @@ enum { RESULT_VOID = 0x0001, RESULT_ROWS = 0x0002, RESULT_SET_KEYSPACE = 0x0003, - RESULT_SET_PREPARED = 0x0004, - RESULT_SET_SCHEMA_CHANGE = 0x0005 + RESULT_PREPARED = 0x0004, + RESULT_SCHEMA_CHANGE = 0x0005 }; enum { @@ -362,6 +362,8 @@ struct QueryParameters { String keyspace; }; +int32_t encode_int32(int32_t value, String* output); +int32_t encode_string(const String& value, String* output); int32_t encode_string_map(const Map >& value, String* output); class Type { diff --git a/cpp-driver/gtests/src/unit/tests/test_schema_agreement.cpp b/cpp-driver/gtests/src/unit/tests/test_schema_agreement.cpp new file mode 100644 index 000000000..a87d88983 --- /dev/null +++ b/cpp-driver/gtests/src/unit/tests/test_schema_agreement.cpp @@ -0,0 +1,238 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "loop_test.hpp" + +#include "query_request.hpp" +#include "session.hpp" +#include "uuids.hpp" + +using namespace mockssandra; +using datastax::internal::core::Config; +using datastax::internal::core::Future; +using datastax::internal::core::QueryRequest; +using datastax::internal::core::Session; +using datastax::internal::core::UuidGen; + +#define SELECT_LOCAL_SCHEMA_CHANGE "SELECT schema_version FROM system.local WHERE key='local'" +#define SELECT_PEERS_SCHEMA_CHANGE \ + "SELECT peer, rpc_address, host_id, schema_version FROM system.peers" + +class SchemaAgreementUnitTest : public LoopTest { +public: + static void connect(Session* session, uint64_t wait_for_time_us = WAIT_FOR_TIME) { + Config config; + config.set_max_schema_wait_time_ms(500); + config.contact_points().push_back(Address("127.0.0.1", 9042)); + Future::Ptr connect_future(session->connect(config)); + ASSERT_TRUE(connect_future->wait_for(wait_for_time_us)) + << "Timed out waiting for session to connect"; + ASSERT_FALSE(connect_future->error()) << cass_error_desc(connect_future->error()->code) << ": " + << connect_future->error()->message; + } + + static void close(Session* session, uint64_t wait_for_time_us = WAIT_FOR_TIME) { + Future::Ptr close_future(session->close()); + ASSERT_TRUE(close_future->wait_for(wait_for_time_us)) + << "Timed out waiting for session to close"; + ASSERT_FALSE(close_future->error()) + << cass_error_desc(close_future->error()->code) << ": " << close_future->error()->message; + } + + static void execute(Session* session, const String& query) { + Future::Ptr request_future(session->execute(QueryRequest::ConstPtr(new QueryRequest(query)))); + EXPECT_TRUE(request_future->wait_for(WAIT_FOR_TIME)); + EXPECT_FALSE(request_future->error()); + } + + struct SchemaVersionCheckCounts { + SchemaVersionCheckCounts() + : local_count(0) + , peers_count(0) {} + Atomic local_count; + Atomic peers_count; + }; + + class SystemSchemaVersion : public Action { + public: + enum AgreementType { NEVER_REACH_AGREEMENT, IMMEDIATE_AGREEMENT }; + + SystemSchemaVersion(AgreementType type, SchemaVersionCheckCounts* counts) + : type_(type) + , check_counts_(counts) { + uuid_gen_.generate_random(&uuid_); + } + + void on_run(Request* request) const { + String query; + QueryParameters params; + if (!request->decode_query(&query, ¶ms)) { + request->error(ERROR_PROTOCOL_ERROR, "Invalid query message"); + } else if (query.find(SELECT_LOCAL_SCHEMA_CHANGE) != String::npos) { + ResultSet local_rs = ResultSet::Builder("system", "local") + .column("schema_version", Type::uuid()) + .row(Row::Builder().uuid(generate_version()).build()) + .build(); + request->write(OPCODE_RESULT, local_rs.encode(request->version())); + check_counts_->local_count.fetch_add(1); + } else if (query.find(SELECT_PEERS_SCHEMA_CHANGE) != String::npos) { + ResultSet::Builder peers_builder = ResultSet::Builder("system", "peers") + .column("peer", Type::inet()) + .column("rpc_address", Type::inet()) + .column("host_id", Type::uuid()) + .column("schema_version", Type::uuid()); + Hosts hosts(request->hosts()); + for (Hosts::const_iterator it = hosts.begin(), end = hosts.end(); it != end; ++it) { + const Host& host(*it); + if (host.address == request->address()) { + continue; + } + peers_builder.row(Row::Builder() + .inet(host.address) + .inet(host.address) + .uuid(generate_version()) // Doesn't matter + .uuid(generate_version()) + .build()); + } + ResultSet peers_rs = peers_builder.build(); + request->write(OPCODE_RESULT, peers_rs.encode(request->version())); + check_counts_->peers_count.fetch_add(1); + } else { + run_next(request); + } + } + + private: + CassUuid generate_version() const { + CassUuid version; + if (type_ == IMMEDIATE_AGREEMENT) { + version = uuid_; + } else { + uuid_gen_.generate_random(&version); + } + return version; + } + + private: + AgreementType type_; + CassUuid uuid_; + SchemaVersionCheckCounts* check_counts_; + mutable UuidGen uuid_gen_; + }; + + class SchemaChange : public Action { + public: + void on_run(Request* request) const { + String query; + QueryParameters params; + if (!request->decode_query(&query, ¶ms)) { + request->error(ERROR_PROTOCOL_ERROR, "Invalid query message"); + } else if (query.find("CREATE TABLE") != String::npos) { + request->write(OPCODE_RESULT, encode_schema_change("CREATE", "TABLE")); + } else if (query.find("DROP TABLE") != String::npos) { + request->write(OPCODE_RESULT, encode_schema_change("DROP", "TABLE")); + } else { + run_next(request); + } + } + + private: + String encode_schema_change(String change_type, String target) const { + String body; + encode_int32(RESULT_SCHEMA_CHANGE, &body); // Result type + encode_string(change_type, &body); + encode_string("keyspace", &body); + if (target == "TABLE") { + encode_string("table", &body); + } + return body; + } + }; +}; + +/** + * Verify that schema changes wait for schema agreement. + */ +TEST_F(SchemaAgreementUnitTest, Simple) { + SchemaVersionCheckCounts check_counts; + + mockssandra::SimpleRequestHandlerBuilder builder; + builder.on(OPCODE_QUERY) + .execute(new SystemSchemaVersion(SystemSchemaVersion::IMMEDIATE_AGREEMENT, &check_counts)) + .execute(new SchemaChange()) + .system_local() + .system_peers() + .empty_rows_result(1); + + mockssandra::SimpleCluster cluster(builder.build(), 3); + ASSERT_EQ(cluster.start_all(), 0); + + Session session; + connect(&session); + + add_logging_critera("Found schema agreement in"); + + execute(&session, "CREATE TABLE tbl (key text PRIMARY KEY, value text)"); + EXPECT_EQ(check_counts.local_count.load(), 1); + EXPECT_EQ(check_counts.peers_count.load(), 1); + EXPECT_EQ(logging_criteria_count(), 1); + + cluster.stop(2); + execute(&session, "DROP TABLE tbl"); + EXPECT_EQ(check_counts.local_count.load(), 2); + EXPECT_EQ(check_counts.peers_count.load(), 2); + EXPECT_EQ(logging_criteria_count(), 2); + + cluster.stop(3); + execute(&session, "CREATE TABLE tbl (key text PRIMARY KEY, value text)"); + EXPECT_EQ(check_counts.local_count.load(), 3); + EXPECT_EQ(check_counts.peers_count.load(), 3); + EXPECT_EQ(logging_criteria_count(), 3); + + close(&session); +} + +/** + * Verify that schema changes will timeout properly while waiting for schema agreement. + */ +TEST_F(SchemaAgreementUnitTest, Timeout) { + SchemaVersionCheckCounts check_counts; + + mockssandra::SimpleRequestHandlerBuilder builder; + builder.on(OPCODE_QUERY) + .execute(new SystemSchemaVersion(SystemSchemaVersion::NEVER_REACH_AGREEMENT, &check_counts)) + .execute(new SchemaChange()) + .system_local() + .system_peers() + .empty_rows_result(1); + + mockssandra::SimpleCluster cluster(builder.build(), 3); + ASSERT_EQ(cluster.start_all(), 0); + + Session session; + connect(&session); + + add_logging_critera("No schema agreement on live nodes after "); + + execute(&session, "CREATE TABLE tbl (key text PRIMARY KEY, value text)"); + + EXPECT_GT(check_counts.local_count.load(), 1); + EXPECT_GT(check_counts.peers_count.load(), 1); + + EXPECT_EQ(logging_criteria_count(), 1); + + close(&session); +} diff --git a/cpp-driver/test/integration_tests/src/test_schema_agreement.cpp b/cpp-driver/test/integration_tests/src/test_schema_agreement.cpp deleted file mode 100644 index cd5c4bc49..000000000 --- a/cpp-driver/test/integration_tests/src/test_schema_agreement.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "cassandra.h" -#include "test_utils.hpp" - -#include -#include -#include -#include - -#include - -#include - -struct ClusterInit { - ClusterInit() - : inst(3, 0) - , session(NULL) - , schema_alter_session(NULL) { - new_session(); - } - - ~ClusterInit() { close_session(); } - - void new_session() { - close_session(); - session = cass_session_new(); - test_utils::CassFuturePtr connect_future(cass_session_connect(session, inst.cluster)); - test_utils::wait_and_check_error(connect_future.get()); - } - - void prepare_alter_schema_version_session() { - // Create a new session for altering node2 and node3 system tables - std::string ip_prefix = inst.ccm->get_ip_prefix(); - std::stringstream whitelist_hosts; - whitelist_hosts << ip_prefix << "1"; - cass_cluster_set_whitelist_filtering(inst.cluster, whitelist_hosts.str().c_str()); - schema_alter_session = cass_session_new(); - test_utils::CassFuturePtr connect_future( - cass_session_connect(schema_alter_session, inst.cluster)); - test_utils::wait_and_check_error(connect_future.get()); - } - - void close_session() { - if (session != NULL) { - cass_session_free(session); - session = NULL; - } - - if (schema_alter_session != NULL) { - cass_session_free(schema_alter_session); - schema_alter_session = NULL; - } - } - - test_utils::MultipleNodesTest inst; - CassSession* session; - CassSession* schema_alter_session; -}; - -BOOST_FIXTURE_TEST_SUITE(schema_agreement, ClusterInit) - -// only doing a keyspace for now since there is no difference for types or tables -BOOST_AUTO_TEST_CASE(keyspace_add_drop) { - test_utils::CassLog::reset("Found schema agreement in"); - - // "USE" in fast succession would normally fail on the next node if the previous query did not - // wait - const std::string use_simple = str(boost::format("USE %s") % test_utils::SIMPLE_KEYSPACE); - test_utils::execute_query(session, str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % - test_utils::SIMPLE_KEYSPACE % 2)); - test_utils::execute_query(session, use_simple); - test_utils::execute_query(session, "USE system"); - test_utils::execute_query(session, - str(boost::format("DROP KEYSPACE %s") % test_utils::SIMPLE_KEYSPACE)); - test_utils::CassResultPtr result; - CassError rc = test_utils::execute_query_with_error(session, use_simple, &result); - BOOST_CHECK_EQUAL(rc, CASS_ERROR_SERVER_INVALID_QUERY); - - // close session to flush logger - close_session(); - - BOOST_CHECK_EQUAL(test_utils::CassLog::message_count(), 2ul); -} - -BOOST_AUTO_TEST_CASE(agreement_node_down) { - test_utils::CassLog::reset("Node " + inst.ccm->get_ip_prefix() + "3 is down"); - - inst.ccm->stop_node(3); - - size_t t = 0; - size_t max_tries = 15; - for (; t < max_tries; ++t) { - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - if (test_utils::CassLog::message_count() > 0) break; - } - BOOST_REQUIRE_MESSAGE(t < max_tries, "Timed out waiting for node down log message"); - - test_utils::CassLog::reset("Found schema agreement in"); - const std::string use_simple = str(boost::format("USE %s") % test_utils::SIMPLE_KEYSPACE); - test_utils::execute_query(session, str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % - test_utils::SIMPLE_KEYSPACE % 2)); - test_utils::execute_query(session, use_simple); - test_utils::execute_query(session, "USE system"); - test_utils::execute_query(session, - str(boost::format("DROP KEYSPACE %s") % test_utils::SIMPLE_KEYSPACE)); - test_utils::CassResultPtr result; - CassError rc = test_utils::execute_query_with_error(session, use_simple, &result); - BOOST_CHECK_EQUAL(rc, CASS_ERROR_SERVER_INVALID_QUERY); - - // close session to flush logger - close_session(); - - BOOST_CHECK_EQUAL(test_utils::CassLog::message_count(), 2ul); - - inst.ccm->start_node(3); -} - -#define MAX_SCHEMA_AGREEMENT_WAIT_MS 10000 -static void alter_schema_version(void* arg) { - ClusterInit* cluster_init = static_cast(arg); - std::string ip_prefix = cluster_init->inst.ccm->get_ip_prefix(); - boost::chrono::steady_clock::time_point end = - boost::chrono::steady_clock::now() + - boost::chrono::milliseconds(MAX_SCHEMA_AGREEMENT_WAIT_MS + 2000); - size_t i = 0; - - do { - std::stringstream ss; - // Update node 2 and 3's schema version in node 1's peers table. - ss << "UPDATE system.peers SET schema_version=? WHERE peer='" << ip_prefix << ((i++ % 2) + 2) - << "'"; - test_utils::CassStatementPtr schema_stmt(cass_statement_new(ss.str().c_str(), 1)); - cass_statement_bind_uuid(schema_stmt.get(), 0, - test_utils::generate_random_uuid(cluster_init->inst.uuid_gen)); - test_utils::CassFuturePtr future( - cass_session_execute(cluster_init->schema_alter_session, schema_stmt.get())); - cass_future_wait(future.get()); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future.get()), CASS_OK); - - } while (boost::chrono::steady_clock::now() < end && test_utils::CassLog::message_count() == 0); -} - -BOOST_AUTO_TEST_CASE(no_agreement_timeout) { - // Create and prepare a separate session for altering the schema version - prepare_alter_schema_version_session(); - - test_utils::CassLog::reset("No schema agreement on live nodes after "); - - // Alter the schema_version for nodes 2 and 3 in a separate thread - uv_thread_t thread; - uv_thread_create(&thread, alter_schema_version, this); - - test_utils::CassStatementPtr create_stmt( - cass_statement_new(str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % - test_utils::SIMPLE_KEYSPACE % 2) - .c_str(), - 0)); - test_utils::CassFuturePtr create_future(cass_session_execute(session, create_stmt.get())); - - cass_future_wait(create_future.get()); - BOOST_CHECK_EQUAL(cass_future_error_code(create_future.get()), CASS_OK); - BOOST_CHECK_EQUAL(test_utils::CassLog::message_count(), 1ul); - - uv_thread_join(&thread); - - // Drop the keyspace (ignore any and all errors) - test_utils::execute_query_with_error( - session, str(boost::format(test_utils::DROP_KEYSPACE_FORMAT) % test_utils::SIMPLE_KEYSPACE)); - - close_session(); -} - -BOOST_AUTO_TEST_SUITE_END() From 4eb5dbef61a470c3e90aa3ba739d1572df0ef043 Mon Sep 17 00:00:00 2001 From: Fero Date: Wed, 30 Oct 2019 07:29:12 -0400 Subject: [PATCH 04/42] CPP-828 - Port heartbeat tests to Google test framework (#314) --- .../gtests/src/integration/integration.cpp | 46 ++++-- .../gtests/src/integration/integration.hpp | 30 +++- .../src/integration/objects/cluster.hpp | 12 ++ .../src/integration/objects/session.hpp | 11 ++ .../src/integration/tests/test_heartbeat.cpp | 101 +++++++++++++ .../integration_tests/src/test_heartbeat.cpp | 133 ------------------ 6 files changed, 186 insertions(+), 147 deletions(-) create mode 100644 cpp-driver/gtests/src/integration/tests/test_heartbeat.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_heartbeat.cpp diff --git a/cpp-driver/gtests/src/integration/integration.cpp b/cpp-driver/gtests/src/integration/integration.cpp index b6cd6c2b6..0ab4c4c72 100644 --- a/cpp-driver/gtests/src/integration/integration.cpp +++ b/cpp-driver/gtests/src/integration/integration.cpp @@ -187,15 +187,22 @@ void Integration::SetUp() { } void Integration::TearDown() { - // Restart all stopped nodes + // Restart and resume all stopped and paused nodes if (!is_test_chaotic_) { // No need to restart as cluster will be destroyed - for (std::vector::iterator iterator = stopped_nodes_.begin(); - iterator != stopped_nodes_.end(); ++iterator) { - TEST_LOG("Restarting Node Stopped in " << test_name_ << ": " << *iterator); - ccm_->start_node(*iterator); + for (std::vector::iterator it = stopped_nodes_.begin(), + end = stopped_nodes_.end(); + it != end; ++it) { + TEST_LOG("Restarting Node Stopped in " << test_name_ << ": " << *it); + ccm_->start_node(*it); + } + for (std::vector::iterator it = paused_nodes_.begin(), end = paused_nodes_.end(); + it != end; ++it) { + TEST_LOG("Resuming Node Paused in " << test_name_ << ": " << *it); + ccm_->resume_node(*it); } } stopped_nodes_.clear(); + paused_nodes_.clear(); // Drop keyspace for integration test (may or may have not been created) if (!is_test_chaotic_) { // No need to drop keyspace as cluster will be destroyed @@ -400,11 +407,11 @@ bool Integration::force_decommission_node(unsigned int node) { } bool Integration::start_node(unsigned int node) { - // Stop the requested node - if (ccm_->is_node_down(node, true)) { + // Start the requested node and ensure paused nodes are ignored + std::vector::iterator it = std::find(paused_nodes_.begin(), paused_nodes_.end(), node); + if (it == paused_nodes_.end() && ccm_->is_node_down(node, true)) { bool status = ccm_->start_node(node); - std::vector::iterator it = - std::find(stopped_nodes_.begin(), stopped_nodes_.end(), node); + it = std::find(stopped_nodes_.begin(), stopped_nodes_.end(), node); if (it != stopped_nodes_.end()) { stopped_nodes_.erase(it); } @@ -414,7 +421,6 @@ bool Integration::start_node(unsigned int node) { } bool Integration::stop_node(unsigned int node, bool is_kill /*= false*/) { - // Stop the requested node if (ccm_->is_node_up(node, true)) { bool status = ccm_->stop_node(node, is_kill); if (status) { @@ -425,6 +431,26 @@ bool Integration::stop_node(unsigned int node, bool is_kill /*= false*/) { return false; } +bool Integration::pause_node(unsigned int node) { + std::vector::iterator it = std::find(paused_nodes_.begin(), paused_nodes_.end(), node); + if (it == paused_nodes_.end() && ccm_->is_node_up(node, true)) { + ccm_->pause_node(node); + paused_nodes_.push_back(node); + return true; + } + return false; +} + +bool Integration::resume_node(unsigned int node) { + std::vector::iterator it = std::find(paused_nodes_.begin(), paused_nodes_.end(), node); + if (it != paused_nodes_.end()) { + ccm_->resume_node(node); + paused_nodes_.erase(it); + return true; + } + return false; +} + std::string Integration::generate_contact_points(const std::string& ip_prefix, size_t number_of_nodes) { // Iterate over the total number of nodes to create the contact list diff --git a/cpp-driver/gtests/src/integration/integration.hpp b/cpp-driver/gtests/src/integration/integration.hpp index 665d592c4..5b3b042fd 100644 --- a/cpp-driver/gtests/src/integration/integration.hpp +++ b/cpp-driver/gtests/src/integration/integration.hpp @@ -333,6 +333,10 @@ class Integration : public testing::Test { * Vector of nodes that have been stopped */ std::vector stopped_nodes_; + /** + * Vector of nodes that have been paused + */ + std::vector paused_nodes_; /** * Get the default keyspace name (based on the current test case and test @@ -457,12 +461,12 @@ class Integration : public testing::Test { virtual bool force_decommission_node(unsigned int node); /** - * Start a node that was previously stopped to ensure that it is not restarted - * after test is completed + * Start a node that was previously stopped to ensure that it is not restarted after test is + * completed; paused nodes are ignored * * @param node Node that should be started - * @return True if node was started; false otherwise (the node is invalid or - * was already started) + * @return True if node was started; false otherwise (the node is invalid or was already + * started or is paused) */ virtual bool start_node(unsigned int node); @@ -477,6 +481,24 @@ class Integration : public testing::Test { */ virtual bool stop_node(unsigned int node, bool is_kill = false); + /** + * Pause a node that should be resumed after test is completed + * + * @param node Node that should be paused + * @return True if node was paused; false otherwise (the node is invalid or + * was already paused) + */ + virtual bool pause_node(unsigned int node); + + /** + * Resume a node that was previously paused to ensure that it is not resumed after test is + * completed + * + * @param node Node that should be resumed + * @return True if node was resumed; false otherwise + */ + virtual bool resume_node(unsigned int node); + /** * Generate the contact points for the cluster * diff --git a/cpp-driver/gtests/src/integration/objects/cluster.hpp b/cpp-driver/gtests/src/integration/objects/cluster.hpp index 52b5efc57..8edbac930 100644 --- a/cpp-driver/gtests/src/integration/objects/cluster.hpp +++ b/cpp-driver/gtests/src/integration/objects/cluster.hpp @@ -117,6 +117,18 @@ class Cluster : public Object { return *this; } + /** + * Sets the amount of time a connection is allowed to be without a successful + * heartbeat response before being terminated and scheduled for reconnection. + * + * @param interval_s Idle timeout (in seconds); 0 to disable heartbeat messages (default: 60s) + * @return Cluster object + */ + Cluster& with_connection_idle_timeout(unsigned int interval_s = 60u) { + cass_cluster_set_connection_idle_timeout(get(), interval_s); + return *this; + } + /** * Assign/Append the contact points; passing an empty string will clear * the contact points diff --git a/cpp-driver/gtests/src/integration/objects/session.hpp b/cpp-driver/gtests/src/integration/objects/session.hpp index 27bc5246b..600c3c31f 100644 --- a/cpp-driver/gtests/src/integration/objects/session.hpp +++ b/cpp-driver/gtests/src/integration/objects/session.hpp @@ -105,6 +105,17 @@ class Session : public Object { */ const std::string connect_error_message() { return connect_future_.error_message(); } + /** + * Get the current driver metrics + * + * @return Driver metrics + */ + CassMetrics metrics() const { + CassMetrics metrics; + cass_session_get_metrics(get(), &metrics); + return metrics; + } + /** * Execute a batch statement synchronously * diff --git a/cpp-driver/gtests/src/integration/tests/test_heartbeat.cpp b/cpp-driver/gtests/src/integration/tests/test_heartbeat.cpp new file mode 100644 index 000000000..9fb3404f0 --- /dev/null +++ b/cpp-driver/gtests/src/integration/tests/test_heartbeat.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "integration.hpp" + +class HeartbeatTests : public Integration { +public: + HeartbeatTests() { + is_session_requested_ = false; + number_dc1_nodes_ = 2; + } +}; + +/** + * Heartbeat interval (enabled) + * + * This test ensures the heartbeat interval is enabled when connected to a cluster + * + * @since 2.1.0 + * @jira_ticket CPP-152 + * @expected_result Heartbeat is enabled. + */ +CASSANDRA_INTEGRATION_TEST_F(HeartbeatTests, HeartbeatEnabled) { + CHECK_FAILURE; + + logger_.add_critera("Heartbeat completed on host " + ccm_->get_ip_prefix()); + Cluster cluster = default_cluster().with_connection_heartbeat_interval(1); // Quick heartbeat + connect(cluster); + + start_timer(); + while (elapsed_time() < 2000) { + session_.execute(SELECT_ALL_SYSTEM_LOCAL_CQL); + } + EXPECT_GE(logger_.count(), 1u); +} + +/** + * Heartbeat interval (disabled) + * + * This test ensures the heartbeat interval is disabled when connected to a cluster + * + * @since 2.1.0 + * @jira_ticket CPP-152 + * @expected_result Heartbeat is disabled. + */ +CASSANDRA_INTEGRATION_TEST_F(HeartbeatTests, HeartbeatDisabled) { + CHECK_FAILURE; + + logger_.add_critera("Heartbeat completed on host " + ccm_->get_ip_prefix()); + Cluster cluster = default_cluster().with_connection_heartbeat_interval(0); + connect(cluster); + + start_timer(); + while (elapsed_time() < 2000) { + session_.execute(SELECT_ALL_SYSTEM_LOCAL_CQL); + } + EXPECT_EQ(0u, logger_.count()); +} + +/** + * Heartbeat interval (failed) + * + * This test ensures the heartbeat interval is enabled when connected to a cluster and fails to get + * a response from a node resulting in connection termination. + * + * @since 2.1.0 + * @jira_ticket CPP-152 + * @expected_result Heartbeat failure on node 2; node 2 connection is lost. + */ +CASSANDRA_INTEGRATION_TEST_F(HeartbeatTests, HeartbeatFailed) { + CHECK_FAILURE; + + logger_.add_critera("Failed to send a heartbeat within connection idle interval."); + Cluster cluster = + default_cluster().with_connection_heartbeat_interval(1).with_connection_idle_timeout(5); + connect(cluster); + + cass_uint64_t initial_connections = session_.metrics().stats.total_connections; + pause_node(2); + start_timer(); + while (session_.metrics().stats.total_connections >= initial_connections && + elapsed_time() < 60000) { + session_.execute_async(SELECT_ALL_SYSTEM_LOCAL_CQL); // Simply execute statements ignore any + // error that can occur from paused node + } + EXPECT_LT(session_.metrics().stats.total_connections, initial_connections); + EXPECT_GE(logger_.count(), 1u); +} diff --git a/cpp-driver/test/integration_tests/src/test_heartbeat.cpp b/cpp-driver/test/integration_tests/src/test_heartbeat.cpp deleted file mode 100644 index 769f63d1c..000000000 --- a/cpp-driver/test/integration_tests/src/test_heartbeat.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "cassandra.h" -#include "test_utils.hpp" - -#include - -struct HeartbestTest : public test_utils::MultipleNodesTest { -public: - HeartbestTest() - : MultipleNodesTest(2, 0) {} - - /** - * Execute a select statement against the system tables for a specified amount - * of time. - * - * NOTE: Results and errors are ignored - * - * @param duration Duration in seconds to execute queries - * @param session Session instance - */ - void execute_system_query(int duration, test_utils::CassSessionPtr session) { - boost::posix_time::ptime start = boost::posix_time::second_clock::universal_time(); - while ((boost::posix_time::second_clock::universal_time() - start).total_seconds() < duration) { - test_utils::CassStatementPtr statement(cass_statement_new("SELECT * FROM system.local", 0)); - cass_statement_set_consistency(statement.get(), CASS_CONSISTENCY_ONE); - test_utils::CassFuturePtr future(cass_session_execute(session.get(), statement.get())); - cass_future_wait_timed(future.get(), test_utils::ONE_SECOND_IN_MICROS); - } - } - - /** - * Get the number of connections established by the driver. - * - * @param session Session instance - * @param Total number of connections - */ - cass_uint64_t get_total_connections(test_utils::CassSessionPtr session) { - CassMetrics metrics; - cass_session_get_metrics(session.get(), &metrics); - return metrics.stats.total_connections; - } -}; - -BOOST_FIXTURE_TEST_SUITE(heartbeat, HeartbestTest) - -/** - * Heartbeat Interval - * - * This test ensures the heartbeat interval settings when connected to a - * cluster - * - * @since 2.1.0 - * @jira_ticket CPP-152 - * @test_category connection:heartbeat - */ -BOOST_AUTO_TEST_CASE(interval) { - // Heartbeat disabled - cass_cluster_set_connection_heartbeat_interval(cluster, 0); - test_utils::CassLog::reset("Heartbeat completed on host " + ccm->get_ip_prefix()); - { - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - execute_system_query(5, session); - } - BOOST_CHECK_EQUAL(test_utils::CassLog::message_count(), 0); - - // Heartbeat enabled - cass_cluster_set_connection_heartbeat_interval(cluster, 1); - test_utils::CassLog::reset("Heartbeat completed on host " + ccm->get_ip_prefix()); - { - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - execute_system_query(2, session); - } - BOOST_CHECK_GE(test_utils::CassLog::message_count(), 1); - - // Failed heartbeat - cass_uint64_t start_total_connections = 0; - cass_uint64_t end_total_connections = 0; - cass_cluster_set_load_balance_round_robin(cluster); - cass_cluster_set_connection_idle_timeout(cluster, 5); - cass_cluster_set_connection_heartbeat_interval(cluster, 1); - { - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - start_total_connections = get_total_connections(session); - ccm->pause_node(2); - int duration = 0; - do { - execute_system_query(1, session); - ++duration; - end_total_connections = get_total_connections(session); - } while (end_total_connections > (start_total_connections / 2) && duration < 60); - ccm->resume_node(2); - } - BOOST_CHECK_EQUAL(end_total_connections, start_total_connections / 2); -} - -/** - * Heartbeat Idle Timeout - * - * This test ensures the heartbeat idle timeout interval on a connection. - * - * @since 2.1.0 - * @jira_ticket CPP-152 - * @test_category connection:heartbeat - */ -BOOST_AUTO_TEST_CASE(idle_timeout) { - cass_cluster_set_connection_idle_timeout(cluster, 5); - cass_cluster_set_connection_heartbeat_interval(cluster, 1); - test_utils::CassLog::reset("Failed to send a heartbeat within connection idle interval."); - { - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - ccm->pause_node(2); - execute_system_query(10, session); - ccm->resume_node(2); - } - BOOST_CHECK_GE(test_utils::CassLog::message_count(), 1); -} - -BOOST_AUTO_TEST_SUITE_END() From b71949392a9e83e62631ac970cad1ec8c13bdb18 Mon Sep 17 00:00:00 2001 From: Fero Date: Wed, 30 Oct 2019 07:54:05 -0400 Subject: [PATCH 05/42] test: CPP-822 - Simplify CCM objects to use enums (#305) --- cpp-driver/gtests/src/integration/options.cpp | 48 ++---- .../ccm_bridge/src/authentication_type.cpp | 85 --------- .../ccm_bridge/src/authentication_type.hpp | 158 ++++++----------- cpp-driver/test/ccm_bridge/src/bridge.cpp | 41 ++--- .../test/ccm_bridge/src/deployment_type.cpp | 86 --------- .../test/ccm_bridge/src/deployment_type.hpp | 163 ++++++------------ .../ccm_bridge/src/dse_credentials_type.cpp | 85 --------- .../ccm_bridge/src/dse_credentials_type.hpp | 158 ++++++----------- 8 files changed, 183 insertions(+), 641 deletions(-) delete mode 100644 cpp-driver/test/ccm_bridge/src/authentication_type.cpp delete mode 100644 cpp-driver/test/ccm_bridge/src/deployment_type.cpp delete mode 100644 cpp-driver/test/ccm_bridge/src/dse_credentials_type.cpp diff --git a/cpp-driver/gtests/src/integration/options.cpp b/cpp-driver/gtests/src/integration/options.cpp index ab3ca1a55..dc0071b56 100644 --- a/cpp-driver/gtests/src/integration/options.cpp +++ b/cpp-driver/gtests/src/integration/options.cpp @@ -123,20 +123,12 @@ bool Options::initialize(int argc, char* argv[]) { dse_password_ = value; } } else if (key == "--dse-credentials") { - bool is_found = false; - if (!value.empty()) { - for (CCM::DseCredentialsType::iterator iterator = CCM::DseCredentialsType::begin(); - iterator != CCM::DseCredentialsType::end(); ++iterator) { - if (*iterator == value) { - dse_credentials_type_ = *iterator; - is_found = true; - break; - } - } - } - if (!is_found) { + CCM::DseCredentialsType type = CCM::DseCredentialsType::from_string(value); + if (type == CCM::DseCredentialsType::INVALID) { std::cerr << "Invalid DSE/DDAC Credentials Type: Using default " << dse_credentials_type_.to_string() << std::endl; + } else { + dse_credentials_type_ = type; } } else if (key == "--git") { use_git_ = true; @@ -198,36 +190,20 @@ bool Options::initialize(int argc, char* argv[]) { } #ifdef CASS_USE_LIBSSH2 else if (key == "--authentication") { - bool is_found = false; - if (!value.empty()) { - for (CCM::AuthenticationType::iterator iterator = CCM::AuthenticationType::begin(); - iterator != CCM::AuthenticationType::end(); ++iterator) { - if (*iterator == value) { - authentication_type_ = *iterator; - is_found = true; - break; - } - } - } - if (!is_found) { + CCM::AuthenticationType type = CCM::AuthenticationType::from_string(value); + if (type == CCM::AuthenticationType::INVALID) { std::cerr << "Invalid Authentication Type: Using default " << authentication_type_.to_string() << std::endl; + } else { + authentication_type_ = type; } } else if (key == "--deployment") { - bool is_found = false; - if (!value.empty()) { - for (CCM::DeploymentType::iterator iterator = CCM::DeploymentType::begin(); - iterator != CCM::DeploymentType::end(); ++iterator) { - if (*iterator == value) { - deployment_type_ = *iterator; - is_found = true; - break; - } - } - } - if (!is_found) { + CCM::DeploymentType type = CCM::DeploymentType::from_string(value); + if (type == CCM::DeploymentType::INVALID) { std::cerr << "Invalid Deployment Type: Using default " << deployment_type_.to_string() << std::endl; + } else { + deployment_type_ = type; } } else if (key == "--host") { if (!value.empty()) { diff --git a/cpp-driver/test/ccm_bridge/src/authentication_type.cpp b/cpp-driver/test/ccm_bridge/src/authentication_type.cpp deleted file mode 100644 index 16a2d9c2f..000000000 --- a/cpp-driver/test/ccm_bridge/src/authentication_type.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "authentication_type.hpp" - -#include - -using namespace CCM; - -// Constant value definitions for authentication type -const AuthenticationType AuthenticationType::USERNAME_PASSWORD("USERNAME_PASSWORD", 0, - "Username and Password"); -const AuthenticationType AuthenticationType::PUBLIC_KEY("PUBLIC_KEY", 1, "Public Key"); - -// Static declarations for authentication type -std::set AuthenticationType::constants_; - -AuthenticationType::AuthenticationType() - : name_("INVALID") - , ordinal_(-1) - , display_name_("Invalid authentication") {} - -const std::string& AuthenticationType::name() const { return name_; } - -short AuthenticationType::ordinal() const { return ordinal_; } - -const std::string& AuthenticationType::to_string() const { return display_name_; } - -const std::set& AuthenticationType::get_constants() { - if (constants_.empty()) { - constants_.insert(USERNAME_PASSWORD); - constants_.insert(PUBLIC_KEY); - } - - return constants_; -} - -AuthenticationType::AuthenticationType(const std::string& name, int ordinal, - const std::string& display_name) - : name_(name) - , ordinal_(ordinal) - , display_name_(display_name) {} - -std::set::iterator AuthenticationType::end() { return get_constants().end(); } - -std::set::iterator AuthenticationType::begin() { - return get_constants().begin(); -} - -bool AuthenticationType::operator<(const AuthenticationType& object) const { - return ordinal_ < object.ordinal_; -} - -bool AuthenticationType::operator==(const AuthenticationType& object) const { - if (name_ == object.name_) { - if (ordinal_ == object.ordinal_) { - if (display_name_ == object.display_name_) { - return true; - } - } - } - - return false; -} - -bool AuthenticationType::operator==(const std::string& object) const { - std::string lhs = name_; - std::string rhs = object; - std::transform(lhs.begin(), lhs.end(), lhs.begin(), ::tolower); - std::transform(rhs.begin(), rhs.end(), rhs.begin(), ::tolower); - return lhs.compare(rhs) == 0; -} diff --git a/cpp-driver/test/ccm_bridge/src/authentication_type.hpp b/cpp-driver/test/ccm_bridge/src/authentication_type.hpp index 9c61c68ca..49bfd873c 100644 --- a/cpp-driver/test/ccm_bridge/src/authentication_type.hpp +++ b/cpp-driver/test/ccm_bridge/src/authentication_type.hpp @@ -14,10 +14,10 @@ limitations under the License. */ -#ifndef __CCM_AUTHENTICATION_TYPE_HPP__ -#define __CCM_AUTHENTICATION_TYPE_HPP__ +#ifndef CCM_AUTHENTICATION_TYPE_HPP +#define CCM_AUTHENTICATION_TYPE_HPP -#include +#include #include namespace CCM { @@ -27,115 +27,55 @@ namespace CCM { */ class AuthenticationType { public: - /** - * Iterator for authentication type constants - */ - typedef std::set::iterator iterator; - - /** - * Username/Password authentication type; SSH process is authenticated via - * plain text username and password - */ - static const AuthenticationType USERNAME_PASSWORD; - /** - * Public key authentication type; SSH process is authenticated via public - * key - */ - static const AuthenticationType PUBLIC_KEY; - - /** - * Name of constant - * - * @return Name of constant - */ - const std::string& name() const; - /** - * Ordinal of constant - * - * @return Ordinal of constant - */ - short ordinal() const; - /** - * Get the display name - * - * @return Display name of authentication type - */ - const std::string& to_string() const; - - /** - * Less than (can be used for sorting) - * - * @param object Right hand side comparison object - * @return True if LHS < RHS; false otherwise - */ - bool operator<(const AuthenticationType& object) const; - /** - * Equal to - * - * @param object Right hand side comparison object - * @return True if LHS == RHS; false otherwise - */ - bool operator==(const AuthenticationType& object) const; - /** - * Equal to (case-incentive string comparison) - * - * @param object Right hand side comparison object - * @return True if LHS == RHS; false otherwise - */ - bool operator==(const std::string& object) const; - - /** - * First item in the authentication constants - * - * @return Iterator pointing to the first element in the set - */ - static std::set::iterator begin(); - /** - * Last item in the authentication constants - * - * @return Iterator pointing to the last element in the set - */ - static std::set::iterator end(); - - /** - * Default constructor to handle issues with static initialization of - * constant authentication types - */ - AuthenticationType(); + enum Type { INVALID, USERNAME_PASSWORD, PUBLIC_KEY }; + + AuthenticationType(Type type = USERNAME_PASSWORD) + : type_(type) {} + + const char* name() const { + switch (type_) { + case USERNAME_PASSWORD: + return "USERNAME_PASSWORD"; + case PUBLIC_KEY: + return "PUBLIC_KEY"; + default: + return "INVALID"; + } + } + + const char* to_string() const { + switch (type_) { + case USERNAME_PASSWORD: + return "Username and Password"; + case PUBLIC_KEY: + return "Public Key"; + default: + return "Invalid Authentication Type"; + } + } + + bool operator==(const AuthenticationType& other) const { return type_ == other.type_; } + + static AuthenticationType from_string(const std::string& str) { + if (iequals(AuthenticationType(USERNAME_PASSWORD).name(), str)) { + return AuthenticationType(USERNAME_PASSWORD); + } else if (iequals(AuthenticationType(PUBLIC_KEY).name(), str)) { + return AuthenticationType(PUBLIC_KEY); + } + return AuthenticationType(INVALID); + } private: - /** - * Authentication type constants - */ - static std::set constants_; - /** - * Name of constant - */ - std::string name_; - /** - * Ordinal of constant - */ - short ordinal_; - /** - * Display name for constant - */ - std::string display_name_; - - /** - * Constructor - * - * @param name Name for authentication type - * @param display_name Display name for authentication type - */ - AuthenticationType(const std::string& name, int ordinal, const std::string& display_name); - /** - * Get the authentication type constants - * - * @return List of authentication type constants - */ - static const std::set& get_constants(); + static bool iequalsc(char l, char r) { return std::tolower(l) == std::tolower(r); } + + static bool iequals(const std::string& lhs, const std::string& rhs) { + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin(), iequalsc); + } + +private: + Type type_; }; } // namespace CCM -#endif // __CCM_AUTHENTICATION_TYPE_HPP__ +#endif // CCM_AUTHENTICATION_TYPE_HPP diff --git a/cpp-driver/test/ccm_bridge/src/bridge.cpp b/cpp-driver/test/ccm_bridge/src/bridge.cpp index cb2c0f20c..d5a7261ee 100644 --- a/cpp-driver/test/ccm_bridge/src/bridge.cpp +++ b/cpp-driver/test/ccm_bridge/src/bridge.cpp @@ -298,15 +298,12 @@ CCM::Bridge::Bridge(const std::string& configuration_file) server_type_ = DEFAULT_SERVER_TYPE; } } else if (key.compare(CCM_CONFIGURATION_KEY_DSE_CREDENTIALS_TYPE) == 0) { - // Determine the DSE/DDAC credentials type - for (DseCredentialsType::iterator iterator = DseCredentialsType::begin(); - iterator != DseCredentialsType::end(); ++iterator) { - if (*iterator == value) { - dse_credentials_type_ = *iterator; - break; - } else { - LOG_ERROR("Invalid DSE/DDAC credential type \"" << value << "\""); - } + // Determine the DSE credentials type + DseCredentialsType type = DseCredentialsType::from_string(value); + if (type == DseCredentialsType::INVALID) { + LOG_ERROR("Invalid DSE/DDAC credential type \"" << value << "\""); + } else { + dse_credentials_type_ = type; } } else if (key.compare(CCM_CONFIGURATION_KEY_DSE_USERNAME) == 0) { dse_username_ = value; @@ -315,26 +312,20 @@ CCM::Bridge::Bridge(const std::string& configuration_file) #ifdef CASS_USE_LIBSSH2 } else if (key.compare(CCM_CONFIGURATION_KEY_DEPLOYMENT_TYPE) == 0) { // Determine the deployment type - for (DeploymentType::iterator iterator = DeploymentType::begin(); - iterator != DeploymentType::end(); ++iterator) { - if (*iterator == value) { - deployment_type_ = *iterator; - break; - } else { - LOG_ERROR("Invalid deployment type \"" << value << "\""); - } + DeploymentType type = DeploymentType::from_string(value); + if (type == DeploymentType::INVALID) { + LOG_ERROR("Invalid deployment type \"" << value << "\""); + } else { + deployment_type_ = type; } #endif } else if (key.compare(CCM_CONFIGURATION_KEY_AUTHENTICATION_TYPE) == 0) { // Determine the authentication type - for (AuthenticationType::iterator iterator = AuthenticationType::begin(); - iterator != AuthenticationType::end(); ++iterator) { - if (*iterator == value) { - authentication_type_ = *iterator; - break; - } else { - LOG_ERROR("Invalid authentication type \"" << value << "\""); - } + AuthenticationType type = AuthenticationType::from_string(value); + if (type == AuthenticationType::INVALID) { + LOG_ERROR("Invalid authentication type \"" << value << "\""); + } else { + authentication_type_ = type; } #ifdef CASS_USE_LIBSSH2 } else if (key.compare(CCM_CONFIGURATION_KEY_HOST) == 0) { diff --git a/cpp-driver/test/ccm_bridge/src/deployment_type.cpp b/cpp-driver/test/ccm_bridge/src/deployment_type.cpp deleted file mode 100644 index 780934d5f..000000000 --- a/cpp-driver/test/ccm_bridge/src/deployment_type.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "deployment_type.hpp" - -#include - -using namespace CCM; - -// Constant value definitions for deployment type -const DeploymentType DeploymentType::LOCAL("LOCAL", 0, "Local"); -#ifdef CASS_USE_LIBSSH2 -const DeploymentType DeploymentType::REMOTE("REMOTE", 1, "Remote"); -#endif - -// Static declarations for deployment type -std::set DeploymentType::constants_; - -DeploymentType::DeploymentType() - : name_("INVALID") - , ordinal_(-1) - , display_name_("Invalid deployment") {} - -const std::string& DeploymentType::name() const { return name_; } - -short DeploymentType::ordinal() const { return ordinal_; } - -const std::string& DeploymentType::to_string() const { return display_name_; } - -const std::set& DeploymentType::get_constants() { - if (constants_.empty()) { - constants_.insert(LOCAL); -#ifdef CASS_USE_LIBSSH2 - constants_.insert(REMOTE); -#endif - } - - return constants_; -} - -DeploymentType::DeploymentType(const std::string& name, int ordinal, - const std::string& display_name) - : name_(name) - , ordinal_(ordinal) - , display_name_(display_name) {} - -std::set::iterator DeploymentType::end() { return get_constants().end(); } - -std::set::iterator DeploymentType::begin() { return get_constants().begin(); } - -bool DeploymentType::operator<(const DeploymentType& object) const { - return ordinal_ < object.ordinal_; -} - -bool DeploymentType::operator==(const DeploymentType& object) const { - if (name_ == object.name_) { - if (ordinal_ == object.ordinal_) { - if (display_name_ == object.display_name_) { - return true; - } - } - } - - return false; -} - -bool DeploymentType::operator==(const std::string& object) const { - std::string lhs = name_; - std::string rhs = object; - std::transform(lhs.begin(), lhs.end(), lhs.begin(), ::tolower); - std::transform(rhs.begin(), rhs.end(), rhs.begin(), ::tolower); - return lhs.compare(rhs) == 0; -} diff --git a/cpp-driver/test/ccm_bridge/src/deployment_type.hpp b/cpp-driver/test/ccm_bridge/src/deployment_type.hpp index 1ee60330e..4c74a2e7c 100644 --- a/cpp-driver/test/ccm_bridge/src/deployment_type.hpp +++ b/cpp-driver/test/ccm_bridge/src/deployment_type.hpp @@ -14,10 +14,10 @@ limitations under the License. */ -#ifndef __CCM_DEPLOYMENT_TYPE_HPP__ -#define __CCM_DEPLOYMENT_TYPE_HPP__ +#ifndef CCM_DEPLOYMENT_TYPE_HPP +#define CCM_DEPLOYMENT_TYPE_HPP -#include +#include #include namespace CCM { @@ -27,115 +27,66 @@ namespace CCM { */ class DeploymentType { public: - /** - * Iterator for deployment type constants - */ - typedef std::set::iterator iterator; - - /** - * Local deployment type; commands are executed through local process - */ - static const DeploymentType LOCAL; #ifdef CASS_USE_LIBSSH2 - /** - * Remote deployment type; commands are executed though libssh2 - */ - static const DeploymentType REMOTE; + enum Type { INVALID, LOCAL, REMOTE }; +#else + enum Type { INVALID, LOCAL }; #endif - /** - * Name of constant - * - * @return Name of constant - */ - const std::string& name() const; - /** - * Ordinal of constant - * - * @return Ordinal of constant - */ - short ordinal() const; - /** - * Get the display name - * - * @return Display name of deployment type - */ - const std::string& to_string() const; - - /** - * Less than (can be used for sorting) - * - * @param object Right hand side comparison object - * @return True if LHS < RHS; false otherwise - */ - bool operator<(const DeploymentType& object) const; - /** - * Equal to - * - * @param object Right hand side comparison object - * @return True if LHS == RHS; false otherwise - */ - bool operator==(const DeploymentType& object) const; - /** - * Equal to (case-incentive string comparison) - * - * @param object Right hand side comparison object - * @return True if LHS == RHS; false otherwise - */ - bool operator==(const std::string& object) const; - - /** - * First item in the deployment constants - * - * @return Iterator pointing to the first element in the set - */ - static std::set::iterator begin(); - /** - * Last item in the deployment constants - * - * @return Iterator pointing to the last element in the set - */ - static std::set::iterator end(); - - /** - * Default constructor to handle issues with static initialization of - * constant deployment types - */ - DeploymentType(); + DeploymentType(Type type = LOCAL) + : type_(type) {} + + const char* name() const { + switch (type_) { + case LOCAL: + return "LOCAL"; +#ifdef CASS_USE_LIBSSH2 + case REMOTE: + return "REMOTE"; +#endif + default: + return "INVALID"; + } + } + + const char* to_string() const { + switch (type_) { + case LOCAL: + return "Local"; +#ifdef CASS_USE_LIBSSH2 + case REMOTE: + return "Remote"; +#endif + default: + return "Invalid Deployment Type"; + } + } + + bool operator==(const DeploymentType& other) const { return type_ == other.type_; } + + static DeploymentType from_string(const std::string& str) { + if (iequals(DeploymentType(LOCAL).name(), str)) { + return DeploymentType(LOCAL); + } +#ifdef CASS_USE_LIBSSH2 + else if (iequals(DeploymentType(REMOTE).name(), str)) { + return DeploymentType(REMOTE); + } +#endif + return DeploymentType(INVALID); + } + +private: + static bool iequalsc(char l, char r) { return std::tolower(l) == std::tolower(r); } + + static bool iequals(const std::string& lhs, const std::string& rhs) { + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin(), iequalsc); + } private: - /** - * Deployment type constants - */ - static std::set constants_; - /** - * Name of constant - */ - std::string name_; - /** - * Ordinal of constant - */ - short ordinal_; - /** - * Display name for constant - */ - std::string display_name_; - - /** - * Constructor - * - * @param name Name for deployment type - * @param display_name Display name for deployment type - */ - DeploymentType(const std::string& name, int ordinal, const std::string& display_name); - /** - * Get the deployment type constants - * - * @return List of deployment type constants - */ - static const std::set& get_constants(); + Type type_; }; } // namespace CCM -#endif // __CCM_DEPLOYMENT_TYPE_HPP__ +#endif // CCM_DEPLOYMENT_TYPE_HPP diff --git a/cpp-driver/test/ccm_bridge/src/dse_credentials_type.cpp b/cpp-driver/test/ccm_bridge/src/dse_credentials_type.cpp deleted file mode 100644 index 53563e538..000000000 --- a/cpp-driver/test/ccm_bridge/src/dse_credentials_type.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "dse_credentials_type.hpp" - -#include - -using namespace CCM; - -// Constant value definitions for DSE credentials type -const DseCredentialsType DseCredentialsType::USERNAME_PASSWORD("USERNAME_PASSWORD", 0, - "Username and Password"); -const DseCredentialsType DseCredentialsType::INI_FILE("INI_FILE", 1, "INI Credentials File"); - -// Static declarations for DSE credentials type -std::set DseCredentialsType::constants_; - -DseCredentialsType::DseCredentialsType() - : name_("INVALID") - , ordinal_(-1) - , display_name_("Invalid DSE credentials") {} - -const std::string& DseCredentialsType::name() const { return name_; } - -short DseCredentialsType::ordinal() const { return ordinal_; } - -const std::string& DseCredentialsType::to_string() const { return display_name_; } - -const std::set& DseCredentialsType::get_constants() { - if (constants_.empty()) { - constants_.insert(USERNAME_PASSWORD); - constants_.insert(INI_FILE); - } - - return constants_; -} - -DseCredentialsType::DseCredentialsType(const std::string& name, int ordinal, - const std::string& display_name) - : name_(name) - , ordinal_(ordinal) - , display_name_(display_name) {} - -std::set::iterator DseCredentialsType::end() { return get_constants().end(); } - -std::set::iterator DseCredentialsType::begin() { - return get_constants().begin(); -} - -bool DseCredentialsType::operator<(const DseCredentialsType& object) const { - return ordinal_ < object.ordinal_; -} - -bool DseCredentialsType::operator==(const DseCredentialsType& object) const { - if (name_ == object.name_) { - if (ordinal_ == object.ordinal_) { - if (display_name_ == object.display_name_) { - return true; - } - } - } - - return false; -} - -bool DseCredentialsType::operator==(const std::string& object) const { - std::string lhs = name_; - std::string rhs = object; - std::transform(lhs.begin(), lhs.end(), lhs.begin(), ::tolower); - std::transform(rhs.begin(), rhs.end(), rhs.begin(), ::tolower); - return lhs.compare(rhs) == 0; -} diff --git a/cpp-driver/test/ccm_bridge/src/dse_credentials_type.hpp b/cpp-driver/test/ccm_bridge/src/dse_credentials_type.hpp index 307972ac0..70d58913d 100644 --- a/cpp-driver/test/ccm_bridge/src/dse_credentials_type.hpp +++ b/cpp-driver/test/ccm_bridge/src/dse_credentials_type.hpp @@ -14,10 +14,10 @@ limitations under the License. */ -#ifndef __CCM_DSE_CREDENTIALS_TYPE_HPP__ -#define __CCM_DSE_CREDENTIALS_TYPE_HPP__ +#ifndef CCM_DSE_CREDENTIALS_TYPE_HPP +#define CCM_DSE_CREDENTIALS_TYPE_HPP -#include +#include #include namespace CCM { @@ -28,115 +28,55 @@ namespace CCM { */ class DseCredentialsType { public: - /** - * Iterator for DSE credentials type constants - */ - typedef std::set::iterator iterator; - - /** - * Username/Password credentials type; DSE download process is authenticated - * via plain text username and password - */ - static const DseCredentialsType USERNAME_PASSWORD; - /** - * File credentials type; DSE download process is authenticated via the - * CCM DSE credentials default file location (e.g. ~/.ccm/.dse.ini) - */ - static const DseCredentialsType INI_FILE; - - /** - * Name of constant - * - * @return Name of constant - */ - const std::string& name() const; - /** - * Ordinal of constant - * - * @return Ordinal of constant - */ - short ordinal() const; - /** - * Get the display name - * - * @return Display name of DSE credentials type - */ - const std::string& to_string() const; - - /** - * Less than (can be used for sorting) - * - * @param object Right hand side comparison object - * @return True if LHS < RHS; false otherwise - */ - bool operator<(const DseCredentialsType& object) const; - /** - * Equal to - * - * @param object Right hand side comparison object - * @return True if LHS == RHS; false otherwise - */ - bool operator==(const DseCredentialsType& object) const; - /** - * Equal to (case-incentive string comparison) - * - * @param object Right hand side comparison object - * @return True if LHS == RHS; false otherwise - */ - bool operator==(const std::string& object) const; - - /** - * First item in the DSE credentials constants - * - * @return Iterator pointing to the first element in the set - */ - static std::set::iterator begin(); - /** - * Last item in the DSE credentials constants - * - * @return Iterator pointing to the last element in the set - */ - static std::set::iterator end(); - - /** - * Default constructor to handle issues with static initialization of - * constant DSE credentials types - */ - DseCredentialsType(); + enum Type { INVALID, USERNAME_PASSWORD, INI_FILE }; + + DseCredentialsType(Type type = USERNAME_PASSWORD) + : type_(type) {} + + const char* name() const { + switch (type_) { + case USERNAME_PASSWORD: + return "USERNAME_PASSWORD"; + case INI_FILE: + return "INI_FILE"; + default: + return "INVALID"; + } + } + + const char* to_string() const { + switch (type_) { + case USERNAME_PASSWORD: + return "Username and Password"; + case INI_FILE: + return "INI Credentials File"; + default: + return "Invalid DSE Credentials Type"; + } + } + + bool operator==(const DseCredentialsType& other) const { return type_ == other.type_; } + + static DseCredentialsType from_string(const std::string& str) { + if (iequals(DseCredentialsType(USERNAME_PASSWORD).name(), str)) { + return DseCredentialsType(USERNAME_PASSWORD); + } else if (iequals(DseCredentialsType(INI_FILE).name(), str)) { + return DseCredentialsType(INI_FILE); + } + return DseCredentialsType(INVALID); + } private: - /** - * DSE credentials type constants - */ - static std::set constants_; - /** - * Name of constant - */ - std::string name_; - /** - * Ordinal of constant - */ - short ordinal_; - /** - * Display name for constant - */ - std::string display_name_; - - /** - * Constructor - * - * @param name Name for DSE credentials type - * @param display_name Display name for DSE credentials type - */ - DseCredentialsType(const std::string& name, int ordinal, const std::string& display_name); - /** - * Get the DSE credential type constants - * - * @return List of DSE credentials type constants - */ - static const std::set& get_constants(); + static bool iequalsc(char l, char r) { return std::tolower(l) == std::tolower(r); } + + static bool iequals(const std::string& lhs, const std::string& rhs) { + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin(), iequalsc); + } + +private: + Type type_; }; } // namespace CCM -#endif // __CCM_DSE_CREDENTIALS_TYPE_HPP__ +#endif // CCM_DSE_CREDENTIALS_TYPE_HPP From b04eca2d09090e75a06b78ec9d8a07aff1a89f1f Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Wed, 30 Oct 2019 14:17:09 -0400 Subject: [PATCH 06/42] CPP-829/CPP-830 Port prepare on XXX tests to Google test framework (#315) --- .../src/integration/objects/cluster.hpp | 36 ++ .../src/integration/tests/test_prepare_on.cpp | 471 ++++++++++++++++++ .../src/test_prepare_on_all.cpp | 287 ----------- .../src/test_prepare_on_up_or_add_host.cpp | 356 ------------- 4 files changed, 507 insertions(+), 643 deletions(-) create mode 100644 cpp-driver/gtests/src/integration/tests/test_prepare_on.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_prepare_on_all.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_prepare_on_up_or_add_host.cpp diff --git a/cpp-driver/gtests/src/integration/objects/cluster.hpp b/cpp-driver/gtests/src/integration/objects/cluster.hpp index 8edbac930..93553a140 100644 --- a/cpp-driver/gtests/src/integration/objects/cluster.hpp +++ b/cpp-driver/gtests/src/integration/objects/cluster.hpp @@ -363,6 +363,42 @@ class Cluster : public Object { return *this; } + /** + * Enable whitelist filtering. + * + * @param hosts A comma delimited list of hosts (addresses or + * names) + * @return Cluster object + */ + Cluster& with_whitelist_filtering(const std::string& hosts) { + cass_cluster_set_whitelist_filtering(get(), hosts.c_str()); + return *this; + } + + /** + * Enable/Disable preparing all hosts when preparing a new statement. + * + * @param enable + * @return Cluster object + */ + Cluster& with_prepare_on_all_hosts(bool enable) { + EXPECT_EQ(CASS_OK, + cass_cluster_set_prepare_on_all_hosts(get(), enable ? cass_true : cass_false)); + return *this; + } + + /** + * Enable/Disable preparing existing statements on new or down hosts. + * + * @param enable + * @return Cluster object + */ + Cluster& with_prepare_on_up_or_add_host(bool enable) { + EXPECT_EQ(CASS_OK, + cass_cluster_set_prepare_on_up_or_add_host(get(), enable ? cass_true : cass_false)); + return *this; + } + /** * Create a new session and establish a connection to the server; * synchronously diff --git a/cpp-driver/gtests/src/integration/tests/test_prepare_on.cpp b/cpp-driver/gtests/src/integration/tests/test_prepare_on.cpp new file mode 100644 index 000000000..ee85c8f18 --- /dev/null +++ b/cpp-driver/gtests/src/integration/tests/test_prepare_on.cpp @@ -0,0 +1,471 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "integration.hpp" + +#include + +/** + * Base class for "prepare on" tests. + */ +class PrepareOn : public Integration { +public: + void SetUp() { + Integration::SetUp(); + CHECK_VERSION(3.10); + sessions_.reserve(number_dc1_nodes_ + 1); + for (size_t node = 1; node <= number_dc1_nodes_; ++node) { + truncate_prepared_statements(node); + } + } + + /** + * Get a session that is only connected to the given node. + * + * @param node The node the session should be connected to + * @return The connected session + */ + Session session_for_node(size_t node) { + if (node >= sessions_.size() || !sessions_[node]) { + sessions_.resize(node + 1); + + std::stringstream ip_address; + ip_address << ccm_->get_ip_prefix() << node; + + Cluster cluster; + cluster.with_contact_points(ip_address.str()); + cluster.with_whitelist_filtering(ip_address.str()); + sessions_[node] = cluster.connect(keyspace_name_); + } + + return sessions_[node]; + } + + /** + * Verify that all nodes have empty "system.prepared_statements" tables. + */ + void prepared_statements_is_empty_on_all_nodes() { + for (size_t node = 1; node <= number_dc1_nodes_; ++node) { + prepared_statements_is_empty(node); + } + } + + /** + * Verify that a nodes "system.prepared_statements" table is empty. + * + * @param node The node to check + */ + void prepared_statements_is_empty(size_t node) { + EXPECT_EQ( + session_for_node(node).execute("SELECT * FROM system.prepared_statements").row_count(), 0u); + } + + /** + * Check to see if a query has been prepared on a given node. + * + * @param node The node to check + * @param query A query string + * @return true if the query has been prepared on the node, otherwise false + */ + bool prepared_statement_is_present(size_t node, const std::string& query) { + Result result = session_for_node(node).execute("SELECT * FROM system.prepared_statements"); + if (!result) return false; + + Rows rows = result.rows(); + for (size_t i = 0; rows.row_count(); ++i) { + Row row = rows.next(); + if (row.column_by_name("query_string").str() == query) { + return true; + } + } + return false; + } + + /** + * Get the count of nodes in the cluster where the provided query is prepared. + * + * @param query A query string + * @return The number of nodes that have the prepared statement + */ + int prepared_statement_is_present_count(const std::string& query) { + int count = 0; + for (size_t node = 1; node <= number_dc1_nodes_; ++node) { + if (prepared_statement_is_present(node, query)) { + count++; + } + } + return count; + } + + /** + * Truncate the "system.prepared_statements" table on a given node + * + * @param node The node to truncate + */ + void truncate_prepared_statements(size_t node) { + session_for_node(node).execute("TRUNCATE TABLE system.prepared_statements"); + } + + /** + * Wait for a session to reconnect to a node. + * + * @param session The session to use for waiting + * @param node The node to wait for + */ + void wait_for_node(size_t node) { + for (int i = 0; i < 300; ++i) { + Result result = session_for_node(node).execute(SELECT_ALL_SYSTEM_LOCAL_CQL, + CASS_CONSISTENCY_LOCAL_ONE, false, false); + CassError rc = result.error_code(); + if (rc == CASS_OK) return; + msleep(200); + } + ASSERT_TRUE(false) << "Node " << node << " didn't become available within the allocated time"; + } + +private: + std::vector sessions_; +}; + +/** + * Prepare on all hosts test suite. + */ +class PrepareOnAllTests : public PrepareOn { +public: + PrepareOnAllTests() { number_dc1_nodes_ = 3; } + + void SetUp() { + PrepareOn::SetUp(); + prepared_query_ = default_select_all(); + session_.execute( + format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "int")); + } + + /** + * Verify that a given number of nodes contain a prepared statement. + * + * @param session The session to use for preparing the query + * @param count The number of nodes the query should be prepared on + */ + void verify_prepared_statement_count(Session session, int count) { + prepared_statements_is_empty_on_all_nodes(); + session.prepare(prepared_query_); + EXPECT_EQ(prepared_statement_is_present_count(prepared_query_), count); + } + + const std::string& prepared_query() const { return prepared_query_; } + + Cluster cluster() { + // Ensure existing prepared statements are not re-prepared when they become + // available again. + return default_cluster().with_prepare_on_up_or_add_host(false).with_constant_reconnect(200); + } + +private: + std::string prepared_query_; +}; + +/** + * Verify that only a single node is prepared when the prepare on all hosts + * setting is disabled. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PrepareOnAllTests, SingleNodeWhenDisabled) { + CHECK_FAILURE; + CHECK_VERSION(3.10); + + // Prepare on all hosts disabled + Session session = cluster().with_prepare_on_all_hosts(false).connect(); + + // Only a single host should have the statement prepared + verify_prepared_statement_count(session, 1); +} + +/** + * Verify that all nodes are prepared properly when the prepare on all hosts + * setting is enabled. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PrepareOnAllTests, AllNodesWhenEnabled) { + CHECK_FAILURE; + CHECK_VERSION(3.10); + + // Prepare on all hosts enabled + Session session = cluster().with_prepare_on_all_hosts(true).connect(); + + // All hosts should have the statement prepared + verify_prepared_statement_count(session, 3); +} + +/** + * Verify that all available nodes are prepared properly when the prepare on + * all hosts setting is enabled and one of the nodes is not available. + * + * The statement should be prepared on all available nodes, but not the node + * that was down. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PrepareOnAllTests, NodeOutage) { + CHECK_FAILURE; + CHECK_VERSION(3.10); + + // Ensure there are no existing prepared statements + prepared_statements_is_empty_on_all_nodes(); + + stop_node(2, true); + + { // Prepare the statement with prepare on all enabled + Session session = cluster().with_prepare_on_all_hosts(true).connect(); + session.prepare(prepared_query()); + } + + start_node(2); + + // Wait for the session to reconnect to the node + wait_for_node(2); + + // The statement should only be prepared on the previously available nodes + EXPECT_EQ(prepared_statement_is_present_count(prepared_query()), 2); +} + +/** + * Prepare on host UP and ADD test suite. + */ +class PrepareOnUpAndAddTests : public PrepareOn { +public: + PrepareOnUpAndAddTests() { number_dc1_nodes_ = 1; } + + void SetUp() { + PrepareOn::SetUp(); + + for (int i = 1; i <= 3; ++i) { + std::stringstream ss; + ss << table_name_ << i; + std::string table_name_with_suffix(ss.str()); + session_.execute(format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, + table_name_with_suffix.c_str(), "int", "int")); + prepared_queries_.push_back(format_string("SELECT * FROM %s.%s", keyspace_name_.c_str(), + table_name_with_suffix.c_str())); + } + } + + /** + * Prepare all queries on a given session. + * + * @param session The session to prepare the queries on + */ + void prepare_all_queries(Session session) { + for (std::vector::const_iterator it = prepared_queries_.begin(), + end = prepared_queries_.end(); + it != end; ++it) { + session.prepare(*it); + } + } + + /** + * Verify that all prepared queries are available on the specified node. + * + * @param node The node to verify + */ + void prepared_statements_are_present(size_t node) { + wait_for_node(node); + for (std::vector::const_iterator it = prepared_queries_.begin(), + end = prepared_queries_.end(); + it != end; ++it) { + EXPECT_TRUE(prepared_statement_is_present(node, (*it))) + << "Prepared statement should be present on node " << node; + } + } + + /** + * Verify that all prepared queries are not available on the specified node. + * + * @param node The node to verify + */ + void prepared_statements_are_not_present(size_t node) { + wait_for_node(node); + for (std::vector::const_iterator it = prepared_queries_.begin(), + end = prepared_queries_.end(); + it != end; ++it) { + EXPECT_FALSE(prepared_statement_is_present(node, (*it))) + << "Prepare statement should not be present on node " << node; + } + } + + /** + * Wait for a session to reconnect to a node. + * + * @param session The session to use for waiting + * @param node The node to wait for + */ + void wait_for_node_on_session(Session session, size_t node) { + std::stringstream ip_address; + ip_address << ccm_->get_ip_prefix() << node; + + for (int i = 0; i < 300; ++i) { + Result result = + session.execute(SELECT_ALL_SYSTEM_LOCAL_CQL, CASS_CONSISTENCY_LOCAL_ONE, false, false); + if (result && result.error_code() == CASS_OK && result.host() == ip_address.str()) { + return; + } + msleep(200); + } + ASSERT_TRUE(false) << "Node " << node << " didn't become available within the allocated time"; + } + + Cluster cluster() { + // Make sure we equally try all available hosts + return default_cluster().with_load_balance_round_robin().with_constant_reconnect(200); + } + +private: + std::vector prepared_queries_; +}; + +/** + * Verify that statements are not prepared when a node becomes available and + * the prepare on up/add feature is disabled. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PrepareOnUpAndAddTests, NotPreparedOnUpWhenDisabled) { + CHECK_FAILURE; + CHECK_VERSION(3.10); + + // Disable the prepare on up/add setting + Session session = cluster().with_prepare_on_up_or_add_host(false).connect(); + + // Verify that there are no statements prepared + truncate_prepared_statements(1); + prepared_statements_is_empty(1); + + // Populate the driver's prepared metadata cache + prepare_all_queries(session); + prepared_statements_are_present(1); + + // Clear all prepared queries on the server-side + truncate_prepared_statements(1); + prepared_statements_is_empty(1); + + // Simulate an UP event + stop_node(1); + start_node(1); + + // Wait for the node to become available and verify no statements have been + // prepared + wait_for_node_on_session(session, 1); + prepared_statements_are_not_present(1); +} + +/** + * Verify that statements are prepared properly when a node becomes available + * and the prepare on up/add feature is enabled. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PrepareOnUpAndAddTests, PreparedOnUpWhenEnabled) { + CHECK_FAILURE; + CHECK_VERSION(3.10); + + // Enable the prepare on up/add setting + Session session = cluster().with_prepare_on_up_or_add_host(true).connect(); + + // Verify that there are no statements prepared + truncate_prepared_statements(1); + prepared_statements_is_empty(1); + + // Populate the driver's prepared metadata cache + prepare_all_queries(session); + prepared_statements_are_present(1); + + // Clear all prepared queries on the server-side + truncate_prepared_statements(1); + prepared_statements_is_empty(1); + + // Simulate an UP event + stop_node(1); + start_node(1); + + // Wait for the node to become available and verify that the statements + // in the prepared metadata cache have been prepared + wait_for_node_on_session(session, 1); + prepared_statements_are_present(1); +} + +/** + * Verify that statements are not prepared when a new node is added to a cluster + * and the prepare on up/add feature is disabled. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PrepareOnUpAndAddTests, NotPreparedOnAddWhenDisabled) { + CHECK_FAILURE; + CHECK_VERSION(3.10); + is_test_chaotic_ = true; + + // Disable the prepare on up/add setting + Session session = cluster().with_prepare_on_up_or_add_host(false).connect(); + + // Verify that there are no statements prepared + truncate_prepared_statements(1); + prepared_statements_is_empty(1); + + // Populate the driver's prepared metadata cache + prepare_all_queries(session); + prepared_statements_are_present(1); + + // Add a new node + size_t node = ccm_->bootstrap_node(); + + // Wait for the new node to become available and verify no statements have + // been prepared + wait_for_node_on_session(session, node); + prepared_statements_are_not_present(node); +} + +/** + * Verify that statements are prepared properly when a new node is added to a + * cluster and the prepare on up/add feature is enabled. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PrepareOnUpAndAddTests, PreparedOnAddWhenEnabled) { + CHECK_FAILURE; + CHECK_VERSION(3.10); + is_test_chaotic_ = true; + + // Enable the prepare on up/add setting + Session session = cluster().with_prepare_on_up_or_add_host(true).connect(); + + // Verify that there are no statements prepared + truncate_prepared_statements(1); + prepared_statements_is_empty(1); + + // Populate the driver's prepared metadata cache + prepare_all_queries(session); + prepared_statements_are_present(1); + + // Add a new node + size_t node = ccm_->bootstrap_node(); + + // Wait for the new node to become available and verify that the statements + // in the prepared metadata cache have been prepared + wait_for_node_on_session(session, node); + prepared_statements_are_present(node); +} diff --git a/cpp-driver/test/integration_tests/src/test_prepare_on_all.cpp b/cpp-driver/test/integration_tests/src/test_prepare_on_all.cpp deleted file mode 100644 index c08c96f4f..000000000 --- a/cpp-driver/test/integration_tests/src/test_prepare_on_all.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include -#include -#include -#include -#include -#include - -#include "cassandra.h" -#include "execute_request.hpp" -#include "statement.hpp" -#include "test_utils.hpp" - -#define NUM_LOCAL_NODES 3 - -/** - * Test harness for prepare on all host functionality. - */ -struct PrepareOnAllTests : public test_utils::SingleSessionTest { - - /** - * Create a basic schema (system table queries won't always prepare properly) - * and clear all prepared statements. - */ - PrepareOnAllTests() - : SingleSessionTest(NUM_LOCAL_NODES, 0) - , keyspace(str(boost::format("ks_%s") % test_utils::generate_unique_str(uuid_gen))) - , prepared_query_(str(boost::format("SELECT * FROM %s.test") % keyspace)) { - test_utils::execute_query( - session, str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % keyspace % "1")); - test_utils::execute_query(session, str(boost::format("USE %s") % keyspace)); - test_utils::execute_query(session, "CREATE TABLE test (k text PRIMARY KEY, v text)"); - - // The "system.prepare_statements" table only exists in C* 3.10+ - if (version >= "3.10") { - for (int node = 1; node <= NUM_LOCAL_NODES; ++node) { - test_utils::execute_query(session_for_node(node).get(), - "TRUNCATE TABLE system.prepared_statements"); - } - } - - // Ensure existing prepared statements are not re-prepared when they become - // available again. - cass_cluster_set_prepare_on_up_or_add_host(cluster, cass_false); - } - - /** - * Get a session that is only connected to the given node. - * - * @param node The node the session should be connected to - * @return The connected session - */ - const test_utils::CassSessionPtr& session_for_node(int node) { - if (node >= static_cast(sessions.size()) || !sessions[node]) { - sessions.resize(node + 1); - - std::stringstream ip_address; - ip_address << ccm->get_ip_prefix() << node; - - test_utils::CassClusterPtr cluster(cass_cluster_new()); - cass_cluster_set_contact_points(cluster.get(), ip_address.str().c_str()); - cass_cluster_set_whitelist_filtering(cluster.get(), ip_address.str().c_str()); - sessions[node] = test_utils::create_session(cluster.get()); - } - - return sessions[node]; - } - - /** - * Verify that all nodes have empty "system.prepared_statements" tables. - */ - void prepared_statements_is_empty_on_all_nodes() { - for (int node = 1; node <= NUM_LOCAL_NODES; ++node) { - prepared_statements_is_empty(node); - } - } - - /** - * Verify that a nodes "system.prepared_statements" table is empty. - * - * @param node The node to check - */ - void prepared_statements_is_empty(int node) { - test_utils::CassResultPtr result; - test_utils::execute_query(session_for_node(node).get(), - "SELECT * FROM system.prepared_statements", &result); - BOOST_REQUIRE_EQUAL(cass_result_row_count(result.get()), 0u); - } - - /** - * Check to see if a query has been prepared on a given node. - * - * @param node The node to check - * @param query A query string - * @return true if the query has been prepared on the node, otherwise false - */ - bool prepared_statement_is_present(int node, const std::string& query) { - test_utils::CassResultPtr result; - test_utils::execute_query(session_for_node(node).get(), - "SELECT * FROM system.prepared_statements", &result); - - test_utils::CassIteratorPtr iterator(cass_iterator_from_result(result.get())); - while (cass_iterator_next(iterator.get())) { - const CassRow* row = cass_iterator_get_row(iterator.get()); - BOOST_REQUIRE(row); - - const CassValue* query_column = cass_row_get_column_by_name(row, "query_string"); - const char* query_string; - size_t query_string_len; - BOOST_REQUIRE_EQUAL(cass_value_get_string(query_column, &query_string, &query_string_len), - CASS_OK); - - if (query == std::string(query_string, query_string_len)) { - return true; - } - } - - return false; - } - - /** - * Get the count of nodes in the cluster where the provided query is prepared. - * - * @param query A query string - * @return The number of nodes that have the prepared statement - */ - int prepared_statement_is_present_count(const std::string& query) { - int count = 0; - for (int node = 1; node <= NUM_LOCAL_NODES; ++node) { - if (prepared_statement_is_present(node, query)) { - count++; - } - } - return count; - } - - /** - * Verify that a given number of nodes contain a prepared statement. - * - * @param count The number of nodes the query should be prepared on - */ - void verify_prepared_statement_count(int count) { - prepared_statements_is_empty_on_all_nodes(); - - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - - test_utils::CassFuturePtr future(cass_session_prepare(session.get(), prepared_query_.c_str())); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future.get()), CASS_OK); - - test_utils::CassPreparedPtr prepared(cass_future_get_prepared(future.get())); - BOOST_REQUIRE(prepared); - - BOOST_CHECK_EQUAL(prepared_statement_is_present_count(prepared_query_), count); - } - - /** - * Wait for a session to reconnect to a node. - * - * @param node The node to wait for - */ - void wait_for_node(int node) { - for (int i = 0; i < 10; ++i) { - test_utils::CassStatementPtr statement(cass_statement_new("SELECT * FROM system.peers", 0)); - test_utils::CassFuturePtr future( - cass_session_execute(session_for_node(node).get(), statement.get())); - if (cass_future_error_code(future.get()) == CASS_OK) { - return; - } - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - } - BOOST_REQUIRE(false); - } - - /** - * A vector of sessions that are only connected to a single host (via the - * whitelist policy). - */ - std::vector sessions; - - /** - * The test's keyspace - */ - std::string keyspace; - - /** - * The query to be prepared - */ - std::string prepared_query_; -}; - -BOOST_FIXTURE_TEST_SUITE(prepare_on_all, PrepareOnAllTests) - -/** - * Verify that only a single node is prepared when the prepare on all hosts - * setting is disabled. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(only_prepares_a_single_node_when_disabled) { - if (!check_version("3.10")) return; - - // Prepare on all hosts disabled - cass_cluster_set_prepare_on_all_hosts(cluster, cass_false); - - // Only a single host should have the statement prepared - verify_prepared_statement_count(1); -} - -/** - * Verify that all nodes are prepared properly when the prepare on all hosts - * setting is enabled. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(prepares_on_all_nodes_when_enabled) { - if (!check_version("3.10")) return; - - // Prepare on all hosts enabled - cass_cluster_set_prepare_on_all_hosts(cluster, cass_true); - - // All hosts should have the statement prepared - verify_prepared_statement_count(3); -} - -/** - * Verify that all available nodes are prepared properly when the prepare on - * all hosts setting is enabled and one of the nodes is not available. - * - * The statement should be prepared on all available nodes, but not the node - * that was down. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(prepare_on_all_handles_node_outage) { - if (!check_version("3.10")) return; - - // Prepare on all hosts enabled - cass_cluster_set_prepare_on_all_hosts(cluster, cass_true); - - // Ensure there are no existing prepared statements - prepared_statements_is_empty_on_all_nodes(); - - ccm->kill_node(2); - - { // Prepare the statement - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - - test_utils::CassFuturePtr future(cass_session_prepare(session.get(), prepared_query_.c_str())); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future.get()), CASS_OK); - - test_utils::CassPreparedPtr prepared(cass_future_get_prepared(future.get())); - BOOST_REQUIRE(prepared); - } - - ccm->start_node(2); - - // Wait for the session to reconnect to the node - wait_for_node(2); - - // The statement should only be prepared on the previously available nodes - BOOST_CHECK_EQUAL(prepared_statement_is_present_count(prepared_query_), 2); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/cpp-driver/test/integration_tests/src/test_prepare_on_up_or_add_host.cpp b/cpp-driver/test/integration_tests/src/test_prepare_on_up_or_add_host.cpp deleted file mode 100644 index d3723338e..000000000 --- a/cpp-driver/test/integration_tests/src/test_prepare_on_up_or_add_host.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* - Copyright (c) 2017 DataStax - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include -#include -#include -#include -#include -#include - -#include "cassandra.h" -#include "execute_request.hpp" -#include "statement.hpp" -#include "test_utils.hpp" -#include "testing.hpp" - -using namespace datastax::internal::testing; - -/** - * Test harness for prepare on up or add host functionality. - */ -struct PrepareOnUpOrAddHostTests : public test_utils::SingleSessionTest { - - /** - * Create a basic schema (system table queries won't always prepare properly) - * and initialize prepared query strings. - */ - PrepareOnUpOrAddHostTests() - : SingleSessionTest(1, 0) - , keyspace(str(boost::format("ks_%s") % test_utils::generate_unique_str(uuid_gen))) { - test_utils::execute_query( - session, str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % keyspace % "1")); - test_utils::execute_query(session, str(boost::format("USE %s") % keyspace)); - - for (int i = 1; i <= 3; ++i) { - test_utils::execute_query( - session, str(boost::format("CREATE TABLE test%d (k text PRIMARY KEY, v text)") % i)); - prepared_queries_.push_back(str(boost::format("SELECT * FROM %s.test%d") % keyspace % i)); - } - - // Make sure we equally try all available hosts - cass_cluster_set_load_balance_round_robin(cluster); - } - - /** - * Get a session that is only connected to the given node. - * - * @param node The node the session should be connected to - * @return The connected session - */ - const test_utils::CassSessionPtr& session_for_node(int node) { - if (node >= static_cast(sessions.size()) || !sessions[node]) { - sessions.resize(node + 1); - - std::stringstream ip_address; - ip_address << ccm->get_ip_prefix() << node; - - test_utils::CassClusterPtr cluster(cass_cluster_new()); - cass_cluster_set_contact_points(cluster.get(), ip_address.str().c_str()); - cass_cluster_set_whitelist_filtering(cluster.get(), ip_address.str().c_str()); - sessions[node] = test_utils::create_session(cluster.get()); - } - - return sessions[node]; - } - - /** - * Truncate the "system.prepared_statements" table on a given node - * - * @param node The node to truncate - */ - void truncate_prepared_statements(int node) { - test_utils::execute_query(session_for_node(node).get(), - "TRUNCATE TABLE system.prepared_statements"); - } - - /** - * Verify that a nodes "system.prepared_statements" table is empty. - * - * @param node The node to check - */ - void prepared_statements_is_empty(int node) { - test_utils::CassResultPtr result; - test_utils::execute_query(session_for_node(node).get(), - "SELECT * FROM system.prepared_statements", &result); - BOOST_REQUIRE_EQUAL(cass_result_row_count(result.get()), 0u); - } - - /** - * Check to see if a query has been prepared on a given node. - * - * @param node The node to check - * @param query A query string - * @return true if the query has been prepared on the node, otherwise false - */ - bool prepared_statement_is_present(int node, const std::string& query) { - test_utils::CassResultPtr result; - test_utils::execute_query(session_for_node(node).get(), - "SELECT * FROM system.prepared_statements", &result); - - test_utils::CassIteratorPtr iterator(cass_iterator_from_result(result.get())); - while (cass_iterator_next(iterator.get())) { - const CassRow* row = cass_iterator_get_row(iterator.get()); - BOOST_REQUIRE(row); - - const CassValue* query_column = cass_row_get_column_by_name(row, "query_string"); - const char* query_string; - size_t query_string_len; - BOOST_REQUIRE_EQUAL(cass_value_get_string(query_column, &query_string, &query_string_len), - CASS_OK); - - if (query == std::string(query_string, query_string_len)) { - return true; - } - } - - return false; - } - - /** - * Verify that all prepared queries are available on the specifed node. - * - * @param node The node to verify - */ - void prepared_statements_are_present(int node) { - for (std::vector::const_iterator it = prepared_queries_.begin(); - it != prepared_queries_.end(); ++it) { - BOOST_CHECK(prepared_statement_is_present(node, (*it))); - } - } - - /** - * Verify that all prepared queries are not available on the specifed node. - * - * @param node The node to verify - */ - void prepared_statements_are_not_present(int node) { - for (std::vector::const_iterator it = prepared_queries_.begin(); - it != prepared_queries_.end(); ++it) { - BOOST_CHECK(!prepared_statement_is_present(node, (*it))); - } - } - - /** - * Prepare all queries on a given session. - * - * @param session The session to prepare the queries on - */ - void prepare_all_queries(test_utils::CassSessionPtr session) { - for (std::vector::const_iterator it = prepared_queries_.begin(); - it != prepared_queries_.end(); ++it) { - test_utils::CassFuturePtr future(cass_session_prepare(session.get(), it->c_str())); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future.get()), CASS_OK); - } - } - - /** - * Wait for a session to reconnect to a node. - * - * @param session The session to use for waiting - * @param node The node to wait for - */ - void wait_for_node(test_utils::CassSessionPtr session, int node) { - std::stringstream ip_address; - ip_address << ccm->get_ip_prefix() << node; - - for (int i = 0; i < 30; ++i) { - test_utils::CassStatementPtr statement(cass_statement_new("SELECT * FROM system.peers", 0)); - test_utils::CassFuturePtr future(cass_session_execute(session.get(), statement.get())); - std::string host(get_host_from_future(future.get()).c_str()); - if (cass_future_error_code(future.get()) == CASS_OK && host == ip_address.str()) { - return; - } - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - } - BOOST_REQUIRE_MESSAGE(false, - "Failed to wait for node " << ip_address.str() << " to become availble"); - } - - /** - * A vector of sessions that are only connected to a single host (via the - * whitelist policy). - */ - std::vector sessions; - - /** - * The test's keyspace - */ - std::string keyspace; - - /** - * A vector of query strings to be prepared. - */ - std::vector prepared_queries_; -}; - -BOOST_FIXTURE_TEST_SUITE(prepare_on_up_or_add_host, PrepareOnUpOrAddHostTests) - -/** - * Verify that statements are not prepared when a node becomes available and - * the prepare on up/add feature is disabled. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(statements_should_not_be_prepared_on_up_when_disabled) { - if (!check_version("3.10")) return; - - // Disable the prepare on up/add setting - cass_cluster_set_prepare_on_up_or_add_host(cluster, cass_false); - - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - - // Verify that there are no statements prepared - truncate_prepared_statements(1); - prepared_statements_is_empty(1); - - // Populate the driver's prepared metadata cache - prepare_all_queries(session); - prepared_statements_are_present(1); - - // Clear all prepared queries on the server-side - truncate_prepared_statements(1); - prepared_statements_is_empty(1); - - // Simulate an UP event - ccm->stop_node(1); - ccm->start_node(1); - - // Wait for the node to become available and verify no statements have been - // prepared - wait_for_node(session, 1); - prepared_statements_are_not_present(1); -} - -/** - * Verify that statements are prepared properly when a node becomes available - * and the prepare on up/add feature is enabled. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(statements_should_be_prepared_on_up) { - if (!check_version("3.10")) return; - - // Enable the prepare on up/add setting - cass_cluster_set_prepare_on_up_or_add_host(cluster, cass_true); - - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - - // Verify that there are no statements prepared - truncate_prepared_statements(1); - prepared_statements_is_empty(1); - - // Populate the driver's prepared metadata cache - prepare_all_queries(session); - prepared_statements_are_present(1); - - // Clear all prepared queries on the server-side - truncate_prepared_statements(1); - prepared_statements_is_empty(1); - - // Simulate an UP event - ccm->stop_node(1); - ccm->start_node(1); - - // Wait for the node to become available and verify that the statements - // in the prepared metadata cache have been prepared - wait_for_node(session, 1); - prepared_statements_are_present(1); -} - -/** - * Verify that statements are not prepared when a new node is added to a cluster - * and the prepare on up/add feature is disabled. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(statements_should_not_be_prepared_on_add_when_disabled) { - if (!check_version("3.10")) return; - - // Disable the prepare on up/add setting - cass_cluster_set_prepare_on_up_or_add_host(cluster, cass_false); - - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - - // Verify that there are no statements prepared - truncate_prepared_statements(1); - prepared_statements_is_empty(1); - - // Populate the driver's prepared metadata cache - prepare_all_queries(session); - prepared_statements_are_present(1); - - // Add a new node - int node = ccm->bootstrap_node(); - - // Wait for the new node to become available and verify no statements have - // been prepared - wait_for_node(session, node); - prepared_statements_are_not_present(node); -} - -/** - * Verify that statements are prepared properly when a new node is added to a - * cluster and the prepare on up/add feature is enabled. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(statements_should_be_prepared_on_add) { - if (!check_version("3.10")) return; - - // Enable the prepare on up/add setting - cass_cluster_set_prepare_on_up_or_add_host(cluster, cass_true); - - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - - // Verify that there are no statements prepared - truncate_prepared_statements(1); - prepared_statements_is_empty(1); - - // Populate the driver's prepared metadata cache - prepare_all_queries(session); - prepared_statements_are_present(1); - - // Add a new node - int node = ccm->bootstrap_node(); - - // Wait for the new node to become available and verify that the statements - // in the prepared metadata cache have been prepared - wait_for_node(session, node); - prepared_statements_are_present(node); -} - -BOOST_AUTO_TEST_SUITE_END() From f0bdf050f493fb17d44daa84a5cd67861e43d2ae Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Wed, 30 Oct 2019 14:17:29 -0400 Subject: [PATCH 07/42] CPP-835 Port prepare from existing tests to Google test framework (#316) --- .../src/integration/objects/session.hpp | 15 ++ .../src/integration/tests/test_prepared.cpp | 89 +++++++++ cpp-driver/src/testing.cpp | 17 ++ cpp-driver/src/testing.hpp | 8 + cpp-driver/test/ccm_bridge/src/process.cpp | 2 +- .../src/test_prepared_existing.cpp | 177 ------------------ 6 files changed, 130 insertions(+), 178 deletions(-) delete mode 100644 cpp-driver/test/integration_tests/src/test_prepared_existing.cpp diff --git a/cpp-driver/gtests/src/integration/objects/session.hpp b/cpp-driver/gtests/src/integration/objects/session.hpp index 600c3c31f..219423e71 100644 --- a/cpp-driver/gtests/src/integration/objects/session.hpp +++ b/cpp-driver/gtests/src/integration/objects/session.hpp @@ -217,6 +217,21 @@ class Session : public Object { return Prepared(cass_future_get_prepared(prepare_future.get())); } + /** + * Create a prepared statement from an existing statement inheriting the existing statement's + * settings. + * + * @param statement The existing statement + * @param assert_ok True if error code for future should be asserted + * CASS_OK; false otherwise (default: true) + * @return Prepared statement generated by the server for the query + */ + Prepared prepare_from_existing(Statement statement, bool assert_ok = true) { + Future prepare_future = cass_session_prepare_from_existing(get(), statement.get()); + prepare_future.wait(assert_ok); + return Prepared(cass_future_get_prepared(prepare_future.get())); + } + /** * Get a schema snapshot * diff --git a/cpp-driver/gtests/src/integration/tests/test_prepared.cpp b/cpp-driver/gtests/src/integration/tests/test_prepared.cpp index 4dd52158d..d8d333ece 100644 --- a/cpp-driver/gtests/src/integration/tests/test_prepared.cpp +++ b/cpp-driver/gtests/src/integration/tests/test_prepared.cpp @@ -15,6 +15,9 @@ */ #include "integration.hpp" +#include "testing.hpp" + +using namespace datastax::internal::testing; /** * Prepared integration tests; common operations @@ -108,3 +111,89 @@ CASSANDRA_INTEGRATION_TEST_F(PreparedTests, PreparedIDUnchangedDuringReprepare) EXPECT_EQ(CASS_OK, result.error_code()); EXPECT_EQ(1u, logger_.count()); } + +/** + * Verify that a statement is correctly prepared from an existing simple + * statement. The settings from the original statement should be inherited. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PreparedTests, PrepareFromExistingSimpleStatement) { + CHECK_FAILURE; + + use_keyspace(keyspace_name_); + + session_.execute( + format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "int")); + session_.execute( + format_string(CASSANDRA_KEY_VALUE_INSERT_FORMAT, table_name_.c_str(), "1", "99")); + + DowngradingConsistencyRetryPolicy retry_policy; + Statement statement(format_string(CASSANDRA_SELECT_VALUE_FORMAT, table_name_.c_str(), "?"), 1); + + // Set unique settings to validate later + statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + statement.set_serial_consistency(CASS_CONSISTENCY_SERIAL); + statement.set_request_timeout(99999u); + statement.set_retry_policy(retry_policy); + + // Prepare from the existing bound statement + Statement bound_statement = session_.prepare_from_existing(statement).bind(); + + // Validate that the bound statement inherited the settings from the original statement + EXPECT_EQ(get_consistency(bound_statement.get()), CASS_CONSISTENCY_LOCAL_QUORUM); + EXPECT_EQ(get_serial_consistency(bound_statement.get()), CASS_CONSISTENCY_SERIAL); + EXPECT_EQ(get_request_timeout_ms(bound_statement.get()), 99999u); + EXPECT_EQ(get_retry_policy(bound_statement.get()), retry_policy.get()); + + bound_statement.bind(0, Integer(1)); + + Result result = session_.execute(bound_statement); + ASSERT_EQ(result.row_count(), 1); + EXPECT_EQ(result.first_row().column_by_name("value").value(), 99); +} + +/** + * Verify that a statement is correctly prepared from an existing bound + * statement. The settings from the original bound statement should be + * inherited. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PreparedTests, PrepareFromExistingBoundStatement) { + CHECK_FAILURE; + + use_keyspace(keyspace_name_); + + session_.execute( + format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "int")); + session_.execute( + format_string(CASSANDRA_KEY_VALUE_INSERT_FORMAT, table_name_.c_str(), "1", "99")); + + Statement bound_statement1 = + session_.prepare(format_string(CASSANDRA_SELECT_VALUE_FORMAT, table_name_.c_str(), "?")) + .bind(); + + DowngradingConsistencyRetryPolicy retry_policy; + + // Set unique settings to validate later + bound_statement1.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + bound_statement1.set_serial_consistency(CASS_CONSISTENCY_SERIAL); + bound_statement1.set_request_timeout(99999u); + bound_statement1.set_retry_policy(retry_policy); + + // Prepare from the existing bound statement + Statement bound_statement2 = session_.prepare_from_existing(bound_statement1).bind(); + + // Validate that the bound statement inherited the settings from the original statement + EXPECT_EQ(get_consistency(bound_statement2.get()), CASS_CONSISTENCY_LOCAL_QUORUM); + EXPECT_EQ(get_serial_consistency(bound_statement2.get()), CASS_CONSISTENCY_SERIAL); + EXPECT_EQ(get_request_timeout_ms(bound_statement2.get()), 99999u); + EXPECT_EQ(get_retry_policy(bound_statement2.get()), retry_policy.get()); + + bound_statement2.bind(0, Integer(1)); + + Result result = session_.execute(bound_statement2); + ASSERT_EQ(result.row_count(), 1); + EXPECT_EQ(result.first_row().column_by_name("value").value(), 99); +} diff --git a/cpp-driver/src/testing.cpp b/cpp-driver/src/testing.cpp index 5f1e8c851..c1c6e3919 100644 --- a/cpp-driver/src/testing.cpp +++ b/cpp-driver/src/testing.cpp @@ -27,6 +27,7 @@ #include "request_handler.hpp" #include "result_response.hpp" #include "session.hpp" +#include "statement.hpp" namespace datastax { namespace internal { namespace testing { @@ -77,4 +78,20 @@ uint64_t get_host_latency_average(CassSession* session, String ip_address, int p return 0; } +CassConsistency get_consistency(const CassStatement* statement) { + return statement->from()->consistency(); +} + +CassConsistency get_serial_consistency(const CassStatement* statement) { + return statement->from()->serial_consistency(); +} + +uint64_t get_request_timeout_ms(const CassStatement* statement) { + return statement->from()->request_timeout_ms(); +} + +const CassRetryPolicy* get_retry_policy(const CassStatement* statement) { + return CassRetryPolicy::to(statement->from()->retry_policy().get()); +} + }}} // namespace datastax::internal::testing diff --git a/cpp-driver/src/testing.hpp b/cpp-driver/src/testing.hpp index b616707a0..effe741c9 100644 --- a/cpp-driver/src/testing.hpp +++ b/cpp-driver/src/testing.hpp @@ -39,6 +39,14 @@ CASS_EXPORT uint64_t get_time_since_epoch_in_ms(); CASS_EXPORT uint64_t get_host_latency_average(CassSession* session, String ip_address, int port); +CASS_EXPORT CassConsistency get_consistency(const CassStatement* statement); + +CASS_EXPORT CassConsistency get_serial_consistency(const CassStatement* statement); + +CASS_EXPORT uint64_t get_request_timeout_ms(const CassStatement* statement); + +CASS_EXPORT const CassRetryPolicy* get_retry_policy(const CassStatement* statement); + }}} // namespace datastax::internal::testing #endif diff --git a/cpp-driver/test/ccm_bridge/src/process.cpp b/cpp-driver/test/ccm_bridge/src/process.cpp index 42d0741cc..56c8ce199 100644 --- a/cpp-driver/test/ccm_bridge/src/process.cpp +++ b/cpp-driver/test/ccm_bridge/src/process.cpp @@ -127,4 +127,4 @@ void Process::on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { delete[] buf->base; } -}; // namespace utils +} // namespace utils diff --git a/cpp-driver/test/integration_tests/src/test_prepared_existing.cpp b/cpp-driver/test/integration_tests/src/test_prepared_existing.cpp deleted file mode 100644 index 0dafe20e6..000000000 --- a/cpp-driver/test/integration_tests/src/test_prepared_existing.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include -#include -#include -#include - -#include "cassandra.h" -#include "execute_request.hpp" -#include "statement.hpp" -#include "test_utils.hpp" - -using namespace datastax::internal::core; - -/** - * Test harness for prepared from existing functionality. - */ -struct PreparedFromExistingTests : public test_utils::SingleSessionTest { - PreparedFromExistingTests() - : SingleSessionTest(1, 0) - , keyspace(str(boost::format("ks_%s") % test_utils::generate_unique_str(uuid_gen))) { - test_utils::execute_query( - session, str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % keyspace % "1")); - test_utils::execute_query(session, str(boost::format("USE %s") % keyspace)); - test_utils::execute_query(session, "CREATE TABLE test (k text PRIMARY KEY, v text)"); - test_utils::execute_query(session, "INSERT INTO test (k, v) VALUES ('key1', 'value1')"); - } - - /** - * Validate the result of the provided future. - * - * @param future The result future to validate - */ - void validate_query_result(test_utils::CassFuturePtr future) { - BOOST_REQUIRE(future); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future.get()), CASS_OK); - - test_utils::CassResultPtr result(cass_future_get_result(future.get())); - BOOST_REQUIRE(result); - BOOST_REQUIRE_EQUAL(cass_result_row_count(result.get()), 1u); - BOOST_REQUIRE_EQUAL(cass_result_column_count(result.get()), 1u); - - const CassRow* row = cass_result_first_row(result.get()); - BOOST_REQUIRE(row != NULL); - - const char* value; - size_t value_length; - BOOST_REQUIRE_EQUAL(cass_value_get_string(cass_row_get_column(row, 0), &value, &value_length), - CASS_OK); - BOOST_CHECK_EQUAL(std::string(value, value_length), "value1"); - } - - /** - * The test's keyspace - */ - std::string keyspace; -}; - -BOOST_FIXTURE_TEST_SUITE(prepared_existing, PreparedFromExistingTests) - -/** - * Verify that a statement is correctly prepared from an existing simple - * statement. The settings from the original statement should be inherited. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(prepare_from_existing_simple_statement) { - test_utils::CassStatementPtr statement( - cass_statement_new("SELECT v FROM test WHERE k = 'key1'", 0)); - - test_utils::CassRetryPolicyPtr retry_policy(cass_retry_policy_downgrading_consistency_new()); - - // Set unique settings to validate later - BOOST_CHECK_EQUAL(cass_statement_set_consistency(statement.get(), CASS_CONSISTENCY_LOCAL_QUORUM), - CASS_OK); - BOOST_CHECK_EQUAL(cass_statement_set_serial_consistency(statement.get(), CASS_CONSISTENCY_SERIAL), - CASS_OK); - BOOST_CHECK_EQUAL(cass_statement_set_request_timeout(statement.get(), 99999u), CASS_OK); - BOOST_CHECK_EQUAL(cass_statement_set_retry_policy(statement.get(), retry_policy.get()), CASS_OK); - - // Prepare from the existing simple statement - test_utils::CassFuturePtr future(cass_session_prepare_from_existing(session, statement.get())); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future.get()), CASS_OK); - - test_utils::CassPreparedPtr prepared(cass_future_get_prepared(future.get())); - BOOST_REQUIRE(prepared); - - test_utils::CassStatementPtr bound_statement(cass_prepared_bind(prepared.get())); - BOOST_REQUIRE(bound_statement); - - ExecuteRequest* execute_request = static_cast(bound_statement->from()); - - // Validate that the bound statement inherited the settings from the original statement - BOOST_CHECK_EQUAL(execute_request->consistency(), CASS_CONSISTENCY_LOCAL_QUORUM); - BOOST_CHECK_EQUAL(execute_request->serial_consistency(), CASS_CONSISTENCY_SERIAL); - BOOST_CHECK_EQUAL(execute_request->request_timeout_ms(), 99999u); - BOOST_CHECK_EQUAL(execute_request->retry_policy().get(), retry_policy.get()); - - validate_query_result( - test_utils::CassFuturePtr(cass_session_execute(session, bound_statement.get()))); -} - -/** - * Verify that a statement is correctly prepared from an existing bound - * statement. The settings from the original bound statement should be - * inherited. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(prepare_from_existing_bound_statement) { - test_utils::CassFuturePtr future1( - cass_session_prepare(session, "SELECT v FROM test WHERE k = 'key1'")); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future1.get()), CASS_OK); - - test_utils::CassPreparedPtr prepared1(cass_future_get_prepared(future1.get())); - BOOST_REQUIRE(prepared1); - - test_utils::CassStatementPtr bound_statement1(cass_prepared_bind(prepared1.get())); - BOOST_REQUIRE(bound_statement1); - - test_utils::CassRetryPolicyPtr retry_policy(cass_retry_policy_downgrading_consistency_new()); - - // Set unique settings to validate later - BOOST_CHECK_EQUAL( - cass_statement_set_consistency(bound_statement1.get(), CASS_CONSISTENCY_LOCAL_QUORUM), - CASS_OK); - BOOST_CHECK_EQUAL( - cass_statement_set_serial_consistency(bound_statement1.get(), CASS_CONSISTENCY_SERIAL), - CASS_OK); - BOOST_CHECK_EQUAL(cass_statement_set_request_timeout(bound_statement1.get(), 99999u), CASS_OK); - BOOST_CHECK_EQUAL(cass_statement_set_retry_policy(bound_statement1.get(), retry_policy.get()), - CASS_OK); - - // Prepare from the existing bound statement - test_utils::CassFuturePtr future2( - cass_session_prepare_from_existing(session, bound_statement1.get())); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future2.get()), CASS_OK); - - test_utils::CassPreparedPtr prepared2(cass_future_get_prepared(future2.get())); - BOOST_REQUIRE(prepared2); - - test_utils::CassStatementPtr bound_statement2(cass_prepared_bind(prepared2.get())); - BOOST_REQUIRE(bound_statement2); - - ExecuteRequest* execute_request = static_cast(bound_statement2->from()); - - // Validate that the bound statement inherited the settings from the original bound statement - BOOST_CHECK_EQUAL(execute_request->consistency(), CASS_CONSISTENCY_LOCAL_QUORUM); - BOOST_CHECK_EQUAL(execute_request->serial_consistency(), CASS_CONSISTENCY_SERIAL); - BOOST_CHECK_EQUAL(execute_request->request_timeout_ms(), 99999u); - BOOST_CHECK_EQUAL(execute_request->retry_policy().get(), retry_policy.get()); - - validate_query_result( - test_utils::CassFuturePtr(cass_session_execute(session, bound_statement2.get()))); -} - -BOOST_AUTO_TEST_SUITE_END() From 266517fea1e7311f19666840d2d53ad7ef28f217 Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Wed, 30 Oct 2019 14:17:46 -0400 Subject: [PATCH 08/42] CPP-836 Port prepared metadata tests to Google test framework (#317) --- .../tests/test_prepared_metadata.cpp | 97 +++++++++++++ .../src/test_prepared_metadata.cpp | 135 ------------------ 2 files changed, 97 insertions(+), 135 deletions(-) create mode 100644 cpp-driver/gtests/src/integration/tests/test_prepared_metadata.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_prepared_metadata.cpp diff --git a/cpp-driver/gtests/src/integration/tests/test_prepared_metadata.cpp b/cpp-driver/gtests/src/integration/tests/test_prepared_metadata.cpp new file mode 100644 index 000000000..8f6e05e47 --- /dev/null +++ b/cpp-driver/gtests/src/integration/tests/test_prepared_metadata.cpp @@ -0,0 +1,97 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "integration.hpp" + +/** + * Prepared metadata related tests + */ +class PreparedMetadataTests : public Integration { +public: + void SetUp() { + Integration::SetUp(); + session_.execute( + format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "int")); + session_.execute( + format_string(CASSANDRA_KEY_VALUE_INSERT_FORMAT, table_name_.c_str(), "1", "99")); + } + + /** + * Check the column count of a bound statement before and after adding a + * column to a table. + * + * @param session + * @param expected_column_count_after_update + */ + void prepared_check_column_count_after_alter(Session session, + size_t expected_column_count_after_update) { + Statement bound_statement = + session.prepare(format_string("SELECT * FROM %s WHERE key = 1", table_name_.c_str())) + .bind(); + + // Verify that the table has two columns in the metadata + { + Result result = session.execute(bound_statement); + EXPECT_EQ(2u, result.column_count()); + } + + // Add a column to the table + session.execute(format_string("ALTER TABLE %s ADD value2 int", table_name_.c_str())); + + // The column count should match the expected after the alter + { + Result result = session.execute(bound_statement); + EXPECT_EQ(expected_column_count_after_update, result.column_count()); + } + } +}; + +/** + * Verify that the column count of a bound statement's result metadata doesn't + * change for older protocol versions (v4 and less) when a table's schema is altered. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PreparedMetadataTests, AlterDoesntUpdateColumnCount) { + CHECK_FAILURE; + + // Ensure beta protocol is not set + Session session = default_cluster() + .with_beta_protocol(false) + .with_protocol_version(CASS_PROTOCOL_VERSION_V4) + .connect(keyspace_name_); + + // The column count will stay the same even after the alter + prepared_check_column_count_after_alter(session, 2u); +} + +/** + * Verify that the column count of a bound statement's result metadata is + * properly updated for newer protocol versions (v5 and greater) when a table's + * schema is altered. + * + * @since 2.8 + */ +CASSANDRA_INTEGRATION_TEST_F(PreparedMetadataTests, AlterProperlyUpdatesColumnCount) { + CHECK_FAILURE; + CHECK_VERSION(4.0.0); + + // Ensure protocol v5 or greater + Session session = default_cluster().with_beta_protocol(true).connect(keyspace_name_); + + // The column count will properly update after the alter + prepared_check_column_count_after_alter(session, 3u); +} diff --git a/cpp-driver/test/integration_tests/src/test_prepared_metadata.cpp b/cpp-driver/test/integration_tests/src/test_prepared_metadata.cpp deleted file mode 100644 index 78159fd27..000000000 --- a/cpp-driver/test/integration_tests/src/test_prepared_metadata.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include -#include -#include -#include - -#include "cassandra.h" -#include "execute_request.hpp" -#include "statement.hpp" -#include "test_utils.hpp" - -/** - * Test harness for prepared from existing functionality. - */ -struct PreparedMetadataTests : public test_utils::SingleSessionTest { - PreparedMetadataTests() - : SingleSessionTest(1, 0) - , keyspace(str(boost::format("ks_%s") % test_utils::generate_unique_str(uuid_gen))) { - test_utils::execute_query( - session, str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % keyspace % "1")); - test_utils::execute_query(session, str(boost::format("USE %s") % keyspace)); - test_utils::execute_query(session, "CREATE TABLE test (k text PRIMARY KEY, v text)"); - test_utils::execute_query(session, "INSERT INTO test (k, v) VALUES ('key1', 'value1')"); - } - - /** - * Check the column count of a bound statement before and after adding a - * column to a table. - * - * @param expected_column_count_after_update - */ - void prepared_check_column_count_after_alter(size_t expected_column_count_after_update) { - test_utils::CassSessionPtr session(test_utils::create_session(cluster)); - - test_utils::execute_query(session.get(), str(boost::format("USE %s") % keyspace)); - - test_utils::CassFuturePtr future( - cass_session_prepare(session.get(), "SELECT * FROM test WHERE k = 'key1'")); - BOOST_REQUIRE_EQUAL(cass_future_error_code(future.get()), CASS_OK); - - test_utils::CassPreparedPtr prepared(cass_future_get_prepared(future.get())); - BOOST_REQUIRE(prepared); - - test_utils::CassStatementPtr bound_statement(cass_prepared_bind(prepared.get())); - BOOST_REQUIRE(bound_statement); - - // Verify that the table has two columns in the metadata - { - test_utils::CassFuturePtr result_future( - cass_session_execute(session.get(), bound_statement.get())); - BOOST_REQUIRE_EQUAL(cass_future_error_code(result_future.get()), CASS_OK); - - test_utils::CassResultPtr result(cass_future_get_result(result_future.get())); - - BOOST_CHECK_EQUAL(cass_result_column_count(result.get()), 2u); - } - - // Add a column to the table - test_utils::execute_query(session.get(), "ALTER TABLE test ADD v2 int"); - - // The column count shouldn't have changed - { - test_utils::CassFuturePtr result_future( - cass_session_execute(session.get(), bound_statement.get())); - BOOST_REQUIRE_EQUAL(cass_future_error_code(result_future.get()), CASS_OK); - - test_utils::CassResultPtr result(cass_future_get_result(result_future.get())); - - BOOST_CHECK_EQUAL(cass_result_column_count(result.get()), expected_column_count_after_update); - } - } - - /** - * The test's keyspace - */ - std::string keyspace; -}; - -BOOST_FIXTURE_TEST_SUITE(prepared_metadata, PreparedMetadataTests) - -/** - * Verify that the column count of a bound statement's result metadata doesn't - * change for older protocol versions (v4 and less) when a table's schema is altered. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(alter_doesnt_update_column_count) { - cass_cluster_set_use_beta_protocol_version(cluster, - cass_false); // Ensure beta protocol is not set - BOOST_REQUIRE_EQUAL(cass_cluster_set_protocol_version(cluster, CASS_PROTOCOL_VERSION_V4), - CASS_OK); - - // The column count will stay the same even after the alter - prepared_check_column_count_after_alter(2u); -} - -/** - * Verify that the column count of a bound statement's result metadata is - * properly updated for newer protocol versions (v5 and greater) when a table's - * schema is altered. - * - * @since 2.8 - * @test_category prepared - * - */ -BOOST_AUTO_TEST_CASE(alter_properly_updates_column_count) { - if (!check_version("4.0.0")) return; - - // Ensure protocol v5 or greater - BOOST_REQUIRE_EQUAL(cass_cluster_set_use_beta_protocol_version(cluster, cass_true), CASS_OK); - - // The column count will properly update after the alter - prepared_check_column_count_after_alter(3u); -} - -BOOST_AUTO_TEST_SUITE_END() From 163aba6b4dc3a0b514ce4113d642bd94371c2b0c Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Wed, 30 Oct 2019 14:15:16 -0400 Subject: [PATCH 09/42] CPP-849 Error result doesn't allow access to keyspace, table, and function data --- cpp-driver/src/error_response.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp-driver/src/error_response.cpp b/cpp-driver/src/error_response.cpp index 99e87a255..5587f598a 100644 --- a/cpp-driver/src/error_response.cpp +++ b/cpp-driver/src/error_response.cpp @@ -60,8 +60,8 @@ CassWriteType cass_error_result_write_type(const CassErrorResult* error_result) CassError cass_error_result_keyspace(const CassErrorResult* error_result, const char** keyspace, size_t* keyspace_length) { - if (error_result->code() != CASS_ERROR_SERVER_ALREADY_EXISTS || - error_result->code() != CASS_ERROR_SERVER_FUNCTION_FAILURE) { + if (error_result->code() != CQL_ERROR_ALREADY_EXISTS && + error_result->code() != CQL_ERROR_FUNCTION_FAILURE) { return CASS_ERROR_LIB_INVALID_ERROR_RESULT_TYPE; } *keyspace = error_result->keyspace().data(); @@ -71,7 +71,7 @@ CassError cass_error_result_keyspace(const CassErrorResult* error_result, const CassError cass_error_result_table(const CassErrorResult* error_result, const char** table, size_t* table_length) { - if (error_result->code() != CASS_ERROR_SERVER_ALREADY_EXISTS) { + if (error_result->code() != CQL_ERROR_ALREADY_EXISTS) { return CASS_ERROR_LIB_INVALID_ERROR_RESULT_TYPE; } *table = error_result->table().data(); @@ -81,7 +81,7 @@ CassError cass_error_result_table(const CassErrorResult* error_result, const cha CassError cass_error_result_function(const CassErrorResult* error_result, const char** function, size_t* function_length) { - if (error_result->code() != CASS_ERROR_SERVER_FUNCTION_FAILURE) { + if (error_result->code() != CQL_ERROR_FUNCTION_FAILURE) { return CASS_ERROR_LIB_INVALID_ERROR_RESULT_TYPE; } *function = error_result->function().data(); @@ -95,7 +95,7 @@ size_t cass_error_num_arg_types(const CassErrorResult* error_result) { CassError cass_error_result_arg_type(const CassErrorResult* error_result, size_t index, const char** arg_type, size_t* arg_type_length) { - if (error_result->code() != CASS_ERROR_SERVER_FUNCTION_FAILURE) { + if (error_result->code() != CQL_ERROR_FUNCTION_FAILURE) { return CASS_ERROR_LIB_INVALID_ERROR_RESULT_TYPE; } if (index > error_result->arg_types().size()) { From 88c7c3c57aadb1946209ade555e73671e61c7a4a Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Wed, 30 Oct 2019 14:16:03 -0400 Subject: [PATCH 10/42] CPP-839/CPP-840 Port server-side errors/warnings tests to Google test framework --- .../src/integration/objects/error_result.hpp | 94 ++++++++ .../gtests/src/integration/objects/future.hpp | 7 + .../gtests/src/integration/objects/result.hpp | 8 + .../tests/test_server_side_failure.cpp | 222 ++++++++++++++++++ .../src/test_server_failures.cpp | 133 ----------- .../integration_tests/src/test_warnings.cpp | 47 ---- 6 files changed, 331 insertions(+), 180 deletions(-) create mode 100644 cpp-driver/gtests/src/integration/objects/error_result.hpp create mode 100644 cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_server_failures.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_warnings.cpp diff --git a/cpp-driver/gtests/src/integration/objects/error_result.hpp b/cpp-driver/gtests/src/integration/objects/error_result.hpp new file mode 100644 index 000000000..9645101dc --- /dev/null +++ b/cpp-driver/gtests/src/integration/objects/error_result.hpp @@ -0,0 +1,94 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef TEST_ERROR_RESULT_HPP +#define TEST_ERROR_RESULT_HPP + +#include "cassandra.h" + +#include "objects/future.hpp" +#include "objects/object_base.hpp" + +#include + +namespace test { namespace driver { + +/** + * Wrapped error result object + */ +class ErrorResult : public Object { +public: + /** + * Create an empty error result object + */ + ErrorResult() {} + + /** + * Create the error result object from the native driver object + * + * @param result Native driver object + */ + ErrorResult(Future future) + : Object(future.error_result()) {} + + CassError error_code() const { return cass_error_result_code(get()); } + + CassConsistency consistency() const { return cass_error_result_consistency(get()); } + + int32_t responses_received() const { return cass_error_result_responses_received(get()); } + + int32_t responses_required() const { return cass_error_result_responses_required(get()); } + + int32_t num_failures() const { return cass_error_result_num_failures(get()); } + + bool data_present() const { return cass_error_result_data_present(get()) == cass_true; } + + CassWriteType write_type() const { return cass_error_result_write_type(get()); } + + std::string keyspace() const { + const char* keyspace; + size_t keyspace_length; + EXPECT_EQ(CASS_OK, cass_error_result_keyspace(get(), &keyspace, &keyspace_length)); + return std::string(keyspace, keyspace_length); + } + + std::string table() const { + const char* table; + size_t table_length; + EXPECT_EQ(CASS_OK, cass_error_result_table(get(), &table, &table_length)); + return std::string(table, table_length); + } + + std::string function() const { + const char* function; + size_t function_length; + EXPECT_EQ(CASS_OK, cass_error_result_function(get(), &function, &function_length)); + return std::string(function, function_length); + } + + size_t num_arg_types() const { return cass_error_num_arg_types(get()); } + + std::string arg_type(size_t index) const { + const char* arg_type; + size_t arg_type_length; + EXPECT_EQ(CASS_OK, cass_error_result_arg_type(get(), index, &arg_type, &arg_type_length)); + return std::string(arg_type, arg_type_length); + } +}; + +}} // namespace test::driver + +#endif // TEST_ERROR_RESULT_HPP diff --git a/cpp-driver/gtests/src/integration/objects/future.hpp b/cpp-driver/gtests/src/integration/objects/future.hpp index 8df887f86..48804021f 100644 --- a/cpp-driver/gtests/src/integration/objects/future.hpp +++ b/cpp-driver/gtests/src/integration/objects/future.hpp @@ -111,6 +111,13 @@ class Future : public Object { */ const CassResult* result() { return cass_future_get_result(get()); } + /** + * Get the error result from the future + * + * @return Error result from future + */ + const CassErrorResult* error_result() { return cass_future_get_error_result(get()); } + /** * Wait for the future to resolve itself * diff --git a/cpp-driver/gtests/src/integration/objects/result.hpp b/cpp-driver/gtests/src/integration/objects/result.hpp index aae9a0188..8ac366d8b 100644 --- a/cpp-driver/gtests/src/integration/objects/result.hpp +++ b/cpp-driver/gtests/src/integration/objects/result.hpp @@ -21,6 +21,7 @@ #include "objects/object_base.hpp" #include "objects/custom_payload.hpp" +#include "objects/error_result.hpp" #include "objects/future.hpp" #include "objects/iterator.hpp" @@ -106,6 +107,13 @@ class Result : public Object { */ const std::string error_message() { return future_.error_message(); } + /** + * Get the error result object for the server-side failure + * + * @return Error result object + */ + ErrorResult error_result() const { return ErrorResult(future_); } + /** * Get the host/address of the future * diff --git a/cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp b/cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp new file mode 100644 index 000000000..93e71c1e1 --- /dev/null +++ b/cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp @@ -0,0 +1,222 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "integration.hpp" + +/** + * Server-side warnings and errors integration tests + */ +class ServerSideFailureTests : public Integration { +public: + ServerSideFailureTests() { + number_dc1_nodes_ = 3; + replication_factor_ = 3; + } +}; + +/** + * Validate that server-side warnings are logged. + * + * @since 2.2.0-beta + * @jira_ticket CPP-292 + * @cassandra_version 2.2.x + */ +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, Warning) { + CHECK_FAILURE; + CHECK_VERSION(2.2); + + logger_.add_critera("Server-side warning: Aggregation query used without partition key"); + session_.execute("SELECT sum(gossip_generation) FROM system.local"); + EXPECT_EQ(logger_.count(), 1); +} + +/** + * Validate UDF Function_failures are returned from Cassandra + * + * Create a function that will throw an exception when invoked; ensure the + * Function_failure is returned from Cassandra. + * + * @since 2.2.0-beta + * @jira_ticket CPP-294 + * @cassandra_version 2.2.x + */ +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorFunctionFailure) { + CHECK_FAILURE; + CHECK_VERSION(2.2); + + // Create the table and associated failing function + session_.execute("CREATE TABLE server_function_failures (id int PRIMARY KEY, value double)"); + session_.execute("CREATE FUNCTION function_failure(value double) RETURNS NULL ON NULL INPUT " + "RETURNS double LANGUAGE java " + "AS 'throw new RuntimeException(\"failure\");'"); + + // Bind and insert values into Cassandra + Statement insert_statement("INSERT INTO server_function_failures(id, value) VALUES (?, ?)", 2); + insert_statement.bind("id", Integer(1)); + insert_statement.bind("value", Double(3.14)); + session_.execute(insert_statement); + + // Execute the failing function + Statement select_statement( + "SELECT function_failure(value) FROM server_function_failures WHERE id = ?", 1); + select_statement.bind("id", Integer(1)); + Result result = session_.execute(select_statement, false); + EXPECT_EQ(CASS_ERROR_SERVER_FUNCTION_FAILURE, result.error_code()); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(CASS_ERROR_SERVER_FUNCTION_FAILURE, error_result.error_code()); + EXPECT_EQ(keyspace_name_, error_result.keyspace()); + EXPECT_EQ("function_failure", error_result.function()); + EXPECT_EQ(1u, error_result.num_arg_types()); + EXPECT_EQ("double", error_result.arg_type(0)); +} + +/** + * Validate Already_exists failures are returned when creating the same table twice. + */ +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorTableAlreadyExists) { + CHECK_FAILURE; + + std::string create_table_query = + "CREATE TABLE already_exists_table (id int PRIMARY KEY, value double)"; + session_.execute(create_table_query); + Result result = session_.execute(Statement(create_table_query), false); + EXPECT_EQ(CASS_ERROR_SERVER_ALREADY_EXISTS, result.error_code()); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(CASS_ERROR_SERVER_ALREADY_EXISTS, error_result.error_code()); + EXPECT_EQ(keyspace_name_, error_result.keyspace()); + EXPECT_EQ("already_exists_table", error_result.table()); +} + +/** + * Validate a failure is returned when creating the same function twice. + * + * @cassandra_version 2.2.x + */ +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorFunctionAlreadyExists) { + CHECK_FAILURE; + CHECK_VERSION(2.2); + + std::string create_function_query = + "CREATE FUNCTION already_exists_function(value double) RETURNS NULL ON NULL INPUT " + "RETURNS double LANGUAGE java " + "AS 'return 3.14;'"; + session_.execute(create_function_query); + Result result = session_.execute(Statement(create_function_query), false); + EXPECT_EQ(CASS_ERROR_SERVER_INVALID_QUERY, result.error_code()); + EXPECT_TRUE(result.error_message().find("(double) -> double already exists") != + std::string::npos); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(CASS_ERROR_SERVER_INVALID_QUERY, error_result.error_code()); +} + +/** + * Validate read/write timeout server-side failures and error result data. + */ +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorReadWriteTimeout) { + Session session = + default_cluster().with_retry_policy(FallthroughRetryPolicy()).connect(keyspace_name_); + + session.execute("CREATE TABLE read_write_timeout (id int PRIMARY KEY, value double)"); + + pause_node(2); + pause_node(3); + + { + Statement insert_statement("INSERT INTO read_write_timeout(id, value) VALUES (?, ?)", 2); + insert_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + insert_statement.bind("id", Integer(2)); + insert_statement.bind("value", Double(2.71)); + insert_statement.set_host("127.0.0.1", 9042); + Result result = session.execute(insert_statement, false); + + EXPECT_EQ(CASS_ERROR_SERVER_WRITE_TIMEOUT, result.error_code()); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(CASS_ERROR_SERVER_WRITE_TIMEOUT, error_result.error_code()); + EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); + EXPECT_EQ(1, error_result.responses_received()); + EXPECT_EQ(2, error_result.responses_required()); + } + + { + Statement select_statement("SELECT * FROM read_write_timeout"); + select_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + select_statement.set_host("127.0.0.1", 9042); + Result result = session.execute(select_statement, false); + EXPECT_EQ(CASS_ERROR_SERVER_READ_TIMEOUT, result.error_code()); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(CASS_ERROR_SERVER_READ_TIMEOUT, error_result.error_code()); + EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); + EXPECT_EQ(1, error_result.responses_received()); + EXPECT_EQ(2, error_result.responses_required()); + EXPECT_TRUE(error_result.data_present()); + } +} + +/** + * Validate read/write unavailable server-side failures and error result data. + */ +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorUnavailable) { + Session session = + default_cluster().with_retry_policy(FallthroughRetryPolicy()).connect(keyspace_name_); + + session.execute("CREATE TABLE unavailable (id int PRIMARY KEY, value double)"); + + stop_node(2); + stop_node(3); + + { + Statement insert_statement("INSERT INTO unavailable(id, value) VALUES (?, ?)", 2); + insert_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + insert_statement.bind("id", Integer(2)); + insert_statement.bind("value", Double(2.71)); + insert_statement.set_host("127.0.0.1", 9042); + Result result = session.execute(insert_statement, false); + + EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, result.error_code()); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, error_result.error_code()); + EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); + EXPECT_EQ(1, error_result.responses_received()); + EXPECT_EQ(2, error_result.responses_required()); + } + + { + Statement select_statement("SELECT * FROM unavailable"); + select_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + select_statement.set_host("127.0.0.1", 9042); + Result result = session.execute(select_statement, false); + EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, result.error_code()); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, error_result.error_code()); + EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); + EXPECT_EQ(1, error_result.responses_received()); + EXPECT_EQ(2, error_result.responses_required()); + } +} diff --git a/cpp-driver/test/integration_tests/src/test_server_failures.cpp b/cpp-driver/test/integration_tests/src/test_server_failures.cpp deleted file mode 100644 index 9b02fe734..000000000 --- a/cpp-driver/test/integration_tests/src/test_server_failures.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "cassandra.h" -#include "test_utils.hpp" - -#include -#include - -struct ServerFailuresTest : public test_utils::SingleSessionTest { -public: - ServerFailuresTest() - : test_utils::SingleSessionTest(1, 0) { - test_utils::execute_query(session, - str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % - test_utils::SIMPLE_KEYSPACE % "1")); - test_utils::execute_query(session, str(boost::format("USE %s") % test_utils::SIMPLE_KEYSPACE)); - } - - ~ServerFailuresTest() { - // Drop the keyspace (ignore any and all errors) - test_utils::execute_query_with_error( - session, - str(boost::format(test_utils::DROP_KEYSPACE_FORMAT) % test_utils::SIMPLE_KEYSPACE)); - } -}; - -BOOST_AUTO_TEST_SUITE(server_failures) - -/** - * - * Validate UDF Function_failures are returned from Cassandra - * - * Create a function that will throw an exception when invoked; ensure the - * Function_failure is returned from Cassandra. - * - * @since 2.2.0-beta - * @jira_ticket CPP-294 - * @test_category queries:basic - * @cassandra_version 2.2.x - */ -BOOST_AUTO_TEST_CASE(function_failure) { - CCM::CassVersion version = test_utils::get_version(); - if ((version.major_version >= 2 && version.minor_version >= 2) || version.major_version >= 3) { - ServerFailuresTest tester; - std::string create_table = - "CREATE TABLE server_function_failures (id int PRIMARY KEY, value double)"; - std::string insert_query = "INSERT INTO server_function_failures(id, value) VALUES (?, ?)"; - std::string failing_function = - "CREATE FUNCTION " + test_utils::SIMPLE_KEYSPACE + - ".function_failure(value double) RETURNS NULL ON NULL INPUT RETURNS double LANGUAGE java " - "AS 'throw new RuntimeException(\"failure\");'"; - std::string select_query = - "SELECT function_failure(value) FROM server_function_failures WHERE id = ?"; - - // Create the table and associated failing function - test_utils::execute_query(tester.session, create_table.c_str()); - test_utils::execute_query(tester.session, failing_function.c_str()); - - // Bind and insert values into Cassandra - test_utils::CassStatementPtr statement(cass_statement_new(insert_query.c_str(), 2)); - BOOST_REQUIRE_EQUAL(test_utils::Value::bind_by_name(statement.get(), "id", 1), - CASS_OK); - BOOST_REQUIRE_EQUAL( - test_utils::Value::bind_by_name(statement.get(), "value", 3.14), CASS_OK); - test_utils::wait_and_check_error( - test_utils::CassFuturePtr(cass_session_execute(tester.session, statement.get())).get()); - - // Execute the failing function - statement = test_utils::CassStatementPtr(cass_statement_new(select_query.c_str(), 1)); - BOOST_REQUIRE_EQUAL(test_utils::Value::bind(statement.get(), 0, 1), CASS_OK); - CassError error_code = test_utils::wait_and_return_error( - test_utils::CassFuturePtr(cass_session_execute(tester.session, statement.get())).get()); - BOOST_REQUIRE_EQUAL(error_code, CASS_ERROR_SERVER_FUNCTION_FAILURE); - } else { - std::cout << "Unsupported Test for Cassandra v" << version.to_string() - << ": Skipping server_failures/function_failure" << std::endl; - BOOST_REQUIRE(true); - } -} - -/** - * - * Validate Already_exists failures are returned from Cassandra - * - * Create two identical tables and functions; Ensure Already_exist is returned - * from Cassandra - * - * @since 2.2.0-beta - * @jira_ticket CPP-294 - * @test_category queries:basic - * @cassandra_version 2.2.x - */ -BOOST_AUTO_TEST_CASE(already_exists) { - CCM::CassVersion version = test_utils::get_version(); - if ((version.major_version >= 2 && version.minor_version >= 2) || version.major_version >= 3) { - ServerFailuresTest tester; - std::string create_table = - "CREATE TABLE already_exists_table (id int PRIMARY KEY, value double)"; - std::string create_keyspace = str(boost::format(test_utils::CREATE_KEYSPACE_SIMPLE_FORMAT) % - test_utils::SIMPLE_KEYSPACE % "1"); - - // Create the table - BOOST_REQUIRE_EQUAL(test_utils::execute_query_with_error(tester.session, create_table.c_str()), - CASS_OK); - - // Ensure Cassandra returns Already_exists for table and keyspace - BOOST_REQUIRE_EQUAL(test_utils::execute_query_with_error(tester.session, create_table.c_str()), - CASS_ERROR_SERVER_ALREADY_EXISTS); - BOOST_REQUIRE_EQUAL( - test_utils::execute_query_with_error(tester.session, create_keyspace.c_str()), - CASS_ERROR_SERVER_ALREADY_EXISTS); - } else { - std::cout << "Unsupported Test for Cassandra v" << version.to_string() - << ": Skipping server_failures/already_exists" << std::endl; - BOOST_REQUIRE(true); - } -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/cpp-driver/test/integration_tests/src/test_warnings.cpp b/cpp-driver/test/integration_tests/src/test_warnings.cpp deleted file mode 100644 index 241a4af3e..000000000 --- a/cpp-driver/test/integration_tests/src/test_warnings.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include -#include - -#include "cassandra.h" -#include "test_utils.hpp" - -struct WarningsTests : public test_utils::SingleSessionTest { - WarningsTests() - : SingleSessionTest(1, 0) {} -}; - -BOOST_AUTO_TEST_SUITE(warnings) - -BOOST_AUTO_TEST_CASE(aggregate_without_partition_key) { - CCM::CassVersion version = test_utils::get_version(); - if ((version.major_version >= 2 && version.minor_version >= 2) || version.major_version >= 3) { - WarningsTests tester; - test_utils::CassStatementPtr statement( - cass_statement_new("SELECT sum(gossip_generation) FROM system.local", 0)); - test_utils::CassLog::reset("Server-side warning: Aggregation query used without partition key"); - test_utils::CassFuturePtr future(cass_session_execute(tester.session, statement.get())); - BOOST_CHECK(cass_future_error_code(future.get()) == CASS_OK); - BOOST_CHECK(test_utils::CassLog::message_count() > 0); - } else { - std::cout << "Unsupported Test for Cassandra v" << version.to_string() - << ": Skipping warnings/aggregate_without_partition_key" << std::endl; - BOOST_REQUIRE(true); - } -} - -BOOST_AUTO_TEST_SUITE_END() From 54d292275020653f08769822f0ca7ed0204e1f96 Mon Sep 17 00:00:00 2001 From: Michael Fero Date: Wed, 30 Oct 2019 11:45:58 -0400 Subject: [PATCH 11/42] CPP-831 - Port metrics tests to Google test framework --- .../src/integration/objects/cluster.hpp | 15 + .../src/integration/objects/session.hpp | 11 + .../src/integration/tests/test_metrics.cpp | 209 ++++++++++++ .../integration_tests/src/test_metrics.cpp | 302 ------------------ 4 files changed, 235 insertions(+), 302 deletions(-) create mode 100644 cpp-driver/gtests/src/integration/tests/test_metrics.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_metrics.cpp diff --git a/cpp-driver/gtests/src/integration/objects/cluster.hpp b/cpp-driver/gtests/src/integration/objects/cluster.hpp index 93553a140..3b565e277 100644 --- a/cpp-driver/gtests/src/integration/objects/cluster.hpp +++ b/cpp-driver/gtests/src/integration/objects/cluster.hpp @@ -399,6 +399,21 @@ class Cluster : public Object { return *this; } + /* + * Enable constant speculative execution + * + * + * @param constant_delay_ms Constant delay (in milliseconds) + * @param max_speculative_executions Maximum number of speculative executions + * @return Cluster object + */ + Cluster& with_constant_speculative_execution_policy(int64_t constant_delay_ms, + int max_speculative_executions) { + EXPECT_EQ(CASS_OK, cass_cluster_set_constant_speculative_execution_policy( + get(), constant_delay_ms, max_speculative_executions)); + return *this; + } + /** * Create a new session and establish a connection to the server; * synchronously diff --git a/cpp-driver/gtests/src/integration/objects/session.hpp b/cpp-driver/gtests/src/integration/objects/session.hpp index 219423e71..7e457d91f 100644 --- a/cpp-driver/gtests/src/integration/objects/session.hpp +++ b/cpp-driver/gtests/src/integration/objects/session.hpp @@ -116,6 +116,17 @@ class Session : public Object { return metrics; } + /** + * Get the current driver speculative execution metrics + * + * @return Driver speculative execution metrics + */ + CassSpeculativeExecutionMetrics speculative_execution_metrics() const { + CassSpeculativeExecutionMetrics speculative_execution_metrics; + cass_session_get_speculative_execution_metrics(get(), &speculative_execution_metrics); + return speculative_execution_metrics; + } + /** * Execute a batch statement synchronously * diff --git a/cpp-driver/gtests/src/integration/tests/test_metrics.cpp b/cpp-driver/gtests/src/integration/tests/test_metrics.cpp new file mode 100644 index 000000000..0c34a14ce --- /dev/null +++ b/cpp-driver/gtests/src/integration/tests/test_metrics.cpp @@ -0,0 +1,209 @@ +/* + Copyright (c) DataStax, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "integration.hpp" + +#define SPECULATIVE_EXECUTION_SELECT_FORMAT "SELECT timeout(value) FROM %s WHERE key=%d" +#define SPECULATIVE_EXECUTION_CREATE_TIMEOUT_UDF_FORMAT \ + "CREATE OR REPLACE FUNCTION %s.timeout(arg int) " \ + "RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java " \ + "AS $$ long start = System.currentTimeMillis(); " \ + "while(System.currentTimeMillis() - start < arg) {" \ + ";;" \ + "}" \ + "return arg;" \ + "$$;" + +class MetricsTests : public Integration { +public: + MetricsTests() { number_dc1_nodes_ = 2; } +}; + +/** + * This test ensures that the driver is reporting the proper connection statistics + * + * @since 2.0.0 + * @jira_ticket CPP-188 + */ +CASSANDRA_INTEGRATION_TEST_F(MetricsTests, StatsConnections) { + CHECK_FAILURE; + + Session session = default_cluster() + .with_num_threads_io(1) + .with_core_connections_per_host(1) + .with_constant_reconnect(10) + .connect(); // low reconnect for faster node restart + + CassMetrics metrics = session.metrics(); + EXPECT_EQ(2u, metrics.stats.total_connections); + + stop_node(1); + metrics = session.metrics(); + EXPECT_EQ(1u, metrics.stats.total_connections); + + start_node(1); + metrics = session.metrics(); + EXPECT_EQ(2u, metrics.stats.total_connections); +} + +/** + * This test ensures that the driver is reporting the proper timeouts for connections + * + * @since 2.0.0 + * @jira_ticket CPP-188 + */ +CASSANDRA_INTEGRATION_TEST_F(MetricsTests, ErrorsConnectionTimeouts) { + CHECK_FAILURE; + + Session session = + default_cluster().with_core_connections_per_host(2).with_connect_timeout(1).connect( + "", false); // Quick connection timeout and no assertion + + CassMetrics metrics = session.metrics(); + EXPECT_GE(2u, metrics.errors.connection_timeouts); +} + +/** + * This test ensures that the driver is reporting the proper timeouts for pending requests + * + * @since 2.0.0 + * @jira_ticket CPP-188 + */ +CASSANDRA_INTEGRATION_TEST_F(MetricsTests, ErrorsPendingRequestTimeouts) { + CHECK_FAILURE; + CHECK_VERSION(2.1.2); + + for (int n = 0; n < 1000; ++n) { + session_.execute_async(SELECT_ALL_SYSTEM_LOCAL_CQL); + } + + CassMetrics metrics; + start_timer(); + while (elapsed_time() < 10000 && metrics.errors.pending_request_timeouts == 0) { + msleep(100); + metrics = session_.metrics(); + } + EXPECT_GT(metrics.errors.pending_request_timeouts, 0u); +} + +/** + * This test ensures that the driver is reporting the proper timeouts for requests + * + * @since 2.0.0 + * @jira_ticket CPP-188 + */ +CASSANDRA_INTEGRATION_TEST_F(MetricsTests, ErrorsRequestTimeouts) { + CHECK_FAILURE; + + Session session = default_cluster() + .with_connect_timeout(30000) // 30s timeout + .with_request_timeout(1) + .connect(); // very low request timeout + + for (int n = 0; n < 100; ++n) { + session.execute_async(SELECT_ALL_SYSTEM_LOCAL_CQL); + } + + CassMetrics metrics; + start_timer(); + while (elapsed_time() < 10000 && metrics.errors.request_timeouts == 0) { + msleep(100); + metrics = session.metrics(); + } + EXPECT_GT(metrics.errors.request_timeouts, 0u); +} + +/** + * This test ensures that the histogram data calculated by the driver is being updated. + * + * NOTE: The data returned by the driver is not validated as this is performed + * in the unit tests. + * + * @since 2.0.0 + * @jira_ticket CPP-188 + */ +CASSANDRA_INTEGRATION_TEST_F(MetricsTests, Requests) { + CHECK_FAILURE; + + CassMetrics metrics; + start_timer(); + while (elapsed_time() < 60000 && metrics.requests.fifteen_minute_rate == 0.0) { + session_.execute_async(SELECT_ALL_SYSTEM_LOCAL_CQL); + metrics = session_.metrics(); + } + + EXPECT_LT(metrics.requests.min, CASS_UINT64_MAX); + EXPECT_GT(metrics.requests.max, 0u); + EXPECT_GT(metrics.requests.mean, 0u); + EXPECT_GT(metrics.requests.stddev, 0u); + EXPECT_GT(metrics.requests.median, 0u); + EXPECT_GT(metrics.requests.percentile_75th, 0u); + EXPECT_GT(metrics.requests.percentile_95th, 0u); + EXPECT_GT(metrics.requests.percentile_98th, 0u); + EXPECT_GT(metrics.requests.percentile_99th, 0u); + EXPECT_GT(metrics.requests.percentile_999th, 0u); + EXPECT_GT(metrics.requests.mean_rate, 0.0); + EXPECT_GT(metrics.requests.one_minute_rate, 0.0); + EXPECT_GT(metrics.requests.five_minute_rate, 0.0); + EXPECT_GT(metrics.requests.fifteen_minute_rate, 0.0); +} + +/** + * This test ensures that the histogram data for the speculative execution metrics calculated by the + * driver is being updated. + * + * NOTE: The data returned by the driver is not validated as this is performed + * in the unit tests. + * + * @since 2.0.0 + * @jira_ticket CPP-188 + */ +CASSANDRA_INTEGRATION_TEST_F(MetricsTests, SpeculativeExecutionRequests) { + CHECK_FAILURE; + CHECK_VERSION(2.2.0); + + Session session = default_cluster().with_constant_speculative_execution_policy(100, 20).connect(); + + session.execute(format_string(CASSANDRA_KEY_VALUE_QUALIFIED_TABLE_FORMAT, keyspace_name_.c_str(), + table_name_.c_str(), "int", "int")); + session.execute(format_string(CASSANDRA_KEY_VALUE_QUALIFIED_INSERT_FORMAT, keyspace_name_.c_str(), + table_name_.c_str(), "0", "1000")); + session.execute( + format_string(SPECULATIVE_EXECUTION_CREATE_TIMEOUT_UDF_FORMAT, keyspace_name_.c_str())); + Statement statement(format_string(SPECULATIVE_EXECUTION_SELECT_FORMAT, table_name_.c_str(), 0)); + statement.set_idempotent(true); + statement.set_request_timeout(30000); + + CassSpeculativeExecutionMetrics metrics; + start_timer(); + while (elapsed_time() < 60000 && metrics.count < 1000u) { + session.execute_async(statement); + metrics = session.speculative_execution_metrics(); + } + + EXPECT_LT(metrics.min, CASS_UINT64_MAX); + EXPECT_GT(metrics.max, 0u); + EXPECT_GT(metrics.mean, 0u); + EXPECT_GT(metrics.stddev, 0u); + EXPECT_GT(metrics.median, 0u); + EXPECT_GT(metrics.percentile_75th, 0u); + EXPECT_GT(metrics.percentile_95th, 0u); + EXPECT_GT(metrics.percentile_98th, 0u); + EXPECT_GT(metrics.percentile_99th, 0u); + EXPECT_GT(metrics.percentile_999th, 0u); + EXPECT_GT(metrics.percentage, 0.0); + EXPECT_GT(metrics.count, 0u); +} diff --git a/cpp-driver/test/integration_tests/src/test_metrics.cpp b/cpp-driver/test/integration_tests/src/test_metrics.cpp deleted file mode 100644 index 374f22831..000000000 --- a/cpp-driver/test/integration_tests/src/test_metrics.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* - Copyright (c) DataStax, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include -#include -#include // Sleep functionality - -#include "cassandra.h" -#include "constants.hpp" -#include "test_utils.hpp" - -struct MetricsTest { -public: - test_utils::CassClusterPtr cluster_; - boost::shared_ptr ccm_; - - MetricsTest() - : cluster_(cass_cluster_new()) - , ccm_(new CCM::Bridge("config.txt")) {} - - ~MetricsTest() { close_session(); } - - /** - * Create the session - * - * @param is_timeout True if for timeout tests; false otherwise - */ - void create_session(bool is_timeout = false) { - close_session(); - test_utils::CassSessionPtr session(cass_session_new()); - test_utils::CassFuturePtr connect_future(cass_session_connect(session.get(), cluster_.get())); - CassError error_code = test_utils::wait_and_return_error(connect_future.get()); - session_ = test_utils::create_session(cluster_.get(), &error_code); - if (error_code != CASS_OK) { - if (is_timeout) { - if (error_code == CASS_ERROR_LIB_NO_HOSTS_AVAILABLE) { - return; - } - } - - CassString message; - cass_future_error_message(connect_future.get(), &message.data, &message.length); - BOOST_FAIL(std::string(message.data, message.length) - << "' (" << cass_error_desc(error_code) << ")"); - } - } - - /** - * Close the session - */ - void close_session() { - if (session_) { - test_utils::CassFuturePtr close_future(cass_session_close(session_.get())); - cass_future_wait(close_future.get()); - } - } - - /** - * Get the driver metrics - * - * @param metrics Metrics to assign from the active session - */ - void get_metrics(CassMetrics* metrics) { cass_session_get_metrics(session_.get(), metrics); } - - /** - * Get the driver speculative execution metrics - * - * @param metrics Metrics to assign from the active session - */ - void get_speculative_execution_metrics(CassSpeculativeExecutionMetrics* metrics) { - cass_session_get_speculative_execution_metrics(session_.get(), metrics); - } - - /** - * Execute a query on the system table - * - * @param is_async True if async query; false otherwise - */ - void execute_query(bool is_async = false) { - std::string query = "SELECT * FROM system.local"; - test_utils::CassStatementPtr statement(cass_statement_new_n(query.data(), query.size(), 0)); - test_utils::CassFuturePtr future(cass_session_execute(session_.get(), statement.get())); - if (!is_async) { - test_utils::wait_and_check_error(future.get()); - } - } - -private: - test_utils::CassSessionPtr session_; -}; - -BOOST_FIXTURE_TEST_SUITE(metrics, MetricsTest) - -/** - * Driver Metrics - Connection statistics - * - * This test ensures that the driver is reporting the proper connection - * statistics - * - * @since 2.0.0 - * @jira_ticket CPP-188 - * @test_category metrics - */ -BOOST_AUTO_TEST_CASE(connections) { - // Create one connections per host - cass_cluster_set_num_threads_io(cluster_.get(), 1); - cass_cluster_set_core_connections_per_host(cluster_.get(), 1); - cass_cluster_set_constant_reconnect(cluster_.get(), 10); // Low re-connect for node restart - test_utils::initialize_contact_points(cluster_.get(), ccm_->get_ip_prefix(), 3); - if (ccm_->create_cluster(3)) { - ccm_->start_cluster(); - } - create_session(); - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - - CassMetrics metrics; - get_metrics(&metrics); - BOOST_CHECK_EQUAL(metrics.stats.total_connections, 3); - ccm_->stop_node(1); - get_metrics(&metrics); - BOOST_CHECK_EQUAL(metrics.stats.total_connections, 2); - ccm_->stop_node(2); - get_metrics(&metrics); - BOOST_CHECK_EQUAL(metrics.stats.total_connections, 1); - ccm_->stop_node(3); - get_metrics(&metrics); - BOOST_CHECK_EQUAL(metrics.stats.total_connections, 0); - ccm_->start_node(1); - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - get_metrics(&metrics); - BOOST_CHECK_EQUAL(metrics.stats.total_connections, 1); - ccm_->start_node(2); - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - get_metrics(&metrics); - BOOST_CHECK_EQUAL(metrics.stats.total_connections, 2); - ccm_->start_node(3); - boost::this_thread::sleep_for(boost::chrono::seconds(1)); - get_metrics(&metrics); - BOOST_CHECK_EQUAL(metrics.stats.total_connections, 3); -} - -/** - * Driver Metrics - Timeouts - * - * This test ensures that the driver is reporting the proper timeouts for - * connection and requests - * - * @since 2.0.0 - * @jira_ticket CPP-188 - * @test_category metrics - */ -BOOST_AUTO_TEST_CASE(timeouts) { - CassMetrics metrics; - cass_cluster_set_core_connections_per_host(cluster_.get(), 2); - test_utils::initialize_contact_points(cluster_.get(), ccm_->get_ip_prefix(), 2); - - /* - * Check for connection timeouts - */ - cass_cluster_set_connect_timeout(cluster_.get(), 1); - if (ccm_->create_cluster(2)) { - ccm_->start_cluster(); - } - create_session(true); - get_metrics(&metrics); - BOOST_CHECK_GE(metrics.errors.connection_timeouts, 2); - - /* - * Check for pending request timeouts - */ - CCM::CassVersion version = test_utils::get_version(); - if ((version.major_version <= 2 && version.minor_version < 1) || version.major_version < 2) { - // Limit the connections to one - cass_cluster_set_core_connections_per_host(cluster_.get(), 1); - cass_cluster_set_max_connections_per_host(cluster_.get(), 1); - // Lower connect timeout because this is what affects pending request timeout - cass_cluster_set_connect_timeout(cluster_.get(), 100); - if (ccm_->create_cluster(2)) { - ccm_->start_cluster(); - } - create_session(true); - - // Execute async queries to create pending request timeouts - for (int n = 0; n < 1000; ++n) { - execute_query(true); - } - - // Ensure the pending request has occurred - boost::chrono::steady_clock::time_point end = - boost::chrono::steady_clock::now() + boost::chrono::seconds(10); - do { - boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); - get_metrics(&metrics); - } while (boost::chrono::steady_clock::now() < end && - metrics.errors.pending_request_timeouts == 0); - BOOST_CHECK_GT(metrics.errors.pending_request_timeouts, 0); - } else { - std::cout << "Skipping Pending Request Timeout for Cassandra v" << version.to_string() - << std::endl; - } - - /* - * Check for request timeouts - */ - cass_cluster_set_connect_timeout(cluster_.get(), 30 * test_utils::ONE_SECOND_IN_MILLISECONDS); - cass_cluster_set_request_timeout(cluster_.get(), 1); - if (ccm_->create_cluster()) { - ccm_->start_cluster(); - } - create_session(true); - for (int n = 0; n < 100; ++n) { - execute_query(true); - } - - // Ensure the request timeout has occurred - boost::chrono::steady_clock::time_point end = - boost::chrono::steady_clock::now() + boost::chrono::seconds(10); - do { - boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); - get_metrics(&metrics); - } while (boost::chrono::steady_clock::now() < end && metrics.errors.request_timeouts == 0); - BOOST_CHECK_GT(metrics.errors.request_timeouts, 0); -} - -/** - * Driver Metrics - Request Statistics - * - * This test ensures that the histogram data calculated by the driver is being - * updated. - * - * NOTE: The data returned by the driver is not validated as this is performed - * in the unit tests. - * - * @since 2.0.0 - * @jira_ticket CPP-188 - * @test_category metrics - */ -BOOST_AUTO_TEST_CASE(request_statistics) { - // Create one connections per host - cass_cluster_set_num_threads_io(cluster_.get(), 1); - cass_cluster_set_core_connections_per_host(cluster_.get(), 1); - test_utils::initialize_contact_points(cluster_.get(), ccm_->get_ip_prefix(), 1); - if (ccm_->create_cluster()) { - ccm_->start_cluster(); - } - create_session(); - - CassMetrics metrics; - CassSpeculativeExecutionMetrics spec_metrics; - boost::chrono::steady_clock::time_point end = - boost::chrono::steady_clock::now() + boost::chrono::seconds(70); - do { - execute_query(); - get_metrics(&metrics); - get_speculative_execution_metrics(&spec_metrics); - } while (boost::chrono::steady_clock::now() < end && metrics.requests.one_minute_rate == 0.0); - - BOOST_CHECK_LT(metrics.requests.min, CASS_UINT64_MAX); - BOOST_CHECK_GT(metrics.requests.max, 0); - BOOST_CHECK_GT(metrics.requests.mean, 0); - BOOST_CHECK_GT(metrics.requests.stddev, 0); - BOOST_CHECK_GT(metrics.requests.median, 0); - BOOST_CHECK_GT(metrics.requests.percentile_75th, 0); - BOOST_CHECK_GT(metrics.requests.percentile_95th, 0); - BOOST_CHECK_GT(metrics.requests.percentile_98th, 0); - BOOST_CHECK_GT(metrics.requests.percentile_99th, 0); - BOOST_CHECK_GT(metrics.requests.percentile_999th, 0); - BOOST_CHECK_GT(metrics.requests.mean_rate, 0.0); - BOOST_CHECK_GT(metrics.requests.one_minute_rate, 0.0); - BOOST_CHECK_EQUAL(metrics.requests.five_minute_rate, metrics.requests.one_minute_rate); - BOOST_CHECK_EQUAL(metrics.requests.fifteen_minute_rate, metrics.requests.one_minute_rate); - - // Since speculative retries are disabled, this stat should be 0. - // test_speculative_execution_policy tests the non-zero case. - BOOST_CHECK_EQUAL(spec_metrics.min, 0); - BOOST_CHECK_EQUAL(spec_metrics.max, 0); - BOOST_CHECK_EQUAL(spec_metrics.mean, 0); - BOOST_CHECK_EQUAL(spec_metrics.stddev, 0); - BOOST_CHECK_EQUAL(spec_metrics.median, 0); - BOOST_CHECK_EQUAL(spec_metrics.percentile_75th, 0); - BOOST_CHECK_EQUAL(spec_metrics.percentile_95th, 0); - BOOST_CHECK_EQUAL(spec_metrics.percentile_98th, 0); - BOOST_CHECK_EQUAL(spec_metrics.percentile_99th, 0); - BOOST_CHECK_EQUAL(spec_metrics.percentile_999th, 0); - BOOST_CHECK_EQUAL(spec_metrics.percentage, 0.0); - BOOST_CHECK_EQUAL(spec_metrics.count, 0); -} - -BOOST_AUTO_TEST_SUITE_END() From b5fcf05dd05756a525546c981bb26c43fb0aaad6 Mon Sep 17 00:00:00 2001 From: Michael Penick Date: Mon, 4 Nov 2019 11:37:07 -0500 Subject: [PATCH 12/42] Fix code review issues --- .../tests/test_server_side_failure.cpp | 139 +++++++----------- 1 file changed, 56 insertions(+), 83 deletions(-) diff --git a/cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp b/cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp index 93e71c1e1..a15099359 100644 --- a/cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp +++ b/cpp-driver/gtests/src/integration/tests/test_server_side_failure.cpp @@ -19,12 +19,54 @@ /** * Server-side warnings and errors integration tests */ -class ServerSideFailureTests : public Integration { +class ServerSideFailureTests : public Integration {}; + +/** + * Server-side errors integration tests that require three nodes. + */ +class ServerSideFailureThreeNodeTests : public Integration { public: - ServerSideFailureTests() { + ServerSideFailureThreeNodeTests() { number_dc1_nodes_ = 3; replication_factor_ = 3; } + + void SetUp() { + Integration::SetUp(); + session_.execute( + format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", "double")); + } + + void validate_write_response(Session session, CassError expected_error_code) { + Statement insert_statement( + format_string(CASSANDRA_KEY_VALUE_INSERT_FORMAT, table_name_.c_str(), "2", "2.71")); + insert_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + insert_statement.set_host("127.0.0.1", 9042); + Result result = session.execute(insert_statement, false); + validate_response(result, expected_error_code); + } + + void validate_read_response(Session session, CassError expected_error_code) { + Statement select_statement(default_select_all()); + select_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); + select_statement.set_host("127.0.0.1", 9042); + Result result = session.execute(select_statement, false); + validate_response(result, expected_error_code); + } + +private: + void validate_response(Result result, CassError expected_error_code) { + EXPECT_EQ(expected_error_code, result.error_code()); + + ErrorResult error_result = result.error_result(); + ASSERT_TRUE(error_result); + EXPECT_EQ(expected_error_code, error_result.error_code()); + EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); + EXPECT_EQ(1, error_result.responses_received()); + EXPECT_EQ(2, error_result.responses_required()); + EXPECT_TRUE(expected_error_code != CASS_ERROR_SERVER_READ_TIMEOUT || + error_result.data_present()); + } }; /** @@ -40,7 +82,7 @@ CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, Warning) { logger_.add_critera("Server-side warning: Aggregation query used without partition key"); session_.execute("SELECT sum(gossip_generation) FROM system.local"); - EXPECT_EQ(logger_.count(), 1); + EXPECT_EQ(logger_.count(), 1u); } /** @@ -64,16 +106,12 @@ CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorFunctionFailure) { "AS 'throw new RuntimeException(\"failure\");'"); // Bind and insert values into Cassandra - Statement insert_statement("INSERT INTO server_function_failures(id, value) VALUES (?, ?)", 2); - insert_statement.bind("id", Integer(1)); - insert_statement.bind("value", Double(3.14)); - session_.execute(insert_statement); + session_.execute("INSERT INTO server_function_failures(id, value) VALUES (1, 3.14)"); // Execute the failing function - Statement select_statement( - "SELECT function_failure(value) FROM server_function_failures WHERE id = ?", 1); - select_statement.bind("id", Integer(1)); - Result result = session_.execute(select_statement, false); + Result result = session_.execute( + Statement("SELECT function_failure(value) FROM server_function_failures WHERE id = 1"), + false); EXPECT_EQ(CASS_ERROR_SERVER_FUNCTION_FAILURE, result.error_code()); ErrorResult error_result = result.error_result(); @@ -86,7 +124,7 @@ CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorFunctionFailure) { } /** - * Validate Already_exists failures are returned when creating the same table twice. + * Validate already exists failures are returned when creating the same table twice. */ CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorTableAlreadyExists) { CHECK_FAILURE; @@ -131,92 +169,27 @@ CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorFunctionAlreadyExists) /** * Validate read/write timeout server-side failures and error result data. */ -CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorReadWriteTimeout) { +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureThreeNodeTests, ErrorReadWriteTimeout) { Session session = default_cluster().with_retry_policy(FallthroughRetryPolicy()).connect(keyspace_name_); - session.execute("CREATE TABLE read_write_timeout (id int PRIMARY KEY, value double)"); - pause_node(2); pause_node(3); - { - Statement insert_statement("INSERT INTO read_write_timeout(id, value) VALUES (?, ?)", 2); - insert_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); - insert_statement.bind("id", Integer(2)); - insert_statement.bind("value", Double(2.71)); - insert_statement.set_host("127.0.0.1", 9042); - Result result = session.execute(insert_statement, false); - - EXPECT_EQ(CASS_ERROR_SERVER_WRITE_TIMEOUT, result.error_code()); - - ErrorResult error_result = result.error_result(); - ASSERT_TRUE(error_result); - EXPECT_EQ(CASS_ERROR_SERVER_WRITE_TIMEOUT, error_result.error_code()); - EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); - EXPECT_EQ(1, error_result.responses_received()); - EXPECT_EQ(2, error_result.responses_required()); - } - - { - Statement select_statement("SELECT * FROM read_write_timeout"); - select_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); - select_statement.set_host("127.0.0.1", 9042); - Result result = session.execute(select_statement, false); - EXPECT_EQ(CASS_ERROR_SERVER_READ_TIMEOUT, result.error_code()); - - ErrorResult error_result = result.error_result(); - ASSERT_TRUE(error_result); - EXPECT_EQ(CASS_ERROR_SERVER_READ_TIMEOUT, error_result.error_code()); - EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); - EXPECT_EQ(1, error_result.responses_received()); - EXPECT_EQ(2, error_result.responses_required()); - EXPECT_TRUE(error_result.data_present()); - } + validate_write_response(session, CASS_ERROR_SERVER_WRITE_TIMEOUT); + validate_read_response(session, CASS_ERROR_SERVER_READ_TIMEOUT); } /** * Validate read/write unavailable server-side failures and error result data. */ -CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureTests, ErrorUnavailable) { +CASSANDRA_INTEGRATION_TEST_F(ServerSideFailureThreeNodeTests, ErrorUnavailable) { Session session = default_cluster().with_retry_policy(FallthroughRetryPolicy()).connect(keyspace_name_); - session.execute("CREATE TABLE unavailable (id int PRIMARY KEY, value double)"); - stop_node(2); stop_node(3); - { - Statement insert_statement("INSERT INTO unavailable(id, value) VALUES (?, ?)", 2); - insert_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); - insert_statement.bind("id", Integer(2)); - insert_statement.bind("value", Double(2.71)); - insert_statement.set_host("127.0.0.1", 9042); - Result result = session.execute(insert_statement, false); - - EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, result.error_code()); - - ErrorResult error_result = result.error_result(); - ASSERT_TRUE(error_result); - EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, error_result.error_code()); - EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); - EXPECT_EQ(1, error_result.responses_received()); - EXPECT_EQ(2, error_result.responses_required()); - } - - { - Statement select_statement("SELECT * FROM unavailable"); - select_statement.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM); - select_statement.set_host("127.0.0.1", 9042); - Result result = session.execute(select_statement, false); - EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, result.error_code()); - - ErrorResult error_result = result.error_result(); - ASSERT_TRUE(error_result); - EXPECT_EQ(CASS_ERROR_SERVER_UNAVAILABLE, error_result.error_code()); - EXPECT_EQ(CASS_CONSISTENCY_LOCAL_QUORUM, error_result.consistency()); - EXPECT_EQ(1, error_result.responses_received()); - EXPECT_EQ(2, error_result.responses_required()); - } + validate_write_response(session, CASS_ERROR_SERVER_UNAVAILABLE); + validate_read_response(session, CASS_ERROR_SERVER_UNAVAILABLE); } From 3eeb974f50611e0263db07f7acbc97b1b96e0da0 Mon Sep 17 00:00:00 2001 From: Fero Date: Tue, 5 Nov 2019 16:52:07 -0500 Subject: [PATCH 13/42] CPP-851 - Disable deprecated warnings for std::ptr_fun (#322) --- cpp-driver/gtests/src/unit/tests/test_utils.cpp | 16 ++++++++++++++++ cpp-driver/src/utils.cpp | 9 ++++----- .../test/integration_tests/src/test_utils.cpp | 9 ++++----- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/cpp-driver/gtests/src/unit/tests/test_utils.cpp b/cpp-driver/gtests/src/unit/tests/test_utils.cpp index 49f0f3e69..c92b66501 100644 --- a/cpp-driver/gtests/src/unit/tests/test_utils.cpp +++ b/cpp-driver/gtests/src/unit/tests/test_utils.cpp @@ -87,3 +87,19 @@ TEST(UtilsUnitTest, NextPow2) { EXPECT_EQ(STATIC_NEXT_POW_2(31u), 32u); EXPECT_EQ(STATIC_NEXT_POW_2(32u), 32u); } + +TEST(UtilsUnitTest, Trim) { + String s; + + s = " abc"; + EXPECT_EQ(trim(s), String("abc")); + + s = "abc "; + EXPECT_EQ(trim(s), String("abc")); + + s = " abc "; + EXPECT_EQ(trim(s), String("abc")); + + s = " a bc "; + EXPECT_EQ(trim(s), String("a bc")); +} diff --git a/cpp-driver/src/utils.cpp b/cpp-driver/src/utils.cpp index 2e105c302..208b90917 100644 --- a/cpp-driver/src/utils.cpp +++ b/cpp-driver/src/utils.cpp @@ -101,14 +101,13 @@ String implode(const Vector& vec, const char delimiter /* = ' ' */) { return str; } +bool not_isspace(int c) { return !::isspace(c); } + String& trim(String& str) { // Trim front - str.erase(str.begin(), - std::find_if(str.begin(), str.end(), std::not1(std::ptr_fun(::isspace)))); + str.erase(str.begin(), std::find_if(str.begin(), str.end(), not_isspace)); // Trim back - str.erase( - std::find_if(str.rbegin(), str.rend(), std::not1(std::ptr_fun(::isspace))).base(), - str.end()); + str.erase(std::find_if(str.rbegin(), str.rend(), not_isspace).base(), str.end()); return str; } diff --git a/cpp-driver/test/integration_tests/src/test_utils.cpp b/cpp-driver/test/integration_tests/src/test_utils.cpp index 79245b6b6..fe3c0dd21 100644 --- a/cpp-driver/test/integration_tests/src/test_utils.cpp +++ b/cpp-driver/test/integration_tests/src/test_utils.cpp @@ -535,14 +535,13 @@ void wait_for_node_connections(const std::string& ip_prefix, std::vector no } } +bool not_isspace(int c) { return !::isspace(c); } + std::string& trim(std::string& str) { // Trim front - str.erase(str.begin(), - std::find_if(str.begin(), str.end(), std::not1(std::ptr_fun(::isspace)))); + str.erase(str.begin(), std::find_if(str.begin(), str.end(), not_isspace)); // Trim back - str.erase( - std::find_if(str.rbegin(), str.rend(), std::not1(std::ptr_fun(::isspace))).base(), - str.end()); + str.erase(std::find_if(str.rbegin(), str.rend(), not_isspace).base(), str.end()); return str; } From 9b58ab5d4103fff8ff6c2153971de9898dddb367 Mon Sep 17 00:00:00 2001 From: Fero Date: Wed, 6 Nov 2019 15:41:35 -0500 Subject: [PATCH 14/42] CPP-853 - Fix linking on unix based OS for CASS_USE_STATIC_LIBS=On (#321) --- cpp-driver/cmake/modules/CppDriver.cmake | 6 ++++++ cpp-driver/cmake/modules/Gtest.cmake | 7 +------ cpp-driver/gtests/CMakeLists.txt | 1 - 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp-driver/cmake/modules/CppDriver.cmake b/cpp-driver/cmake/modules/CppDriver.cmake index 9ad759a55..9037400f0 100644 --- a/cpp-driver/cmake/modules/CppDriver.cmake +++ b/cpp-driver/cmake/modules/CppDriver.cmake @@ -441,6 +441,12 @@ macro(CassUseLibuv) # Assign libuv include and libraries set(CASS_INCLUDES ${CASS_INCLUDES} ${LIBUV_INCLUDE_DIRS}) set(CASS_LIBS ${CASS_LIBS} ${LIBUV_LIBRARIES}) + + # libuv and gtests require thread library + set (THREADS_PREFER_PTHREAD_FLAG 1) + find_package(Threads REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_THREAD_LIBS_INIT}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_THREAD_LIBS_INIT}") endmacro() #------------------------ diff --git a/cpp-driver/cmake/modules/Gtest.cmake b/cpp-driver/cmake/modules/Gtest.cmake index 7d8df577a..d82ffce74 100644 --- a/cpp-driver/cmake/modules/Gtest.cmake +++ b/cpp-driver/cmake/modules/Gtest.cmake @@ -6,8 +6,7 @@ cmake_minimum_required(VERSION 2.6.4) # Initialize the Google Test framework # # Input: VENDOR_DIR -# Output: GOOGLE_TEST_DIR, GOOGLE_TEST_HEADER_FILES, GOOGLE_TEST_SOURCE_FILES, -# GOOGLE_TEST_LIBRARIES +# Output: GOOGLE_TEST_DIR, GOOGLE_TEST_HEADER_FILES, GOOGLE_TEST_SOURCE_FILES #------------------------ macro(GtestFramework) #------------------------------ @@ -26,9 +25,6 @@ macro(GtestFramework) # VS 2012 11 1700 std::tr1::tuple + _VARIADIC_MAX=10 # VS 2013 12 1800 std::tr1::tuple add_definitions(-D_VARIADIC_MAX=10) - else() - find_package(Threads REQUIRED) - set(GOOGLE_TEST_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) endif() endmacro() @@ -229,7 +225,6 @@ macro(GtestUnitTests project_name extra_files extra_includes excluded_test_files ${CCM_BRIDGE_SOURCE_DIR}) endif() target_link_libraries(${UNIT_TESTS_NAME} - ${GOOGLE_TEST_LIBRARIES} ${DSE_LIBS} ${PROJECT_LIB_NAME_TARGET}) set_property(TARGET ${UNIT_TESTS_NAME} PROPERTY PROJECT_LABEL ${UNIT_TESTS_DISPLAY_NAME}) diff --git a/cpp-driver/gtests/CMakeLists.txt b/cpp-driver/gtests/CMakeLists.txt index b70790d4f..bcc71bda6 100644 --- a/cpp-driver/gtests/CMakeLists.txt +++ b/cpp-driver/gtests/CMakeLists.txt @@ -93,7 +93,6 @@ if(CASS_BUILD_INTEGRATION_TESTS) target_link_libraries(${INTEGRATION_TESTS_NAME} CCMBridge ${CCM_BRIDGE_LIBRARIES} - ${GOOGLE_TEST_LIBRARIES} ${CASS_LIBS} ${PROJECT_LIB_NAME_TARGET}) set_property(TARGET ${INTEGRATION_TESTS_NAME} PROPERTY PROJECT_LABEL ${INTEGRATION_TESTS_DISPLAY_NAME}) From 32b2259892a315b4b7562a4213418a62e39d4f1e Mon Sep 17 00:00:00 2001 From: Michael Fero Date: Thu, 7 Nov 2019 13:10:59 -0500 Subject: [PATCH 15/42] CPP-833 - Port named parameters tests to Google test framework --- .../tests/test_cassandra_types.cpp | 226 ++++--- .../tests/test_named_parameters.cpp | 227 +++++++ .../src/test_named_parameters.cpp | 617 ------------------ 3 files changed, 371 insertions(+), 699 deletions(-) create mode 100644 cpp-driver/gtests/src/integration/tests/test_named_parameters.cpp delete mode 100644 cpp-driver/test/integration_tests/src/test_named_parameters.cpp diff --git a/cpp-driver/gtests/src/integration/tests/test_cassandra_types.cpp b/cpp-driver/gtests/src/integration/tests/test_cassandra_types.cpp index 87f63cbb1..8643c3a4b 100644 --- a/cpp-driver/gtests/src/integration/tests/test_cassandra_types.cpp +++ b/cpp-driver/gtests/src/integration/tests/test_cassandra_types.cpp @@ -20,14 +20,14 @@ * Cassandra type integration tests */ template -class CassandraTypesTest : public Integration { +class CassandraTypesTests : public Integration { public: /** * Cassandra type values */ static const std::vector values_; - CassandraTypesTest() + CassandraTypesTests() : is_key_allowed_(true) {} void SetUp() { @@ -64,9 +64,9 @@ class CassandraTypesTest : public Integration { /** * Default setup for most of the tests */ - void default_setup() { + void default_setup(bool is_named = false) { // Create the table, insert, and select queries - initialize(values_[0].cql_type()); + initialize(values_[0].cql_type(), is_named); } /** @@ -74,7 +74,7 @@ class CassandraTypesTest : public Integration { * * @param cql_type CQL value type to use for the tables */ - void initialize(const std::string& cql_type) { + void initialize(const std::string& cql_type, bool is_named = false) { if (is_key_allowed_) { session_.execute(format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), cql_type.c_str(), cql_type.c_str())); @@ -82,17 +82,20 @@ class CassandraTypesTest : public Integration { session_.execute(format_string(CASSANDRA_KEY_VALUE_TABLE_FORMAT, table_name_.c_str(), "int", cql_type.c_str())); } - insert_query_ = format_string(CASSANDRA_KEY_VALUE_INSERT_FORMAT, table_name_.c_str(), "?", "?"); - select_query_ = format_string(CASSANDRA_SELECT_VALUE_FORMAT, table_name_.c_str(), "?"); + insert_query_ = + format_string(CASSANDRA_KEY_VALUE_INSERT_FORMAT, table_name_.c_str(), + (is_named ? ":named_key" : "?"), (is_named ? ":named_value" : "?")); + select_query_ = format_string(CASSANDRA_SELECT_VALUE_FORMAT, table_name_.c_str(), + (is_named ? ":named_key" : "?")); prepared_statement_ = session_.prepare(insert_query_); } }; -TYPED_TEST_CASE_P(CassandraTypesTest); +TYPED_TEST_CASE_P(CassandraTypesTests); /** * Specialized duration integration test extension */ -class CassandraTypesDurationTest : public CassandraTypesTest {}; +class CassandraTypesDurationTests : public CassandraTypesTests {}; /** * Perform insert using a simple and prepared statement operation @@ -107,11 +110,11 @@ class CassandraTypesDurationTest : public CassandraTypesTest {}; * @since core:1.0.0 * @expected_result Cassandra values are inserted and validated */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Basic) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, Basic) { CHECK_VALUE_TYPE_VERSION(TypeParam); this->default_setup(); - const std::vector& values = CassandraTypesTest::values_; + const std::vector& values = CassandraTypesTests::values_; // Iterate over all the Cassandra type values for (typename std::vector::const_iterator it = values.begin(); it != values.end(); @@ -156,7 +159,7 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Basic) { * * This test will perform multiple inserts by name using a simple/prepared * statement with the parameterized type values statically assigned against a - * single node cluster. + * single node cluster. * * @test_category queries:basic * @test_category prepared_statements @@ -164,11 +167,11 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Basic) { * @since core:1.0.0 * @expected_result Cassandra values are inserted and validated */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicByName) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, ByName) { CHECK_VALUE_TYPE_VERSION(TypeParam); this->default_setup(); - const std::vector& values = CassandraTypesTest::values_; + const std::vector& values = CassandraTypesTests::values_; // Iterate over all the Cassandra type values for (typename std::vector::const_iterator it = values.begin(); it != values.end(); @@ -207,6 +210,64 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicByName) { } } +/** + * Perform insert by named parameter using a simple and prepared statement operation + * + * This test will perform multiple inserts with named parameter using a simple/prepared + * statement with the parameterized type values statically assigned against a single node cluster. + * + * @test_category queries:basic + * @test_category prepared_statements + * @test_category data_types:primitive + * @test_category queries:named_parameters + * @since core:2.10.0-beta + * @jira_ticket CPP-263 + * @expected_result Cassandra values are inserted and validated + */ +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, NamedParameters) { + CHECK_VERSION(2.1.0); + CHECK_VALUE_TYPE_VERSION(TypeParam); + + this->default_setup(true); + const std::vector& values = CassandraTypesTests::values_; + + // Iterate over all the Cassandra type values + for (typename std::vector::const_iterator it = values.begin(); it != values.end(); + ++it) { + // Get the current value + const TypeParam& value = *it; + + // Create both simple and prepared statements + Statement statements[] = { Statement(this->insert_query_, 2), + this->prepared_statement_.bind() }; + + // Iterate over all the statements + for (size_t i = 0; i < ARRAY_LEN(statements); ++i) { + Statement& statement = statements[i]; + + // Bind both the primary key and the value with the Cassandra type and insert + if (this->is_key_allowed_) { + statement.bind("named_key", value); + } else { + statement.bind("named_key", Integer(i)); + } + statement.bind("named_value", value); + this->session_.execute(statement); + + // Validate the insert and result + Statement select_statement(this->select_query_, 1); + if (this->is_key_allowed_) { + select_statement.bind("named_key", value); + } else { + select_statement.bind("named_key", Integer(i)); + } + Result result = this->session_.execute(select_statement); + ASSERT_EQ(1u, result.row_count()); + ASSERT_EQ(value, result.first_row().next().as()); + } + } +} + /** * Perform NULL value inserts using a simple and prepared statement operation * @@ -219,7 +280,7 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicByName) { * @since core:1.0.0 * @expected_result Cassandra NULL values are inserted and validated */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullValues) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, NullValues) { CHECK_VALUE_TYPE_VERSION(TypeParam); this->is_key_allowed_ = false; // Ensure the TypeParam is not allowed as a key @@ -262,7 +323,7 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullValues) { * @since core:1.0.0 * @expected_result Cassandra NULL values are inserted and validated */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullList) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, NullList) { CHECK_VALUE_TYPE_VERSION(TypeParam); this->is_key_allowed_ = false; // Ensure the TypeParam is not allowed as a key @@ -306,7 +367,7 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullList) { * @since core:1.0.0 * @expected_result Cassandra NULL values are inserted and validated */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullMap) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, NullMap) { CHECK_VALUE_TYPE_VERSION(TypeParam); this->is_key_allowed_ = false; // Ensure the TypeParam is not allowed as a key @@ -350,7 +411,7 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullMap) { * @since core:1.0.0 * @expected_result Cassandra NULL values are inserted and validated */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullSet) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, NullSet) { CHECK_VALUE_TYPE_VERSION(TypeParam); this->is_key_allowed_ = false; // Ensure the TypeParam is not allowed as a key @@ -395,11 +456,11 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, BasicNullSet) { * @expected_result Cassandra values are inserted using a list and then * validated via simple and prepared statement operations */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, List) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, List) { CHECK_VALUE_TYPE_VERSION(TypeParam); // Initialize the table and assign the values for the list - List list(CassandraTypesTest::values_); + List list(CassandraTypesTests::values_); this->initialize("frozen<" + list.cql_type() + ">"); // Create both simple and prepared statements @@ -446,14 +507,14 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, List) { * @expected_result Cassandra values are inserted using a set and then validated * via simple and prepared statement operations */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Set) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, Set) { CHECK_VALUE_TYPE_VERSION(TypeParam); - if (CassandraTypesTest::values_[0].cql_type().compare("duration") == 0) { + if (CassandraTypesTests::values_[0].cql_type().compare("duration") == 0) { SKIP_TEST("Unsupported CQL Type Duration: Set does not support duration"); } // Initialize the table and assign the values for the set - Set set(CassandraTypesTest::values_); + Set set(CassandraTypesTests::values_); this->initialize("frozen<" + set.cql_type() + ">"); // Create both simple and prepared statements @@ -500,14 +561,14 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Set) { * @expected_result Cassandra values are inserted using a map and then validated * via simple and prepared statement operations */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Map) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, Map) { CHECK_VALUE_TYPE_VERSION(TypeParam); // TODO(fero): Move this into its own parameterized method or keep this branching? if (this->is_key_allowed_) { // Initialize the table and assign the values for the map std::map map_values; - const std::vector& values = CassandraTypesTest::values_; + const std::vector& values = CassandraTypesTests::values_; for (typename std::vector::const_iterator it = values.begin(); it != values.end(); ++it) { map_values[*it] = *it; @@ -540,7 +601,7 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Map) { } else { // Initialize the table and assign the values for the map std::map map_values; - const std::vector& values = CassandraTypesTest::values_; + const std::vector& values = CassandraTypesTests::values_; cass_int32_t count = 1; for (typename std::vector::const_iterator it = values.begin(); it != values.end(); ++it) { @@ -589,12 +650,12 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Map) { * @expected_result Cassandra values are inserted using a tuple and then * validated via simple and prepared statement operations */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Tuple) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, Tuple) { CHECK_VERSION(2.1.0); CHECK_VALUE_TYPE_VERSION(TypeParam); // Initialize the table and assign the values for the tuple - const std::vector& values = CassandraTypesTest::values_; + const std::vector& values = CassandraTypesTests::values_; Tuple tuple(values.size()); std::string cql_type("tuple<"); for (size_t i = 0; i < values.size(); ++i) { @@ -650,12 +711,12 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, Tuple) { * @expected_result Cassandra values are inserted using a user data type and * then validated via simple and prepared statement operations */ -CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, UDT) { +CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTests, UDT) { CHECK_VERSION(2.2.0); CHECK_VALUE_TYPE_VERSION(TypeParam); // Build the UDT type name e.g. udt_pointtype, udt_line_string, etc. - const std::vector& values = CassandraTypesTest::values_; + const std::vector& values = CassandraTypesTests::values_; std::string cql_type("udt_" + Utils::to_lower(Utils::replace_all(values[0].cql_type(), "'", ""))); // Create the UDT type @@ -722,12 +783,13 @@ CASSANDRA_INTEGRATION_TYPED_TEST_P(CassandraTypesTest, UDT) { } // Register all parameterized test cases for primitives (excludes duration) -REGISTER_TYPED_TEST_CASE_P(CassandraTypesTest, Integration_Cassandra_Basic, - Integration_Cassandra_BasicByName, Integration_Cassandra_BasicNullValues, - Integration_Cassandra_BasicNullList, Integration_Cassandra_BasicNullMap, - Integration_Cassandra_BasicNullSet, Integration_Cassandra_List, - Integration_Cassandra_Set, Integration_Cassandra_Map, - Integration_Cassandra_Tuple, Integration_Cassandra_UDT); +REGISTER_TYPED_TEST_CASE_P(CassandraTypesTests, Integration_Cassandra_Basic, + Integration_Cassandra_ByName, Integration_Cassandra_NamedParameters, + Integration_Cassandra_NullValues, Integration_Cassandra_NullList, + Integration_Cassandra_NullMap, Integration_Cassandra_NullSet, + Integration_Cassandra_List, Integration_Cassandra_Set, + Integration_Cassandra_Map, Integration_Cassandra_Tuple, + Integration_Cassandra_UDT); /** * Attempt to utilize an invalid duration value on a statement @@ -742,7 +804,7 @@ REGISTER_TYPED_TEST_CASE_P(CassandraTypesTest, Integration_Cassandra_Basic, * @expected_result Statement request will execute and a server error will * occur. */ -CASSANDRA_INTEGRATION_TEST_F(CassandraTypesDurationTest, MixedValues) { +CASSANDRA_INTEGRATION_TEST_F(CassandraTypesDurationTests, MixedValues) { CHECK_FAILURE; CHECK_VALUE_TYPE_VERSION(Duration); @@ -766,7 +828,7 @@ typedef testing::Types CassandraTypes; -INSTANTIATE_TYPED_TEST_CASE_P(CassandraTypes, CassandraTypesTest, CassandraTypes); +INSTANTIATE_TYPED_TEST_CASE_P(CassandraTypes, CassandraTypesTests, CassandraTypes); /** * Values for ASCII tests @@ -774,8 +836,8 @@ INSTANTIATE_TYPED_TEST_CASE_P(CassandraTypes, CassandraTypesTest, CassandraTypes const Ascii ASCII_VALUES[] = { Ascii("DataStax"), Ascii("C/C++"), Ascii("Driver"), Ascii("Cassandra") }; template <> -const std::vector CassandraTypesTest::values_(ASCII_VALUES, - ASCII_VALUES + ARRAY_LEN(ASCII_VALUES)); +const std::vector + CassandraTypesTests::values_(ASCII_VALUES, ASCII_VALUES + ARRAY_LEN(ASCII_VALUES)); /** * Values for bigint tests @@ -783,9 +845,9 @@ const std::vector CassandraTypesTest::values_(ASCII_VALUES, const BigInteger BIGINT_VALUES[] = { BigInteger::max(), BigInteger::min(), BigInteger(static_cast(0)), BigInteger(37) }; template <> -const std::vector CassandraTypesTest::values_(BIGINT_VALUES, - BIGINT_VALUES + - ARRAY_LEN(BIGINT_VALUES)); +const std::vector + CassandraTypesTests::values_(BIGINT_VALUES, + BIGINT_VALUES + ARRAY_LEN(BIGINT_VALUES)); /** * Values for blob tests @@ -793,17 +855,17 @@ const std::vector CassandraTypesTest::values_(BIGINT_VAL const Blob BLOB_VALUES[] = { Blob("DataStax C/C++ Driver"), Blob("Cassandra"), Blob("DataStax Enterprise") }; template <> -const std::vector CassandraTypesTest::values_(BLOB_VALUES, - BLOB_VALUES + ARRAY_LEN(BLOB_VALUES)); +const std::vector CassandraTypesTests::values_(BLOB_VALUES, + BLOB_VALUES + ARRAY_LEN(BLOB_VALUES)); /** * Values for boolean tests */ const Boolean BOOLEAN_VALUES[] = { Boolean(true), Boolean(false) }; template <> -const std::vector CassandraTypesTest::values_(BOOLEAN_VALUES, - BOOLEAN_VALUES + - ARRAY_LEN(BOOLEAN_VALUES)); +const std::vector CassandraTypesTests::values_(BOOLEAN_VALUES, + BOOLEAN_VALUES + + ARRAY_LEN(BOOLEAN_VALUES)); /** * Values for date tests @@ -812,8 +874,8 @@ const Date DATE_VALUES[] = { Date::max(), // maximum for strftime Date::min(), // minimum for strftime Date(static_cast(0)), Date(12345u) }; template <> -const std::vector CassandraTypesTest::values_(DATE_VALUES, - DATE_VALUES + ARRAY_LEN(DATE_VALUES)); +const std::vector CassandraTypesTests::values_(DATE_VALUES, + DATE_VALUES + ARRAY_LEN(DATE_VALUES)); /** * Values for decimal tests @@ -828,9 +890,9 @@ const Decimal DECIMAL_VALUES[] = { Decimal("3." "6180339887498948482045868343656381177203091798057628621" "354486227052604628189024497072072041893911374") }; template <> -const std::vector CassandraTypesTest::values_(DECIMAL_VALUES, - DECIMAL_VALUES + - ARRAY_LEN(DECIMAL_VALUES)); +const std::vector CassandraTypesTests::values_(DECIMAL_VALUES, + DECIMAL_VALUES + + ARRAY_LEN(DECIMAL_VALUES)); /** * Values for double tests @@ -839,7 +901,7 @@ const Double DOUBLE_VALUES[] = { Double::max(), Double::min(), Double(3.14159265 Double(2.7182818284), Double(1.6180339887) }; template <> const std::vector - CassandraTypesTest::values_(DOUBLE_VALUES, DOUBLE_VALUES + ARRAY_LEN(DOUBLE_VALUES)); + CassandraTypesTests::values_(DOUBLE_VALUES, DOUBLE_VALUES + ARRAY_LEN(DOUBLE_VALUES)); /** * Values for duration tests @@ -854,9 +916,9 @@ const Duration DURATION_VALUES[] = { Duration(CassDuration(0, std::numeric_limits::min(), -1)) }; template <> -const std::vector CassandraTypesTest::values_(DURATION_VALUES, - DURATION_VALUES + - ARRAY_LEN(DURATION_VALUES)); +const std::vector CassandraTypesTests::values_(DURATION_VALUES, + DURATION_VALUES + + ARRAY_LEN(DURATION_VALUES)); /** * Values for float tests @@ -864,8 +926,8 @@ const std::vector CassandraTypesTest::values_(DURATION_VALUE const Float FLOAT_VALUES[] = { Float::max(), Float::min(), Float(3.14159f), Float(2.71828f), Float(1.61803f) }; template <> -const std::vector CassandraTypesTest::values_(FLOAT_VALUES, - FLOAT_VALUES + ARRAY_LEN(FLOAT_VALUES)); +const std::vector + CassandraTypesTests::values_(FLOAT_VALUES, FLOAT_VALUES + ARRAY_LEN(FLOAT_VALUES)); /** * Values for inet tests @@ -873,16 +935,16 @@ const std::vector CassandraTypesTest::values_(FLOAT_VALUES, const Inet INET_VALUES[] = { Inet::max(), Inet::min(), Inet("127.0.0.1"), Inet("0:0:0:0:0:0:0:1"), Inet("2001:db8:85a3:0:0:8a2e:370:7334") }; template <> -const std::vector CassandraTypesTest::values_(INET_VALUES, - INET_VALUES + ARRAY_LEN(INET_VALUES)); +const std::vector CassandraTypesTests::values_(INET_VALUES, + INET_VALUES + ARRAY_LEN(INET_VALUES)); /** * Values for int tests */ const Integer INT_VALUES[] = { Integer::max(), Integer::min(), Integer(0), Integer(148) }; template <> -const std::vector CassandraTypesTest::values_(INT_VALUES, - INT_VALUES + ARRAY_LEN(INT_VALUES)); +const std::vector + CassandraTypesTests::values_(INT_VALUES, INT_VALUES + ARRAY_LEN(INT_VALUES)); /** * Values for smallint tests @@ -891,8 +953,8 @@ const SmallInteger SMALLINT_VALUES[] = { SmallInteger::max(), SmallInteger::min( SmallInteger(static_cast(0)), SmallInteger(148) }; template <> const std::vector - CassandraTypesTest::values_(SMALLINT_VALUES, - SMALLINT_VALUES + ARRAY_LEN(SMALLINT_VALUES)); + CassandraTypesTests::values_(SMALLINT_VALUES, + SMALLINT_VALUES + ARRAY_LEN(SMALLINT_VALUES)); /** * Values for text tests @@ -900,8 +962,8 @@ const std::vector const Text TEXT_VALUES[] = { Text("The quick brown fox jumps over the lazy dog"), Text("Hello World"), Text("DataStax C/C++ Driver") }; template <> -const std::vector CassandraTypesTest::values_(TEXT_VALUES, - TEXT_VALUES + ARRAY_LEN(TEXT_VALUES)); +const std::vector CassandraTypesTests::values_(TEXT_VALUES, + TEXT_VALUES + ARRAY_LEN(TEXT_VALUES)); /** * Values for time tests @@ -909,8 +971,8 @@ const std::vector CassandraTypesTest::values_(TEXT_VALUES, const Time TIME_VALUES[] = { Time::max(), Time::min(), Time(static_cast(0)), Time(9876543210) }; template <> -const std::vector