/**
*
* 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.
*
*/
package org.apache.bookkeeper.client;
import static org.apache.bookkeeper.client.RackawareEnsemblePlacementPolicy.REPP_DNS_RESOLVER_CLASS;
import static org.apache.bookkeeper.feature.SettableFeatureProvider.DISABLE_ALL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.bookkeeper.client.BKException.BKNotEnoughBookiesException;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.bookkeeper.net.BookieSocketAddress;
import org.apache.bookkeeper.net.CommonConfigurationKeys;
import org.apache.bookkeeper.net.DNSToSwitchMapping;
import org.apache.bookkeeper.net.ScriptBasedMapping;
import org.apache.bookkeeper.util.Shell;
import org.jboss.netty.util.HashedWheelTimer;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* In this testsuite, ScriptBasedMapping is used as DNS_RESOLVER_CLASS for
* mapping nodes to racks. Shell Script -
* src/test/resources/networkmappingscript.sh is used in ScriptBasedMapping for
* resolving racks. This script maps HostAddress to rack depending on the last
* character of the HostAddress string. for eg. 127.0.0.1 :- /1, 127.0.0.2 :-
* /2, 99.12.34.21 :- /1
*
* This testsuite has same testscenarios as in
* TestRackawareEnsemblePlacementPolicy.java.
*
* For now this Testsuite works only on Unix based OS.
*/
public class TestRackawareEnsemblePlacementPolicyUsingScript {
static final Logger LOG = LoggerFactory.getLogger(TestRackawareEnsemblePlacementPolicyUsingScript.class);
HashedWheelTimer timer;
RackawareEnsemblePlacementPolicy repp;
ClientConfiguration conf = new ClientConfiguration();
@Before
public void setUp() throws Exception {
conf.setProperty(REPP_DNS_RESOLVER_CLASS, ScriptBasedMapping.class.getName());
conf.setProperty(CommonConfigurationKeys.NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY,
"src/test/resources/networkmappingscript.sh");
timer = new HashedWheelTimer(
new ThreadFactoryBuilder().setNameFormat("TestTimer-%d").build(),
conf.getTimeoutTimerTickDurationMs(), TimeUnit.MILLISECONDS,
conf.getTimeoutTimerNumTicks());
repp = new RackawareEnsemblePlacementPolicy();
repp.initialize(conf, Optional.<DNSToSwitchMapping>absent(), timer, DISABLE_ALL, null);
}
@After
public void tearDown() throws Exception {
repp.uninitalize();
}
private void ignoreTestIfItIsWindowsOS() {
Assume.assumeTrue(!Shell.WINDOWS);
}
@Test(timeout = 60000)
public void testReplaceBookieWithEnoughBookiesInSameRack() throws Exception {
ignoreTestIfItIsWindowsOS();
BookieSocketAddress addr1 = new BookieSocketAddress("127.0.0.1", 3181); // /1 rack
BookieSocketAddress addr2 = new BookieSocketAddress("127.0.0.2", 3181); // /2 rack
BookieSocketAddress addr3 = new BookieSocketAddress("127.0.1.2", 3181); // /2 rack
BookieSocketAddress addr4 = new BookieSocketAddress("127.0.0.4", 3181); // /4 rack
// Update cluster
Set<BookieSocketAddress> addrs = new HashSet<BookieSocketAddress>();
addrs.add(addr1);
addrs.add(addr2);
addrs.add(addr3);
addrs.add(addr4);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
// replace node under r2
BookieSocketAddress replacedBookie = repp.replaceBookie(1, 1, 1, null, new HashSet<BookieSocketAddress>(), addr2, new HashSet<BookieSocketAddress>());
assertEquals(addr3, replacedBookie);
}
@Test(timeout = 60000)
public void testReplaceBookieWithEnoughBookiesInDifferentRack() throws Exception {
ignoreTestIfItIsWindowsOS();
BookieSocketAddress addr1 = new BookieSocketAddress("127.0.0.1", 3181); // /1 rack
BookieSocketAddress addr2 = new BookieSocketAddress("127.0.0.2", 3181); // /2 rack
BookieSocketAddress addr3 = new BookieSocketAddress("127.0.0.3", 3181); // /3 rack
BookieSocketAddress addr4 = new BookieSocketAddress("127.0.0.4", 3181); // /4 rack
// Update cluster
Set<BookieSocketAddress> addrs = new HashSet<BookieSocketAddress>();
addrs.add(addr1);
addrs.add(addr2);
addrs.add(addr3);
addrs.add(addr4);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
// replace node under r2
Set<BookieSocketAddress> excludedAddrs = new HashSet<BookieSocketAddress>();
excludedAddrs.add(addr1);
BookieSocketAddress replacedBookie = repp.replaceBookie(1, 1, 1, null, new HashSet<BookieSocketAddress>(), addr2, excludedAddrs);
assertFalse(addr1.equals(replacedBookie));
assertTrue(addr3.equals(replacedBookie) || addr4.equals(replacedBookie));
}
@Test(timeout = 60000)
public void testReplaceBookieWithNotEnoughBookies() throws Exception {
ignoreTestIfItIsWindowsOS();
BookieSocketAddress addr1 = new BookieSocketAddress("127.0.0.1", 3181); // /1 rack
BookieSocketAddress addr2 = new BookieSocketAddress("127.0.0.2", 3181); // /2 rack
BookieSocketAddress addr3 = new BookieSocketAddress("127.0.0.3", 3181); // /3 rack
BookieSocketAddress addr4 = new BookieSocketAddress("127.0.0.4", 3181); // /4 rack
// Update cluster
Set<BookieSocketAddress> addrs = new HashSet<BookieSocketAddress>();
addrs.add(addr1);
addrs.add(addr2);
addrs.add(addr3);
addrs.add(addr4);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
// replace node under r2
Set<BookieSocketAddress> excludedAddrs = new HashSet<BookieSocketAddress>();
excludedAddrs.add(addr1);
excludedAddrs.add(addr3);
excludedAddrs.add(addr4);
try {
repp.replaceBookie(1, 1, 1, null, new HashSet<BookieSocketAddress>(), addr2, excludedAddrs);
fail("Should throw BKNotEnoughBookiesException when there is not enough bookies");
} catch (BKNotEnoughBookiesException bnebe) {
// should throw not BKNotEnoughBookiesException
}
}
@Test(timeout = 60000)
public void testNewEnsembleWithSingleRack() throws Exception {
ignoreTestIfItIsWindowsOS();
BookieSocketAddress addr1 = new BookieSocketAddress("127.0.0.1", 3181); // /1 rack
BookieSocketAddress addr2 = new BookieSocketAddress("127.0.1.1", 3181); // /1 rack
BookieSocketAddress addr3 = new BookieSocketAddress("127.0.2.1", 3181); // /1 rack
BookieSocketAddress addr4 = new BookieSocketAddress("127.0.3.1", 3181); // /1 rack
// Update cluster
Set<BookieSocketAddress> addrs = new HashSet<BookieSocketAddress>();
addrs.add(addr1);
addrs.add(addr2);
addrs.add(addr3);
addrs.add(addr4);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
try {
ArrayList<BookieSocketAddress> ensemble = repp.newEnsemble(3, 2, 2, null, new HashSet<BookieSocketAddress>());
assertEquals(0, getNumCoveredWriteQuorums(ensemble, 2));
ArrayList<BookieSocketAddress> ensemble2 = repp.newEnsemble(4, 2, 2, null, new HashSet<BookieSocketAddress>());
assertEquals(0, getNumCoveredWriteQuorums(ensemble2, 2));
} catch (BKNotEnoughBookiesException bnebe) {
fail("Should not get not enough bookies exception even there is only one rack.");
}
}
@Test(timeout = 60000)
public void testNewEnsembleWithMultipleRacks() throws Exception {
ignoreTestIfItIsWindowsOS();
BookieSocketAddress addr1 = new BookieSocketAddress("127.0.0.1", 3181); // /1 rack
BookieSocketAddress addr2 = new BookieSocketAddress("127.0.0.2", 3181); // /2 rack
BookieSocketAddress addr3 = new BookieSocketAddress("127.0.1.2", 3181); // /2 rack
BookieSocketAddress addr4 = new BookieSocketAddress("127.0.2.2", 3181); // /2 rack
// Update cluster
Set<BookieSocketAddress> addrs = new HashSet<BookieSocketAddress>();
addrs.add(addr1);
addrs.add(addr2);
addrs.add(addr3);
addrs.add(addr4);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
try {
ArrayList<BookieSocketAddress> ensemble = repp.newEnsemble(3, 2, 2, null, new HashSet<BookieSocketAddress>());
int numCovered = getNumCoveredWriteQuorums(ensemble, 2);
assertTrue(numCovered == 2);
ArrayList<BookieSocketAddress> ensemble2 = repp.newEnsemble(4, 2, 2, null, new HashSet<BookieSocketAddress>());
numCovered = getNumCoveredWriteQuorums(ensemble2, 2);
assertTrue(numCovered == 2);
} catch (BKNotEnoughBookiesException bnebe) {
fail("Should not get not enough bookies exception");
}
}
@Test(timeout = 90000)
public void testNewEnsembleWithEnoughRacks() throws Exception {
ignoreTestIfItIsWindowsOS();
BookieSocketAddress addr1 = new BookieSocketAddress("127.0.0.1", 3181); // /1 rack
BookieSocketAddress addr2 = new BookieSocketAddress("127.0.0.2", 3181); // /2 rack
BookieSocketAddress addr3 = new BookieSocketAddress("127.0.0.3", 3181); // /3 rack
BookieSocketAddress addr4 = new BookieSocketAddress("127.0.0.4", 3181); // /4 rack
BookieSocketAddress addr5 = new BookieSocketAddress("127.0.1.1", 3181); // /1 rack
BookieSocketAddress addr6 = new BookieSocketAddress("127.0.1.2", 3181); // /2 rack
BookieSocketAddress addr7 = new BookieSocketAddress("127.0.1.3", 3181); // /3 rack
BookieSocketAddress addr8 = new BookieSocketAddress("127.0.1.4", 3181); // /4 rack
// Update cluster
Set<BookieSocketAddress> addrs = new HashSet<BookieSocketAddress>();
addrs.add(addr1);
addrs.add(addr2);
addrs.add(addr3);
addrs.add(addr4);
addrs.add(addr5);
addrs.add(addr6);
addrs.add(addr7);
addrs.add(addr8);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
try {
ArrayList<BookieSocketAddress> ensemble1 = repp.newEnsemble(3, 2, 2, null, new HashSet<BookieSocketAddress>());
assertEquals(3, getNumCoveredWriteQuorums(ensemble1, 2));
ArrayList<BookieSocketAddress> ensemble2 = repp.newEnsemble(4, 2, 2, null, new HashSet<BookieSocketAddress>());
assertEquals(4, getNumCoveredWriteQuorums(ensemble2, 2));
} catch (BKNotEnoughBookiesException bnebe) {
fail("Should not get not enough bookies exception.");
}
}
/**
* Test for BOOKKEEPER-633
*/
@Test(timeout = 60000)
public void testRemoveBookieFromCluster() {
ignoreTestIfItIsWindowsOS();
BookieSocketAddress addr1 = new BookieSocketAddress("127.0.0.1", 3181); // /1 rack
BookieSocketAddress addr2 = new BookieSocketAddress("127.0.0.2", 3181); // /2 rack
BookieSocketAddress addr3 = new BookieSocketAddress("127.0.1.2", 3181); // /2 rack
BookieSocketAddress addr4 = new BookieSocketAddress("127.0.0.4", 3181); // /4 rack
// Update cluster
Set<BookieSocketAddress> addrs = new HashSet<BookieSocketAddress>();
addrs.add(addr1);
addrs.add(addr2);
addrs.add(addr3);
addrs.add(addr4);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
addrs.remove(addr1);
repp.onClusterChanged(addrs, new HashSet<BookieSocketAddress>());
}
private int getNumCoveredWriteQuorums(ArrayList<BookieSocketAddress> ensemble, int writeQuorumSize)
throws Exception {
int ensembleSize = ensemble.size();
int numCoveredWriteQuorums = 0;
for (int i = 0; i < ensembleSize; i++) {
Set<String> racks = new HashSet<String>();
for (int j = 0; j < writeQuorumSize; j++) {
int bookieIdx = (i + j) % ensembleSize;
BookieSocketAddress addr = ensemble.get(bookieIdx);
String hostAddress = addr.getSocketAddress().getAddress().getHostAddress();
String rack = "/" + hostAddress.charAt(hostAddress.length() - 1);
racks.add(rack);
}
numCoveredWriteQuorums += (racks.size() > 1 ? 1 : 0);
}
return numCoveredWriteQuorums;
}
}