blob: ae3557315d0d1ff9c0ed79337f0febc33ddbe3a9 [file] [log] [blame]
/*
* 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.zookeeper.server.quorum;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.DummyWatcher;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.admin.ZooKeeperAdmin;
import org.apache.zookeeper.test.ClientBase;
import org.apache.zookeeper.test.ReconfigTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class QuorumPeerMainMultiAddressTest extends QuorumPeerTestBase {
private static final int FIRST_SERVER = 0;
private static final int SECOND_SERVER = 1;
private static final int THIRD_SERVER = 2;
private static final int FIRST_ADDRESS = 0;
private static final int SECOND_ADDRESS = 1;
private static final String UNREACHABLE_HOST = "invalid.hostname.unreachable.com";
private static final String IPV6_LOCALHOST = "[0:0:0:0:0:0:0:1]";
// IPv4 by default, change to IPV6_LOCALHOST to test with servers binding to IPv6
private String hostName = "127.0.0.1";
private int zNodeId = 0;
@Before
public void setUp() throws Exception {
ClientBase.setupTestEnv();
System.setProperty("zookeeper.DigestAuthenticationProvider.superDigest", "super:D/InIHSb7yEEbrWz8b9l71RjZJU="/* password is 'test'*/);
QuorumPeerConfig.setReconfigEnabled(true);
// just to get rid of the unrelated 'InstanceAlreadyExistsException' in the logs
System.setProperty("zookeeper.jmx.log4j.disable", "true");
}
@After
public void tearDown() throws Exception {
super.tearDown();
System.clearProperty("zookeeper.jmx.log4j.disable");
}
@Test
public void shouldStartClusterWithMultipleAddresses() throws Exception {
// we have three ZK servers, each server has two quorumPort and two electionPort registered
QuorumServerConfigBuilder quorumConfig = new QuorumServerConfigBuilder(hostName, 3, 2);
// we launch the three servers, each server having the same configuration
QuorumServerConfigBuilder builderForServer1 = new QuorumServerConfigBuilder(quorumConfig);
QuorumServerConfigBuilder builderForServer2 = new QuorumServerConfigBuilder(quorumConfig);
QuorumServerConfigBuilder builderForServer3 = new QuorumServerConfigBuilder(quorumConfig);
launchServers(Arrays.asList(builderForServer1, builderForServer2, builderForServer3));
checkIfZooKeeperQuorumWorks(quorumConfig);
}
@Test
public void shouldStartClusterWithMultipleAddresses_IPv6() throws Exception {
hostName = IPV6_LOCALHOST;
shouldStartClusterWithMultipleAddresses();
}
@Test
public void shouldStartClusterWhenSomeAddressesAreUnreachable() throws Exception {
// we have three ZK servers, each server has two quorumPort and two electionPort registered
// in the config we misconfigure one of the addresses for each servers
QuorumServerConfigBuilder quorumConfig = new QuorumServerConfigBuilder(hostName, 3, 2)
.changeHostName(FIRST_SERVER, SECOND_ADDRESS, UNREACHABLE_HOST)
.changeHostName(SECOND_SERVER, SECOND_ADDRESS, UNREACHABLE_HOST)
.changeHostName(THIRD_SERVER, SECOND_ADDRESS, UNREACHABLE_HOST);
// we prepare the same initial config for all the three servers
QuorumServerConfigBuilder builderForServer1 = new QuorumServerConfigBuilder(quorumConfig);
QuorumServerConfigBuilder builderForServer2 = new QuorumServerConfigBuilder(quorumConfig);
QuorumServerConfigBuilder builderForServer3 = new QuorumServerConfigBuilder(quorumConfig);
// we test here:
// - if the Leader can bind to the correct address and not die with BindException or
// SocketException for trying to bind to a wrong address / port
// - if the ZK server can 'select' the correct address to connect when trying to form a quorum
// with the other servers
launchServers(Arrays.asList(builderForServer1, builderForServer2, builderForServer3));
checkIfZooKeeperQuorumWorks(quorumConfig);
}
@Test
public void shouldStartClusterWhenSomeAddressesAreUnreachable_IPv6() throws Exception {
hostName = IPV6_LOCALHOST;
shouldStartClusterWhenSomeAddressesAreUnreachable();
}
@Test
public void shouldReconfigIncrementallyByAddingMoreAddresses() throws Exception {
// we have three ZK servers, each server has two quorumPort and two electionPort registered
QuorumServerConfigBuilder initialQuorumConfig = new QuorumServerConfigBuilder(hostName, 3, 2);
// we launch the three servers, each server should use the same initial config
launchServers(Arrays.asList(initialQuorumConfig, initialQuorumConfig, initialQuorumConfig));
checkIfZooKeeperQuorumWorks(initialQuorumConfig);
// we create a new config where we add a new address to each server with random available ports
QuorumServerConfigBuilder newQuorumConfig = new QuorumServerConfigBuilder(initialQuorumConfig)
.addNewServerAddress(FIRST_SERVER);
ZooKeeperAdmin zkAdmin = newZooKeeperAdmin(initialQuorumConfig);
// initiating a new incremental reconfig, by using the updated ports
ReconfigTest.reconfig(zkAdmin, newQuorumConfig.buildAsStringList(), null, null, -1);
checkIfZooKeeperQuorumWorks(newQuorumConfig);
}
@Test
public void shouldReconfigIncrementallyByDeletingSomeAddresses() throws Exception {
// we have three ZK servers, each server has three quorumPort and three electionPort registered
QuorumServerConfigBuilder initialQuorumConfig = new QuorumServerConfigBuilder(hostName, 3, 3);
// we launch the three servers, each server should use the same initial config
launchServers(Arrays.asList(initialQuorumConfig, initialQuorumConfig, initialQuorumConfig));
checkIfZooKeeperQuorumWorks(initialQuorumConfig);
// we create a new config where we delete a few address from each server
QuorumServerConfigBuilder newQuorumConfig = new QuorumServerConfigBuilder(initialQuorumConfig)
.deleteLastServerAddress(FIRST_SERVER)
.deleteLastServerAddress(SECOND_SERVER)
.deleteLastServerAddress(SECOND_SERVER)
.deleteLastServerAddress(THIRD_SERVER);
ZooKeeperAdmin zkAdmin = newZooKeeperAdmin(initialQuorumConfig);
// initiating a new incremental reconfig, by using the updated ports
ReconfigTest.reconfig(zkAdmin, newQuorumConfig.buildAsStringList(), null, null, -1);
checkIfZooKeeperQuorumWorks(newQuorumConfig);
}
@Test
public void shouldReconfigNonIncrementally() throws Exception {
// we have three ZK servers, each server has two quorumPort and two electionPort registered
QuorumServerConfigBuilder initialQuorumConfig = new QuorumServerConfigBuilder(hostName, 3, 2);
// we launch the three servers, each server should use the same initial config
launchServers(Arrays.asList(initialQuorumConfig, initialQuorumConfig, initialQuorumConfig));
checkIfZooKeeperQuorumWorks(initialQuorumConfig);
// we create a new config where we delete and add a few address for each server
QuorumServerConfigBuilder newQuorumConfig = new QuorumServerConfigBuilder(initialQuorumConfig)
.deleteLastServerAddress(FIRST_SERVER)
.deleteLastServerAddress(SECOND_SERVER)
.deleteLastServerAddress(SECOND_SERVER)
.deleteLastServerAddress(THIRD_SERVER)
.addNewServerAddress(SECOND_SERVER)
.addNewServerAddress(THIRD_SERVER);
ZooKeeperAdmin zkAdmin = newZooKeeperAdmin(initialQuorumConfig);
// initiating a new non-incremental reconfig, by using the updated ports
ReconfigTest.reconfig(zkAdmin, null, null, newQuorumConfig.buildAsStringList(), -1);
checkIfZooKeeperQuorumWorks(newQuorumConfig);
}
@Test
public void shouldReconfigIncrementally_IPv6() throws Exception {
hostName = IPV6_LOCALHOST;
// we have three ZK servers, each server has two quorumPort and two electionPort registered
QuorumServerConfigBuilder initialQuorumConfig = new QuorumServerConfigBuilder(hostName, 3, 2);
// we launch the three servers, each server should use the same initial config
launchServers(Arrays.asList(initialQuorumConfig, initialQuorumConfig, initialQuorumConfig));
checkIfZooKeeperQuorumWorks(initialQuorumConfig);
// we create a new config where we delete and add a few address for each server
QuorumServerConfigBuilder newQuorumConfig = new QuorumServerConfigBuilder(initialQuorumConfig)
.deleteLastServerAddress(FIRST_SERVER)
.deleteLastServerAddress(SECOND_SERVER)
.deleteLastServerAddress(SECOND_SERVER)
.deleteLastServerAddress(THIRD_SERVER)
.addNewServerAddress(SECOND_SERVER)
.addNewServerAddress(THIRD_SERVER);
ZooKeeperAdmin zkAdmin = newZooKeeperAdmin(initialQuorumConfig);
// initiating a new incremental reconfig, by using the updated ports
ReconfigTest.reconfig(zkAdmin, newQuorumConfig.buildAsStringList(), null, null, -1);
checkIfZooKeeperQuorumWorks(newQuorumConfig);
}
private void launchServers(List<QuorumServerConfigBuilder> builders) throws IOException, InterruptedException {
numServers = builders.size();
servers = new Servers();
servers.clientPorts = new int[numServers];
servers.mt = new MainThread[numServers];
servers.zk = new ZooKeeper[numServers];
for (int i = 0; i < numServers; i++) {
QuorumServerConfigBuilder quorumServerConfigBuilder = builders.get(i);
String quorumCfgSection = quorumServerConfigBuilder.build();
LOG.info(String.format("starting server %d with quorum config:\n%s", i, quorumCfgSection));
servers.clientPorts[i] = quorumServerConfigBuilder.getClientPort(i);
servers.mt[i] = new MainThread(i, servers.clientPorts[i], quorumCfgSection);
servers.mt[i].start();
servers.restartClient(i, this);
}
waitForAll(servers, ZooKeeper.States.CONNECTED);
for (int i = 0; i < numServers; i++) {
servers.zk[i].close(5000);
}
}
private void checkIfZooKeeperQuorumWorks(QuorumServerConfigBuilder builder) throws IOException,
InterruptedException, KeeperException {
LOG.info("starting to verify if Quorum works");
zNodeId += 1;
String zNodePath = "/foo_" + zNodeId;
ZooKeeper zk = connectToZkServer(builder, FIRST_SERVER);
zk.create(zNodePath, "foobar1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
assertEquals(new String(zk.getData(zNodePath, null, null)), "foobar1");
zk.close(1000);
zk = connectToZkServer(builder, SECOND_SERVER);
assertEquals(new String(zk.getData(zNodePath, null, null)), "foobar1");
zk.close(1000);
zk = connectToZkServer(builder, THIRD_SERVER);
assertEquals(new String(zk.getData(zNodePath, null, null)), "foobar1");
zk.close(1000);
LOG.info("Quorum verification finished successfully");
}
private ZooKeeper connectToZkServer(QuorumServerConfigBuilder builder, int serverId) throws IOException, InterruptedException {
QuorumServerConfigBuilder.ServerAddress server = builder.getServerAddress(serverId, FIRST_ADDRESS);
int clientPort = builder.getClientPort(serverId);
ZooKeeper zk = new ZooKeeper(server.getHost() + ":" + clientPort, ClientBase.CONNECTION_TIMEOUT, this);
waitForOne(zk, ZooKeeper.States.CONNECTED);
return zk;
}
private ZooKeeperAdmin newZooKeeperAdmin(
QuorumServerConfigBuilder quorumConfig) throws IOException {
ZooKeeperAdmin zkAdmin = new ZooKeeperAdmin(
hostName + ":" + quorumConfig.getClientPort(FIRST_SERVER),
ClientBase.CONNECTION_TIMEOUT,
DummyWatcher.INSTANCE);
zkAdmin.addAuthInfo("digest", "super:test".getBytes());
return zkAdmin;
}
}