HADOOP-7774. HA: Administrative CLI to control HA daemons. Contributed by Todd Lipcon.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/HDFS-1623@1190584 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
efb2d93f77
commit
b4992f671d
|
@ -5,3 +5,4 @@ branch is merged.
|
|||
------------------------------
|
||||
|
||||
HADOOP-7455. HA: Introduce HA Service Protocol Interface. (suresh)
|
||||
HADOOP-7774. HA: Administrative CLI to control HA daemons. (todd)
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* 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.hadoop.ha;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hadoop.classification.InterfaceAudience;
|
||||
import org.apache.hadoop.conf.Configured;
|
||||
import org.apache.hadoop.ipc.RPC;
|
||||
import org.apache.hadoop.net.NetUtils;
|
||||
import org.apache.hadoop.util.Tool;
|
||||
import org.apache.hadoop.util.ToolRunner;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
/**
|
||||
* A command-line tool for making calls in the HAServiceProtocol.
|
||||
* For example,. this can be used to force a daemon to standby or active
|
||||
* mode, or to trigger a health-check.
|
||||
*/
|
||||
@InterfaceAudience.Private
|
||||
public class HAAdmin extends Configured implements Tool {
|
||||
|
||||
private static Map<String, UsageInfo> USAGE =
|
||||
ImmutableMap.<String, UsageInfo>builder()
|
||||
.put("-transitionToActive",
|
||||
new UsageInfo("<host:port>", "Transitions the daemon into Active state"))
|
||||
.put("-transitionToStandby",
|
||||
new UsageInfo("<host:port>", "Transitions the daemon into Passive state"))
|
||||
.put("-checkHealth",
|
||||
new UsageInfo("<host:port>",
|
||||
"Requests that the daemon perform a health check.\n" +
|
||||
"The HAAdmin tool will exit with a non-zero exit code\n" +
|
||||
"if the check fails."))
|
||||
.put("-help",
|
||||
new UsageInfo("<command>", "Displays help on the specified command"))
|
||||
.build();
|
||||
|
||||
/** Output stream for errors, for use in tests */
|
||||
PrintStream errOut = System.err;
|
||||
PrintStream out = System.out;
|
||||
|
||||
private static void printUsage(PrintStream errOut) {
|
||||
errOut.println("Usage: java HAAdmin");
|
||||
for (Map.Entry<String, UsageInfo> e : USAGE.entrySet()) {
|
||||
String cmd = e.getKey();
|
||||
UsageInfo usage = e.getValue();
|
||||
|
||||
errOut.println(" [" + cmd + " " + usage.args + "]");
|
||||
}
|
||||
errOut.println();
|
||||
ToolRunner.printGenericCommandUsage(errOut);
|
||||
}
|
||||
|
||||
private static void printUsage(PrintStream errOut, String cmd) {
|
||||
UsageInfo usage = USAGE.get(cmd);
|
||||
if (usage == null) {
|
||||
throw new RuntimeException("No usage for cmd " + cmd);
|
||||
}
|
||||
errOut.println("Usage: java HAAdmin [" + cmd + " " + usage.args + "]");
|
||||
}
|
||||
|
||||
private int transitionToActive(final String[] argv)
|
||||
throws IOException, ServiceFailedException {
|
||||
if (argv.length != 2) {
|
||||
errOut.println("transitionToActive: incorrect number of arguments");
|
||||
printUsage(errOut, "-transitionToActive");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HAServiceProtocol proto = getProtocol(argv[1]);
|
||||
proto.transitionToActive();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private int transitionToStandby(final String[] argv)
|
||||
throws IOException, ServiceFailedException {
|
||||
if (argv.length != 2) {
|
||||
errOut.println("transitionToStandby: incorrect number of arguments");
|
||||
printUsage(errOut, "-transitionToStandby");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HAServiceProtocol proto = getProtocol(argv[1]);
|
||||
proto.transitionToStandby();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int checkHealth(final String[] argv)
|
||||
throws IOException, ServiceFailedException {
|
||||
if (argv.length != 2) {
|
||||
errOut.println("checkHealth: incorrect number of arguments");
|
||||
printUsage(errOut, "-checkHealth");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HAServiceProtocol proto = getProtocol(argv[1]);
|
||||
try {
|
||||
proto.monitorHealth();
|
||||
} catch (HealthCheckFailedException e) {
|
||||
errOut.println("Health check failed: " + e.getLocalizedMessage());
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a proxy to the specified target host:port.
|
||||
*/
|
||||
protected HAServiceProtocol getProtocol(String target)
|
||||
throws IOException {
|
||||
InetSocketAddress addr = NetUtils.createSocketAddr(target);
|
||||
return (HAServiceProtocol)RPC.getProxy(
|
||||
HAServiceProtocol.class, HAServiceProtocol.versionID,
|
||||
addr, getConf());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int run(String[] argv) throws Exception {
|
||||
if (argv.length < 1) {
|
||||
printUsage(errOut);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
String cmd = argv[i++];
|
||||
|
||||
if (!cmd.startsWith("-")) {
|
||||
errOut.println("Bad command '" + cmd + "': expected command starting with '-'");
|
||||
printUsage(errOut);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ("-transitionToActive".equals(cmd)) {
|
||||
return transitionToActive(argv);
|
||||
} else if ("-transitionToStandby".equals(cmd)) {
|
||||
return transitionToStandby(argv);
|
||||
} else if ("-checkHealth".equals(cmd)) {
|
||||
return checkHealth(argv);
|
||||
} else if ("-help".equals(cmd)) {
|
||||
return help(argv);
|
||||
} else {
|
||||
errOut.println(cmd.substring(1) + ": Unknown command");
|
||||
printUsage(errOut);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int help(String[] argv) {
|
||||
if (argv.length != 2) {
|
||||
printUsage(errOut, "-help");
|
||||
return -1;
|
||||
}
|
||||
String cmd = argv[1];
|
||||
if (!cmd.startsWith("-")) {
|
||||
cmd = "-" + cmd;
|
||||
}
|
||||
UsageInfo usageInfo = USAGE.get(cmd);
|
||||
if (usageInfo == null) {
|
||||
errOut.println(cmd + ": Unknown command");
|
||||
printUsage(errOut);
|
||||
return -1;
|
||||
}
|
||||
|
||||
errOut .println(cmd + " [" + usageInfo.args + "]: " + usageInfo.help);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
int res = ToolRunner.run(new HAAdmin(), argv);
|
||||
System.exit(res);
|
||||
}
|
||||
|
||||
|
||||
private static class UsageInfo {
|
||||
private final String args;
|
||||
private final String help;
|
||||
|
||||
public UsageInfo(String args, String help) {
|
||||
this.args = args;
|
||||
this.help = help;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* 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.hadoop.ha;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
public class TestHAAdmin {
|
||||
private static final Log LOG = LogFactory.getLog(TestHAAdmin.class);
|
||||
|
||||
private HAAdmin tool;
|
||||
private ByteArrayOutputStream errOutBytes = new ByteArrayOutputStream();
|
||||
private String errOutput;
|
||||
private HAServiceProtocol mockProtocol;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mockProtocol = Mockito.mock(HAServiceProtocol.class);
|
||||
tool = new HAAdmin() {
|
||||
@Override
|
||||
protected HAServiceProtocol getProtocol(String target) throws IOException {
|
||||
return mockProtocol;
|
||||
}
|
||||
};
|
||||
tool.setConf(new Configuration());
|
||||
tool.errOut = new PrintStream(errOutBytes);
|
||||
}
|
||||
|
||||
private void assertOutputContains(String string) {
|
||||
if (!errOutput.contains(string)) {
|
||||
fail("Expected output to contain '" + string + "' but was:\n" +
|
||||
errOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdminUsage() throws Exception {
|
||||
assertEquals(-1, runTool());
|
||||
assertOutputContains("Usage:");
|
||||
assertOutputContains("-transitionToActive");
|
||||
|
||||
assertEquals(-1, runTool("badCommand"));
|
||||
assertOutputContains("Bad command 'badCommand'");
|
||||
|
||||
assertEquals(-1, runTool("-badCommand"));
|
||||
assertOutputContains("badCommand: Unknown");
|
||||
|
||||
// valid command but not enough arguments
|
||||
assertEquals(-1, runTool("-transitionToActive"));
|
||||
assertOutputContains("transitionToActive: incorrect number of arguments");
|
||||
assertEquals(-1, runTool("-transitionToActive", "x", "y"));
|
||||
assertOutputContains("transitionToActive: incorrect number of arguments");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelp() throws Exception {
|
||||
assertEquals(-1, runTool("-help"));
|
||||
assertEquals(1, runTool("-help", "transitionToActive"));
|
||||
assertOutputContains("Transitions the daemon into Active");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransitionToActive() throws Exception {
|
||||
assertEquals(0, runTool("-transitionToActive", "xxx"));
|
||||
Mockito.verify(mockProtocol).transitionToActive();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransitionToStandby() throws Exception {
|
||||
assertEquals(0, runTool("-transitionToStandby", "xxx"));
|
||||
Mockito.verify(mockProtocol).transitionToStandby();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckHealth() throws Exception {
|
||||
assertEquals(0, runTool("-checkHealth", "xxx"));
|
||||
Mockito.verify(mockProtocol).monitorHealth();
|
||||
|
||||
Mockito.doThrow(new HealthCheckFailedException("fake health check failure"))
|
||||
.when(mockProtocol).monitorHealth();
|
||||
assertEquals(1, runTool("-checkHealth", "xxx"));
|
||||
assertOutputContains("Health check failed: fake health check failure");
|
||||
}
|
||||
|
||||
private Object runTool(String ... args) throws Exception {
|
||||
errOutBytes.reset();
|
||||
LOG.info("Running: HAAdmin " + Joiner.on(" ").join(args));
|
||||
int ret = tool.run(args);
|
||||
errOutput = new String(errOutBytes.toByteArray(), Charsets.UTF_8);
|
||||
LOG.info("Output:\n" + errOutput);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue