blob: 4d3b14287b81dcd2ca74c32d82aeceaa2e83f047 [file]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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 <gtest/gtest.h>
#include "address.hpp"
#include "blacklist_dc_policy.hpp"
#include "blacklist_policy.hpp"
#include "constants.hpp"
#include "dc_aware_policy.hpp"
#include "event_loop.hpp"
#include "latency_aware_policy.hpp"
#include "murmur3.hpp"
#include "query_request.hpp"
#include "random.hpp"
#include "request_handler.hpp"
#include "scoped_ptr.hpp"
#include "string.hpp"
#include "token_aware_policy.hpp"
#include "whitelist_dc_policy.hpp"
#include "whitelist_policy.hpp"
#include "test_token_map_utils.hpp"
#include "test_utils.hpp"
#include <uv.h>
using namespace datastax;
using namespace datastax::internal;
using namespace datastax::internal::core;
const String LOCAL_DC = "local";
const String REMOTE_DC = "remote";
const String BACKUP_DC = "backup";
#define VECTOR_FROM(t, a) Vector<t>(a, a + sizeof(a) / sizeof(a[0]))
Address addr_for_sequence(size_t i) {
char temp[64];
sprintf(temp, "%d.%d.%d.%d", static_cast<int>(i & 0xFF), static_cast<int>((i >> 8) & 0xFF),
static_cast<int>((i >> 16) & 0xFF), static_cast<int>((i >> 24) & 0xFF));
return Address(temp, 9042);
}
SharedRefPtr<Host> host_for_addr(const Address addr, const String& rack = "rack",
const String& dc = "dc") {
SharedRefPtr<Host> host(new Host(addr));
host->set_rack_and_dc(rack, dc);
return host;
}
void populate_hosts(size_t count, const String& rack, const String& dc, HostMap* hosts) {
Address addr;
size_t first = hosts->size() + 1;
for (size_t i = first; i < first + count; ++i) {
addr = addr_for_sequence(i);
(*hosts)[addr] = host_for_addr(addr, rack, dc);
}
}
void verify_sequence(QueryPlan* qp, const Vector<size_t>& sequence) {
Address received;
for (Vector<size_t>::const_iterator it = sequence.begin(); it != sequence.end(); ++it) {
ASSERT_TRUE(qp->compute_next(&received));
EXPECT_EQ(addr_for_sequence(*it), received);
}
EXPECT_FALSE(qp->compute_next(&received));
}
typedef Map<Address, int> QueryCounts;
QueryCounts run_policy(LoadBalancingPolicy& policy, int count) {
QueryCounts counts;
for (int i = 0; i < 12; ++i) {
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
Host::Ptr host(qp->compute_next());
if (host) {
counts[host->address()] += 1;
}
}
return counts;
}
void verify_dcs(const QueryCounts& counts, const HostMap& hosts, const String& expected_dc) {
for (QueryCounts::const_iterator it = counts.begin(), end = counts.end(); it != end; ++it) {
HostMap::const_iterator host_it = hosts.find(it->first);
ASSERT_NE(host_it, hosts.end());
EXPECT_EQ(expected_dc, host_it->second->dc());
}
}
void verify_query_counts(const QueryCounts& counts, int expected_count) {
for (QueryCounts::const_iterator it = counts.begin(), end = counts.end(); it != end; ++it) {
EXPECT_EQ(expected_count, it->second);
}
}
struct RunPeriodicTask : public EventLoop {
RunPeriodicTask(LatencyAwarePolicy* policy)
: policy(policy) {
async.data = this;
}
int init() {
int rc = EventLoop::init();
if (rc != 0) return rc;
rc = uv_async_init(loop(), &async, on_async);
if (rc != 0) return rc;
policy->register_handles(loop());
return rc;
}
void done() { uv_async_send(&async); }
static void on_async(uv_async_t* handle) {
RunPeriodicTask* task = static_cast<RunPeriodicTask*>(handle->data);
task->close_handles();
task->policy->close_handles();
uv_close(reinterpret_cast<uv_handle_t*>(&task->async), NULL);
}
uv_async_t async;
LatencyAwarePolicy* policy;
};
// Latency-aware utility functions
// Don't make "time_between_ns" too high because it spin waits
uint64_t calculate_moving_average(uint64_t first_latency_ns, uint64_t second_latency_ns,
uint64_t time_between_ns) {
const uint64_t scale = 100LL;
const uint64_t min_measured = 15LL;
const uint64_t threshold_to_account = (30LL * min_measured) / 100LL;
Host host(Address("0.0.0.0", 9042));
host.enable_latency_tracking(scale, min_measured);
for (uint64_t i = 0; i < threshold_to_account; ++i) {
host.update_latency(0); // This can be anything because it's not recorded
}
host.update_latency(first_latency_ns);
// Spin wait
uint64_t start = uv_hrtime();
while (uv_hrtime() - start < time_between_ns) {
}
host.update_latency(second_latency_ns);
TimestampedAverage current = host.get_current_average();
return current.average;
}
void test_dc_aware_policy(size_t local_count, size_t remote_count) {
HostMap hosts;
populate_hosts(local_count, "rack", LOCAL_DC, &hosts);
populate_hosts(remote_count, "rack", REMOTE_DC, &hosts);
DCAwarePolicy policy(LOCAL_DC, remote_count, false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
const size_t total_hosts = local_count + remote_count;
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
Vector<size_t> seq(total_hosts);
for (size_t i = 0; i < total_hosts; ++i)
seq[i] = i + 1;
verify_sequence(qp.get(), seq);
}
TEST(RoundRobinLoadBalancingUnitTest, Simple) {
HostMap hosts;
populate_hosts(2, "rack", "dc", &hosts);
RoundRobinPolicy policy;
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
// start on first elem
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
const size_t seq1[] = { 1, 2 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
// rotate starting element
ScopedPtr<QueryPlan> qp2(policy.new_query_plan("ks", NULL, NULL));
const size_t seq2[] = { 2, 1 };
verify_sequence(qp2.get(), VECTOR_FROM(size_t, seq2));
// back around
ScopedPtr<QueryPlan> qp3(policy.new_query_plan("ks", NULL, NULL));
verify_sequence(qp3.get(), VECTOR_FROM(size_t, seq1));
}
TEST(RoundRobinLoadBalancingUnitTest, OnAdd) {
HostMap hosts;
populate_hosts(2, "rack", "dc", &hosts);
RoundRobinPolicy policy;
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
// baseline
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
const size_t seq1[] = { 1, 2 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
const size_t seq_new = 5;
Address addr_new = addr_for_sequence(seq_new);
SharedRefPtr<Host> host = host_for_addr(addr_new);
policy.on_host_added(host);
policy.on_host_up(host);
ScopedPtr<QueryPlan> qp2(policy.new_query_plan("ks", NULL, NULL));
const size_t seq2[] = { 2, seq_new, 1 };
verify_sequence(qp2.get(), VECTOR_FROM(size_t, seq2));
}
TEST(RoundRobinLoadBalancingUnitTest, OnRemove) {
HostMap hosts;
populate_hosts(3, "rack", "dc", &hosts);
RoundRobinPolicy policy;
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
SharedRefPtr<Host> host = hosts.begin()->second;
policy.on_host_removed(host);
ScopedPtr<QueryPlan> qp2(policy.new_query_plan("ks", NULL, NULL));
// Both should not have the removed host
const size_t seq1[] = { 2, 3 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
const size_t seq2[] = { 3, 2 };
verify_sequence(qp2.get(), VECTOR_FROM(size_t, seq2));
}
TEST(RoundRobinLoadBalancingUnitTest, OnUpAndDown) {
HostMap hosts;
populate_hosts(3, "rack", "dc", &hosts);
RoundRobinPolicy policy;
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp_before1(policy.new_query_plan("ks", NULL, NULL));
ScopedPtr<QueryPlan> qp_before2(policy.new_query_plan("ks", NULL, NULL));
SharedRefPtr<Host> host = hosts.begin()->second;
// 'before' qp both have the down host
// Ahead of set_down, it will be returned
{
const size_t seq[] = { 1, 2, 3 };
verify_sequence(qp_before1.get(), VECTOR_FROM(size_t, seq));
}
policy.on_host_down(host->address());
// Following set_down, it is dynamically excluded
{
const size_t seq[] = { 2, 3 };
verify_sequence(qp_before2.get(), VECTOR_FROM(size_t, seq));
}
// host is added to the list, but not 'up'
policy.on_host_up(host);
ScopedPtr<QueryPlan> qp_after1(policy.new_query_plan("ks", NULL, NULL));
ScopedPtr<QueryPlan> qp_after2(policy.new_query_plan("ks", NULL, NULL));
policy.on_host_down(host->address());
// 1 is dynamically excluded from plan
{
const size_t seq[] = { 2, 3 };
verify_sequence(qp_after1.get(), VECTOR_FROM(size_t, seq));
}
policy.on_host_up(host);
// now included
{
const size_t seq[] = { 2, 3, 1 };
verify_sequence(qp_after2.get(), VECTOR_FROM(size_t, seq));
}
}
TEST(RoundRobinLoadBalancingUnitTest, VerifyEqualDistribution) {
HostMap hosts;
populate_hosts(3, "rack", "dc", &hosts);
RoundRobinPolicy policy;
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
{ // All nodes
QueryCounts counts(run_policy(policy, 12));
ASSERT_EQ(counts.size(), 3u);
verify_query_counts(counts, 4);
}
policy.on_host_down(hosts.begin()->first);
{ // One node down
QueryCounts counts(run_policy(policy, 12));
ASSERT_EQ(counts.size(), 2u);
verify_query_counts(counts, 6);
}
policy.on_host_up(hosts.begin()->second);
{ // All nodes again
QueryCounts counts(run_policy(policy, 12));
ASSERT_EQ(counts.size(), 3u);
verify_query_counts(counts, 4);
}
policy.on_host_removed(hosts.begin()->second);
{ // One node removed
QueryCounts counts(run_policy(policy, 12));
ASSERT_EQ(counts.size(), 2u);
verify_query_counts(counts, 6);
}
}
TEST(DatacenterAwareLoadBalancingUnitTest, SomeDatacenterLocalUnspecified) {
const size_t total_hosts = 3;
HostMap hosts;
populate_hosts(total_hosts, "rack", LOCAL_DC, &hosts);
Host* h = hosts.begin()->second.get();
h->set_rack_and_dc("", "");
DCAwarePolicy policy(LOCAL_DC, 1, false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
const size_t seq[] = { 2, 3, 1 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
TEST(DatacenterAwareLoadBalancingUnitTest, SingleLocalDown) {
HostMap hosts;
populate_hosts(3, "rack", LOCAL_DC, &hosts);
SharedRefPtr<Host> target_host = hosts.begin()->second;
populate_hosts(1, "rack", REMOTE_DC, &hosts);
DCAwarePolicy policy(LOCAL_DC, 1, false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp_before(
policy.new_query_plan("ks", NULL, NULL)); // has down host ptr in plan
ScopedPtr<QueryPlan> qp_after(
policy.new_query_plan("ks", NULL, NULL)); // should not have down host ptr in plan
policy.on_host_down(target_host->address());
{
const size_t seq[] = { 2, 3, 4 };
verify_sequence(qp_before.get(), VECTOR_FROM(size_t, seq));
}
policy.on_host_up(target_host);
{
const size_t seq[] = { 2, 3, 1, 4 }; // local dc wrapped before remote offered
verify_sequence(qp_after.get(), VECTOR_FROM(size_t, seq));
}
}
TEST(DatacenterAwareLoadBalancingUnitTest, AllLocalRemovedReturned) {
HostMap hosts;
populate_hosts(1, "rack", LOCAL_DC, &hosts);
SharedRefPtr<Host> target_host = hosts.begin()->second;
populate_hosts(1, "rack", REMOTE_DC, &hosts);
DCAwarePolicy policy(LOCAL_DC, 1, false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp_before(
policy.new_query_plan("ks", NULL, NULL)); // has down host ptr in plan
policy.on_host_down(target_host->address());
ScopedPtr<QueryPlan> qp_after(
policy.new_query_plan("ks", NULL, NULL)); // should not have down host ptr in plan
{
const size_t seq[] = { 2 };
verify_sequence(qp_before.get(), VECTOR_FROM(size_t, seq));
verify_sequence(qp_after.get(), VECTOR_FROM(size_t, seq));
}
policy.on_host_up(target_host);
// make sure we get the local node first after on_up
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
{
const size_t seq[] = { 1, 2 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
}
TEST(DatacenterAwareLoadBalancingUnitTest, RemoteRemovedReturned) {
HostMap hosts;
populate_hosts(1, "rack", LOCAL_DC, &hosts);
populate_hosts(1, "rack", REMOTE_DC, &hosts);
Address target_addr("2.0.0.0", 9042);
SharedRefPtr<Host> target_host = hosts[target_addr];
DCAwarePolicy policy(LOCAL_DC, 1, false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp_before(
policy.new_query_plan("ks", NULL, NULL)); // has down host ptr in plan
policy.on_host_down(target_host->address());
ScopedPtr<QueryPlan> qp_after(
policy.new_query_plan("ks", NULL, NULL)); // should not have down host ptr in plan
{
const size_t seq[] = { 1 };
verify_sequence(qp_before.get(), VECTOR_FROM(size_t, seq));
verify_sequence(qp_after.get(), VECTOR_FROM(size_t, seq));
}
policy.on_host_up(target_host);
// make sure we get both nodes, correct order after
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
{
const size_t seq[] = { 1, 2 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
}
TEST(DatacenterAwareLoadBalancingUnitTest, UsedHostsPerDatacenter) {
HostMap hosts;
populate_hosts(3, "rack", LOCAL_DC, &hosts);
populate_hosts(3, "rack", REMOTE_DC, &hosts);
for (size_t used_hosts = 0; used_hosts < 4; ++used_hosts) {
DCAwarePolicy policy(LOCAL_DC, used_hosts, false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
Vector<size_t> seq;
size_t index = 0;
seq.reserve(3 + used_hosts);
// Local DC hosts
for (size_t i = 0; i < 3; ++i) {
seq.push_back(index++ + 1);
}
// Remote DC hosts
for (size_t i = 0; i < used_hosts; ++i) {
// DC-aware only uses hosts up to the used host count so we need to wrap
// around.
seq.push_back(3 + (index++ % used_hosts) + 1);
}
verify_sequence(qp.get(), seq);
}
}
TEST(DatacenterAwareLoadBalancingUnitTest, AllowRemoteDatacentersForLocalConsistencyLevel) {
HostMap hosts;
populate_hosts(3, "rack", LOCAL_DC, &hosts);
populate_hosts(3, "rack", REMOTE_DC, &hosts);
{
// Not allowing remote DCs for local CLs
bool allow_remote_dcs_for_local_cl = false;
DCAwarePolicy policy(LOCAL_DC, 3, !allow_remote_dcs_for_local_cl);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
// Set local CL
QueryRequest::Ptr request(new QueryRequest("", 0));
request->set_consistency(CASS_CONSISTENCY_LOCAL_ONE);
SharedRefPtr<RequestHandler> request_handler(
new RequestHandler(request, ResponseFuture::Ptr()));
// Check for only local hosts are used
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", request_handler.get(), NULL));
const size_t seq[] = { 1, 2, 3 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
{
// Allowing remote DCs for local CLs
bool allow_remote_dcs_for_local_cl = true;
DCAwarePolicy policy(LOCAL_DC, 3, !allow_remote_dcs_for_local_cl);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
// Set local CL
QueryRequest::Ptr request(new QueryRequest("", 0));
request->set_consistency(CASS_CONSISTENCY_LOCAL_QUORUM);
SharedRefPtr<RequestHandler> request_handler(
new RequestHandler(request, ResponseFuture::Ptr()));
// Check for only local hosts are used
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", request_handler.get(), NULL));
const size_t seq[] = { 1, 2, 3, 4, 5, 6 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
}
TEST(DatacenterAwareLoadBalancingUnitTest, StartWithEmptyLocalDatacenter) {
HostMap hosts;
populate_hosts(1, "rack", REMOTE_DC, &hosts);
populate_hosts(3, "rack", LOCAL_DC, &hosts);
// Set local DC using connected host
{
DCAwarePolicy policy("", 0, false);
policy.init(hosts[Address("2.0.0.0", 9042)], hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
const size_t seq[] = { 2, 3, 4 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
// Set local DC using first host with non-empty DC
{
DCAwarePolicy policy("", 0, false);
policy.init(SharedRefPtr<Host>(new Host(Address("0.0.0.0", 9042))), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
const size_t seq[] = { 1 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
}
Vector<String> single_token(int64_t token) {
OStringStream ss;
ss << token;
return Vector<String>(1, ss.str());
}
TEST(DatacenterAwareLoadBalancingUnitTest, VerifyEqualDistributionLocalDc) {
HostMap hosts;
populate_hosts(3, "rack", LOCAL_DC, &hosts);
populate_hosts(3, "rack", REMOTE_DC, &hosts);
DCAwarePolicy policy("", 0, false);
policy.init(hosts.begin()->second, hosts, NULL, "");
{ // All local nodes
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, LOCAL_DC);
ASSERT_EQ(counts.size(), 3u);
verify_query_counts(counts, 4);
}
policy.on_host_down(hosts.begin()->first);
{ // One local node down
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, LOCAL_DC);
ASSERT_EQ(counts.size(), 2u);
verify_query_counts(counts, 6);
}
policy.on_host_up(hosts.begin()->second);
{ // All local nodes again
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, LOCAL_DC);
ASSERT_EQ(counts.size(), 3u);
verify_query_counts(counts, 4);
}
policy.on_host_removed(hosts.begin()->second);
{ // One local node removed
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, LOCAL_DC);
ASSERT_EQ(counts.size(), 2u);
verify_query_counts(counts, 6);
}
}
TEST(DatacenterAwareLoadBalancingUnitTest, VerifyEqualDistributionRemoteDc) {
HostMap hosts;
populate_hosts(3, "rack", LOCAL_DC, &hosts);
populate_hosts(3, "rack", REMOTE_DC, &hosts);
DCAwarePolicy policy("", 3, false); // Allow all remote DC nodes
policy.init(hosts.begin()->second, hosts, NULL, "");
Host::Ptr remote_dc_node1;
{ // Mark down all local nodes
HostMap::iterator it = hosts.begin();
for (int i = 0; i < 3; ++i) {
policy.on_host_down(it->first);
it++;
}
remote_dc_node1 = it->second;
}
{ // All remote nodes
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, REMOTE_DC);
ASSERT_EQ(counts.size(), 3u);
verify_query_counts(counts, 4);
}
policy.on_host_down(remote_dc_node1->address());
{ // One remote node down
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, REMOTE_DC);
ASSERT_EQ(counts.size(), 2u);
verify_query_counts(counts, 6);
}
policy.on_host_up(remote_dc_node1);
{ // All remote nodes again
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, REMOTE_DC);
ASSERT_EQ(counts.size(), 3u);
verify_query_counts(counts, 4);
}
policy.on_host_removed(remote_dc_node1);
{ // One remote node removed
QueryCounts counts(run_policy(policy, 12));
verify_dcs(counts, hosts, REMOTE_DC);
ASSERT_EQ(counts.size(), 2u);
verify_query_counts(counts, 6);
}
}
TEST(TokenAwareLoadBalancingUnitTest, Simple) {
const int64_t num_hosts = 4;
HostMap hosts;
TokenMap::Ptr token_map(TokenMap::from_partitioner(Murmur3Partitioner::name()));
// Tokens
// 1.0.0.0 -4611686018427387905
// 2.0.0.0 -2
// 3.0.0.0 4611686018427387901
// 4.0.0.0 9223372036854775804
const uint64_t partition_size = CASS_UINT64_MAX / num_hosts;
Murmur3Partitioner::Token token = CASS_INT64_MIN + static_cast<int64_t>(partition_size);
for (size_t i = 1; i <= num_hosts; ++i) {
Host::Ptr host(create_host(addr_for_sequence(i), single_token(token),
Murmur3Partitioner::name().to_string(), "rack1", LOCAL_DC));
hosts[host->address()] = host;
token_map->add_host(host);
token += partition_size;
}
add_keyspace_simple("test", 3, token_map.get());
token_map->build();
TokenAwarePolicy policy(new RoundRobinPolicy(), false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
QueryRequest::Ptr request(new QueryRequest("", 1));
const char* value = "kjdfjkldsdjkl"; // hash: 9024137376112061887
request->set(0, CassString(value, strlen(value)));
request->add_key_index(0);
SharedRefPtr<RequestHandler> request_handler(new RequestHandler(request, ResponseFuture::Ptr()));
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("test", request_handler.get(), token_map.get()));
const size_t seq[] = { 4, 1, 2, 3 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
// Bring down the first host
HostMap::iterator curr_host_it = hosts.begin(); // 1.0.0.0
policy.on_host_down(curr_host_it->second->address());
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("test", request_handler.get(), token_map.get()));
const size_t seq[] = { 4, 2, 3 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
// Restore the first host and bring down the first token aware replica
policy.on_host_up(curr_host_it->second);
++curr_host_it; // 2.0.0.0
++curr_host_it; // 3.0.0.0
++curr_host_it; // 4.0.0.0
policy.on_host_down(curr_host_it->second->address());
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("test", request_handler.get(), token_map.get()));
const size_t seq[] = { 1, 2, 3 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
}
TEST(TokenAwareLoadBalancingUnitTest, NetworkTopology) {
const size_t num_hosts = 7;
HostMap hosts;
TokenMap::Ptr token_map(TokenMap::from_partitioner(Murmur3Partitioner::name()));
// Tokens
// 1.0.0.0 local -6588122883467697006
// 2.0.0.0 remote -3952873730080618204
// 3.0.0.0 local -1317624576693539402
// 4.0.0.0 remote 1317624576693539400
// 5.0.0.0 local 3952873730080618202
// 6.0.0.0 remote 6588122883467697004
// 7.0.0.0 local 9223372036854775806
const uint64_t partition_size = CASS_UINT64_MAX / num_hosts;
Murmur3Partitioner::Token token = CASS_INT64_MIN + static_cast<int64_t>(partition_size);
for (size_t i = 1; i <= num_hosts; ++i) {
Host::Ptr host(create_host(addr_for_sequence(i), single_token(token),
Murmur3Partitioner::name().to_string(), "rack1",
i % 2 == 0 ? REMOTE_DC : LOCAL_DC));
hosts[host->address()] = host;
token_map->add_host(host);
token += partition_size;
}
ReplicationMap replication;
replication[LOCAL_DC] = "3";
replication[REMOTE_DC] = "2";
add_keyspace_network_topology("test", replication, token_map.get());
token_map->build();
TokenAwarePolicy policy(new DCAwarePolicy(LOCAL_DC, num_hosts / 2, false), false);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
QueryRequest::Ptr request(new QueryRequest("", 1));
const char* value = "abc"; // hash: -5434086359492102041
request->set(0, CassString(value, strlen(value)));
request->add_key_index(0);
SharedRefPtr<RequestHandler> request_handler(new RequestHandler(request, ResponseFuture::Ptr()));
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("test", request_handler.get(), token_map.get()));
const size_t seq[] = { 3, 5, 7, 1, 4, 6, 2 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
// Bring down the first host
HostMap::iterator curr_host_it = hosts.begin(); // 1.0.0.0
policy.on_host_down(curr_host_it->second->address());
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("test", request_handler.get(), token_map.get()));
const size_t seq[] = { 3, 5, 7, 4, 6, 2 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
// Restore the first host and bring down the first token aware replica
policy.on_host_up(curr_host_it->second);
++curr_host_it; // 2.0.0.0
++curr_host_it; // 3.0.0.0
policy.on_host_down(curr_host_it->second->address());
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("test", request_handler.get(), token_map.get()));
const size_t seq[] = { 5, 7, 1, 6, 2, 4 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq));
}
}
TEST(TokenAwareLoadBalancingUnitTest, ShuffleReplicas) {
Random random;
const int64_t num_hosts = 4;
HostMap hosts;
TokenMap::Ptr token_map(TokenMap::from_partitioner(Murmur3Partitioner::name()));
// Tokens
// 1.0.0.0 -4611686018427387905
// 2.0.0.0 -2
// 3.0.0.0 4611686018427387901
// 4.0.0.0 9223372036854775804
const uint64_t partition_size = CASS_UINT64_MAX / num_hosts;
Murmur3Partitioner::Token token = CASS_INT64_MIN + static_cast<int64_t>(partition_size);
for (size_t i = 1; i <= num_hosts; ++i) {
Host::Ptr host(create_host(addr_for_sequence(i), single_token(token),
Murmur3Partitioner::name().to_string(), "rack1", LOCAL_DC));
hosts[host->address()] = host;
token_map->add_host(host);
token += partition_size;
}
add_keyspace_simple("test", 3, token_map.get());
token_map->build();
QueryRequest::Ptr request(new QueryRequest("", 1));
const char* value = "kjdfjkldsdjkl"; // hash: 9024137376112061887
request->set(0, CassString(value, strlen(value)));
request->add_key_index(0);
SharedRefPtr<RequestHandler> request_handler(new RequestHandler(request, ResponseFuture::Ptr()));
HostVec not_shuffled;
{
TokenAwarePolicy policy(new RoundRobinPolicy(), false); // Not shuffled
policy.init(SharedRefPtr<Host>(), hosts, &random, "");
ScopedPtr<QueryPlan> qp1(policy.new_query_plan("test", request_handler.get(), token_map.get()));
for (int i = 0; i < num_hosts; ++i) {
not_shuffled.push_back(qp1->compute_next());
}
// Verify that not shuffled will repeat the same order
HostVec not_shuffled_again;
ScopedPtr<QueryPlan> qp2(policy.new_query_plan("test", request_handler.get(), token_map.get()));
for (int i = 0; i < num_hosts; ++i) {
not_shuffled_again.push_back(qp2->compute_next());
}
EXPECT_EQ(not_shuffled_again, not_shuffled);
}
// Verify that the shuffle setting does indeed shuffle the replicas
{
TokenAwarePolicy shuffle_policy(new RoundRobinPolicy(), true); // Shuffled
shuffle_policy.init(SharedRefPtr<Host>(), hosts, &random, "");
HostVec shuffled_previous;
ScopedPtr<QueryPlan> qp(
shuffle_policy.new_query_plan("test", request_handler.get(), token_map.get()));
for (int i = 0; i < num_hosts; ++i) {
shuffled_previous.push_back(qp->compute_next());
}
int count;
const int max_iterations = num_hosts * num_hosts;
for (count = 0; count < max_iterations; ++count) {
ScopedPtr<QueryPlan> qp(
shuffle_policy.new_query_plan("test", request_handler.get(), token_map.get()));
HostVec shuffled;
for (int j = 0; j < num_hosts; ++j) {
Host::Ptr host(qp->compute_next());
EXPECT_GT(std::count(not_shuffled.begin(), not_shuffled.end(), host), 0);
shuffled.push_back(host);
}
// Exit if we prove that we shuffled the hosts
if (shuffled != not_shuffled && shuffled != shuffled_previous) {
break;
}
}
EXPECT_NE(count, max_iterations);
}
}
TEST(LatencyAwareLoadBalancingUnitTest, ThreadholdToAccount) {
const uint64_t scale = 100LL;
const uint64_t min_measured = 15LL;
const uint64_t threshold_to_account = (30LL * min_measured) / 100LL;
const uint64_t one_ms = 1000000LL; // 1 ms in ns
Host host(Address("0.0.0.0", 9042));
host.enable_latency_tracking(scale, min_measured);
TimestampedAverage current = host.get_current_average();
for (uint64_t i = 0; i < threshold_to_account; ++i) {
host.update_latency(one_ms);
current = host.get_current_average();
EXPECT_EQ(current.num_measured, i + 1);
EXPECT_EQ(current.average, -1);
}
host.update_latency(one_ms);
current = host.get_current_average();
EXPECT_EQ(current.num_measured, threshold_to_account + 1);
EXPECT_EQ(current.average, static_cast<int64_t>(one_ms));
}
TEST(LatencyAwareLoadBalancingUnitTest, MovingAverage) {
const uint64_t one_ms = 1000000LL; // 1 ms in ns
// Verify average is approx. the same when recording the same latency twice
EXPECT_NEAR(static_cast<double>(calculate_moving_average(one_ms, one_ms, 100LL)),
static_cast<double>(one_ms), 0.2 * one_ms);
EXPECT_NEAR(static_cast<double>(calculate_moving_average(one_ms, one_ms, 1000LL)),
static_cast<double>(one_ms), 0.2 * one_ms);
// First average is 100 us and second average is 50 us, expect a 75 us average approx.
// after a short wait time. This has a high tolerance because the time waited varies.
EXPECT_NEAR(
static_cast<double>(calculate_moving_average(one_ms, one_ms / 2LL, 50LL)),
static_cast<double>((3LL * one_ms) / 4LL),
50.0 * one_ms); // Highly variable because it's in the early part of the logarithmic curve
// First average is 100 us and second average is 50 us, expect a 50 us average approx.
// after a longer wait time. This has a high tolerance because the time waited varies
EXPECT_NEAR(static_cast<double>(calculate_moving_average(one_ms, one_ms / 2LL, 100000LL)),
static_cast<double>(one_ms / 2LL), 2.0 * one_ms);
}
#if _MSC_VER == 1700 && _M_IX86
TEST(LatencyAwareLoadBalancingUnitTest,
DISABLED_Simple) { // Disabled: See https://datastax-oss.atlassian.net/browse/CPP-654
#else
TEST(LatencyAwareLoadBalancingUnitTest, Simple) {
#endif
LatencyAwarePolicy::Settings settings;
// Disable min_measured
settings.min_measured = 0L;
// Latencies can't exceed 2x the minimum latency
settings.exclusion_threshold = 2.0;
// Set the retry period to 1 second
settings.retry_period_ns = 1000LL * 1000LL * 1000L;
const int64_t num_hosts = 4;
HostMap hosts;
populate_hosts(num_hosts, "rack1", LOCAL_DC, &hosts);
LatencyAwarePolicy policy(new RoundRobinPolicy(), settings);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
// Record some latencies with 100 ns being the minimum
for (HostMap::iterator i = hosts.begin(); i != hosts.end(); ++i) {
i->second->enable_latency_tracking(settings.scale_ns, settings.min_measured);
}
hosts[Address("1.0.0.0", 9042)]->update_latency(100);
hosts[Address("4.0.0.0", 9042)]->update_latency(150);
// Hosts 2 and 3 will exceed the exclusion threshold
hosts[Address("2.0.0.0", 9042)]->update_latency(201);
hosts[Address("3.0.0.0", 9042)]->update_latency(1000);
// Verify we don't have a current minimum average
EXPECT_EQ(policy.min_average(), -1);
// Run minimum average calculation
RunPeriodicTask task(&policy);
ASSERT_EQ(task.init(), 0);
task.run();
// Wait for task to run (minimum average calculation will happen after 100 ms)
test::Utils::msleep(150);
task.done();
task.join();
// Verify current minimum average
EXPECT_EQ(policy.min_average(), 100);
// 1 and 4 are under the minimum, but 2 and 3 will be skipped
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("", NULL, NULL));
const size_t seq1[] = { 1, 4, 2, 3 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
}
// Exceed retry period
test::Utils::msleep(1000); // 1 second
// After waiting no hosts should be skipped (notice 2 and 3 tried first)
{
ScopedPtr<QueryPlan> qp(policy.new_query_plan("", NULL, NULL));
const size_t seq1[] = { 2, 3, 4, 1 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
}
}
#if _MSC_VER == 1700 && _M_IX86
TEST(LatencyAwareLoadBalancingUnitTest,
DISABLED_MinAverageUnderMinMeasured) { // Disabled: See
// https://datastax-oss.atlassian.net/browse/CPP-654
#else
TEST(LatencyAwareLoadBalancingUnitTest, MinAverageUnderMinMeasured) {
#endif
LatencyAwarePolicy::Settings settings;
const int64_t num_hosts = 4;
HostMap hosts;
populate_hosts(num_hosts, "rack1", LOCAL_DC, &hosts);
LatencyAwarePolicy policy(new RoundRobinPolicy(), settings);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
int count = 1;
for (HostMap::iterator i = hosts.begin(); i != hosts.end(); ++i) {
i->second->enable_latency_tracking(settings.scale_ns, settings.min_measured);
i->second->update_latency(100 * count++);
}
// Verify we don't have a current minimum average
EXPECT_EQ(policy.min_average(), -1);
// Run minimum average calculation
RunPeriodicTask task(&policy);
ASSERT_EQ(task.init(), 0);
task.run();
// Wait for task to run (minimum average calculation will happen after 100 ms)
test::Utils::msleep(150);
task.done();
task.join();
// No hosts have the minimum measured
EXPECT_EQ(policy.min_average(), -1);
}
TEST(WhitelistLoadBalancingUnitTest, Hosts) {
const int64_t num_hosts = 100;
HostMap hosts;
populate_hosts(num_hosts, "rack1", LOCAL_DC, &hosts);
ContactPointList whitelist_hosts;
whitelist_hosts.push_back("37.0.0.0");
whitelist_hosts.push_back("83.0.0.0");
WhitelistPolicy policy(new RoundRobinPolicy(), whitelist_hosts);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
// Verify only hosts 37 and 83 are computed in the query plan
const size_t seq1[] = { 37, 83 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
// The query plan should now be exhausted
Address next_address;
ASSERT_FALSE(qp.get()->compute_next(&next_address));
}
TEST(WhitelistLoadBalancingUnitTest, Datacenters) {
HostMap hosts;
populate_hosts(3, "rack1", LOCAL_DC, &hosts);
populate_hosts(3, "rack1", BACKUP_DC, &hosts);
populate_hosts(3, "rack1", REMOTE_DC, &hosts);
DcList whitelist_dcs;
whitelist_dcs.push_back(LOCAL_DC);
whitelist_dcs.push_back(REMOTE_DC);
WhitelistDCPolicy policy(new RoundRobinPolicy(), whitelist_dcs);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
// Verify only hosts LOCAL_DC and REMOTE_DC are computed in the query plan
const size_t seq1[] = { 1, 2, 3, 7, 8, 9 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
// The query plan should now be exhausted
Address next_address;
ASSERT_FALSE(qp.get()->compute_next(&next_address));
}
TEST(BlacklistLoadBalancingUnitTest, Hosts) {
const int64_t num_hosts = 5;
HostMap hosts;
populate_hosts(num_hosts, "rack1", LOCAL_DC, &hosts);
ContactPointList blacklist_hosts;
blacklist_hosts.push_back("2.0.0.0");
blacklist_hosts.push_back("3.0.0.0");
BlacklistPolicy policy(new RoundRobinPolicy(), blacklist_hosts);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
// Verify only hosts 1, 4 and 5 are computed in the query plan
const size_t seq1[] = { 1, 4, 5 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
// The query plan should now be exhausted
Address next_address;
ASSERT_FALSE(qp.get()->compute_next(&next_address));
}
TEST(BlacklistLoadBalancingUnitTest, Datacenters) {
HostMap hosts;
populate_hosts(3, "rack1", LOCAL_DC, &hosts);
populate_hosts(3, "rack1", BACKUP_DC, &hosts);
populate_hosts(3, "rack1", REMOTE_DC, &hosts);
DcList blacklist_dcs;
blacklist_dcs.push_back(LOCAL_DC);
blacklist_dcs.push_back(REMOTE_DC);
BlacklistDCPolicy policy(new RoundRobinPolicy(), blacklist_dcs);
policy.init(SharedRefPtr<Host>(), hosts, NULL, "");
ScopedPtr<QueryPlan> qp(policy.new_query_plan("ks", NULL, NULL));
// Verify only hosts from BACKUP_DC are computed in the query plan
const size_t seq1[] = { 4, 5, 6 };
verify_sequence(qp.get(), VECTOR_FROM(size_t, seq1));
// The query plan should now be exhausted
Address next_address;
ASSERT_FALSE(qp.get()->compute_next(&next_address));
}