|
@@ -19,93 +19,58 @@ package org.apache.hadoop.ha;
|
|
|
|
|
|
import static org.junit.Assert.*;
|
|
import static org.junit.Assert.*;
|
|
|
|
|
|
-import java.io.File;
|
|
|
|
-import java.net.InetSocketAddress;
|
|
|
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
|
|
|
import org.apache.commons.logging.impl.Log4JLogger;
|
|
import org.apache.commons.logging.impl.Log4JLogger;
|
|
import org.apache.hadoop.conf.Configuration;
|
|
import org.apache.hadoop.conf.Configuration;
|
|
-import org.apache.hadoop.fs.CommonConfigurationKeys;
|
|
|
|
import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
|
|
import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
|
|
|
|
+import org.apache.hadoop.ha.HAServiceProtocol.StateChangeRequestInfo;
|
|
import org.apache.hadoop.ha.HealthMonitor.State;
|
|
import org.apache.hadoop.ha.HealthMonitor.State;
|
|
-import org.apache.hadoop.test.MultithreadedTestUtil;
|
|
|
|
-import org.apache.hadoop.test.MultithreadedTestUtil.TestContext;
|
|
|
|
-import org.apache.hadoop.test.MultithreadedTestUtil.TestingThread;
|
|
|
|
|
|
+import org.apache.hadoop.ha.MiniZKFCCluster.DummyZKFC;
|
|
|
|
+import org.apache.hadoop.test.GenericTestUtils;
|
|
import org.apache.log4j.Level;
|
|
import org.apache.log4j.Level;
|
|
-import org.apache.zookeeper.KeeperException.NoNodeException;
|
|
|
|
|
|
+import org.apache.zookeeper.KeeperException;
|
|
|
|
+import org.apache.zookeeper.ZooKeeper;
|
|
import org.apache.zookeeper.data.Stat;
|
|
import org.apache.zookeeper.data.Stat;
|
|
-import org.apache.zookeeper.server.ZooKeeperServer;
|
|
|
|
-import org.apache.zookeeper.test.ClientBase;
|
|
|
|
|
|
+import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
|
|
import org.junit.Before;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.Test;
|
|
import org.mockito.Mockito;
|
|
import org.mockito.Mockito;
|
|
|
|
|
|
-import com.google.common.primitives.Ints;
|
|
|
|
-
|
|
|
|
-public class TestZKFailoverController extends ClientBase {
|
|
|
|
|
|
+public class TestZKFailoverController extends ClientBaseWithFixes {
|
|
private Configuration conf;
|
|
private Configuration conf;
|
|
- private DummyHAService svc1;
|
|
|
|
- private DummyHAService svc2;
|
|
|
|
- private TestContext ctx;
|
|
|
|
- private DummyZKFCThread thr1, thr2;
|
|
|
|
|
|
+ private MiniZKFCCluster cluster;
|
|
|
|
|
|
|
|
+ // Set up ZK digest-based credentials for the purposes of the tests,
|
|
|
|
+ // to make sure all of our functionality works with auth and ACLs
|
|
|
|
+ // present.
|
|
|
|
+ private static final String DIGEST_USER_PASS="test-user:test-password";
|
|
|
|
+ private static final String TEST_AUTH_GOOD =
|
|
|
|
+ "digest:" + DIGEST_USER_PASS;
|
|
|
|
+ private static final String DIGEST_USER_HASH;
|
|
static {
|
|
static {
|
|
- ((Log4JLogger)ActiveStandbyElector.LOG).getLogger().setLevel(Level.ALL);
|
|
|
|
|
|
+ try {
|
|
|
|
+ DIGEST_USER_HASH = DigestAuthenticationProvider.generateDigest(
|
|
|
|
+ DIGEST_USER_PASS);
|
|
|
|
+ } catch (NoSuchAlgorithmException e) {
|
|
|
|
+ throw new RuntimeException(e);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ private static final String TEST_ACL =
|
|
|
|
+ "digest:" + DIGEST_USER_HASH + ":rwcda";
|
|
|
|
|
|
- @Override
|
|
|
|
- public void setUp() throws Exception {
|
|
|
|
- // build.test.dir is used by zookeeper
|
|
|
|
- new File(System.getProperty("build.test.dir", "build")).mkdirs();
|
|
|
|
- super.setUp();
|
|
|
|
|
|
+ static {
|
|
|
|
+ ((Log4JLogger)ActiveStandbyElector.LOG).getLogger().setLevel(Level.ALL);
|
|
}
|
|
}
|
|
|
|
|
|
@Before
|
|
@Before
|
|
public void setupConfAndServices() {
|
|
public void setupConfAndServices() {
|
|
conf = new Configuration();
|
|
conf = new Configuration();
|
|
- conf.set(ZKFailoverController.ZK_QUORUM_KEY, hostPort);
|
|
|
|
- // Fast check interval so tests run faster
|
|
|
|
- conf.setInt(CommonConfigurationKeys.HA_HM_CHECK_INTERVAL_KEY, 50);
|
|
|
|
- conf.setInt(CommonConfigurationKeys.HA_HM_CONNECT_RETRY_INTERVAL_KEY, 50);
|
|
|
|
- conf.setInt(CommonConfigurationKeys.HA_HM_SLEEP_AFTER_DISCONNECT_KEY, 50);
|
|
|
|
- svc1 = new DummyHAService(HAServiceState.INITIALIZING,
|
|
|
|
- new InetSocketAddress("svc1", 1234));
|
|
|
|
- svc2 = new DummyHAService(HAServiceState.INITIALIZING,
|
|
|
|
- new InetSocketAddress("svc2", 1234));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Set up two services and their failover controllers. svc1 is started
|
|
|
|
- * first, so that it enters ACTIVE state, and then svc2 is started,
|
|
|
|
- * which enters STANDBY
|
|
|
|
- */
|
|
|
|
- private void setupFCs() throws Exception {
|
|
|
|
- // Format the base dir, should succeed
|
|
|
|
- assertEquals(0, runFC(svc1, "-formatZK"));
|
|
|
|
|
|
+ conf.set(ZKFailoverController.ZK_ACL_KEY, TEST_ACL);
|
|
|
|
+ conf.set(ZKFailoverController.ZK_AUTH_KEY, TEST_AUTH_GOOD);
|
|
|
|
|
|
- ctx = new MultithreadedTestUtil.TestContext();
|
|
|
|
- thr1 = new DummyZKFCThread(ctx, svc1);
|
|
|
|
- ctx.addThread(thr1);
|
|
|
|
- thr1.start();
|
|
|
|
-
|
|
|
|
- LOG.info("Waiting for svc1 to enter active state");
|
|
|
|
- waitForHAState(svc1, HAServiceState.ACTIVE);
|
|
|
|
-
|
|
|
|
- LOG.info("Adding svc2");
|
|
|
|
- thr2 = new DummyZKFCThread(ctx, svc2);
|
|
|
|
- thr2.start();
|
|
|
|
- waitForHAState(svc2, HAServiceState.STANDBY);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void stopFCs() throws Exception {
|
|
|
|
- if (thr1 != null) {
|
|
|
|
- thr1.interrupt();
|
|
|
|
- }
|
|
|
|
- if (thr2 != null) {
|
|
|
|
- thr2.interrupt();
|
|
|
|
- }
|
|
|
|
- if (ctx != null) {
|
|
|
|
- ctx.stop();
|
|
|
|
- }
|
|
|
|
|
|
+ conf.set(ZKFailoverController.ZK_QUORUM_KEY, hostPort);
|
|
|
|
+ this.cluster = new MiniZKFCCluster(conf, getServer(serverFactory));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -114,20 +79,104 @@ public class TestZKFailoverController extends ClientBase {
|
|
*/
|
|
*/
|
|
@Test(timeout=15000)
|
|
@Test(timeout=15000)
|
|
public void testFormatZK() throws Exception {
|
|
public void testFormatZK() throws Exception {
|
|
|
|
+ DummyHAService svc = cluster.getService(1);
|
|
// Run without formatting the base dir,
|
|
// Run without formatting the base dir,
|
|
// should barf
|
|
// should barf
|
|
assertEquals(ZKFailoverController.ERR_CODE_NO_PARENT_ZNODE,
|
|
assertEquals(ZKFailoverController.ERR_CODE_NO_PARENT_ZNODE,
|
|
- runFC(svc1));
|
|
|
|
|
|
+ runFC(svc));
|
|
|
|
|
|
// Format the base dir, should succeed
|
|
// Format the base dir, should succeed
|
|
- assertEquals(0, runFC(svc1, "-formatZK"));
|
|
|
|
|
|
+ assertEquals(0, runFC(svc, "-formatZK"));
|
|
|
|
|
|
// Should fail to format if already formatted
|
|
// Should fail to format if already formatted
|
|
assertEquals(ZKFailoverController.ERR_CODE_FORMAT_DENIED,
|
|
assertEquals(ZKFailoverController.ERR_CODE_FORMAT_DENIED,
|
|
- runFC(svc1, "-formatZK", "-nonInteractive"));
|
|
|
|
|
|
+ runFC(svc, "-formatZK", "-nonInteractive"));
|
|
|
|
|
|
// Unless '-force' is on
|
|
// Unless '-force' is on
|
|
- assertEquals(0, runFC(svc1, "-formatZK", "-force"));
|
|
|
|
|
|
+ assertEquals(0, runFC(svc, "-formatZK", "-force"));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Test that if ZooKeeper is not running, the correct error
|
|
|
|
+ * code is returned.
|
|
|
|
+ */
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testNoZK() throws Exception {
|
|
|
|
+ stopServer();
|
|
|
|
+ DummyHAService svc = cluster.getService(1);
|
|
|
|
+ assertEquals(ZKFailoverController.ERR_CODE_NO_ZK,
|
|
|
|
+ runFC(svc));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test
|
|
|
|
+ public void testFormatOneClusterLeavesOtherClustersAlone() throws Exception {
|
|
|
|
+ DummyHAService svc = cluster.getService(1);
|
|
|
|
+
|
|
|
|
+ DummyZKFC zkfcInOtherCluster = new DummyZKFC(conf, cluster.getService(1)) {
|
|
|
|
+ @Override
|
|
|
|
+ protected String getScopeInsideParentNode() {
|
|
|
|
+ return "other-scope";
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // Run without formatting the base dir,
|
|
|
|
+ // should barf
|
|
|
|
+ assertEquals(ZKFailoverController.ERR_CODE_NO_PARENT_ZNODE,
|
|
|
|
+ runFC(svc));
|
|
|
|
+
|
|
|
|
+ // Format the base dir, should succeed
|
|
|
|
+ assertEquals(0, runFC(svc, "-formatZK"));
|
|
|
|
+
|
|
|
|
+ // Run the other cluster without formatting, should barf because
|
|
|
|
+ // it uses a different parent znode
|
|
|
|
+ assertEquals(ZKFailoverController.ERR_CODE_NO_PARENT_ZNODE,
|
|
|
|
+ zkfcInOtherCluster.run(new String[]{}));
|
|
|
|
+
|
|
|
|
+ // Should succeed in formatting the second cluster
|
|
|
|
+ assertEquals(0, zkfcInOtherCluster.run(new String[]{"-formatZK"}));
|
|
|
|
+
|
|
|
|
+ // But should not have deleted the original base node from the first
|
|
|
|
+ // cluster
|
|
|
|
+ assertEquals(ZKFailoverController.ERR_CODE_FORMAT_DENIED,
|
|
|
|
+ runFC(svc, "-formatZK", "-nonInteractive"));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Test that automatic failover won't run against a target that hasn't
|
|
|
|
+ * explicitly enabled the feature.
|
|
|
|
+ */
|
|
|
|
+ @Test(timeout=10000)
|
|
|
|
+ public void testWontRunWhenAutoFailoverDisabled() throws Exception {
|
|
|
|
+ DummyHAService svc = cluster.getService(1);
|
|
|
|
+ svc = Mockito.spy(svc);
|
|
|
|
+ Mockito.doReturn(false).when(svc).isAutoFailoverEnabled();
|
|
|
|
+
|
|
|
|
+ assertEquals(ZKFailoverController.ERR_CODE_AUTO_FAILOVER_NOT_ENABLED,
|
|
|
|
+ runFC(svc, "-formatZK"));
|
|
|
|
+ assertEquals(ZKFailoverController.ERR_CODE_AUTO_FAILOVER_NOT_ENABLED,
|
|
|
|
+ runFC(svc));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Test that, if ACLs are specified in the configuration, that
|
|
|
|
+ * it sets the ACLs when formatting the parent node.
|
|
|
|
+ */
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testFormatSetsAcls() throws Exception {
|
|
|
|
+ // Format the base dir, should succeed
|
|
|
|
+ DummyHAService svc = cluster.getService(1);
|
|
|
|
+ assertEquals(0, runFC(svc, "-formatZK"));
|
|
|
|
+
|
|
|
|
+ ZooKeeper otherClient = createClient();
|
|
|
|
+ try {
|
|
|
|
+ // client without auth should not be able to read it
|
|
|
|
+ Stat stat = new Stat();
|
|
|
|
+ otherClient.getData(ZKFailoverController.ZK_PARENT_ZNODE_DEFAULT,
|
|
|
|
+ false, stat);
|
|
|
|
+ fail("Was able to read data without authenticating!");
|
|
|
|
+ } catch (KeeperException.NoAuthException nae) {
|
|
|
|
+ // expected
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -136,14 +185,14 @@ public class TestZKFailoverController extends ClientBase {
|
|
*/
|
|
*/
|
|
@Test(timeout=15000)
|
|
@Test(timeout=15000)
|
|
public void testFencingMustBeConfigured() throws Exception {
|
|
public void testFencingMustBeConfigured() throws Exception {
|
|
- svc1 = Mockito.spy(svc1);
|
|
|
|
|
|
+ DummyHAService svc = Mockito.spy(cluster.getService(0));
|
|
Mockito.doThrow(new BadFencingConfigurationException("no fencing"))
|
|
Mockito.doThrow(new BadFencingConfigurationException("no fencing"))
|
|
- .when(svc1).checkFencingConfigured();
|
|
|
|
|
|
+ .when(svc).checkFencingConfigured();
|
|
// Format the base dir, should succeed
|
|
// Format the base dir, should succeed
|
|
- assertEquals(0, runFC(svc1, "-formatZK"));
|
|
|
|
|
|
+ assertEquals(0, runFC(svc, "-formatZK"));
|
|
// Try to run the actual FC, should fail without a fencer
|
|
// Try to run the actual FC, should fail without a fencer
|
|
assertEquals(ZKFailoverController.ERR_CODE_NO_FENCER,
|
|
assertEquals(ZKFailoverController.ERR_CODE_NO_FENCER,
|
|
- runFC(svc1));
|
|
|
|
|
|
+ runFC(svc));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -155,66 +204,50 @@ public class TestZKFailoverController extends ClientBase {
|
|
@Test(timeout=15000)
|
|
@Test(timeout=15000)
|
|
public void testAutoFailoverOnBadHealth() throws Exception {
|
|
public void testAutoFailoverOnBadHealth() throws Exception {
|
|
try {
|
|
try {
|
|
- setupFCs();
|
|
|
|
|
|
+ cluster.start();
|
|
|
|
+ DummyHAService svc1 = cluster.getService(1);
|
|
|
|
|
|
- LOG.info("Faking svc1 unhealthy, should failover to svc2");
|
|
|
|
- svc1.isHealthy = false;
|
|
|
|
- LOG.info("Waiting for svc1 to enter standby state");
|
|
|
|
- waitForHAState(svc1, HAServiceState.STANDBY);
|
|
|
|
- waitForHAState(svc2, HAServiceState.ACTIVE);
|
|
|
|
|
|
+ LOG.info("Faking svc0 unhealthy, should failover to svc1");
|
|
|
|
+ cluster.setHealthy(0, false);
|
|
|
|
+
|
|
|
|
+ LOG.info("Waiting for svc0 to enter standby state");
|
|
|
|
+ cluster.waitForHAState(0, HAServiceState.STANDBY);
|
|
|
|
+ cluster.waitForHAState(1, HAServiceState.ACTIVE);
|
|
|
|
|
|
- LOG.info("Allowing svc1 to be healthy again, making svc2 unreachable " +
|
|
|
|
|
|
+ LOG.info("Allowing svc0 to be healthy again, making svc1 unreachable " +
|
|
"and fail to gracefully go to standby");
|
|
"and fail to gracefully go to standby");
|
|
- svc1.isHealthy = true;
|
|
|
|
- svc2.actUnreachable = true;
|
|
|
|
-
|
|
|
|
- // Allow fencing to succeed
|
|
|
|
- Mockito.doReturn(true).when(svc2.fencer).fence(Mockito.same(svc2));
|
|
|
|
- // Should fail back to svc1 at this point
|
|
|
|
- waitForHAState(svc1, HAServiceState.ACTIVE);
|
|
|
|
- // and fence svc2
|
|
|
|
- Mockito.verify(svc2.fencer).fence(Mockito.same(svc2));
|
|
|
|
|
|
+ cluster.setUnreachable(1, true);
|
|
|
|
+ cluster.setHealthy(0, true);
|
|
|
|
+
|
|
|
|
+ // Should fail back to svc0 at this point
|
|
|
|
+ cluster.waitForHAState(0, HAServiceState.ACTIVE);
|
|
|
|
+ // and fence svc1
|
|
|
|
+ Mockito.verify(svc1.fencer).fence(Mockito.same(svc1));
|
|
} finally {
|
|
} finally {
|
|
- stopFCs();
|
|
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@Test(timeout=15000)
|
|
@Test(timeout=15000)
|
|
public void testAutoFailoverOnLostZKSession() throws Exception {
|
|
public void testAutoFailoverOnLostZKSession() throws Exception {
|
|
try {
|
|
try {
|
|
- setupFCs();
|
|
|
|
|
|
+ cluster.start();
|
|
|
|
|
|
- // Expire svc1, it should fail over to svc2
|
|
|
|
- expireAndVerifyFailover(thr1, thr2);
|
|
|
|
|
|
+ // Expire svc0, it should fail over to svc1
|
|
|
|
+ cluster.expireAndVerifyFailover(0, 1);
|
|
|
|
|
|
- // Expire svc2, it should fail back to svc1
|
|
|
|
- expireAndVerifyFailover(thr2, thr1);
|
|
|
|
|
|
+ // Expire svc1, it should fail back to svc0
|
|
|
|
+ cluster.expireAndVerifyFailover(1, 0);
|
|
|
|
|
|
LOG.info("======= Running test cases second time to test " +
|
|
LOG.info("======= Running test cases second time to test " +
|
|
"re-establishment =========");
|
|
"re-establishment =========");
|
|
- // Expire svc1, it should fail over to svc2
|
|
|
|
- expireAndVerifyFailover(thr1, thr2);
|
|
|
|
|
|
+ // Expire svc0, it should fail over to svc1
|
|
|
|
+ cluster.expireAndVerifyFailover(0, 1);
|
|
|
|
|
|
- // Expire svc2, it should fail back to svc1
|
|
|
|
- expireAndVerifyFailover(thr2, thr1);
|
|
|
|
|
|
+ // Expire svc1, it should fail back to svc0
|
|
|
|
+ cluster.expireAndVerifyFailover(1, 0);
|
|
} finally {
|
|
} finally {
|
|
- stopFCs();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void expireAndVerifyFailover(DummyZKFCThread fromThr,
|
|
|
|
- DummyZKFCThread toThr) throws Exception {
|
|
|
|
- DummyHAService fromSvc = fromThr.zkfc.localTarget;
|
|
|
|
- DummyHAService toSvc = toThr.zkfc.localTarget;
|
|
|
|
-
|
|
|
|
- fromThr.zkfc.getElectorForTests().preventSessionReestablishmentForTests();
|
|
|
|
- try {
|
|
|
|
- expireActiveLockHolder(fromSvc);
|
|
|
|
-
|
|
|
|
- waitForHAState(fromSvc, HAServiceState.STANDBY);
|
|
|
|
- waitForHAState(toSvc, HAServiceState.ACTIVE);
|
|
|
|
- } finally {
|
|
|
|
- fromThr.zkfc.getElectorForTests().allowSessionReestablishmentForTests();
|
|
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -225,33 +258,32 @@ public class TestZKFailoverController extends ClientBase {
|
|
@Test(timeout=15000)
|
|
@Test(timeout=15000)
|
|
public void testDontFailoverToUnhealthyNode() throws Exception {
|
|
public void testDontFailoverToUnhealthyNode() throws Exception {
|
|
try {
|
|
try {
|
|
- setupFCs();
|
|
|
|
|
|
+ cluster.start();
|
|
|
|
|
|
- // Make svc2 unhealthy, and wait for its FC to notice the bad health.
|
|
|
|
- svc2.isHealthy = false;
|
|
|
|
- waitForHealthState(thr2.zkfc,
|
|
|
|
- HealthMonitor.State.SERVICE_UNHEALTHY);
|
|
|
|
|
|
+ // Make svc1 unhealthy, and wait for its FC to notice the bad health.
|
|
|
|
+ cluster.setHealthy(1, false);
|
|
|
|
+ cluster.waitForHealthState(1, HealthMonitor.State.SERVICE_UNHEALTHY);
|
|
|
|
|
|
- // Expire svc1
|
|
|
|
- thr1.zkfc.getElectorForTests().preventSessionReestablishmentForTests();
|
|
|
|
|
|
+ // Expire svc0
|
|
|
|
+ cluster.getElector(0).preventSessionReestablishmentForTests();
|
|
try {
|
|
try {
|
|
- expireActiveLockHolder(svc1);
|
|
|
|
|
|
+ cluster.expireActiveLockHolder(0);
|
|
|
|
|
|
- LOG.info("Expired svc1's ZK session. Waiting a second to give svc2" +
|
|
|
|
|
|
+ LOG.info("Expired svc0's ZK session. Waiting a second to give svc1" +
|
|
" a chance to take the lock, if it is ever going to.");
|
|
" a chance to take the lock, if it is ever going to.");
|
|
Thread.sleep(1000);
|
|
Thread.sleep(1000);
|
|
|
|
|
|
// Ensure that no one holds the lock.
|
|
// Ensure that no one holds the lock.
|
|
- waitForActiveLockHolder(null);
|
|
|
|
|
|
+ cluster.waitForActiveLockHolder(null);
|
|
|
|
|
|
} finally {
|
|
} finally {
|
|
- LOG.info("Allowing svc1's elector to re-establish its connection");
|
|
|
|
- thr1.zkfc.getElectorForTests().allowSessionReestablishmentForTests();
|
|
|
|
|
|
+ LOG.info("Allowing svc0's elector to re-establish its connection");
|
|
|
|
+ cluster.getElector(0).allowSessionReestablishmentForTests();
|
|
}
|
|
}
|
|
- // svc1 should get the lock again
|
|
|
|
- waitForActiveLockHolder(svc1);
|
|
|
|
|
|
+ // svc0 should get the lock again
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
} finally {
|
|
} finally {
|
|
- stopFCs();
|
|
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -262,29 +294,38 @@ public class TestZKFailoverController extends ClientBase {
|
|
@Test(timeout=15000)
|
|
@Test(timeout=15000)
|
|
public void testBecomingActiveFails() throws Exception {
|
|
public void testBecomingActiveFails() throws Exception {
|
|
try {
|
|
try {
|
|
- setupFCs();
|
|
|
|
|
|
+ cluster.start();
|
|
|
|
+ DummyHAService svc1 = cluster.getService(1);
|
|
|
|
|
|
- LOG.info("Making svc2 fail to become active");
|
|
|
|
- svc2.failToBecomeActive = true;
|
|
|
|
|
|
+ LOG.info("Making svc1 fail to become active");
|
|
|
|
+ cluster.setFailToBecomeActive(1, true);
|
|
|
|
|
|
- LOG.info("Faking svc1 unhealthy, should NOT successfully " +
|
|
|
|
- "failover to svc2");
|
|
|
|
- svc1.isHealthy = false;
|
|
|
|
- waitForHealthState(thr1.zkfc, State.SERVICE_UNHEALTHY);
|
|
|
|
- waitForActiveLockHolder(null);
|
|
|
|
|
|
+ LOG.info("Faking svc0 unhealthy, should NOT successfully " +
|
|
|
|
+ "failover to svc1");
|
|
|
|
+ cluster.setHealthy(0, false);
|
|
|
|
+ cluster.waitForHealthState(0, State.SERVICE_UNHEALTHY);
|
|
|
|
+ cluster.waitForActiveLockHolder(null);
|
|
|
|
|
|
- Mockito.verify(svc2.proxy).transitionToActive();
|
|
|
|
|
|
+
|
|
|
|
+ Mockito.verify(svc1.proxy, Mockito.timeout(2000).atLeastOnce())
|
|
|
|
+ .transitionToActive(Mockito.<StateChangeRequestInfo>any());
|
|
|
|
|
|
- waitForHAState(svc1, HAServiceState.STANDBY);
|
|
|
|
- waitForHAState(svc2, HAServiceState.STANDBY);
|
|
|
|
|
|
+ cluster.waitForHAState(0, HAServiceState.STANDBY);
|
|
|
|
+ cluster.waitForHAState(1, HAServiceState.STANDBY);
|
|
|
|
+
|
|
|
|
+ LOG.info("Faking svc0 healthy again, should go back to svc0");
|
|
|
|
+ cluster.setHealthy(0, true);
|
|
|
|
+ cluster.waitForHAState(0, HAServiceState.ACTIVE);
|
|
|
|
+ cluster.waitForHAState(1, HAServiceState.STANDBY);
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
|
|
- LOG.info("Faking svc1 healthy again, should go back to svc1");
|
|
|
|
- svc1.isHealthy = true;
|
|
|
|
- waitForHAState(svc1, HAServiceState.ACTIVE);
|
|
|
|
- waitForHAState(svc2, HAServiceState.STANDBY);
|
|
|
|
- waitForActiveLockHolder(svc1);
|
|
|
|
|
|
+ // Ensure that we can fail back to svc1 once it it is able
|
|
|
|
+ // to become active (e.g the admin has restarted it)
|
|
|
|
+ LOG.info("Allowing svc1 to become active, expiring svc0");
|
|
|
|
+ svc1.failToBecomeActive = false;
|
|
|
|
+ cluster.expireAndVerifyFailover(0, 1);
|
|
} finally {
|
|
} finally {
|
|
- stopFCs();
|
|
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -296,27 +337,25 @@ public class TestZKFailoverController extends ClientBase {
|
|
@Test(timeout=15000)
|
|
@Test(timeout=15000)
|
|
public void testZooKeeperFailure() throws Exception {
|
|
public void testZooKeeperFailure() throws Exception {
|
|
try {
|
|
try {
|
|
- setupFCs();
|
|
|
|
|
|
+ cluster.start();
|
|
|
|
|
|
// Record initial ZK sessions
|
|
// Record initial ZK sessions
|
|
- long session1 = thr1.zkfc.getElectorForTests().getZKSessionIdForTests();
|
|
|
|
- long session2 = thr2.zkfc.getElectorForTests().getZKSessionIdForTests();
|
|
|
|
|
|
+ long session0 = cluster.getElector(0).getZKSessionIdForTests();
|
|
|
|
+ long session1 = cluster.getElector(1).getZKSessionIdForTests();
|
|
|
|
|
|
LOG.info("====== Stopping ZK server");
|
|
LOG.info("====== Stopping ZK server");
|
|
stopServer();
|
|
stopServer();
|
|
waitForServerDown(hostPort, CONNECTION_TIMEOUT);
|
|
waitForServerDown(hostPort, CONNECTION_TIMEOUT);
|
|
|
|
|
|
LOG.info("====== Waiting for services to enter NEUTRAL mode");
|
|
LOG.info("====== Waiting for services to enter NEUTRAL mode");
|
|
- ActiveStandbyElectorTestUtil.waitForElectorState(ctx,
|
|
|
|
- thr1.zkfc.getElectorForTests(),
|
|
|
|
|
|
+ cluster.waitForElectorState(0,
|
|
ActiveStandbyElector.State.NEUTRAL);
|
|
ActiveStandbyElector.State.NEUTRAL);
|
|
- ActiveStandbyElectorTestUtil.waitForElectorState(ctx,
|
|
|
|
- thr2.zkfc.getElectorForTests(),
|
|
|
|
|
|
+ cluster.waitForElectorState(1,
|
|
ActiveStandbyElector.State.NEUTRAL);
|
|
ActiveStandbyElector.State.NEUTRAL);
|
|
|
|
|
|
LOG.info("====== Checking that the services didn't change HA state");
|
|
LOG.info("====== Checking that the services didn't change HA state");
|
|
- assertEquals(HAServiceState.ACTIVE, svc1.state);
|
|
|
|
- assertEquals(HAServiceState.STANDBY, svc2.state);
|
|
|
|
|
|
+ assertEquals(HAServiceState.ACTIVE, cluster.getService(0).state);
|
|
|
|
+ assertEquals(HAServiceState.STANDBY, cluster.getService(1).state);
|
|
|
|
|
|
LOG.info("====== Restarting server");
|
|
LOG.info("====== Restarting server");
|
|
startServer();
|
|
startServer();
|
|
@@ -324,134 +363,224 @@ public class TestZKFailoverController extends ClientBase {
|
|
|
|
|
|
// Nodes should go back to their original states, since they re-obtain
|
|
// Nodes should go back to their original states, since they re-obtain
|
|
// the same sessions.
|
|
// the same sessions.
|
|
- ActiveStandbyElectorTestUtil.waitForElectorState(ctx,
|
|
|
|
- thr1.zkfc.getElectorForTests(),
|
|
|
|
- ActiveStandbyElector.State.ACTIVE);
|
|
|
|
- ActiveStandbyElectorTestUtil.waitForElectorState(ctx,
|
|
|
|
- thr2.zkfc.getElectorForTests(),
|
|
|
|
- ActiveStandbyElector.State.STANDBY);
|
|
|
|
|
|
+ cluster.waitForElectorState(0, ActiveStandbyElector.State.ACTIVE);
|
|
|
|
+ cluster.waitForElectorState(1, ActiveStandbyElector.State.STANDBY);
|
|
// Check HA states didn't change.
|
|
// Check HA states didn't change.
|
|
- ActiveStandbyElectorTestUtil.waitForElectorState(ctx,
|
|
|
|
- thr1.zkfc.getElectorForTests(),
|
|
|
|
- ActiveStandbyElector.State.ACTIVE);
|
|
|
|
- ActiveStandbyElectorTestUtil.waitForElectorState(ctx,
|
|
|
|
- thr2.zkfc.getElectorForTests(),
|
|
|
|
- ActiveStandbyElector.State.STANDBY);
|
|
|
|
|
|
+ cluster.waitForHAState(0, HAServiceState.ACTIVE);
|
|
|
|
+ cluster.waitForHAState(1, HAServiceState.STANDBY);
|
|
|
|
+
|
|
// Check they re-used the same sessions and didn't spuriously reconnect
|
|
// Check they re-used the same sessions and didn't spuriously reconnect
|
|
|
|
+ assertEquals(session0,
|
|
|
|
+ cluster.getElector(0).getZKSessionIdForTests());
|
|
assertEquals(session1,
|
|
assertEquals(session1,
|
|
- thr1.zkfc.getElectorForTests().getZKSessionIdForTests());
|
|
|
|
- assertEquals(session2,
|
|
|
|
- thr2.zkfc.getElectorForTests().getZKSessionIdForTests());
|
|
|
|
|
|
+ cluster.getElector(1).getZKSessionIdForTests());
|
|
} finally {
|
|
} finally {
|
|
- stopFCs();
|
|
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Expire the ZK session of the given service. This requires
|
|
|
|
- * (and asserts) that the given service be the current active.
|
|
|
|
- * @throws NoNodeException if no service holds the lock
|
|
|
|
- */
|
|
|
|
- private void expireActiveLockHolder(DummyHAService expectedActive)
|
|
|
|
- throws NoNodeException {
|
|
|
|
- ZooKeeperServer zks = getServer(serverFactory);
|
|
|
|
- Stat stat = new Stat();
|
|
|
|
- byte[] data = zks.getZKDatabase().getData(
|
|
|
|
- ZKFailoverController.ZK_PARENT_ZNODE_DEFAULT + "/" +
|
|
|
|
- ActiveStandbyElector.LOCK_FILENAME, stat, null);
|
|
|
|
-
|
|
|
|
- assertArrayEquals(Ints.toByteArray(expectedActive.index), data);
|
|
|
|
- long session = stat.getEphemeralOwner();
|
|
|
|
- LOG.info("Expiring svc " + expectedActive + "'s zookeeper session " + session);
|
|
|
|
- zks.closeSession(session);
|
|
|
|
- }
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Wait for the given HA service to enter the given HA state.
|
|
|
|
|
|
+ * Test that the ZKFC can gracefully cede its active status.
|
|
*/
|
|
*/
|
|
- private void waitForHAState(DummyHAService svc, HAServiceState state)
|
|
|
|
- throws Exception {
|
|
|
|
- while (svc.state != state) {
|
|
|
|
- ctx.checkException();
|
|
|
|
- Thread.sleep(50);
|
|
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testCedeActive() throws Exception {
|
|
|
|
+ try {
|
|
|
|
+ cluster.start();
|
|
|
|
+ DummyZKFC zkfc = cluster.getZkfc(0);
|
|
|
|
+ // It should be in active to start.
|
|
|
|
+ assertEquals(ActiveStandbyElector.State.ACTIVE,
|
|
|
|
+ zkfc.getElectorForTests().getStateForTests());
|
|
|
|
+
|
|
|
|
+ // Ask it to cede active for 3 seconds. It should respond promptly
|
|
|
|
+ // (i.e. the RPC itself should not take 3 seconds!)
|
|
|
|
+ ZKFCProtocol proxy = zkfc.getLocalTarget().getZKFCProxy(conf, 5000);
|
|
|
|
+ long st = System.currentTimeMillis();
|
|
|
|
+ proxy.cedeActive(3000);
|
|
|
|
+ long et = System.currentTimeMillis();
|
|
|
|
+ assertTrue("RPC to cedeActive took " + (et - st) + " ms",
|
|
|
|
+ et - st < 1000);
|
|
|
|
+
|
|
|
|
+ // Should be in "INIT" state since it's not in the election
|
|
|
|
+ // at this point.
|
|
|
|
+ assertEquals(ActiveStandbyElector.State.INIT,
|
|
|
|
+ zkfc.getElectorForTests().getStateForTests());
|
|
|
|
+
|
|
|
|
+ // After the prescribed 3 seconds, should go into STANDBY state,
|
|
|
|
+ // since the other node in the cluster would have taken ACTIVE.
|
|
|
|
+ cluster.waitForElectorState(0, ActiveStandbyElector.State.STANDBY);
|
|
|
|
+ long et2 = System.currentTimeMillis();
|
|
|
|
+ assertTrue("Should take ~3 seconds to rejoin. Only took " + (et2 - et) +
|
|
|
|
+ "ms before rejoining.",
|
|
|
|
+ et2 - et > 2800);
|
|
|
|
+ } finally {
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Wait for the ZKFC to be notified of a change in health state.
|
|
|
|
- */
|
|
|
|
- private void waitForHealthState(DummyZKFC zkfc, State state)
|
|
|
|
- throws Exception {
|
|
|
|
- while (zkfc.getLastHealthState() != state) {
|
|
|
|
- ctx.checkException();
|
|
|
|
- Thread.sleep(50);
|
|
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testGracefulFailover() throws Exception {
|
|
|
|
+ try {
|
|
|
|
+ cluster.start();
|
|
|
|
+
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
+ cluster.getService(1).getZKFCProxy(conf, 5000).gracefulFailover();
|
|
|
|
+ cluster.waitForActiveLockHolder(1);
|
|
|
|
+ cluster.getService(0).getZKFCProxy(conf, 5000).gracefulFailover();
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
+
|
|
|
|
+ assertEquals(0, cluster.getService(0).fenceCount);
|
|
|
|
+ assertEquals(0, cluster.getService(1).fenceCount);
|
|
|
|
+ } finally {
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testGracefulFailoverToUnhealthy() throws Exception {
|
|
|
|
+ try {
|
|
|
|
+ cluster.start();
|
|
|
|
|
|
- /**
|
|
|
|
- * Wait for the given HA service to become the active lock holder.
|
|
|
|
- * If the passed svc is null, waits for there to be no active
|
|
|
|
- * lock holder.
|
|
|
|
- */
|
|
|
|
- private void waitForActiveLockHolder(DummyHAService svc)
|
|
|
|
- throws Exception {
|
|
|
|
- ZooKeeperServer zks = getServer(serverFactory);
|
|
|
|
- ActiveStandbyElectorTestUtil.waitForActiveLockData(ctx, zks,
|
|
|
|
- ZKFailoverController.ZK_PARENT_ZNODE_DEFAULT,
|
|
|
|
- (svc == null) ? null : Ints.toByteArray(svc.index));
|
|
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
+
|
|
|
|
+ // Mark it unhealthy, wait for it to exit election
|
|
|
|
+ cluster.setHealthy(1, false);
|
|
|
|
+ cluster.waitForElectorState(1, ActiveStandbyElector.State.INIT);
|
|
|
|
+
|
|
|
|
+ // Ask for failover, it should fail, because it's unhealthy
|
|
|
|
+ try {
|
|
|
|
+ cluster.getService(1).getZKFCProxy(conf, 5000).gracefulFailover();
|
|
|
|
+ fail("Did not fail to graceful failover to unhealthy service!");
|
|
|
|
+ } catch (ServiceFailedException sfe) {
|
|
|
|
+ GenericTestUtils.assertExceptionContains(
|
|
|
|
+ cluster.getService(1).toString() +
|
|
|
|
+ " is not currently healthy.", sfe);
|
|
|
|
+ }
|
|
|
|
+ } finally {
|
|
|
|
+ cluster.stop();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testGracefulFailoverFailBecomingActive() throws Exception {
|
|
|
|
+ try {
|
|
|
|
+ cluster.start();
|
|
|
|
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
+ cluster.setFailToBecomeActive(1, true);
|
|
|
|
+
|
|
|
|
+ // Ask for failover, it should fail and report back to user.
|
|
|
|
+ try {
|
|
|
|
+ cluster.getService(1).getZKFCProxy(conf, 5000).gracefulFailover();
|
|
|
|
+ fail("Did not fail to graceful failover when target failed " +
|
|
|
|
+ "to become active!");
|
|
|
|
+ } catch (ServiceFailedException sfe) {
|
|
|
|
+ GenericTestUtils.assertExceptionContains(
|
|
|
|
+ "Couldn't make " + cluster.getService(1) + " active", sfe);
|
|
|
|
+ GenericTestUtils.assertExceptionContains(
|
|
|
|
+ "injected failure", sfe);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // No fencing
|
|
|
|
+ assertEquals(0, cluster.getService(0).fenceCount);
|
|
|
|
+ assertEquals(0, cluster.getService(1).fenceCount);
|
|
|
|
|
|
- private int runFC(DummyHAService target, String ... args) throws Exception {
|
|
|
|
- DummyZKFC zkfc = new DummyZKFC(target);
|
|
|
|
- zkfc.setConf(conf);
|
|
|
|
- return zkfc.run(args);
|
|
|
|
|
|
+ // Service 0 should go back to being active after the failed failover
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
+ } finally {
|
|
|
|
+ cluster.stop();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Test-thread which runs a ZK Failover Controller corresponding
|
|
|
|
- * to a given dummy service.
|
|
|
|
- */
|
|
|
|
- private class DummyZKFCThread extends TestingThread {
|
|
|
|
- private final DummyZKFC zkfc;
|
|
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testGracefulFailoverFailBecomingStandby() throws Exception {
|
|
|
|
+ try {
|
|
|
|
+ cluster.start();
|
|
|
|
|
|
- public DummyZKFCThread(TestContext ctx, DummyHAService svc) {
|
|
|
|
- super(ctx);
|
|
|
|
- this.zkfc = new DummyZKFC(svc);
|
|
|
|
- zkfc.setConf(conf);
|
|
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
+
|
|
|
|
+ // Ask for failover when old node fails to transition to standby.
|
|
|
|
+ // This should trigger fencing, since the cedeActive() command
|
|
|
|
+ // still works, but leaves the breadcrumb in place.
|
|
|
|
+ cluster.setFailToBecomeStandby(0, true);
|
|
|
|
+ cluster.getService(1).getZKFCProxy(conf, 5000).gracefulFailover();
|
|
|
|
+
|
|
|
|
+ // Check that the old node was fenced
|
|
|
|
+ assertEquals(1, cluster.getService(0).fenceCount);
|
|
|
|
+ } finally {
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Test(timeout=15000)
|
|
|
|
+ public void testGracefulFailoverFailBecomingStandbyAndFailFence()
|
|
|
|
+ throws Exception {
|
|
|
|
+ try {
|
|
|
|
+ cluster.start();
|
|
|
|
+
|
|
|
|
+ cluster.waitForActiveLockHolder(0);
|
|
|
|
+
|
|
|
|
+ // Ask for failover when old node fails to transition to standby.
|
|
|
|
+ // This should trigger fencing, since the cedeActive() command
|
|
|
|
+ // still works, but leaves the breadcrumb in place.
|
|
|
|
+ cluster.setFailToBecomeStandby(0, true);
|
|
|
|
+ cluster.setFailToFence(0, true);
|
|
|
|
|
|
- @Override
|
|
|
|
- public void doWork() throws Exception {
|
|
|
|
try {
|
|
try {
|
|
- assertEquals(0, zkfc.run(new String[0]));
|
|
|
|
- } catch (InterruptedException ie) {
|
|
|
|
- // Interrupted by main thread, that's OK.
|
|
|
|
|
|
+ cluster.getService(1).getZKFCProxy(conf, 5000).gracefulFailover();
|
|
|
|
+ fail("Failover should have failed when old node wont fence");
|
|
|
|
+ } catch (ServiceFailedException sfe) {
|
|
|
|
+ GenericTestUtils.assertExceptionContains(
|
|
|
|
+ "Unable to fence " + cluster.getService(0), sfe);
|
|
}
|
|
}
|
|
|
|
+ } finally {
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- private static class DummyZKFC extends ZKFailoverController {
|
|
|
|
- private final DummyHAService localTarget;
|
|
|
|
-
|
|
|
|
- public DummyZKFC(DummyHAService localTarget) {
|
|
|
|
- this.localTarget = localTarget;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
- protected byte[] targetToData(HAServiceTarget target) {
|
|
|
|
- return Ints.toByteArray(((DummyHAService)target).index);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- protected HAServiceTarget dataToTarget(byte[] data) {
|
|
|
|
- int index = Ints.fromByteArray(data);
|
|
|
|
- return DummyHAService.getInstance(index);
|
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Test which exercises all of the inputs into ZKFC. This is particularly
|
|
|
|
+ * useful for running under jcarder to check for lock order violations.
|
|
|
|
+ */
|
|
|
|
+ @Test(timeout=30000)
|
|
|
|
+ public void testOneOfEverything() throws Exception {
|
|
|
|
+ try {
|
|
|
|
+ cluster.start();
|
|
|
|
+
|
|
|
|
+ // Failover by session expiration
|
|
|
|
+ LOG.info("====== Failing over by session expiration");
|
|
|
|
+ cluster.expireAndVerifyFailover(0, 1);
|
|
|
|
+ cluster.expireAndVerifyFailover(1, 0);
|
|
|
|
+
|
|
|
|
+ // Restart ZK
|
|
|
|
+ LOG.info("====== Restarting server");
|
|
|
|
+ stopServer();
|
|
|
|
+ waitForServerDown(hostPort, CONNECTION_TIMEOUT);
|
|
|
|
+ startServer();
|
|
|
|
+ waitForServerUp(hostPort, CONNECTION_TIMEOUT);
|
|
|
|
|
|
- @Override
|
|
|
|
- protected HAServiceTarget getLocalTarget() {
|
|
|
|
- return localTarget;
|
|
|
|
|
|
+ // Failover by bad health
|
|
|
|
+ cluster.setHealthy(0, false);
|
|
|
|
+ cluster.waitForHAState(0, HAServiceState.STANDBY);
|
|
|
|
+ cluster.waitForHAState(1, HAServiceState.ACTIVE);
|
|
|
|
+ cluster.setHealthy(1, true);
|
|
|
|
+ cluster.setHealthy(0, false);
|
|
|
|
+ cluster.waitForHAState(1, HAServiceState.ACTIVE);
|
|
|
|
+ cluster.waitForHAState(0, HAServiceState.STANDBY);
|
|
|
|
+ cluster.setHealthy(0, true);
|
|
|
|
+
|
|
|
|
+ cluster.waitForHealthState(0, State.SERVICE_HEALTHY);
|
|
|
|
+
|
|
|
|
+ // Graceful failovers
|
|
|
|
+ cluster.getZkfc(1).gracefulFailoverToYou();
|
|
|
|
+ cluster.getZkfc(0).gracefulFailoverToYou();
|
|
|
|
+ } finally {
|
|
|
|
+ cluster.stop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ private int runFC(DummyHAService target, String ... args) throws Exception {
|
|
|
|
+ DummyZKFC zkfc = new DummyZKFC(conf, target);
|
|
|
|
+ return zkfc.run(args);
|
|
|
|
+ }
|
|
|
|
+
|
|
}
|
|
}
|