// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.net.loadbalancing;
import java.util.Collection;
import com.google.common.base.Function;
import com.google.common.collect.Sets;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.twitter.common.base.Closure;
import com.twitter.common.net.loadbalancing.LoadBalancingStrategy.ConnectionResult;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import com.twitter.common.testing.EasyMockTest;
import com.twitter.common.util.BackoffDecider;
import com.twitter.common.util.Random;
import com.twitter.common.util.TruncatedBinaryBackoff;
import com.twitter.common.util.testing.FakeClock;
/**
* @author Krishna Gade
*/
public class MarkDeadStrategyWithHostCheckTest extends EasyMockTest {
private static final Amount<Long, Time> INITIAL_BACKOFF = Amount.of(1L, Time.SECONDS);
private static final Amount<Long, Time> MAX_BACKOFF = Amount.of(10L, Time.SECONDS);
private static final String BACKEND_1 = "backend1";
private static final String BACKEND_2 = "backend2";
private LoadBalancingStrategy<String> wrappedStrategy;
private Closure<Collection<String>> onBackendsChosen;
private LoadBalancingStrategy<String> markDead;
private Random random;
private FakeClock clock;
@Before
public void setUp() {
wrappedStrategy = createMock(new Clazz<LoadBalancingStrategy<String>>() {});
onBackendsChosen = createMock(new Clazz<Closure<Collection<String>>>() {});
random = createMock(Random.class);
clock = new FakeClock();
Function<String, BackoffDecider> backoffFactory =
new Function<String, BackoffDecider>() {
@Override public BackoffDecider apply(String s) {
return BackoffDecider.builder(s)
.withSeedSize(1)
.withClock(clock)
.withRandom(random)
.withTolerateFailureRate(0.5)
.withStrategy(new TruncatedBinaryBackoff(INITIAL_BACKOFF, MAX_BACKOFF))
// This recovery type is suggested for load balancer strategies to prevent
// connection pool churn that would occur from the random linear recovery type.
.withRecoveryType(BackoffDecider.RecoveryType.FULL_CAPACITY)
.withRequestWindow(MAX_BACKOFF)
.build();
}
};
markDead = new MarkDeadStrategyWithHostCheck<String>(wrappedStrategy, backoffFactory);
}
@After
public void verify() {
control.verify();
}
@Test
public void testDeadHost() {
wrappedStrategy.offerBackends(Sets.newHashSet(BACKEND_1, BACKEND_2), onBackendsChosen);
expectConnected(BACKEND_1, ConnectionResult.SUCCESS, 10);
expectConnected(BACKEND_2, ConnectionResult.SUCCESS, 4);
expectConnected(BACKEND_2, ConnectionResult.FAILED, 4);
wrappedStrategy.offerBackends(Sets.newHashSet(BACKEND_1), onBackendsChosen);
expectConnected(BACKEND_2, ConnectionResult.SUCCESS, 1);
wrappedStrategy.offerBackends(Sets.newHashSet(BACKEND_1, BACKEND_2), onBackendsChosen);
control.replay();
markDead.offerBackends(Sets.newHashSet(BACKEND_1, BACKEND_2), onBackendsChosen);
connect(BACKEND_1, ConnectionResult.SUCCESS, 10);
connect(BACKEND_2, ConnectionResult.SUCCESS, 4);
connect(BACKEND_2, ConnectionResult.FAILED, 10);
clock.advance(INITIAL_BACKOFF); // Wait for backoff period to expire.
clock.waitFor(1);
clock.advance(INITIAL_BACKOFF); // Wait for recovery period to expire.
connect(BACKEND_2, ConnectionResult.SUCCESS, 1);
}
@Test
public void testDeadHostWithMaxBackOff() {
wrappedStrategy.offerBackends(Sets.newHashSet(BACKEND_1, BACKEND_2), onBackendsChosen);
expectConnected(BACKEND_1, ConnectionResult.SUCCESS, 10);
expectConnected(BACKEND_2, ConnectionResult.SUCCESS, 4);
expectConnected(BACKEND_2, ConnectionResult.FAILED, 4);
wrappedStrategy.offerBackends(Sets.newHashSet(BACKEND_1), onBackendsChosen);
expectConnected(BACKEND_2, ConnectionResult.SUCCESS, 1);
wrappedStrategy.offerBackends(Sets.newHashSet(BACKEND_1, BACKEND_2), onBackendsChosen);
control.replay();
markDead.offerBackends(Sets.newHashSet(BACKEND_1, BACKEND_2), onBackendsChosen);
connect(BACKEND_1, ConnectionResult.SUCCESS, 10);
connect(BACKEND_2, ConnectionResult.SUCCESS, 4);
connect(BACKEND_2, ConnectionResult.FAILED, 10);
clock.advance(INITIAL_BACKOFF); // Wait for backoff period to expire.
clock.waitFor(1);
clock.advance(INITIAL_BACKOFF); // Wait for recovery period to expire.
clock.advance(MAX_BACKOFF); // Wait for recovery period to expire.
connect(BACKEND_2, ConnectionResult.SUCCESS, 1);
}
private int connect(String backend, ConnectionResult result, int count) {
for (int i = 0; i < count; i++) {
markDead.addConnectResult(backend, result, 0L);
}
return count;
}
private void expectConnected(String backend, ConnectionResult result, int count) {
for (int i = 0; i < count; i++) {
wrappedStrategy.addConnectResult(backend, result, 0L);
}
}
}