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:
Todd Lipcon 2011-10-28 21:24:04 +00:00
parent efb2d93f77
commit b4992f671d
3 changed files with 328 additions and 0 deletions

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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;
}
}