blob: a6e02c967d0a6596ff9e4427b6f18d4c2c3ea7df [file]
/*
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 "event_loop_test.hpp"
#include "query_request.hpp"
#include "session.hpp"
#define KEYSPACE "datastax"
#define NUM_THREADS 2 // Number of threads to execute queries using a session
#define OUTAGE_PLAN_DELAY 250 // Reduced delay to incorporate larger outage plan
using namespace datastax::internal;
using namespace datastax::internal::core;
class SessionUnitTest : public EventLoopTest {
public:
SessionUnitTest()
: EventLoopTest("SessionUnitTest") {}
void populate_outage_plan(OutagePlan* outage_plan) {
// Multiple rolling restarts
for (int i = 1; i <= 9; ++i) {
int node = i % 3;
outage_plan->stop_node(node, OUTAGE_PLAN_DELAY);
outage_plan->start_node(node, OUTAGE_PLAN_DELAY);
}
// Add/Remove entries from the "system" tables
outage_plan->remove_node(2, OUTAGE_PLAN_DELAY);
outage_plan->stop_node(1, OUTAGE_PLAN_DELAY);
outage_plan->add_node(2, OUTAGE_PLAN_DELAY);
outage_plan->start_node(1, OUTAGE_PLAN_DELAY);
outage_plan->stop_node(3, OUTAGE_PLAN_DELAY);
outage_plan->stop_node(1, OUTAGE_PLAN_DELAY);
}
void query_on_threads(Session* session, bool is_chaotic = false) {
uv_thread_t threads[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; ++i) {
if (is_chaotic) {
ASSERT_EQ(0, uv_thread_create(&threads[i], query_is_chaotic, session));
} else {
ASSERT_EQ(0, uv_thread_create(&threads[i], query, session));
}
}
for (int i = 0; i < NUM_THREADS; ++i) {
uv_thread_join(&threads[i]);
}
}
static void connect(const Config& config, Session* session,
uint64_t wait_for_time_us = WAIT_FOR_TIME) {
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 connect(Session* session, SslContext* ssl_context = NULL,
uint64_t wait_for_time_us = WAIT_FOR_TIME, size_t num_nodes = 3) {
Config config;
config.set_constant_reconnect(100); // Faster reconnect time to handle cluster starts and stops
for (size_t i = 1; i <= num_nodes; ++i) {
OStringStream ss;
ss << "127.0.0." << i;
config.contact_points().push_back(Address(ss.str(), 9042));
}
if (ssl_context) {
config.set_ssl_context(ssl_context);
}
connect(config, session, wait_for_time_us);
}
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 query(Session* session, bool is_chaotic = false) {
QueryRequest::Ptr request(new QueryRequest("blah", 0));
request->set_is_idempotent(true);
Future::Ptr future = session->execute(Request::ConstPtr(request));
ASSERT_TRUE(future->wait_for(WAIT_FOR_TIME)) << "Timed out executing query";
if (future->error()) fprintf(stderr, "%s\n", cass_error_desc(future->error()->code));
if (is_chaotic) {
ASSERT_TRUE(future->error() == NULL ||
future->error()->code == CASS_ERROR_LIB_NO_HOSTS_AVAILABLE)
<< cass_error_desc(future->error()->code) << ": " << future->error()->message;
} else {
ASSERT_FALSE(future->error())
<< cass_error_desc(future->error()->code) << ": " << future->error()->message;
}
}
// uv_thread_create
static void query(void* arg) {
Session* session = static_cast<Session*>(arg);
query(session);
}
static void query_is_chaotic(void* arg) {
Session* session = static_cast<Session*>(arg);
query(session, true);
}
bool check_consistency(const Session& session, CassConsistency expected_consistency,
CassConsistency expected_profile_consistency) {
Config session_config = session.config();
EXPECT_EQ(expected_consistency, session_config.consistency());
const ExecutionProfile::Map& profiles = session_config.profiles();
for (ExecutionProfile::Map::const_iterator it = profiles.begin(), end = profiles.end();
it != end; ++it) {
if (expected_profile_consistency != it->second.consistency()) {
return false;
}
}
return true;
}
class HostEventFuture : public Future {
public:
typedef SharedRefPtr<HostEventFuture> Ptr;
enum Type { INVALID, START_NODE, STOP_NODE, ADD_NODE, REMOVE_NODE };
typedef std::pair<Type, Address> Event;
HostEventFuture()
: Future(Future::FUTURE_TYPE_GENERIC) {}
Type type() { return event_.first; }
void set_event(Type type, const Address& host) {
ScopedMutex lock(&mutex_);
if (!is_set()) {
event_ = Event(type, host);
internal_set(lock);
}
}
Event wait_for_event(uint64_t timeout_us) {
ScopedMutex lock(&mutex_);
return internal_wait_for(lock, timeout_us) ? event_ : Event(INVALID, Address());
}
private:
Event event_;
};
class TestHostListener : public DefaultHostListener {
public:
typedef SharedRefPtr<TestHostListener> Ptr;
TestHostListener() {
events_.push_back(HostEventFuture::Ptr(new HostEventFuture()));
uv_mutex_init(&mutex_);
}
~TestHostListener() { uv_mutex_destroy(&mutex_); }
virtual void on_host_up(const Host::Ptr& host) { push_back(HostEventFuture::START_NODE, host); }
virtual void on_host_down(const Host::Ptr& host) {
push_back(HostEventFuture::STOP_NODE, host);
}
virtual void on_host_added(const Host::Ptr& host) {
push_back(HostEventFuture::ADD_NODE, host);
}
virtual void on_host_removed(const Host::Ptr& host) {
push_back(HostEventFuture::REMOVE_NODE, host);
}
HostEventFuture::Event wait_for_event(uint64_t timeout_us) {
HostEventFuture::Event event(front()->wait_for_event(timeout_us));
if (event.first != HostEventFuture::INVALID) pop_front();
return event;
}
size_t event_count() {
ScopedMutex lock(&mutex_);
size_t count = events_.size();
return events_.front()->ready() ? count : count - 1;
}
private:
typedef Deque<HostEventFuture::Ptr> EventQueue;
HostEventFuture::Ptr front() {
ScopedMutex lock(&mutex_);
return events_.front();
}
void pop_front() {
ScopedMutex lock(&mutex_);
events_.pop_front();
}
void push_back(HostEventFuture::Type type, const Host::Ptr& host) {
ScopedMutex lock(&mutex_);
events_.back()->set_event(type, host->address());
events_.push_back(HostEventFuture::Ptr(new HostEventFuture()));
}
private:
uv_mutex_t mutex_;
EventQueue events_;
};
class LocalDcClusterMetadataResolver : public ClusterMetadataResolver {
public:
LocalDcClusterMetadataResolver(const String& local_dc)
: desired_local_dc_(local_dc) {}
private:
virtual void internal_resolve(uv_loop_t* loop, const AddressVec& contact_points) {
resolved_contact_points_ = contact_points;
local_dc_ = desired_local_dc_;
callback_(this);
}
virtual void internal_cancel() {}
private:
String desired_local_dc_;
};
class LocalDcClusterMetadataResolverFactory : public ClusterMetadataResolverFactory {
public:
LocalDcClusterMetadataResolverFactory(const String& local_dc)
: local_dc_(local_dc) {}
virtual ClusterMetadataResolver::Ptr new_instance(const ClusterSettings& settings) const {
return ClusterMetadataResolver::Ptr(new LocalDcClusterMetadataResolver(local_dc_));
}
virtual const char* name() const { return "LocalDc"; }
private:
String local_dc_;
};
class SupportedDbaasOptions : public mockssandra::Action {
public:
virtual void on_run(mockssandra::Request* request) const {
Vector<String> product_type;
product_type.push_back("DATASTAX_APOLLO");
StringMultimap supported;
supported["PRODUCT_TYPE"] = product_type;
String body;
mockssandra::encode_string_map(supported, &body);
request->write(mockssandra::OPCODE_SUPPORTED, body);
}
};
};
TEST_F(SessionUnitTest, ExecuteQueryNotConnected) {
Session session;
Future::Ptr future = session.execute(Request::ConstPtr(new QueryRequest("blah", 0)));
ASSERT_EQ(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE, future->error()->code);
}
TEST_F(SessionUnitTest, InvalidKeyspace) {
mockssandra::SimpleRequestHandlerBuilder builder;
builder.on(mockssandra::OPCODE_QUERY)
.system_local()
.system_peers()
.use_keyspace("blah")
.empty_rows_result(1);
mockssandra::SimpleCluster cluster(builder.build());
ASSERT_EQ(cluster.start_all(), 0);
Config config;
config.contact_points().push_back(Address("127.0.0.1", 9042));
Session session;
Future::Ptr connect_future(session.connect(config, "invalid"));
ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
ASSERT_EQ(CASS_ERROR_LIB_UNABLE_TO_SET_KEYSPACE, connect_future->error()->code);
ASSERT_TRUE(session.close()->wait_for(WAIT_FOR_TIME));
}
TEST_F(SessionUnitTest, InvalidDataCenter) {
mockssandra::SimpleCluster cluster(simple());
ASSERT_EQ(cluster.start_all(), 0);
Config config;
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_load_balancing_policy(new DCAwarePolicy("invalid_data_center", 0, false));
Session session;
Future::Ptr connect_future(session.connect(config));
ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
ASSERT_EQ(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE, connect_future->error()->code);
ASSERT_TRUE(session.close()->wait_for(WAIT_FOR_TIME));
}
TEST_F(SessionUnitTest, InvalidLocalAddress) {
mockssandra::SimpleCluster cluster(simple());
ASSERT_EQ(cluster.start_all(), 0);
Config config;
config.set_local_address(Address("1.1.1.1", PORT)); // Invalid
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_load_balancing_policy(new DCAwarePolicy("invalid_data_center", 0, false));
Session session;
Future::Ptr connect_future(session.connect(config, "invalid"));
ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME));
ASSERT_EQ(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE, connect_future->error()->code);
ASSERT_TRUE(session.close()->wait_for(WAIT_FOR_TIME));
}
TEST_F(SessionUnitTest, ExecuteQueryReusingSession) {
mockssandra::SimpleCluster cluster(simple());
ASSERT_EQ(cluster.start_all(), 0);
Session session;
for (int i = 0; i < 2; ++i) {
connect(&session);
query(&session);
close(&session);
}
}
TEST_F(SessionUnitTest, ExecuteQueryReusingSessionUsingSsl) {
mockssandra::SimpleCluster cluster(simple());
SslContext::Ptr ssl_context = use_ssl(&cluster).socket_settings.ssl_context;
ASSERT_EQ(cluster.start_all(), 0);
Session session;
for (int i = 0; i < 2; ++i) {
connect(&session, ssl_context.get());
query(&session);
close(&session);
}
}
TEST_F(SessionUnitTest, ExecuteQueryReusingSessionChaotic) {
mockssandra::SimpleCluster cluster(simple(), 4);
ASSERT_EQ(cluster.start_all(), 0);
OutagePlan outage_plan(loop(), &cluster);
populate_outage_plan(&outage_plan);
Session session;
Future::Ptr outage_future = execute_outage_plan(&outage_plan);
while (!outage_future->wait_for(1000)) { // 1 millisecond wait
connect(&session, NULL, WAIT_FOR_TIME * 3, 4);
query(&session, true);
close(&session, WAIT_FOR_TIME * 3);
}
}
TEST_F(SessionUnitTest, ExecuteQueryReusingSessionUsingSslChaotic) {
mockssandra::SimpleCluster cluster(simple(), 4);
SslContext::Ptr ssl_context = use_ssl(&cluster).socket_settings.ssl_context;
ASSERT_EQ(cluster.start_all(), 0);
OutagePlan outage_plan(loop(), &cluster);
populate_outage_plan(&outage_plan);
Session session;
Future::Ptr outage_future = execute_outage_plan(&outage_plan);
while (!outage_future->wait_for(1000)) { // 1 millisecond wait
connect(&session, ssl_context.get(), WAIT_FOR_TIME * 3, 4);
query(&session, true);
close(&session, WAIT_FOR_TIME * 3);
}
}
TEST_F(SessionUnitTest, ExecuteQueryWithCompleteOutage) {
mockssandra::SimpleCluster cluster(simple(), 3);
ASSERT_EQ(cluster.start_all(), 0);
Session session;
connect(&session);
// Full outage
cluster.stop_all();
Future::Ptr future = session.execute(Request::ConstPtr(new QueryRequest("blah", 0)));
ASSERT_TRUE(future->wait_for(WAIT_FOR_TIME));
ASSERT_TRUE(future->error());
EXPECT_TRUE(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE == future->error()->code ||
CASS_ERROR_LIB_REQUEST_TIMED_OUT == future->error()->code);
// Restart a node and execute query to ensure session recovers
ASSERT_EQ(cluster.start(2), 0);
test::Utils::msleep(200); // Give time for the reconnect to start
query(&session);
close(&session);
}
TEST_F(SessionUnitTest, ExecuteQueryWithCompleteOutageSpinDown) {
mockssandra::SimpleCluster cluster(simple(), 3);
ASSERT_EQ(cluster.start_all(), 0);
Session session;
connect(&session);
// Spin down nodes while querying
query(&session);
cluster.stop(3);
query(&session);
cluster.stop(1);
query(&session);
cluster.stop(2);
// Full outage
Future::Ptr future = session.execute(Request::ConstPtr(new QueryRequest("blah", 0)));
ASSERT_TRUE(future->wait_for(WAIT_FOR_TIME));
EXPECT_TRUE(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE == future->error()->code ||
CASS_ERROR_LIB_REQUEST_TIMED_OUT == future->error()->code);
// Restart a node and execute query to ensure session recovers
ASSERT_EQ(cluster.start(2), 0);
test::Utils::msleep(200); // Give time for the reconnect to start
query(&session);
close(&session);
}
TEST_F(SessionUnitTest, ExecuteQueryWithThreads) {
mockssandra::SimpleCluster cluster(simple());
ASSERT_EQ(cluster.start_all(), 0);
Session session;
connect(&session);
query_on_threads(&session);
close(&session);
}
TEST_F(SessionUnitTest, ExecuteQueryWithThreadsUsingSsl) {
mockssandra::SimpleCluster cluster(simple());
SslContext::Ptr ssl_context = use_ssl(&cluster).socket_settings.ssl_context;
ASSERT_EQ(cluster.start_all(), 0);
Session session;
connect(&session, ssl_context.get());
query_on_threads(&session);
close(&session);
}
TEST_F(SessionUnitTest, ExecuteQueryWithThreadsChaotic) {
mockssandra::SimpleCluster cluster(simple(), 4);
ASSERT_EQ(cluster.start_all(), 0);
Session session;
connect(&session);
OutagePlan outage_plan(loop(), &cluster);
populate_outage_plan(&outage_plan);
Future::Ptr outage_future = execute_outage_plan(&outage_plan);
while (!outage_future->wait_for(1000)) { // 1 millisecond wait
query_on_threads(&session, true);
}
close(&session);
}
TEST_F(SessionUnitTest, ExecuteQueryWithThreadsUsingSslChaotic) {
mockssandra::SimpleCluster cluster(simple(), 4);
SslContext::Ptr ssl_context = use_ssl(&cluster).socket_settings.ssl_context;
ASSERT_EQ(cluster.start_all(), 0);
Session session;
connect(&session, ssl_context.get());
OutagePlan outage_plan(loop(), &cluster);
populate_outage_plan(&outage_plan);
Future::Ptr outage_future = execute_outage_plan(&outage_plan);
while (!outage_future->wait_for(1000)) { // 1 millisecond wait
query_on_threads(&session, true);
}
close(&session);
}
TEST_F(SessionUnitTest, HostListener) {
mockssandra::SimpleCluster cluster(simple(), 2);
ASSERT_EQ(cluster.start_all(), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.set_constant_reconnect(100); // Reconnect immediately
config.contact_points().push_back(Address("127.0.0.2", 9042));
config.set_host_listener(listener);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{
cluster.remove(1);
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::STOP_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::REMOVE_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{
cluster.add(1);
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{
cluster.stop(2);
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::STOP_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{
cluster.start(2);
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
TEST_F(SessionUnitTest, HostListenerDCAwareLocal) {
mockssandra::SimpleCluster cluster(simple(), 2, 1);
ASSERT_EQ(cluster.start_all(), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.set_constant_reconnect(100); // Reconnect immediately
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_host_listener(listener);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{ // Node 3 is DC2 should be ignored
cluster.stop(3);
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::INVALID, Address()),
listener->wait_for_event(WAIT_FOR_TIME));
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
// TODO: Remove HostListenerDCAwareRemote after remote DC settings are removed from API
TEST_F(SessionUnitTest, HostListenerDCAwareRemote) {
mockssandra::SimpleCluster cluster(simple(), 2, 1);
ASSERT_EQ(cluster.start_all(), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.set_constant_reconnect(100); // Reconnect immediately
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_load_balancing_policy(new DCAwarePolicy("dc1", 1, false));
config.set_host_listener(listener);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.3", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.3", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{
cluster.stop(3);
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::STOP_NODE, Address("127.0.0.3", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
TEST_F(SessionUnitTest, HostListenerNodeDown) {
mockssandra::SimpleCluster cluster(simple(), 3);
ASSERT_EQ(cluster.start(1), 0);
ASSERT_EQ(cluster.start(3), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.set_constant_reconnect(100); // Reconnect immediately
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_host_listener(listener);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.3", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.3", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{ // Node 2 connection should not be established (node down event)
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::STOP_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
{
cluster.start(2);
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
TEST_F(SessionUnitTest, LocalDcUpdatedOnPolicy) {
mockssandra::SimpleCluster cluster(simple(), 3, 1);
ASSERT_EQ(cluster.start_all(), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.contact_points().push_back(Address("127.0.0.4", 9042));
config.set_cluster_metadata_resolver_factory(
ClusterMetadataResolverFactory::Ptr(new LocalDcClusterMetadataResolverFactory("dc2")));
config.set_host_listener(listener);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table (should skip DC1)
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.4", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.4", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
for (int i = 0; i < 20; ++i) { // Validate the request processors are using DC2 only
ResponseFuture::Ptr future = session.execute(Request::ConstPtr(new QueryRequest("blah", 0)));
EXPECT_TRUE(future->wait_for(WAIT_FOR_TIME));
EXPECT_FALSE(future->error());
EXPECT_EQ("127.0.0.4", future->address().to_string());
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
TEST_F(SessionUnitTest, LocalDcNotOverriddenOnPolicy) {
mockssandra::SimpleCluster cluster(simple(), 1, 3);
ASSERT_EQ(cluster.start_all(), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_load_balancing_policy(new DCAwarePolicy("dc1"));
config.set_cluster_metadata_resolver_factory(
ClusterMetadataResolverFactory::Ptr(new LocalDcClusterMetadataResolverFactory("dc2")));
config.set_host_listener(listener);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table (should be DC1)
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
for (int i = 0; i < 20; ++i) { // Validate the request processors are using DC1 only
ResponseFuture::Ptr future = session.execute(Request::ConstPtr(new QueryRequest("blah", 0)));
EXPECT_TRUE(future->wait_for(WAIT_FOR_TIME));
EXPECT_FALSE(future->error());
EXPECT_EQ("127.0.0.1", future->address().to_string());
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
TEST_F(SessionUnitTest, LocalDcOverriddenOnPolicyUsingExecutionProfiles) {
mockssandra::SimpleCluster cluster(simple(), 3, 1);
ASSERT_EQ(cluster.start_all(), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.contact_points().push_back(Address("127.0.0.4", 9042));
config.set_use_randomized_contact_points(
false); // Ensure round robin order over DC for query execution
config.set_cluster_metadata_resolver_factory(
ClusterMetadataResolverFactory::Ptr(new LocalDcClusterMetadataResolverFactory("dc2")));
config.set_host_listener(listener);
ExecutionProfile profile;
profile.set_load_balancing_policy(new DCAwarePolicy());
config.set_execution_profile("use_propagated_local_dc", &profile);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table (should be DC2)
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.4", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.4", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
for (int i = 0; i < 20; ++i) { // Validate the default profile is using DC2 only
ResponseFuture::Ptr future = session.execute(Request::ConstPtr(new QueryRequest("blah", 0)));
EXPECT_TRUE(future->wait_for(WAIT_FOR_TIME));
EXPECT_FALSE(future->error());
EXPECT_EQ("127.0.0.4", future->address().to_string());
}
for (int i = 0; i < 20; ++i) { // Validate the default profile is using DC2 only
QueryRequest::Ptr request(new QueryRequest("blah", 0));
request->set_execution_profile_name("use_propagated_local_dc");
ResponseFuture::Ptr future = session.execute(Request::ConstPtr(request));
EXPECT_TRUE(future->wait_for(WAIT_FOR_TIME));
EXPECT_FALSE(future->error());
EXPECT_EQ("127.0.0.4", future->address().to_string());
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
TEST_F(SessionUnitTest, LocalDcNotOverriddenOnPolicyUsingExecutionProfiles) {
mockssandra::SimpleCluster cluster(simple(), 3, 1);
ASSERT_EQ(cluster.start_all(), 0);
TestHostListener::Ptr listener(new TestHostListener());
Config config;
config.contact_points().push_back(Address("127.0.0.4", 9042));
config.set_use_randomized_contact_points(
false); // Ensure round robin order over DC for query execution
config.set_cluster_metadata_resolver_factory(
ClusterMetadataResolverFactory::Ptr(new LocalDcClusterMetadataResolverFactory("dc2")));
config.set_host_listener(listener);
ExecutionProfile profile;
profile.set_load_balancing_policy(new DCAwarePolicy("dc1"));
config.set_execution_profile("use_dc1", &profile);
Session session;
connect(config, &session);
{ // Initial nodes available from peers table (should be DC1 and DC2)
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.1", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.2", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.3", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.3", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::ADD_NODE, Address("127.0.0.4", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
EXPECT_EQ(HostEventFuture::Event(HostEventFuture::START_NODE, Address("127.0.0.4", 9042)),
listener->wait_for_event(WAIT_FOR_TIME));
}
for (int i = 0; i < 20; ++i) { // Validate the default profile is using DC2 only
ResponseFuture::Ptr future = session.execute(Request::ConstPtr(new QueryRequest("blah", 0)));
EXPECT_TRUE(future->wait_for(WAIT_FOR_TIME));
EXPECT_FALSE(future->error());
EXPECT_EQ("127.0.0.4", future->address().to_string());
}
for (int i = 0; i < 20; ++i) { // Validate the default profile is using DC1 only
QueryRequest::Ptr request(new QueryRequest("blah", 0));
request->set_execution_profile_name("use_dc1");
ResponseFuture::Ptr future = session.execute(Request::ConstPtr(request));
EXPECT_TRUE(future->wait_for(WAIT_FOR_TIME));
EXPECT_FALSE(future->error());
EXPECT_NE("127.0.0.4", future->address().to_string());
}
close(&session);
ASSERT_EQ(0u, listener->event_count());
}
TEST_F(SessionUnitTest, NoContactPoints) {
// No cluster needed
Config config;
config.contact_points().clear();
Session session;
Future::Ptr connect_future(session.connect(config));
ASSERT_TRUE(connect_future->wait_for(WAIT_FOR_TIME))
<< "Timed out waiting for session to connect";
ASSERT_TRUE(connect_future->error());
EXPECT_EQ(connect_future->error()->code, CASS_ERROR_LIB_NO_HOSTS_AVAILABLE);
}
TEST_F(SessionUnitTest, DefaultConsistency) {
mockssandra::SimpleCluster cluster(simple());
ASSERT_EQ(cluster.start_all(), 0);
Session session;
{
Config session_config = session.config();
EXPECT_EQ(CASS_CONSISTENCY_UNKNOWN, session_config.consistency());
}
ExecutionProfile profile;
Config config;
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_execution_profile("profile", &profile);
connect(config, &session);
EXPECT_TRUE(check_consistency(session, CASS_DEFAULT_CONSISTENCY, CASS_DEFAULT_CONSISTENCY));
close(&session);
}
TEST_F(SessionUnitTest, DefaultConsistencyExecutionProfileNotUpdated) {
mockssandra::SimpleCluster cluster(simple());
ASSERT_EQ(cluster.start_all(), 0);
Session session;
{
Config session_config = session.config();
EXPECT_EQ(CASS_CONSISTENCY_UNKNOWN, session_config.consistency());
}
ExecutionProfile profile;
profile.set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM);
Config config;
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_execution_profile("profile", &profile);
connect(config, &session);
EXPECT_TRUE(check_consistency(session, CASS_DEFAULT_CONSISTENCY, CASS_CONSISTENCY_LOCAL_QUORUM));
close(&session);
}
TEST_F(SessionUnitTest, RemoteDCNodeRecovery) {
mockssandra::SimpleCluster cluster(simple(), 1, 1); // 1 local DC node and 1 remote DC node
ASSERT_EQ(cluster.start_all(), 0);
ExecutionProfile profile;
Config config;
config.set_constant_reconnect(100); // Faster reconnect time to handle node outages
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_load_balancing_policy(new DCAwarePolicy("dc1", 1));
Session session;
connect(config, &session);
cluster.stop(1); // Force using the remote node
cluster.stop(2); // Force the remote node down and up
cluster.start(2);
bool remote_dc_node_recovered = false;
// Wait for the remote DC node to become available
for (int i = 0; i < 20; ++i) { // Around 2 seconds
QueryRequest::Ptr request(new QueryRequest("blah", 0));
request->set_consistency(CASS_CONSISTENCY_ONE); // Don't use a LOCAL consistency
request->set_record_attempted_addresses(true);
ResponseFuture::Ptr future = session.execute(Request::ConstPtr(request));
EXPECT_TRUE(future->wait_for(WAIT_FOR_TIME));
if (!future->error() && !future->attempted_addresses().empty() &&
Address("127.0.0.2", 9042) == future->attempted_addresses()[0]) {
remote_dc_node_recovered = true;
break;
}
test::Utils::msleep(100);
}
EXPECT_TRUE(remote_dc_node_recovered);
close(&session);
}
TEST_F(SessionUnitTest, DbaasDetectionUpdateDefaultConsistency) {
mockssandra::SimpleRequestHandlerBuilder builder;
builder.on(mockssandra::OPCODE_OPTIONS).execute(new SupportedDbaasOptions());
mockssandra::SimpleCluster cluster(builder.build());
ASSERT_EQ(cluster.start_all(), 0);
Session session;
{
Config session_config = session.config();
EXPECT_EQ(CASS_CONSISTENCY_UNKNOWN, session_config.consistency());
}
ExecutionProfile profile;
Config config;
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_execution_profile("profile", &profile);
connect(config, &session);
EXPECT_TRUE(
check_consistency(session, CASS_DEFAULT_DBAAS_CONSISTENCY, CASS_DEFAULT_DBAAS_CONSISTENCY));
close(&session);
}
TEST_F(SessionUnitTest, DbaasDefaultConsistencyExecutionProfileNotUpdate) {
mockssandra::SimpleRequestHandlerBuilder builder;
builder.on(mockssandra::OPCODE_OPTIONS).execute(new SupportedDbaasOptions());
mockssandra::SimpleCluster cluster(builder.build());
ASSERT_EQ(cluster.start_all(), 0);
Session session;
{
Config session_config = session.config();
EXPECT_EQ(CASS_CONSISTENCY_UNKNOWN, session_config.consistency());
}
ExecutionProfile profile;
profile.set_consistency(CASS_CONSISTENCY_LOCAL_ONE);
Config config;
config.contact_points().push_back(Address("127.0.0.1", 9042));
config.set_execution_profile("profile", &profile);
connect(config, &session);
EXPECT_TRUE(
check_consistency(session, CASS_DEFAULT_DBAAS_CONSISTENCY, CASS_CONSISTENCY_LOCAL_ONE));
close(&session);
}