/*
* Copyright (C) 2012-2015 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.
*/
package com.datastax.driver.core;
import com.datastax.driver.core.policies.DelegatingLoadBalancingPolicy;
import com.datastax.driver.core.policies.LoadBalancingPolicy;
import com.datastax.driver.core.policies.RoundRobinPolicy;
import com.datastax.driver.core.utils.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import java.util.Collection;
import java.util.List;
import static com.datastax.driver.core.LoadBalancingPolicyBootstrapTest.HistoryPolicy.Action.*;
import static com.datastax.driver.core.LoadBalancingPolicyBootstrapTest.HistoryPolicy.entry;
import static org.assertj.core.api.Assertions.assertThat;
@CCMConfig(numberOfNodes = 2, dirtiesContext = true, createCluster = false)
public class LoadBalancingPolicyBootstrapTest extends CCMTestsSupport {
private static final Logger logger = LoggerFactory.getLogger(LoadBalancingPolicyBootstrapTest.class);
/**
* Ensures that when a cluster is initialized that {@link LoadBalancingPolicy#init(Cluster, Collection)} is called
* with each reachable contact point.
*
* @test_category load_balancing:notification
* @expected_result init() is called for each of two contact points.
*/
@Test(groups = "short")
public void should_init_policy_with_up_contact_points() throws Exception {
HistoryPolicy policy = new HistoryPolicy(new RoundRobinPolicy());
Cluster cluster = register(Cluster.builder()
.addContactPoints(getContactPoints())
.withPort(ccm().getBinaryPort())
.withLoadBalancingPolicy(policy)
.build());
try {
cluster.init();
assertThat(policy.history).containsOnly(
entry(INIT, TestUtils.findHost(cluster, 1)),
entry(INIT, TestUtils.findHost(cluster, 2))
);
} finally {
cluster.close();
}
}
/**
* Ensures that {@link LoadBalancingPolicy#onDown(Host)} is called for a contact point that couldn't
* be reached while initializing the control connection, but only after
* {@link LoadBalancingPolicy#init(Cluster, Collection)} has been called.
*
* @test_category load_balancing:notification
* @expected_result init() is called with the up host, followed by onDown() for the downed host.
* @jira_ticket JAVA-613
* @since 2.0.10, 2.1.5
*/
@Test(groups = "short", dependsOnMethods = "should_init_policy_with_up_contact_points")
public void should_send_down_notifications_after_init_when_contact_points_are_down() throws Exception {
// In order to validate this behavior, we need to stop the first node that would be attempted to be
// established as the control connection.
// This depends on the internal behavior and will even be made totally random by JAVA-618, therefore
// we retry the scenario until we get the desired preconditions.
int nodeToStop = 1;
int tries = 1, maxTries = 10;
for (; tries <= maxTries; tries++) {
nodeToStop = (nodeToStop == 1) ? 2 : 1; // switch nodes at each try
int activeNode = nodeToStop == 2 ? 1 : 2;
ccm().stop(nodeToStop);
ccm().waitForDown(nodeToStop);
HistoryPolicy policy = new HistoryPolicy(new RoundRobinPolicy());
Cluster cluster = register(Cluster.builder()
.addContactPoints(getContactPoints())
.withPort(ccm().getBinaryPort())
.withLoadBalancingPolicy(policy)
.build());
try {
cluster.init();
if (policy.history.contains(entry(DOWN, TestUtils.findHost(cluster, nodeToStop)))) {
// This is the situation we're testing, the control connection tried the stopped node first.
assertThat(policy.history).containsExactly(
entry(INIT, TestUtils.findHost(cluster, activeNode)),
entry(DOWN, TestUtils.findHost(cluster, nodeToStop))
);
break;
} else {
assertThat(policy.history).containsOnly(
entry(INIT, TestUtils.findHost(cluster, 1)),
entry(INIT, TestUtils.findHost(cluster, 2))
);
logger.info("Could not get first contact point to fail, retrying");
cluster.close();
ccm().start(nodeToStop);
ccm().waitForUp(nodeToStop);
}
} finally {
cluster.close();
}
}
if (tries == maxTries + 1)
logger.warn("Could not get first contact point to fail after {} tries", maxTries);
}
static class HistoryPolicy extends DelegatingLoadBalancingPolicy {
enum Action {INIT, UP, DOWN, ADD, REMOVE}
static class Entry {
final Action action;
final Host host;
public Entry(Action action, Host host) {
this.action = action;
this.host = host;
}
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if (other instanceof Entry) {
Entry that = (Entry) other;
return this.action == that.action && this.host.equals(that.host);
}
return false;
}
@Override
public int hashCode() {
return MoreObjects.hashCode(action, host);
}
@Override
public String toString() {
return String.format("Entry(action=%s, host=%s)", action, host);
}
}
static Entry entry(Action action, Host host) {
return new Entry(action, host);
}
List<Entry> history = Lists.newArrayList();
public HistoryPolicy(LoadBalancingPolicy delegate) {
super(delegate);
}
@Override
public void init(Cluster cluster, Collection<Host> hosts) {
super.init(cluster, hosts);
for (Host host : hosts) {
history.add(entry(INIT, host));
}
}
public void onAdd(Host host) {
history.add(entry(ADD, host));
super.onAdd(host);
}
public void onUp(Host host) {
history.add(entry(UP, host));
super.onUp(host);
}
public void onDown(Host host) {
history.add(entry(DOWN, host));
super.onDown(host);
}
public void onRemove(Host host) {
history.add(entry(REMOVE, host));
super.onRemove(host);
}
}
}