diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
index 0ac1821..80a3811 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
@@ -20,28 +20,51 @@
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Evolving;
+import org.apache.hadoop.service.launcher.LauncherExitCodes;
+import org.apache.hadoop.util.ExitCodeProvider;
/**
* Exception that is raised on state change operations.
*/
@Public
@Evolving
-public class ServiceStateException extends RuntimeException {
+public class ServiceStateException extends RuntimeException implements
+ ExitCodeProvider {
private static final long serialVersionUID = 1110000352259232646L;
+ /**
+ * Exit code. Non final as the constructor logic is too complex for
+ * the compiler to like.
+ */
+ private int exitCode;
public ServiceStateException(String message) {
- super(message);
+ this(message, null);
}
public ServiceStateException(String message, Throwable cause) {
+ this(LauncherExitCodes.EXIT_SERVICE_LIFECYCLE_EXCEPTION, message, cause);
+ }
+
+ private ServiceStateException(int exitCode,
+ String message,
+ Throwable cause) {
super(message, cause);
+
+ this.exitCode = (cause instanceof ExitCodeProvider)?
+ (((ExitCodeProvider) cause).getExitCode())
+ : exitCode;
}
public ServiceStateException(Throwable cause) {
super(cause);
}
+ @Override
+ public int getExitCode() {
+ return exitCode;
+ }
+
/**
* Convert any exception into a {@link RuntimeException}.
* If the caught exception is already of that type, it is typecast to a
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java
new file mode 100644
index 0000000..97a8e27
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java
@@ -0,0 +1,78 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.AbstractService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * Subclass of {@link AbstractService} that provides basic implementations
+ * of the new methods
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public abstract class AbstractLaunchableService extends AbstractService
+ implements LaunchableService {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(AbstractLaunchableService.class);
+
+ /**
+ * Construct an instance with the given name.
+ */
+ public AbstractLaunchableService(String name) {
+ super(name);
+ }
+
+ /**
+ * {@inheritDoc}.
+ *
+ * The base implementation logs all arguments at the debug level,
+ * then returns the passed in config unchanged.
+ *
+ */
+
+ @Override
+ public Configuration bindArgs(Configuration config, List args) throws
+ Exception {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Service {} passed in {} arguments:", getName(),args.size());
+ for (String arg : args) {
+ LOG.debug(arg);
+ }
+ }
+ return config;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * The action is to signal succes by returnind the exit code 0.
+ */
+ @Override
+ public int execute() throws Exception {
+ return LauncherExitCodes.EXIT_SUCCESS;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java
new file mode 100644
index 0000000..6b1afc7
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java
@@ -0,0 +1,203 @@
+/*
+ * 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.service.launcher;
+
+import com.google.common.base.Preconditions;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ExitUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.apache.hadoop.service.launcher.LauncherExitCodes.EXIT_INTERRUPTED;
+
+/**
+ * Handles interrupts by shutting down a service, escalating if the service
+ * does not shut down in time, or when other interrupts are received.
+ *
+ * - The service is given a time in milliseconds to stop:
+ * if it exceeds this it the process exits anyway.
+ * - the exit operation used is {@link ServiceLauncher#exit(int, String)}
+ * with the exit code {@link LauncherExitCodes#EXIT_INTERRUPTED}
+ * - If a second shutdown signal is received during the shutdown
+ * process, {@link ExitUtil#halt(int)} is invoked. This handles the
+ * problem of blocking shutdown hooks.
+ *
+ *
+ */
+
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+
+public class InterruptEscalator implements IrqHandler.Interrupted {
+ private static final Logger LOG = LoggerFactory.getLogger(
+ InterruptEscalator.class);
+
+ /**
+ * Flag to indicate when a shutdown signal has already been received.
+ * This allows the operation to be escalated
+ */
+ private final AtomicBoolean signalAlreadyReceived = new AtomicBoolean(false);
+
+ private final WeakReference ownerRef;
+
+ private final int shutdownTimeMillis;
+
+ /**
+ * Previous interrupt handlers. These are not queried.
+ */
+ private final List interruptHandlers =
+ new ArrayList(2);
+ private boolean forcedShutdownTimedOut;
+
+ public InterruptEscalator(ServiceLauncher owner, int shutdownTimeMillis) {
+ Preconditions.checkArgument(owner != null, "null owner");
+ this.ownerRef = new WeakReference(owner);
+ this.shutdownTimeMillis = shutdownTimeMillis;
+ }
+
+ private ServiceLauncher getOwner() {
+ return ownerRef.get();
+ }
+
+ private Service getService() {
+ ServiceLauncher owner = getOwner();
+ return owner != null ? owner.getService() : null;
+ }
+
+
+ @Override
+ public void interrupted(IrqHandler.InterruptData interruptData) {
+ String message = "Service interrupted by " + interruptData.toString();
+ LOG.warn(message);
+ if (!signalAlreadyReceived.compareAndSet(false, true)) {
+ message = "Repeated interrupt: escalating to a JVM halt";
+ LOG.warn(message);
+ // signal already received. On a second request to a hard JVM
+ // halt and so bypass any blocking shutdown hooks.
+ ExitUtil.halt(LauncherExitCodes.EXIT_INTERRUPTED, message);
+ }
+ Service service = getService();
+ if (service != null) {
+ //start an async shutdown thread with a timeout
+ ServiceForcedShutdown shutdown = new ServiceForcedShutdown(service,
+ shutdownTimeMillis);
+ Thread thread = new Thread(shutdown);
+ thread.setDaemon(true);
+ thread.setName("Service Forced Shutdown");
+ thread.start();
+ //wait for that thread to finish
+ try {
+ thread.join(shutdownTimeMillis);
+ } catch (InterruptedException ignored) {
+ //ignored
+ }
+ forcedShutdownTimedOut = !shutdown.getServiceWasShutdown();
+ if (forcedShutdownTimedOut) {
+ LOG.warn("Service did not shut down in time");
+ }
+ }
+
+ ExitUtil.terminate(EXIT_INTERRUPTED, message);
+ }
+
+ /**
+ * Register an interrupt handler
+ * @param signalName signal name
+ * @throws IllegalArgumentException if the registration failed
+ */
+ public synchronized void register(String signalName) {
+ interruptHandlers.add(new IrqHandler(signalName, this));
+ }
+
+ /**
+ * Look up the handler for a signal
+ * @param signalName signal name
+ * @return a handler if found
+ */
+ public synchronized IrqHandler lookup(String signalName) {
+ for (IrqHandler irqHandler : interruptHandlers) {
+ if (irqHandler.getName().equals(signalName)) {
+ return irqHandler;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Flag set if forced shut down timed out
+ * @return true if a shutdown was attempted and it timed out
+ */
+ public boolean isForcedShutdownTimedOut() {
+ return forcedShutdownTimedOut;
+ }
+
+ /**
+ * Flag set if a signal has been received
+ * @return true if there has been one interrupt already.
+ */
+ public boolean isSignalAlreadyReceived() {
+ return signalAlreadyReceived.get();
+ }
+
+ /**
+ * forced shutdown runnable.
+ */
+ protected static class ServiceForcedShutdown implements Runnable {
+
+ private final int shutdownTimeMillis;
+ private final AtomicBoolean serviceWasShutdown =
+ new AtomicBoolean(false);
+ private Service service;
+
+ public ServiceForcedShutdown(Service service, int shutdownTimeMillis) {
+ this.shutdownTimeMillis = shutdownTimeMillis;
+ this.service = service;
+ }
+
+ /**
+ * shutdown callback: stop the service and set an atomic boolean
+ * if it stopped within the shutdown time
+ */
+ @Override
+ public void run() {
+ if (service != null) {
+ service.stop();
+ serviceWasShutdown.set(
+ service.waitForServiceToStop(shutdownTimeMillis));
+ } else {
+ serviceWasShutdown.set(true);
+ }
+ }
+
+ /**
+ * Probe for the service being shutdown
+ * @return true if the service has been shutdown in the runnable
+ */
+ private boolean getServiceWasShutdown() {
+ return serviceWasShutdown.get();
+ }
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java
new file mode 100644
index 0000000..76cedd6
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java
@@ -0,0 +1,156 @@
+/*
+ * 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.service.launcher;
+
+import com.google.common.base.Preconditions;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Handler of interrupts -relays them to a registered
+ * implementation of {@link IrqHandler.Interrupted}
+ *
+ * This class bundles up all the compiler warnings about abuse of sun.misc
+ * interrupt handling code into one place.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+@SuppressWarnings("UseOfSunClasses")
+public final class IrqHandler implements SignalHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(IrqHandler.class);
+
+ /**
+ * Definition of the Control-C handler name: {@value}
+ */
+ public static final String CONTROL_C = "INT";
+
+ /**
+ * Definition of default kill signal: {@value}
+ */
+ public static final String SIGTERM = "TERM";
+
+ private final String name;
+
+ /**
+ * Handler to relay to
+ */
+ private final Interrupted handler;
+
+ private final AtomicInteger signalCount = new AtomicInteger(0);
+ private final Signal signal;
+
+ /**
+ * Create an IRQ handler bound to the specific interrupt
+ * @param name signal name
+ * @param handler handler
+ * @throws IllegalArgumentException if the exception could not be set
+ */
+ public IrqHandler(String name, Interrupted handler) {
+ Preconditions.checkArgument(name != null, "Null \"name\"");
+ Preconditions.checkArgument(handler != null, "Null \"handler\"");
+ this.handler = handler;
+ this.name = name;
+ try {
+ signal = new Signal(name);
+ Signal.handle(signal, this);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Could not set handler for signal \"" + name + "\"."
+ + "This can happen if the JVM has the -Xrs set.",
+ e);
+ }
+ }
+
+ /**
+ *
+ * @return the signal name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Raise the signal.
+ */
+ public void raise() {
+ Signal.raise(signal);
+ }
+
+ @Override
+ public String toString() {
+ return "IrqHandler for signal " + name;
+ }
+
+ /**
+ * Handler for the JVM API for signal handling
+ * @param s signal raised
+ */
+ @Override
+ public void handle(Signal s) {
+ signalCount.incrementAndGet();
+ InterruptData data =
+ new InterruptData(s.getName(), s.getNumber());
+ LOG.info("Interrupted: {}", data);
+ handler.interrupted(data);
+ }
+
+ /**
+ * Get the count of how many times a signal has been raised
+ * @return the count of signals
+ */
+ public int getSignalCount() {
+ return signalCount.get();
+ }
+
+ /**
+ * Callback issues on an interrupt
+ */
+ public interface Interrupted {
+
+ /**
+ * Handle an interrupt
+ * @param interruptData data
+ */
+ void interrupted(InterruptData interruptData);
+ }
+
+ /**
+ * Interrupt data to pass on.
+ */
+ public static class InterruptData {
+ public final String name;
+ public final int number;
+
+ public InterruptData(String name, int number) {
+ this.name = name;
+ this.number = number;
+ }
+
+ @Override
+ public String toString() {
+ return "signal " + name + '(' + number + ')';
+ }
+ }
+}
\ No newline at end of file
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java
new file mode 100644
index 0000000..964a903
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java
@@ -0,0 +1,92 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.Service;
+
+import java.util.List;
+
+/**
+ * An interface which services can implement to have their
+ * execution managed by the ServiceLauncher.
+ * The command line options will be passed down before the
+ * {@link Service#init(Configuration)} operation is invoked via an
+ * invocation of {@link LaunchableService#bindArgs(Configuration, List)}
+ * After the service has been successfully started via {@link Service#start()}
+ * the {@link LaunchableService#execute()} method is called to execute the
+ * service. When this method returns, the service launcher will exit, using
+ * the return code from the method as its exit option.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public interface LaunchableService extends Service {
+
+ /**
+ * Propagate the command line arguments.
+ * This method is called before {@link #init(Configuration)};
+ * Any non-null configuration that is returned from this operation
+ * becomes the one that is passed on to that {@link #init(Configuration)}
+ * operation.
+ *
+ * This permits implementations to change the configuration before
+ * the init operation. As the ServiceLauncher only creates
+ * an instance of the base {@link Configuration} class, it is
+ * recommended to instantiate any subclass (such as YarnConfiguration)
+ * that injects new resources.
+ *
+ * @param config the initial configuration build up by the
+ * service launcher.
+ * @param args list of arguments passed to the command line
+ * after any launcher-specific commands have been stripped.
+ * @return the configuration to init the service with.
+ * Recommended: pass down the config parameter with any changes
+ * @throws Exception any problem
+ */
+ Configuration bindArgs(Configuration config, List args) throws Exception;
+
+ /**
+ * Run a service -called after {@link Service#start()}.
+ *
+ * The return value becomes the exit code of the launched process.
+ *
+ * If an exception is raised, the policy is
+ *
+ * - Any subset of {@link org.apache.hadoop.util.ExitUtil.ExitException}:
+ * the exception is passed up unmodified
+ *
+ * - Any exception which implements
+ * {@link org.apache.hadoop.util.ExitCodeProvider}:
+ * A new {@link ServiceLaunchException} is created with the exit code
+ * and message of the thrown exception; the thrown exception becomes the
+ * cause.
+ * - Any other exception. A new {@link ServiceLaunchException} is created
+ * with the exit code {@link LauncherExitCodes#EXIT_EXCEPTION_THROWN} and
+ * the message of the original exception (which becomes the cause)
+ *
+ * @return the exit code
+ * @throws org.apache.hadoop.util.ExitUtil.ExitException an exception passed up as the exit code
+ * and error text.
+ * @throws Exception any exception to report. If it provides an exit code
+ * this is used in a wrapping exception.
+ */
+ int execute() throws Exception ;
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java
new file mode 100644
index 0000000..48b2ef1
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java
@@ -0,0 +1,107 @@
+/*
+ * 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.service.launcher;
+
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * Common Exit codes.
+ *
+ * Codes with a YARN prefix are YARN-related.
+ *
+ * Exit codes from 32 up are defined for the {@link ServiceLauncher}.
+ * These are away up from the the base numbers, to distinguish them.
+ * Applications can have their own set of failures -it is recommended to
+ * start them at 64 to differentiate them.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+
+public interface LauncherExitCodes {
+ /**
+ * 0: success
+ */
+ int EXIT_SUCCESS = 0;
+
+ /**
+ * -1: generic "false" response. The operation worked but
+ * the result was not true
+ */
+ int EXIT_FAIL = -1;
+
+ /**
+ * YARN Exit code on a client initiated AM termination: {@value}
+ */
+ int EXIT_CLIENT_INITIATED_SHUTDOWN = 1;
+
+ /**
+ * YARN Exit code when tasks could not be launched: {@value}
+ */
+ int EXIT_TASK_LAUNCH_FAILURE = 2;
+
+ /**
+ * Exit code when an exception was thrown from the service: {@value}
+ */
+ int EXIT_EXCEPTION_THROWN = 32;
+
+ /**
+ * Exit code when a usage message was printed: {@value}
+ */
+ int EXIT_USAGE = 33;
+
+ /**
+ * Exit code when something happened but we can't be specific: {@value}
+ */
+ int EXIT_OTHER_FAILURE = 34;
+
+ /**
+ * Exit code when a control-C, kill -3, signal was picked up: {@value}
+ */
+
+ int EXIT_INTERRUPTED = 35;
+
+ /**
+ * Exit code when the command line doesn't parse: {@value}, or
+ * when it is otherwise invalid.
+ */
+ int EXIT_COMMAND_ARGUMENT_ERROR = 36;
+
+ /**
+ * Exit code when the configurations in valid/incomplete: {@value}
+ */
+ int EXIT_BAD_CONFIGURATION = 37;
+
+ /**
+ * Exit code on network connectivity problems: {@value}
+ */
+ int EXIT_CONNECTIVITY_PROBLEM = 38;
+
+ /**
+ * the service instance could not be created: {@value}
+ */
+ int EXIT_SERVICE_CREATION_FAILURE = 39;
+
+ /**
+ * the service instance could not be created: {@value}
+ */
+ int EXIT_SERVICE_LIFECYCLE_EXCEPTION = 40;
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java
new file mode 100644
index 0000000..dc2f7a8
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java
@@ -0,0 +1,82 @@
+/*
+ * 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.service.launcher;
+
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.util.ExitCodeProvider;
+import org.apache.hadoop.util.ExitUtil;
+
+import java.util.Locale;
+
+/**
+ * A service launch exception that includes an exit code.
+ *
+ * When caught by the ServiceLauncher, it will convert that
+ * into a process exit code.
+ *
+ * The {@link #ServiceLaunchException(int, String, Object...)} constructor
+ * generates formatted exceptions.
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+
+public class ServiceLaunchException extends ExitUtil.ExitException
+ implements ExitCodeProvider, LauncherExitCodes {
+
+ /**
+ * Create an exception with the specific exit code.
+ * @param exitCode exit code
+ * @param cause cause of the exception
+ */
+ public ServiceLaunchException(int exitCode, Throwable cause) {
+ super(exitCode, cause);
+ }
+
+ /**
+ * Create an exception with the specific exit code and text.
+ * @param exitCode exit code
+ * @param message message to use in exception
+ */
+ public ServiceLaunchException(int exitCode, String message) {
+ super(exitCode, message);
+ }
+
+ /**
+ * Create a formatted exception.
+ *
+ * This uses * {@link String#format(String, Object...)}
+ * to build the formatted exception -in the ENGLISH locale.
+ *
+ * If the last argument is a throwable, it becomes the cause of the exception.
+ * It is still be avaialable as a parameter for the format.
+ * @param exitCode exit code
+ * @param format format for message to use in exception
+ * @param args list of arguments
+ */
+ public ServiceLaunchException(int exitCode, String format, Object... args) {
+ super(exitCode, String.format(Locale.ENGLISH, format, args));
+ if (args.length > 0 && (args[args.length - 1] instanceof Throwable)) {
+ initCause((Throwable) args[args.length - 1]);
+ }
+ }
+
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java
new file mode 100644
index 0000000..a4a69ae
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java
@@ -0,0 +1,765 @@
+/*
+ * 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.service.launcher;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ExitCodeProvider;
+import org.apache.hadoop.util.ExitUtil;
+import org.apache.hadoop.util.StringUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class to launch any service by name.
+ *
+ * It's designed to be subclassed for custom entry points.
+ *
+ *
+ * Workflow
+ *
+ * - An instance of the class is created
+ * - If it implements LaunchedService, it is given the binding args off the CLI
+ * - Its service.init() and service.start() methods are called.
+ * - If it implements LaunchedService, runService() is called and its return
+ * code used as the exit code.
+ * - Otherwise: it waits for the service to stop, assuming in its start() method
+ * it begins work
+ * - If an exception returned an exit code, that becomes the exit code of the
+ * command.
+ *
+ * Error and warning messages are logged to stderr. Why? If the classpath
+ * is wrong and logger configurations not on it, then no error messages by
+ * the started app will be seen and the caller is left trying to debug
+ * using exit codes.
+ *
+ */
+@SuppressWarnings("UseOfSystemOutOrSystemErr")
+public class ServiceLauncher implements LauncherExitCodes {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(
+ ServiceLauncher.class);
+
+ /**
+ * Priority for the shutdown hook: {@value}
+ */
+ protected static final int SHUTDOWN_PRIORITY = 30;
+
+ /**
+ * The name of this class
+ */
+ public static final String NAME = "ServiceLauncher";
+
+ /**
+ * Name of the configuration argument on the CLI: {@value}
+ */
+ public static final String ARG_CONF = "-conf";
+
+ /**
+ * Usage message.
+ *
+ * {@value}
+ */
+ public static final String USAGE_MESSAGE =
+ "Usage: " + NAME + " classname " +
+ "[" + ARG_CONF + " ] " +
+ " ";
+
+ /**
+ * The shutdown time on an interrupt: {@value}
+ */
+ private static final int SHUTDOWN_TIME_ON_INTERRUPT = 30 * 1000;
+
+ /**
+ * The launched service.
+ *
+ * Invalid until the service has been created.
+ */
+ private volatile S service;
+
+ /**
+ * Exit code of the service.
+ *
+ * Invalid until a service has
+ * executed or stopped, depending on the service type.
+ */
+ private int serviceExitCode;
+
+ /**
+ * Exception raised during execution.
+ */
+ private ExitUtil.ExitException serviceException;
+
+ /**
+ * The interrupt escalator for the service.
+ */
+ private InterruptEscalator interruptEscalator;
+
+ /**
+ * Configuration used for the service.
+ */
+ private Configuration configuration;
+
+ /**
+ * Classname for the service to create.
+ */
+ private String serviceClassName;
+
+ /**
+ * Create an instance of the launcher
+ * @param serviceClassName classname of the service
+ */
+ public ServiceLauncher(String serviceClassName) {
+ this.serviceClassName = serviceClassName;
+ }
+
+ /**
+ * Get the service.
+ *
+ * Null until and unless
+ * {@link #coreServiceLaunch(Configuration, List, boolean, boolean)} has completed
+ * @return the service
+ */
+ public S getService() {
+ return service;
+ }
+
+ /**
+ * Setter is to give subclasses the ability to manipulate the service.
+ * @param service the new service
+ */
+ protected void setService(S service) {
+ this.service = service;
+ }
+
+ /**
+ * Get the configuration constructed from the command line arguments.
+ * @return the configuration used to create the service
+ */
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * The exit code from a successful service execution.
+ * @return the exit code.
+ */
+ @VisibleForTesting
+ public int getServiceExitCode() {
+ return serviceExitCode;
+ }
+
+ @VisibleForTesting
+ public ExitUtil.ExitException getServiceException() {
+ return serviceException;
+ }
+
+ @Override
+ public String toString() {
+ return "ServiceLauncher for " + serviceClassName;
+ }
+
+ /**
+ * Launch the service and exit.
+ *
+ *
+ * - Parse the command line.
+ * - Build the service configuration from it.
+ * - Start the service.
.
+ * - If it is a {@link LaunchableService}: execute it
+ * - Otherwise: wait for it to finish.
+ * - Exit passing the status code to the {@link #exit(int, String)} method.
+ * @param args arguments to the service. arg[0] is
+ * assumed to be the service classname.
+ */
+ public void launchServiceAndExit(List args) {
+
+ registerFailureHandling();
+ // Currently the config just the default
+ Configuration conf = createConfiguration();
+ List processedArgs = extractConfigurationArgs(conf, args);
+ ExitUtil.ExitException ee = launchService(conf, processedArgs, true, true);
+ exit(ee);
+ }
+
+ /**
+ * Override point: create the base configuration for the service.
+ *
+ * Subclasses can override to create HDFS/YARN configurations etc.
+ * @return the configuration to use as the service initalizer.
+ */
+ protected Configuration createConfiguration() {
+ return new Configuration();
+ }
+
+ /**
+ * Launch a service catching all exceptions and downgrading them to exit codes
+ * after logging.
+ *
+ * Sets {@link #serviceException} to this value.
+ * @param conf configuration to use
+ * @param processedArgs command line after the launcher-specific arguments have
+ * been stripped out.
+ * @param addShutdownHook should a shutdown hook be added to terminate
+ * this service on shutdown. Tests should set this to false.
+ * @param execute execute/wait for the service to stop.
+ * @return an exit exception, which will have a status code of 0 if it worked
+ */
+ @VisibleForTesting
+ public ExitUtil.ExitException launchService(Configuration conf,
+ List processedArgs,
+ boolean addShutdownHook,
+ boolean execute) {
+
+ ExitUtil.ExitException exitException;
+
+ try {
+ int exitCode = coreServiceLaunch(conf, processedArgs, addShutdownHook,
+ execute);
+ if (service != null) {
+ //check to see if the service failed
+ Throwable failure = service.getFailureCause();
+ if (failure != null) {
+ //the service exited with a failure.
+ //check what state it is in
+ Service.STATE failureState = service.getFailureState();
+ if (failureState == Service.STATE.STOPPED) {
+ //the failure occurred during shutdown, not important enough to bother
+ //the user as it may just scare them
+ LOG.debug("Failure during shutdown: {} ", failure, failure);
+ } else {
+ //throw it for the catch handlers to deal with
+ throw failure;
+ }
+ }
+ }
+ String serviceName = getServiceName();
+
+ if (exitCode == 0) {
+ exitException = new ServiceLaunchException(exitCode,
+ "%s succeeded",
+ serviceName);
+ } else {
+ exitException = new ServiceLaunchException(exitCode,
+ "%s failed ", serviceName);
+ }
+ // either the service succeeded, or an error raised during shutdown,
+ // which we don't worry that much about
+ } catch (ExitUtil.ExitException ee) {
+ // exit exceptions are passed through unchanged
+ exitException = ee;
+ } catch (Throwable thrown) {
+ exitException = convertToExitException(thrown);
+ }
+ serviceExitCode = exitException.getExitCode();
+ serviceException = exitException;
+ return exitException;
+ }
+
+ /**
+ * Launch the service.
+ *
+ *
+ * All exceptions that occur are propagated upwards.
+ *
+ * If the method returns a status code, it means that it got as far starting
+ * the service, and if it implements {@link LaunchableService}, that the
+ * method {@link LaunchableService#execute()} has completed.
+ *
+ * After this method returns, the service can be retrieved returned by {@link #getService()}.
+ *
+ * @param conf configuration
+ * @param processedArgs arguments after the configuration parameters
+ * have been stripped out.
+ * @param addShutdownHook should a shutdown hook be added to terminate
+ * this service on shutdown. Tests should set this to false.
+ * @param execute execute/wait for the service to stop
+ * @throws ClassNotFoundException classname not on the classpath
+ * @throws IllegalAccessException not allowed at the class
+ * @throws InstantiationException not allowed to instantiate it
+ * @throws InterruptedException thread interrupted
+ * @throws ExitUtil.ExitException any exception defining the status code.
+ * @throws Exception any other failure -if it implements {@link ExitCodeProvider}
+ * then it defines the exit code for any containing exception
+ */
+
+ protected int coreServiceLaunch(Configuration conf,
+ List processedArgs,
+ boolean addShutdownHook,
+ boolean execute) throws Exception {
+
+ // create the service instance
+ instantiateService(conf);
+ ServiceShutdownHook shutdownHook = null;
+
+ // and the shutdown hook if requested
+ if (addShutdownHook) {
+ shutdownHook = new ServiceShutdownHook(service);
+ shutdownHook.register(SHUTDOWN_PRIORITY);
+ }
+ String serviceName = getServiceName();
+ LOG.debug("Launched service {}", serviceName);
+ LaunchableService launchableService = null;
+
+ if (service instanceof LaunchableService) {
+ // it's a launchedService, pass in the conf and arguments before init)
+ LOG.debug("Service {} implements LaunchedService", serviceName);
+ launchableService = (LaunchableService) service;
+ if (launchableService.isInState(Service.STATE.INITED)) {
+ LOG.warn("LaunchedService {}"
+ + " initialized in constructor before CLI arguments passed in",
+ serviceName);
+ }
+ Configuration newconf = launchableService.bindArgs(configuration, processedArgs);
+ if (newconf != null) {
+ configuration = newconf;
+ }
+ }
+
+ //some class constructors init; here this is picked up on.
+ if (!service.isInState(Service.STATE.INITED)) {
+ service.init(configuration);
+ }
+ int exitCode;
+
+ try {
+ // start the service
+ service.start();
+ exitCode = EXIT_SUCCESS;
+ if (execute && service.isInState(Service.STATE.STARTED) ) {
+ if (launchableService != null) {
+ // assume that runnable services are meant to run from here
+ try {
+ exitCode = launchableService.execute();
+ LOG.debug("Service {} execution returned exit code {}", serviceName, exitCode);
+ } finally {
+ // then stop the service
+ service.stop();
+ }
+ } else {
+ //run the service until it stops or an interrupt happens on a different thread.
+ LOG.debug("waiting for service threads to terminate");
+ service.waitForServiceToStop(0);
+ }
+ }
+ } finally {
+ if (shutdownHook != null) {
+ shutdownHook.unregister();
+ }
+ }
+ return exitCode;
+ }
+
+ /**
+ * Instantiate the service defined in serviceClassName.
+ *
+ * Sets the configuration field
+ * to the the value of conf,
+ * and the service field to the service created.
+ *
+ * @param conf configuration to use
+ */
+ public Service instantiateService(Configuration conf) {
+ Preconditions.checkArgument(conf != null, "null conf");
+ configuration = conf;
+
+ //Instantiate the class -this requires the service to have a public
+ // zero-argument constructor
+ Object instance;
+
+ // in Java7+ the exception catch logic should be consolidated
+ try {
+ Class> serviceClass = getClassLoader().loadClass(serviceClassName);
+ try {
+ instance = serviceClass.getConstructor().newInstance();
+ } catch (NoSuchMethodException noEmptyConstructor) {
+ // no simple constructor, fall back to a string
+ LOG.debug("No empty constructor {}", noEmptyConstructor,
+ noEmptyConstructor);
+ instance = serviceClass.getConstructor(String.class).newInstance(
+ serviceClassName);
+ }
+ } catch (ClassNotFoundException e) {
+ throw serviceCreationFailure(e);
+ } catch (InstantiationException e) {
+ throw serviceCreationFailure(e);
+ } catch (IllegalAccessException e) {
+ throw serviceCreationFailure(e);
+ } catch (IllegalArgumentException e) {
+ throw serviceCreationFailure(e);
+ } catch (InvocationTargetException e) {
+ throw serviceCreationFailure(e);
+ } catch (NoSuchMethodException e) {
+ throw serviceCreationFailure(e);
+ } catch (SecurityException e) {
+ throw serviceCreationFailure(e);
+ }
+ if (!(instance instanceof Service)) {
+ //not a service
+ throw new ServiceLaunchException(
+ LauncherExitCodes.EXIT_SERVICE_CREATION_FAILURE,
+ "Not a service class: \"%s\"", serviceClassName);
+ }
+
+ // cast to the specific instance type of this ServiceLauncher
+ service = (S) instance;
+ return service;
+ }
+
+ /**
+ * Convert an exception to an ExitException.
+ *
+ * This process may just be a simple pass through, otherwise a new
+ * exception is created with an exit code, the text of the supplied
+ * exception, and the supplied exception as an inner cause.
+ *
+ *
+ * - If is already the right type, pass it through.
+ * - If it implements {@link ExitCodeProvider#getExitCode()},
+ * the exit code is extracted and used in the new exception.
+ * - Otherwise, the exit code
+ * {@link LauncherExitCodes#EXIT_EXCEPTION_THROWN} is used.
+ *
+ *
+ * @param thrown the exception thrown
+ * @return an ExitException with a status code
+ */
+ protected ExitUtil.ExitException convertToExitException(Throwable thrown) {
+ ExitUtil.ExitException
+ exitException;// other exceptions are converted to ExitExceptions
+ // get the exception message
+ String message = thrown.toString();
+ int exitCode;
+ if (thrown instanceof ExitCodeProvider) {
+ // the exception provides a status code -extract it
+ exitCode = ((ExitCodeProvider) thrown).getExitCode();
+ message = thrown.getMessage();
+ } else {
+ // no exception code: use the default
+ exitCode = EXIT_EXCEPTION_THROWN;
+ }
+ // construct the new exception with the original message and
+ // an exit code
+ exitException = new ServiceLaunchException(exitCode, message);
+ exitException.initCause(thrown);
+ return exitException;
+ }
+
+ /**
+ * Generate an exception announcing a failure to create the service.
+ * @param exception inner exception.
+ * @return a new exception, with the exit code
+ * {@link LauncherExitCodes#EXIT_SERVICE_CREATION_FAILURE}
+ */
+ protected ServiceLaunchException serviceCreationFailure(Exception exception) {
+ return new ServiceLaunchException(EXIT_SERVICE_CREATION_FAILURE, exception);
+ }
+
+ /**
+ * Register this class as the handler for the control-C interrupt.
+ *
+ * Subclasses can extend this with extra operations, such as
+ * an exception handler:
+ *
+ * Thread.setDefaultUncaughtExceptionHandler(
+ * new YarnUncaughtExceptionHandler());
+ *
+ */
+ protected void registerFailureHandling() {
+ try {
+ interruptEscalator = new InterruptEscalator(this, SHUTDOWN_TIME_ON_INTERRUPT);
+ interruptEscalator.register(IrqHandler.CONTROL_C);
+ interruptEscalator.register(IrqHandler.SIGTERM);
+ } catch (IllegalArgumentException e) {
+ // downgrade interrupt registration to warnings
+ LOG.warn("{}", e, e);
+ }
+ }
+
+ /**
+ * Get the service name via {@link Service#getName()}.
+ *
+ * If the service is not instantiated, the classname is returned instead.
+ * @return the service name
+ */
+ public String getServiceName() {
+ Service s = service;
+ String name = null;
+ if (s != null) {
+ try {
+ name = s.getName();
+ } catch (Exception ignored) {
+ // ignored
+ }
+ }
+ if (name != null) {
+ return "service " + name;
+ } else {
+ return "service classname " + serviceClassName;
+ }
+ }
+
+ /**
+ * Print a warning message.
+ *
+ * This tries to log to the log's warn() operation.
+ * If the log at that level is disabled it logs to system error
+ * @param text warning text
+ */
+ protected void warn(String text) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn(text);
+ } else {
+ System.err.println(text);
+ }
+ }
+
+ /**
+ * Report an error.
+ *
+ * This tries to log to the log's error() operation.
+ * If disabled the message is logged to system error along
+ * with thrown.toString()
+ * @param message message for the user
+ * @param thrown the exception thrown
+ */
+ protected void error(String message, Throwable thrown) {
+ String text = "Exception: " + message;
+ if (LOG.isErrorEnabled()) {
+ LOG.error(text, thrown);
+ } else {
+ System.err.println(text);
+ if (thrown != null) {
+ System.err.println(thrown.toString());
+ }
+ }
+ }
+
+ /**
+ * Exit the JVM.
+ *
+ * This is method can be overridden for testing, throwing an
+ * exception instead. Any subclassed method MUST raise an
+ * ExitException instance/subclass.
+ * The service launcher code assumes that after this method is invoked,
+ * no other code in the same method is called.
+ * @param exitCode code to exit
+ */
+ protected void exit(int exitCode, String message) {
+ ExitUtil.terminate(exitCode, message);
+ }
+
+ /**
+ * Exit the JVM using an exception for the exit code and message.
+ *
+ * This is the standard way a launched service exits.
+ * An error code of 0 means success -nothing is printed.
+ *
+ * By default, calls
+ * {@link ExitUtil#terminate(ExitUtil.ExitException)}.
+ *
+ * This can be subclassed for testing
+ * @param ee exit exception
+ */
+ protected void exit(ExitUtil.ExitException ee) {
+ ExitUtil.terminate(ee);
+ }
+
+ /**
+ * Override point: get the classloader to use.
+ * @return the classloader for loading a service class.
+ */
+ protected ClassLoader getClassLoader() {
+ return this.getClass().getClassLoader();
+ }
+
+ /**
+ * Extract the configuration arguments and apply them to the configuration,
+ * building an array of processed arguments to hand down to the service.
+ *
+ * @param conf configuration to update.
+ * @param args main arguments. args[0]is assumed to be the service
+ * classname and is skipped.
+ * @return the processed list.
+ * @throws ExitUtil.ExitException if JVM exiting is disabled.
+ */
+ public List extractConfigurationArgs(Configuration conf,
+ List args) {
+
+ //convert args to a list
+ int size = args.size();
+ if (size <= 1 ) {
+ return new ArrayList(0);
+ }
+ List argsList = new ArrayList(size);
+ //skip that first entry
+ int index = 1;
+ while (index< size) {
+ String arg = args.get(index);
+ LOG.info("arg[{}]={}", index, arg);
+ if (arg.equals(ARG_CONF)) {
+ //the argument is a --conf file tuple: extract the path and load
+ //it in as a configuration resource.
+
+ //increment the loop iterator
+ index++;
+ if (index == size) {
+ //overshot the end of the file
+ exitWithMessage(EXIT_COMMAND_ARGUMENT_ERROR,
+ ARG_CONF + ": missing configuration file");
+ // never called, but retained for completeness
+ break;
+ }
+
+ String filename = args.get(index);
+ index++;
+ LOG.info("arg[{}] = Conf file {}",index, filename);
+ URL fileURL = extractConfFile(filename);
+ conf.addResource(fileURL);
+ } else {
+ index++;
+ argsList.add(arg);
+ }
+ }
+ return argsList;
+ }
+
+ /**
+ * Extract a {@link Configuration} file from the filename argument.
+ *
+ * This includes tests that the file exists and is a valid
+ * XML Configuration file.
+ *
+ * If the load fails for any reason, the JVM is exited with an error code
+ * {@link LauncherExitCodes#EXIT_COMMAND_ARGUMENT_ERROR} and a message.
+ * @param filename filename argument
+ * @return the URL to the file.
+ */
+ private URL extractConfFile(String filename) {
+ File file = new File(filename);
+ URL fileURL = null;
+ if (!file.exists()) {
+ exitWithMessage(EXIT_COMMAND_ARGUMENT_ERROR,
+ ARG_CONF + ": configuration file not found: " + file);
+ // never called, but retained for completeness
+ } else {
+ try {
+ Configuration c = new Configuration(false);
+ fileURL = file.toURI().toURL();
+ c.addResource(fileURL);
+ c.get("key");
+ } catch (Exception e) {
+ // this means that the file could not be parsed
+ exitWithMessage(EXIT_COMMAND_ARGUMENT_ERROR,
+ ARG_CONF + ": configuration file not loadable: " + file);
+ }
+ }
+ return fileURL;
+
+ }
+
+ /**
+ * Build a log message for starting up and shutting down.
+ * @param classname the class of the server
+ * @param args arguments
+ */
+ public static String startupShutdownMessage(String classname,
+ List args) {
+ final String hostname = NetUtils.getHostname();
+
+ return StringUtils.createStartupShutdownMessage(classname, hostname,
+ args.toArray(new String[args.size()]));
+ }
+
+ /**
+ * Exit with a printed message.
+ * @param status status code
+ * @param message message
+ * @throws ExitUtil.ExitException if exceptions are disabled
+ */
+ private static void exitWithMessage(int status, String message) {
+ ExitUtil.terminate(new ServiceLaunchException(status, message));
+ }
+
+ /**
+ * This is the JVM entry point for the service launcher.
+ *
+ * Converts the arguments to a list, then invokes {@link #serviceMain(List)}
+ * @param args command line arguments.
+ */
+ public static void main(String[] args) {
+ List argsList = Arrays.asList(args);
+ serviceMain(argsList);
+ }
+
+ /**
+ * Varargs version of the entry point for testing and other in-JVM use
+ * -hands off to {@link #serviceMain(List)}
+ * @param args command line arguments.
+ */
+ public static void serviceMain(String ... args) {
+ List argsList = Arrays.asList(args);
+ serviceMain(argsList);
+ }
+
+ /* ====================================================================== */
+ /**
+ * The real main function, which takes the arguments as a list.
+ *
+ * Argument 0 MUST be the service classname
+ * @param argsList the list of arguments
+ */
+ /* ====================================================================== */
+
+ public static void serviceMain(List argsList) {
+ if (argsList.isEmpty()) {
+ exitWithMessage(EXIT_USAGE, USAGE_MESSAGE);
+ } else {
+ String serviceClassName = argsList.get(0);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(startupShutdownMessage(serviceClassName, argsList));
+ StringBuilder builder = new StringBuilder();
+ for (String arg : argsList) {
+ builder.append('"').append(arg).append("\" ");
+ }
+ LOG.debug(builder.toString());
+ }
+
+ ServiceLauncher serviceLauncher =
+ new ServiceLauncher(serviceClassName);
+ serviceLauncher.launchServiceAndExit(argsList);
+ }
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java
new file mode 100644
index 0000000..e43bea7
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java
@@ -0,0 +1,117 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ShutdownHookManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * JVM Shutdown hook for Service which will stop the
+ * Service gracefully in case of JVM shutdown.
+ * This hook uses a weak reference to the service, so
+ * does not cause services to be retained after they have
+ * been stopped and deferenced elsewhere.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+
+/**
+ * A service shutdown hook.
+ *
+ * It stores a weak reference to a service, and, when shut down,
+ * calls {@link Service#stop()} if the reference is valid.
+ */
+public class ServiceShutdownHook implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(
+ ServiceShutdownHook.class);
+
+ /**
+ * A weak reference to the service.
+ */
+ private final WeakReference serviceRef;
+
+ /**
+ * Create an instance.
+ * @param service the service
+ */
+ public ServiceShutdownHook(Service service) {
+ serviceRef = new WeakReference(service);
+ }
+
+ /**
+ * Register the service for shutdown with Hadoop's
+ * {@link ShutdownHookManager}.
+ * @param priority shutdown hook priority
+ */
+ public synchronized void register(int priority) {
+ unregister();
+ ShutdownHookManager.get().addShutdownHook(this, priority);
+ }
+
+ /**
+ * Unregister the hook.
+ */
+ public synchronized void unregister() {
+ try {
+ ShutdownHookManager.get().removeShutdownHook(this);
+ } catch (IllegalStateException e) {
+ LOG.info("Failed to unregister shutdown hook: {}", e, e);
+ }
+ }
+
+ /**
+ * Shutdown handler.
+ * Query the service hook reference -if it is still valid the
+ * {@link Service#stop()} operation is invoked.
+ */
+ @Override
+ public void run() {
+ shutdown();
+ }
+
+ /**
+ * Shutdown operation.
+ *
+ * Subclasses may extend it, but it is primarily
+ * made available for testing.
+ * @return true if the service was stopped and no exception was raised.
+ */
+ protected boolean shutdown() {
+ Service service;
+ boolean result = false;
+ synchronized (this) {
+ service = serviceRef.get();
+ serviceRef.clear();
+ }
+ if (service != null) try {
+ // Stop the Service
+ service.stop();
+ result = true;
+ } catch (Throwable t) {
+ LOG.info("Error stopping {}: {}", service.getName(), t);
+ }
+ return result;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/package-info.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/package-info.java
new file mode 100644
index 0000000..5bf7592
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/package-info.java
@@ -0,0 +1,401 @@
+/*
+ * 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.
+ */
+
+
+/**
+
+ This package contains classes, interfaces and exceptions to launch
+ YARN services from the command line.
+
+
Key Features
+
+
+ General purpose YARN service launcher:
+ The {@link org.apache.hadoop.service.launcher.ServiceLauncher} class parses
+ a command line, then instantiates and launches the specified YARN service. It
+ then waits for the service to finish, converting any exceptions raised or
+ exit codes returned into an exit code for the (then exited) process.
+
+ This class is designed be invokable from the static
+ {@link org.apache.hadoop.service.launcher.ServiceLauncher#main(String[])} method,
+ or from main(String[]) methods implemented by other classes
+ that which to provide their own endpoints.
+
+
+
+ Extended YARN Service Interface:
+ The {@link org.apache.hadoop.service.launcher.LaunchableService} interface
+ extendss {@link org.apache.hadoop.service.Service} with methods to pass
+ down the CLI arguments, to execute an operapation without having to
+ spawn a thread in the {@link org.apache.hadoop.service.Service#start()} phase.
+
+
+
+ Standard Exit codes:
+ {@link org.apache.hadoop.service.launcher.LauncherExitCodes}
+ defines a set of exit codes that can be used by services to standardize exit causes.
+
+
+
+ Escalated shutdown:
+ The {@link org.apache.hadoop.service.launcher.ServiceShutdownHook}
+ shuts down any service via the hadoop shutdown mechanism.
+ The {@link org.apache.hadoop.service.launcher.InterruptEscalator} can be
+ registered to catch interrupts, triggering the shutdown -and forcing a JVM
+ exit if it times our or a second interrupt is received.
+
+
+
+ Tests:
test cases include interrupt handling and
+ lifecycle failures.
+
+
+ Launching a YARN Service
+
+
+ The Service Launcher can launch any YARN service. It will instantiate
+ the service classname provided, by zero-args constructor. Or, if none
+ is available, falling back to a constructor that takes a String
+ as its parameter, on the assumption that the parameter is the service name.
+
+
+ The launcher will initialize the service via {@link org.apache.hadoop.service.Service#init(Configuration)},
+ then start it via its {@link org.apache.hadoop.service.Service#start()} method.
+ It then waits indefinitely for the service to stop.
+
+ After the service has stopped, a non-null value of
+ {@link org.apache.hadoop.service.Service#getFailureCause()} is interpreted
+ as a failure, and, if it didn't happen during the stop phase (i.e. when
+ {@link org.apache.hadoop.service.Service#getFailureState()} is not
+ STATE.STOPPED, escalated into a non-zero return code.
+
+ To view the worklow in sequence, it is:
+
+ - (prepare configuration files -covered later)
+ - instantiate service via its empty or string constructor
+ - call {@link org.apache.hadoop.service.Service#init(Configuration)}
+ - call {@link org.apache.hadoop.service.Service#start()}
+ - call {@link org.apache.hadoop.service.Service#waitForServiceToStop(long)}
+ - If an exception was raised: propagate it
+ - If an exception was recorded in {@link org.apache.hadoop.service.Service#getFailureCause()}
+ while the service was running: propagate it.
+
+
+ For a service to be fully compatible with this launch model, it must
+
+ - Start worker threads, processes and executors in its
+ {@link org.apache.hadoop.service.Service#start()} method
+ - Terminate itself via a call to {@link org.apache.hadoop.service.Service#stop()}
+ in one of these asynchronous methods.
+
+
+ If a service does not stop itself, ever, then it can be launched as a long-lived
+ daemon. The service launcher will never terminate, but neither will the service.
+ The service launcher does register signal handlers to catch kill
+ and control-C signals -calling stop() on the service when signalled.
+ This means that a daemon service may get some warning and time to shut
+ down.
+
+
+ To summarize: provided a service launches its long-lived threads in its Service
+ start() method, the service launcher can create it, configure it
+ and start it -triggering shutdown when signalled.
+
+ What these services can not do is get at the command line parameters or easily
+ propagate exit codes (there is way covered later). These features require
+ some extensions to the base Service interface: the Launchable
+ Service.
+
+
Launching a Launchable YARN Service
+
+ A Launchable YARN Service is a YARN service which implements the interface
+ {@link org.apache.hadoop.service.launcher.LaunchableService}.
+
+ It adds two methods to the service interface -and hence two new features:
+
+
+ - Access to the command line passed to the service launcher
+ - A blocking
int execute() method which can return the exit
+ code for the application.
+
+
+ This design is ideal for implementing services which parse the command line,
+ and which execute short-lived applications. For example, end user
+ commands can be implemented as such services, so integrating with YARN's
+ workflow and YarnClient client-side code.
+
+
+ It can just as easily be used for implementing long-lived services that
+ parse the command line -it just becomes the responsibility of the
+ service to decide when to return from the execute() method.
+ It doesn't even need to stop() itself; the launcher will handle
+ that if necessary.
+
+ The {@link org.apache.hadoop.service.launcher.LaunchableService} interface
+ extends {@link org.apache.hadoop.service.Service} with two new methods.
+
+
+ {@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)}
+ provides the main(String args[]) arguments as a list, after any
+ processing by the Service Launcher to extract configuration file references.
+ This method is called before {@link org.apache.hadoop.service.Service#init(Configuration)}.
+ This is by design: it allows the arguments to be parsed before the service is initialized,
+ so allowing services to tune their configuration data before passing it to
+ any superclass in that init() method.
+ To make this operation even simpler, the {@link org.apache.hadoop.conf.Configuration}
+ that is to be passed in is provided as an argument. This reference passed in is the initial
+ configuration for this service; the one that will be passed to the init operation.
+ In {@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)},
+ a Launchable Service may manipulate this configuration by setting or removing
+ properties. It may also create a new Configuration instance -which may be needed
+ to trigger the injection of HDFS or YARN resources into the default resources
+ of all Configurations. If the return value of the method call is a configuration
+ reference (as opposed to a null value), the returned value becomes that
+ passed in to the init() method.
+
+ After the bindArgs() processing, the service's init()
+ and start() methods are called, as usual.
+
+ At this point, rather than block waiting for the service to terminate (as
+ is done for a basic service), the method
+ {@link org.apache.hadoop.service.launcher.LaunchableService#execute()}
+ is called.
+ This is a method expected to block until completed, returning the intended
+ application exit code of the process when it does so.
+
+ After this execute() operation completes, the
+ service is stopped and exit codes generated. Any exception raised
+ during the execute() method takes priority over any exit codes returned
+ by the method --so services may signal failures simply by returning
+ exceptions with exit codes.
+
+
+
+ To view the worklow in sequence, it is:
+
+ - (prepare configuration files --covered later)
+ - instantiate service via its empty or string constructor
+ - call {@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)}
+ - call {@link org.apache.hadoop.service.Service#init(Configuration)} with the existing config,
+ or any new one returned by {@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)}
+ - call {@link org.apache.hadoop.service.Service#start()}
+ - call {@link org.apache.hadoop.service.launcher.LaunchableService#execute()}
+ - call {@link org.apache.hadoop.service.Service#stop()}
+ - The return code from the {@link org.apache.hadoop.service.launcher.LaunchableService#execute()}
+ becomes the exit code of the process, unless overridden by an exception.
+ - If an exception was raised in this workflow: propagate it
+ - If an exception was recorded in {@link org.apache.hadoop.service.Service#getFailureCause()}
+ while the service was running: propagate it.
+
+
+
+ Exit codes and Exceptions
+
+
+ For a basic service, the return code is 0 unless an exception
+ was raised.
+
+ For a {@link org.apache.hadoop.service.launcher.LaunchableService}, the return
+ code is the number returned from the {@link org.apache.hadoop.service.launcher.LaunchableService#execute()}
+ operation, again, unless overridden an exception was raised.
+
+
+ Exceptions are converted into exit codes -but rather than simply
+ have a "something went wrong" exit code , exceptions >may
+ provide exit codes which will be extracted and used as the return code.
+ This enables LaunchableServices to use exceptions as a way
+ of returning error codes to signal failures -and for
+ normal Services to return any error code at all.
+
+
+ Any exception which implements the {@link org.apache.hadoop.util.ExitCodeProvider}
+ interface is considered be a provider of the exit code: the method
+ {@link org.apache.hadoop.util.ExitCodeProvider#getExitCode()}
+ will be called on the caught exception to generate the return code.
+ This return code and the message in the exception will be used to
+ generate an instance of {@link org.apache.hadoop.util.ExitUtil.ExitException}
+ which can be passed down to {@link org.apache.hadoop.util.ExitUtil#terminate(ExitUtil.ExitException)}
+ to trigger a JVM exit. The initial exception will be used as the cause
+ of the {@link org.apache.hadoop.util.ExitUtil.ExitException}.
+
+
+
+ If the exception is already an instance or subclass of
+ {@link org.apache.hadoop.util.ExitUtil.ExitException}, it is passed
+ directly to {@link org.apache.hadoop.util.ExitUtil#terminate(ExitUtil.ExitException)}
+ without any conversion. One such subclass, {@link org.apache.hadoop.service.launcher.ServiceLaunchException}
+ may be useful: it includes formatted exception message generation.
+ It also declares that it extends {@link org.apache.hadoop.service.launcher.LauncherExitCodes}
+ interface listing common exception codes. These are exception codes
+ that can be raised by the {@link org.apache.hadoop.service.launcher.ServiceLauncher}
+ itself to indicate problems during parsing the command line, creating
+ the service instance and such like. There are also some common exit codes
+ for Hadoop/YARN service failures, such as {@link org.apache.hadoop.service.launcher.LauncherExitCodes#EXIT_CONNECTIVITY_PROBLEM}.
+ Note that {@link org.apache.hadoop.util.ExitUtil.ExitException} itself
+ implements {@link org.apache.hadoop.util.ExitCodeProvider#getExitCode()}
+
+
+ If an exception does not implement {@link org.apache.hadoop.util.ExitCodeProvider#getExitCode()},
+ it will be wrapped in an {@link org.apache.hadoop.util.ExitUtil.ExitException} with
+ the exit code {@link org.apache.hadoop.service.launcher.LauncherExitCodes#EXIT_EXCEPTION_THROWN}.
+
+
+
+
+
+ To view the exit code extraction in sequence, it is:
+
+ - If no exception was triggered by a basic service, a
+ {@link org.apache.hadoop.service.launcher.ServiceLaunchException} with an
+ exit code of 0 s created.
+ - For a LaunchableService, the exit code is the result of
execute().
+ Again, a {@link org.apache.hadoop.service.launcher.ServiceLaunchException}
+ with a return code of 0 is created.
+
+ - Otherwise, if the exception is an instance of
ExitException,
+ it is returned as the service terminating exception.
+ - If the exception implements {@link org.apache.hadoop.util.ExitCodeProvider},
+ its exit code and
getMessage() value become the exit exception.
+ - Otherwise, it is wrapped as a {@link org.apache.hadoop.service.launcher.ServiceLaunchException}
+ with the exit code {@link org.apache.hadoop.service.launcher.LauncherExitCodes#EXIT_EXCEPTION_THROWN}
+ to indicate that an exception was thrown.
+ - This is finally passed to {@link org.apache.hadoop.util.ExitUtil#terminate(ExitUtil.ExitException)},
+ by way of {@link org.apache.hadoop.service.launcher.ServiceLauncher#exit(ExitUtil.ExitException)};
+ a method designed to allow subclasses to override for testing.
+ - The {@link org.apache.hadoop.util.ExitUtil} class then terminates the JVM
+ with the specified exit code, printing the
toString() value
+ of the exception if the return code is non-zero.
+
+ This process may seem convoluted, but it is designed to allow any exception in the
+ Hadoop exception hierarchy to generate exit codes, and to minimise the amount of
+ exception wrapping which takes place.
+
+ Interrupt Handling
+
+ The Service Launcher has a helper class, the {@link org.apache.hadoop.service.launcher.InterruptEscalator}
+ to handle the standard SIGKILL signal and control-C signals. This class
+ Registers for signal callbacks from these signals, and, when received, attempts
+ to stop the service in a limited period of time, then triggers a JVM shutdown
+ by way of {@link org.apache.hadoop.util.ExitUtil#terminate(int, String)}
+
+ If a second signal is received, the {@link org.apache.hadoop.service.launcher.InterruptEscalator}
+ reacts by triggering an immediate JVM halt, invoking
+ {@link org.apache.hadoop.util.ExitUtil#halt(int, String)}. This escalation
+ process is designed to address the situation in which a shutdown-hook can
+ block, yet the caller (such as an init.d daemon) wishes to kill the process.
+ The shutdown script should repeat the kill signal after a chosen time period,
+ to trigger the more aggressive process halt. The exit code will always be
+ {@link org.apache.hadoop.service.launcher.LauncherExitCodes#EXIT_INTERRUPTED}.
+
+
+ The {@link org.apache.hadoop.service.launcher.ServiceLauncher} also registers
+ a {@link org.apache.hadoop.service.launcher.ServiceShutdownHook} with the
+ Hadoop shutdown hook manager, unregistering it afterwards. This hook will
+ stop the service if a shutdown request is received -so ensuring that
+ if the JVM is exited by any thread, an attempt to shut down the service
+ will be made.
+
+
+ Configuration class creation
+
+ The Configuration class used to initialize a service is a basic
+ {@link org.apache.hadoop.conf.Configuration} instance. As the launcher is
+ the entry point for an application, this implies that the HDFS, YARN or other
+ default configurations will not have been forced in through the constructors
+ of HdfsConfiguration or YARNConfiguration.
+
+ There are three strategies for dealing with this
+
+ Subclass the Service launcher and override its {@link org.apache.hadoop.service.launcher.ServiceLauncher#createConfiguration()}
+ method with one that creates the right configuration. This is good if a single
+ launcher can be created for all services launched by a module, such as
+ HDFS or YARN. It does imply a dedicated script to invoke the custom main() method.
+
+ In the {@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)},
+ a new configuration is created:
+
+
+ public Configuration bindArgs(Configuration config, List args) throws
+ Exception {
+ Configuration newConf = new YARNConfiguration(config);
+ return newConf;
+ }
+
+
+ This guarantees a configuration of the right type is generated for all
+ instances created via the service launcher. It does imply that this is
+ expected to be only way that services will be launched.
+
+
+ protected void serviceInit(Configuration conf) throws Exception {
+ super.serviceInit(new YARNConfiguration(conf));
+ }
+
+
+ This is a strategy used by many existing YARN services, and is ideal for
+ services which to not implement the LaunchableService interface. Its one
+ weakness is that the configuration is now private to that instance. Some
+ YARN services use a single shared configuration instance as a way of propagating
+ information between peer services in a {@link org.apache.hadoop.service.CompositeService}.
+ While a dangerous practice, it does happen.
+
+
+
+ Configuration file loading
+
+ Before the service is bound to the CLI, the ServiceLauncher scans through
+ all the arguments after the first one, looking for instances of the argument
+ {@link org.apache.hadoop.service.launcher.ServiceLauncher#ARG_CONF}
+ argument pair: --conf <file>. This must refer to a file
+ in the local filesystem which exists. It will be loaded into the Hadoop configuration
+ class (the one created by the {@link org.apache.hadoop.service.launcher.ServiceLauncher#createConfiguration()}
+ method. If this argument is repeated multiple times, all configuration
+ files are merged -with the latest file on the command line being the
+ last one to be applied.
+
+ All the --conf <file> argument pairs are stripped off
+ the argument list provided to the instantiated service; they get the
+ merged configuration, but not the commands used to create it.
+
+
+ Utility Classes
+
+
+
+ -
+ {@link org.apache.hadoop.service.launcher.IrqHandler}: registers interrupt
+ handlers using
sun.misc APIs.
+
+
+ -
+ {@link org.apache.hadoop.service.launcher.ServiceLaunchException}: a
+ subclass of {@link org.apache.hadoop.util.ExitUtil.ExitException} which
+ takes a String-formatted format string and a list of arguments to create
+ the exception text.
+
+
+
+ */
+
+
+package org.apache.hadoop.service.launcher;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.util.ExitUtil;
+
+import java.util.List;
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitCodeProvider.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitCodeProvider.java
new file mode 100644
index 0000000..2fd6150
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitCodeProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util;
+
+/**
+ * Get the exit code of an exception. Making it an interface makes
+ * it possible to retrofit exit codes onto existing classes,
+ * and add exit code providers under all parts of the Exception tree.
+ */
+
+public interface ExitCodeProvider {
+
+ /**
+ * Method to get the exit code
+ * @return the exit code
+ */
+ int getExitCode();
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitUtil.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitUtil.java
index 5208927..fb4d2ea 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitUtil.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/ExitUtil.java
@@ -17,41 +17,120 @@
*/
package org.apache.hadoop.util;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Facilitates hooking process termination for tests and debugging.
+ * Facilitates hooking process termination for tests, debugging
+ * and embedding.
+ *
+ * Hadoop code that attempts to call {@link System#exit(int)}
+ * or {@link Runtime#halt(int)} MUST invoke it via these methods.
*/
-@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
+@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce", "YARN"})
@InterfaceStability.Unstable
public final class ExitUtil {
- private final static Log LOG = LogFactory.getLog(ExitUtil.class.getName());
+ private static final Logger
+ LOG = LoggerFactory.getLogger(ExitUtil.class.getName());
private static volatile boolean systemExitDisabled = false;
private static volatile boolean systemHaltDisabled = false;
private static volatile ExitException firstExitException;
private static volatile HaltException firstHaltException;
- public static class ExitException extends RuntimeException {
+ /**
+ * An exception raised when a call to {@link #terminate(int)} was
+ * called and system exits were blocked.
+ */
+ public static class ExitException extends RuntimeException
+ implements ExitCodeProvider {
private static final long serialVersionUID = 1L;
+ /**
+ * The status code
+ */
public final int status;
public ExitException(int status, String msg) {
super(msg);
this.status = status;
}
+
+ public ExitException(int status,
+ String message,
+ Throwable cause) {
+ super(message, cause);
+ this.status = status;
+ }
+
+ public ExitException(int status, Throwable cause) {
+ super(cause);
+ this.status = status;
+ }
+
+ @Override
+ public int getExitCode() {
+ return status;
+ }
+
+ /**
+ * String value does not include exception type, just exit code and message
+ * @return the exit code and any message
+ */
+ @Override
+ public String toString() {
+ String message = getMessage();
+ if (message == null) {
+ message = super.toString();
+ }
+ return Integer.toString(status) + ": " + message;
+ }
}
- public static class HaltException extends RuntimeException {
+ /**
+ * An exception raised when a call to {@link #terminate(int)} was
+ * called and system halts were blocked.
+ */
+ public static class HaltException extends RuntimeException
+ implements ExitCodeProvider {
private static final long serialVersionUID = 1L;
public final int status;
+ public HaltException(int status, Throwable cause) {
+ super(cause);
+ this.status = status;
+ }
+
public HaltException(int status, String msg) {
super(msg);
this.status = status;
}
+
+ public HaltException(int status,
+ String message,
+ Throwable cause) {
+ super(message, cause);
+ this.status = status;
+ }
+
+ @Override
+ public int getExitCode() {
+ return status;
+ }
+
+ /**
+ * String value does not include exception type, just exit code and message
+ * @return the exit code and any message
+ */
+ @Override
+ public String toString() {
+ String message = getMessage();
+ if (message == null) {
+ message = super.toString();
+ }
+ return Integer.toString(status) + ": " + message;
+ }
+
}
/**
@@ -110,22 +189,21 @@ public static void resetFirstHaltException() {
}
/**
- * Terminate the current process. Note that terminate is the *only* method
- * that should be used to terminate the daemon processes.
- *
- * @param status
- * exit code
- * @param msg
- * message used to create the {@code ExitException}
- * @throws ExitException
- * if System.exit is disabled for test purposes
+ * Inner termination: either exit with the exception's exit code,
+ * or, if system exits are disabled, rethrow the exception
+ * @param ee exit exception
*/
- public static void terminate(int status, String msg) throws ExitException {
- LOG.info("Exiting with status " + status);
+ public static synchronized void terminate(ExitException ee) throws ExitException {
+ int status = ee.getExitCode();
+ String msg = ee.getMessage();
+ if (status != 0) {
+ //exit indicates a problem, log it
+ LOG.debug("Exiting with status {}: {}", status, msg, ee);
+ LOG.info("Exiting with status {}: {}", status, msg);
+ }
if (systemExitDisabled) {
- ExitException ee = new ExitException(status, msg);
- LOG.fatal("Terminate called", ee);
- if (null == firstExitException) {
+ LOG.error("Terminate called", ee);
+ if (!terminateCalled()) {
firstExitException = ee;
}
throw ee;
@@ -135,20 +213,22 @@ public static void terminate(int status, String msg) throws ExitException {
/**
* Forcibly terminates the currently running Java virtual machine.
- *
- * @param status
- * exit code
- * @param msg
- * message used to create the {@code HaltException}
- * @throws HaltException
- * if Runtime.getRuntime().halt() is disabled for test purposes
- */
- public static void halt(int status, String msg) throws HaltException {
- LOG.info("Halt with status " + status + " Message: " + msg);
+ * The exception argument is rethrown if JVM halitng is disabled.
+ * @param ee the exception containing the status code, message and any stack
+ * trace.
+ * @throws HaltException if {@link Runtime#halt(int)} is disabled.
+ */
+ public static synchronized void halt(HaltException ee) throws HaltException {
+ int status = ee.getExitCode();
+ String msg = ee.getMessage();
+ if (status != 0) {
+ //exit indicates a problem, log it
+ LOG.debug("Halt with status {}: {}", status, msg, ee);
+ LOG.info("Halt with status {}: {}", status, msg, msg);
+ }
if (systemHaltDisabled) {
- HaltException ee = new HaltException(status, msg);
- LOG.fatal("Halt called", ee);
- if (null == firstHaltException) {
+ LOG.error("Halt called", ee);
+ if (!haltCalled()) {
firstHaltException = ee;
}
throw ee;
@@ -157,47 +237,79 @@ public static void halt(int status, String msg) throws HaltException {
}
/**
- * Like {@link terminate(int, String)} but uses the given throwable to
- * initialize the ExitException.
+ * Like {@link #terminate(int, String)} but uses the given throwable to
+ * Used to build the message to display or throw as an
+ * {@link ExitException}
*
- * @param status
- * @param t
- * throwable used to create the ExitException
- * @throws ExitException
- * if System.exit is disabled for test purposes
+ * @param status exit code to use if the exception is not an ExitException.
+ * @param t throwable which triggered the termination. If this exception
+ * is an {@link ExitException} its status overrides that passed in.
+ * @throws ExitException if {@link System#exit(int)} is disabled.
*/
public static void terminate(int status, Throwable t) throws ExitException {
- terminate(status, StringUtils.stringifyException(t));
+ if (t instanceof ExitException) {
+ terminate((ExitException) t);
+ } else {
+ terminate(new ExitException(status, t));
+ }
}
/**
* Forcibly terminates the currently running Java virtual machine.
*
- * @param status
- * @param t
- * @throws ExitException
+ * @param status exit code to use if the exception is not a HaltException.
+ * @param t throwable which triggered the termination. If this exception
+ * is a {@link HaltException} its status overrides that passed in.
+ * @throws HaltException if {@link System#exit(int)} is disabled.
*/
public static void halt(int status, Throwable t) throws HaltException {
- halt(status, StringUtils.stringifyException(t));
+ if (t instanceof HaltException) {
+ halt((HaltException) t);
+ } else {
+ halt(new HaltException(status, t));
+ }
}
/**
- * Like {@link terminate(int, String)} without a message.
+ * Like {@link #terminate(int, Throwable)} without a message.
*
- * @param status
- * @throws ExitException
- * if System.exit is disabled for test purposes
+ * @param status exit code
+ * @throws ExitException if {@link System#exit(int)} is disabled.
*/
public static void terminate(int status) throws ExitException {
- terminate(status, "ExitException");
+ terminate(status, "");
+ }
+
+ /**
+ * Terminate the current process. Note that terminate is the *only* method
+ * that should be used to terminate the daemon processes.
+ *
+ * @param status exit code
+ * @param msg message used to create the {@code ExitException}
+ * @throws ExitException if {@link System#exit(int)} is disabled.
+ */
+ public static void terminate(int status, String msg) throws ExitException {
+ terminate(new ExitException(status, msg));
}
/**
* Forcibly terminates the currently running Java virtual machine.
- * @param status
- * @throws ExitException
+ * @param status status code
+ * @throws HaltException if {@link Runtime#halt(int)} is disabled.
*/
public static void halt(int status) throws HaltException {
- halt(status, "HaltException");
+ halt(status, "");
+ }
+
+ /**
+ * Forcibly terminates the currently running Java virtual machine.
+ * @param status status code
+ * @param message message
+ * @throws HaltException if {@link Runtime#halt(int)} is disabled.
+ */
+ public static void halt(int status, String message) throws HaltException {
+ halt(new HaltException(status, message));
}
+
+
}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java
index e7f983a..8ac0374 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/StringUtils.java
@@ -597,7 +597,7 @@ public static String unEscapeString(String str, char escapeChar,
* @param msg content of the message
* @return a message for logging
*/
- private static String toStartupShutdownString(String prefix, String [] msg) {
+ public static String toStartupShutdownString(String prefix, String [] msg) {
StringBuilder b = new StringBuilder(prefix);
b.append("\n/************************************************************");
for(String s : msg)
@@ -617,18 +617,7 @@ public static void startupShutdownMessage(Class> clazz, String[] args,
final String hostname = NetUtils.getHostname();
final String classname = clazz.getSimpleName();
LOG.info(
- toStartupShutdownString("STARTUP_MSG: ", new String[] {
- "Starting " + classname,
- " host = " + hostname,
- " args = " + Arrays.asList(args),
- " version = " + VersionInfo.getVersion(),
- " classpath = " + System.getProperty("java.class.path"),
- " build = " + VersionInfo.getUrl() + " -r "
- + VersionInfo.getRevision()
- + "; compiled by '" + VersionInfo.getUser()
- + "' on " + VersionInfo.getDate(),
- " java = " + System.getProperty("java.version") }
- )
+ createStartupShutdownMessage(classname, hostname, args)
);
if (SystemUtils.IS_OS_UNIX) {
@@ -650,6 +639,29 @@ public void run() {
}
/**
+ * Generate the text for the startup/shutdown message of processes
+ * @param classname short classname of the class
+ * @param hostname hostname
+ * @param args Command arguments
+ * @return a string to log.
+ */
+ public static String createStartupShutdownMessage(String classname,
+ String hostname, String[] args) {
+ return toStartupShutdownString("STARTUP_MSG: ", new String[] {
+ "Starting " + classname,
+ " host = " + hostname,
+ " args = " + Arrays.asList(args),
+ " version = " + VersionInfo.getVersion(),
+ " classpath = " + System.getProperty("java.class.path"),
+ " build = " + VersionInfo.getUrl() + " -r "
+ + VersionInfo.getRevision()
+ + "; compiled by '" + VersionInfo.getUser()
+ + "' on " + VersionInfo.getDate(),
+ " java = " + System.getProperty("java.version") }
+ );
+ }
+
+ /**
* The traditional binary prefixes, kilo, mega, ..., exa,
* which can be represented by a 64-bit integer.
* TraditionalBinaryPrefix symbol are case insensitive.
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/BreakableService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/BreakableService.java
index eeb1a31..74ea5aa 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/BreakableService.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/BreakableService.java
@@ -20,8 +20,6 @@
package org.apache.hadoop.service;
import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.service.AbstractService;
-import org.apache.hadoop.service.Service;
/**
* This is a service that can be configured to break on any of the lifecycle
@@ -69,12 +67,21 @@ public int getCount(STATE state) {
return counts[convert(state)];
}
- private void maybeFail(boolean fail, String action) {
+ private void maybeFail(boolean fail, String action) throws Exception {
if (fail) {
- throw new BrokenLifecycleEvent(this, action);
+ throw createFailureException(action);
}
}
+ /**
+ * Override point: create the exception to raise
+ * @param action action in progress
+ * @return the exception that will be thrown
+ */
+ protected Exception createFailureException(String action) {
+ return new BrokenLifecycleEvent(this, action);
+ }
+
@Override
protected void serviceInit(Configuration conf) throws Exception {
inc(STATE.INITED);
@@ -83,13 +90,13 @@ protected void serviceInit(Configuration conf) throws Exception {
}
@Override
- protected void serviceStart() {
+ protected void serviceStart() throws Exception {
inc(STATE.STARTED);
maybeFail(failOnStart, "start");
}
@Override
- protected void serviceStop() {
+ protected void serviceStop() throws Exception {
inc(STATE.STOPPED);
maybeFail(failOnStop, "stop");
}
@@ -111,7 +118,7 @@ public void setFailOnStop(boolean failOnStop) {
*/
public static class BrokenLifecycleEvent extends RuntimeException {
- final STATE state;
+ public final STATE state;
public BrokenLifecycleEvent(Service service, String action) {
super("Lifecycle Failure during " + action + " state is "
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/AbstractServiceLauncherTestBase.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/AbstractServiceLauncherTestBase.java
new file mode 100644
index 0000000..cdf4b6a
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/AbstractServiceLauncherTestBase.java
@@ -0,0 +1,349 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.io.IOUtils;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ExitCodeProvider;
+import org.apache.hadoop.util.ExitUtil;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+import org.junit.rules.Timeout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class AbstractServiceLauncherTestBase extends Assert implements
+ LauncherExitCodes {
+ private static final Logger LOG = LoggerFactory.getLogger(
+ AbstractServiceLauncherTestBase.class);
+ public static final String CONF_FILE_DIR = "target/conf";
+
+ /**
+ * All tests have a short life.
+ */
+ @Rule
+ public Timeout testTimeout = new Timeout(15000);
+
+ /**
+ * Rule to provide the method name
+ */
+ @Rule
+ public TestName methodName = new TestName();
+
+ /**
+ * Turn off the exit util JVM exits, downgrading them to exception throws.
+ */
+ @BeforeClass
+ public static void disableJVMExits() {
+ ExitUtil.disableSystemExit();
+ ExitUtil.disableSystemHalt();
+ }
+
+ /**
+ * Formatted fail
+ * @param format format string
+ * @param args argument list. If the last argument is a throwable, it
+ * is used as the inner cause of the exception
+ * @throws AssertionError with the formatted message
+ */
+ protected static void failf(String format, Object... args) {
+ String message = String.format(Locale.ENGLISH, format, args);
+ AssertionError error = new AssertionError(message);
+ int len = args.length;
+ if (len > 0 && args[len - 1] instanceof Throwable) {
+ error.initCause((Throwable) args[len - 1]);
+ }
+ throw error;
+ }
+
+ /**
+ * Conditional formatted fail
+ * @param format format string
+ * @param args argument list. If the last argument is a throwable, it
+ * is used as the inner cause of the exception
+ * @throws AssertionError with the formatted message
+ */
+ protected static void failif(boolean condition,
+ String format,
+ Object... args) {
+ if (condition) {
+ failf(format, args);
+ }
+ }
+
+ /**
+ * rule to name the thread JUnit.
+ */
+ @Before
+ public void nameThread() {
+ Thread.currentThread().setName("JUnit");
+ }
+
+ /**
+ * Assert that a service is in a state
+ * @param service service
+ * @param expected expected state
+ */
+ protected void assertInState(Service service, Service.STATE expected) {
+ assertNotNull(service);
+ Service.STATE actual = service.getServiceState();
+ failif(actual != expected,
+ "Service %s in state %s expected state: %s",
+ service.getName(), actual, expected);
+ }
+
+ /**
+ * Assert a service has stopped
+ * @param service service
+ */
+ protected void assertStopped(Service service) {
+ assertInState(service, Service.STATE.STOPPED);
+ }
+
+ /**
+ * Assert that an exception code matches the value expected
+ * @param expected expected value
+ * @param text text in exception -can be null
+ * @param e exception providing the actual value
+ */
+ protected void assertExceptionDetails(int expected,
+ String text,
+ ExitCodeProvider e) {
+ assertNotNull(e);
+ String toString = e.toString();
+ int exitCode = e.getExitCode();
+ boolean failed = expected != exitCode;
+ failed |= StringUtils.isNotEmpty(text)
+ && !StringUtils.contains(toString, text);
+ if (failed) {
+ String error = String.format(
+ "Expected exception with exit code %d and text \"%s\""
+ + " but got the exit code %d"
+ + " and text \"%s\"",
+ expected, text,
+ exitCode, toString);
+ LOG.error(error, e);
+ AssertionError assertionError = new AssertionError(error);
+ assertionError.initCause((Throwable) e);
+ throw assertionError;
+ }
+ }
+
+ /**
+ * Assert the launch come was a service creation failure
+ * @param classname argument
+ */
+ protected void assertServiceCreationFails(String classname) {
+ assertLaunchOutcome(EXIT_SERVICE_CREATION_FAILURE, "", classname);
+ }
+
+ /**
+ * Assert a launch outcome
+ * @param expected expected value
+ * @param text text in exception -can be null
+ * @param args CLI args
+ */
+ protected void assertLaunchOutcome(int expected,
+ String text,
+ String... args) {
+ try {
+ ServiceLauncher.serviceMain(args);
+ } catch (ServiceLaunchException e) {
+ assertExceptionDetails(expected, text, e);
+ }
+ }
+
+ /**
+ * Assert a launch runs
+ * @param args CLI args
+ */
+ protected void assertRuns(String... args) {
+ assertLaunchOutcome(0, "", args);
+ }
+
+ /**
+ * Init and start a service
+ * @param service the service
+ * @return the service
+ */
+ protected S run(S service) {
+ assertNotNull(service);
+ service.init(new Configuration());
+ service.start();
+ return service;
+ }
+
+ /**
+ * Save a configuration to a config file in the target dir
+ * @param conf config
+ * @return absolute path
+ * @throws IOException problems
+ */
+ protected String configFile(Configuration conf) throws IOException {
+ File directory = new File(CONF_FILE_DIR);
+ directory.mkdirs();
+ File file = File.createTempFile("conf", ".xml", directory);
+ OutputStream fos = new FileOutputStream(file);
+ try {
+ conf.writeXml(fos);
+ } finally {
+ IOUtils.closeStream(fos);
+ }
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * Create a new config from key-val pairs
+ * @param kvp a list of key, value, ...
+ * @return a new configuration
+ */
+ protected Configuration newConf(String... kvp) {
+ int len = kvp.length;
+ assertEquals("unbalanced keypair len of " + len, 0, len % 2);
+ Configuration conf = new Configuration(false);
+ for (int i = 0; i < len; i += 2) {
+ conf.set(kvp[i], kvp[i + 1]);
+ }
+ return conf;
+ }
+
+ /** varargs to list conversion */
+ protected List asList(String... args) {
+ return Arrays.asList(args);
+ }
+
+ /**
+ * Launch a service with the given list of arguments. Returns
+ * the service launcher, from which the created service can be extracted
+ * via {@link ServiceLauncher#getService()}.
+ * The service is has its execute() method called, but
+ * @param serviceClass service class to create
+ * @param conf configuration
+ * @param args list of arguments
+ * @param execute execute/wait for the service to stop
+ * @param service type
+ * @return the service launcher
+ * @throws ExitUtil.ExitException if the launch's exit code != 0
+ */
+ protected ServiceLauncher launchService(
+ Class serviceClass,
+ Configuration conf,
+ List args,
+ boolean execute) throws ExitUtil.ExitException {
+ ServiceLauncher serviceLauncher =
+ new ServiceLauncher(serviceClass.getName());
+ ExitUtil.ExitException exitException =
+ serviceLauncher.launchService(conf, args, false, execute);
+ if (exitException.getExitCode() == 0) {
+ return serviceLauncher;
+ } else {
+ throw exitException;
+ }
+ }
+
+ /**
+ * Launch a service with the given list of arguments. Returns
+ * the service launcher, from which the created service can be extracted.
+ * via {@link ServiceLauncher#getService()}.
+ *
+ * This call DOES NOT call {@link LaunchableService#execute()} or wait for
+ * a simple service to finish. It returns the service that has been created,
+ * initialized and started.
+ * @param serviceClass service class to create
+ * @param conf configuration
+ * @param args varargs launch arguments
+ * @param service type
+ * @return the service launcher
+ * @throws ExitUtil.ExitException if the launch's exit code != 0
+ */
+ protected ServiceLauncher launchService(
+ Class serviceClass,
+ Configuration conf,
+ String... args) throws ExitUtil.ExitException {
+ return launchService(serviceClass, conf, Arrays.asList(args), false);
+ }
+
+ /**
+ * Launch expecting an exception
+ * @param serviceClass service class to create
+ * @param conf configuration
+ * @param expectedText expected text; may be "" or null
+ * @param errorCode error code
+ * @param args varargs launch arguments
+ * @return the exception returned if there was a match
+ * @throws AssertionError on a mismatch of expectation and actual
+ */
+ protected ExitUtil.ExitException launchExpectingException(Class serviceClass,
+ Configuration conf,
+ String expectedText,
+ int errorCode,
+ String... args) {
+ try {
+ ServiceLauncher launch = launchService(serviceClass,
+ conf,
+ Arrays.asList(args),
+ true);
+
+ failf("Expected an exception with error code %d and text \"%s\" "
+ + " -but the service completed with :%s",
+ errorCode, expectedText, launch.getServiceException());
+ return null;
+ } catch (ExitUtil.ExitException e) {
+ int actualCode = e.getExitCode();
+ failif(errorCode != actualCode ||
+ !StringUtils.contains(e.toString(), expectedText),
+ "Expected an exception with error code %d and text \"%s\" "
+ + " -but the service threw an exception with exit code %d: %s",
+ errorCode, expectedText, actualCode, e);
+ return e;
+ }
+ }
+
+ /**
+ * Assert that an exception message contains a search string
+ * @param exception exception to get a message from
+ * @param search search key
+ */
+ protected void assertMessageContains(Exception exception, String search) {
+ assertContains(exception.getMessage(), search);
+ }
+
+ /**
+ * assert that a text string contains the search text
+ * @param text text to look through
+ * @param search search key
+ */
+ protected void assertContains(String text, String search) {
+ failif(!StringUtils.contains(text, search), "String \"%s\" not found in \"%s\"",
+ text, search);
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/NonExitingServiceLauncher.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/NonExitingServiceLauncher.java
new file mode 100644
index 0000000..6d60f4b
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/NonExitingServiceLauncher.java
@@ -0,0 +1,52 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ExitUtil;
+
+/**
+ * Service launcher for testing
+ * @param type of service to launch
+ */
+public class NonExitingServiceLauncher extends
+ ServiceLauncher {
+
+ public ExitUtil.ExitException exitException;
+
+ public NonExitingServiceLauncher(String serviceClassName) {
+ super(serviceClassName);
+ }
+
+ public void setService(S s) {
+ super.setService(s);
+ }
+
+ @Override
+ protected void exit(ExitUtil.ExitException ee) {
+ exitException = ee;
+ super.exit(ee);
+ }
+
+ @Override
+ protected void exit(int exitCode, String message) {
+ exit(new ServiceLaunchException(exitCode, message));
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceConf.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceConf.java
new file mode 100644
index 0000000..0c08615
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceConf.java
@@ -0,0 +1,146 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.testservices.LaunchableRunningService;
+import org.apache.hadoop.service.launcher.testservices.RunningService;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.List;
+
+public class TestServiceConf extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(LaunchableRunningService.NAME);
+ }
+
+ @Test
+ public void testConfPropagationOverInitBindings() throws Throwable {
+ Configuration conf = newConf(LaunchableRunningService.FAIL_IN_RUN, "true");
+ assertLaunchOutcome(EXIT_FAIL,
+ "failed",
+ LaunchableRunningService.NAME,
+ ServiceLauncher.ARG_CONF,
+ configFile(conf));
+ }
+
+ @Test
+ public void testUnbalancedConfArg() throws Throwable {
+ Configuration conf = newConf(RunningService.FAIL_IN_RUN, "true");
+ assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR,
+ "missing",
+ RunningService.NAME,
+ ServiceLauncher.ARG_CONF);
+ }
+
+ @Test
+ public void testConfArgMissingFile() throws Throwable {
+ Configuration conf = newConf(RunningService.FAIL_IN_RUN, "true");
+ assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR,
+ "not found",
+ RunningService.NAME,
+ ServiceLauncher.ARG_CONF,
+ "no-file.xml");
+ }
+
+ @Test
+ public void testConfPropagation() throws Throwable {
+ Configuration conf = newConf(RunningService.FAIL_IN_RUN, "true");
+ assertLaunchOutcome(EXIT_EXCEPTION_THROWN,
+ RunningService.FAILURE_MESSAGE,
+ RunningService.NAME,
+ ServiceLauncher.ARG_CONF,
+ configFile(conf));
+ }
+
+ /**
+ * Low level conf value extraction test...just to make sure
+ * that all works at the lower level
+ * @throws Throwable
+ */
+ @Test
+ public void testConfExtraction() throws Throwable {
+ ServiceLauncher launcher =
+ new ServiceLauncher(RunningService.NAME);
+ Configuration conf = newConf("propagated", "true");
+ assertEquals("true", conf.get("propagated", "unset"));
+
+ Configuration extracted = new Configuration(false);
+
+ List argsList =
+ asList("Name", ServiceLauncher.ARG_CONF, configFile(conf));
+ List args = launcher.extractConfigurationArgs(extracted,
+ argsList);
+ if (!args.isEmpty()) {
+ assertEquals("args beginning with " + args.get(0),
+ 0, args.size());
+ }
+ assertEquals("true", extracted.get("propagated", "unset"));
+ }
+
+ /**
+ * Low level conf value extraction test...just to make sure
+ * that all works at the lower level
+ * @throws Throwable
+ */
+ @Test
+ public void testDualConfArgs() throws Throwable {
+ ServiceLauncher launcher =
+ new ServiceLauncher(RunningService.NAME);
+ String key1 = "key1";
+ Configuration conf1 = newConf(key1, "true");
+ String key2 = "file2";
+ Configuration conf2 = newConf(key2, "7");
+ Configuration extracted = new Configuration(false);
+
+ List argsList =
+ asList("Name",
+ ServiceLauncher.ARG_CONF, configFile(conf1),
+ ServiceLauncher.ARG_CONF, configFile(conf2))
+ ;
+ List args = launcher.extractConfigurationArgs(extracted,
+ argsList);
+ if (!args.isEmpty()) {
+ assertEquals("args beginning with " + args.get(0),
+ 0, args.size());
+ }
+ assertTrue(extracted.getBoolean(key1, false));
+ assertEquals(7, extracted.getInt(key2, -1));
+ }
+
+
+ @Test
+ public void testConfArgWrongMissingFiletype() throws Throwable {
+ File file = new File(CONF_FILE_DIR, methodName.getMethodName());
+ FileWriter fileWriter = new FileWriter(file);
+ fileWriter.write("not-a-conf-file");
+ fileWriter.close();
+ assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR,
+ "not loadable",
+ RunningService.NAME,
+ ServiceLauncher.ARG_CONF,
+ file.getAbsolutePath());
+ }
+
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceExceptionInExecute.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceExceptionInExecute.java
new file mode 100644
index 0000000..167d681
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceExceptionInExecute.java
@@ -0,0 +1,62 @@
+/*
+ * 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.service.launcher;
+
+import static org.apache.hadoop.service.launcher.testservices.ExceptionInExecuteLaunchableService.*;
+import org.junit.Test;
+
+/**
+ * This test verifies that exceptions in the
+ * {@link LaunchableService#execute()} method are relayed if exit exceptions,
+ * forwarded if not
+ */
+public class TestServiceExceptionInExecute extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testEx() throws Throwable {
+ assertLaunchOutcome(EXIT_EXCEPTION_THROWN,
+ OTHER_EXCEPTION_TEXT,
+ NAME);
+ }
+
+ @Test
+ public void testSLE() throws Throwable {
+ assertLaunchOutcome(EXIT_OTHER_FAILURE,
+ SLE_TEXT,
+ NAME,
+ ARG_THROW_SLE);
+ }
+
+ @Test
+ public void testIOE() throws Throwable {
+ assertLaunchOutcome(IOE_EXIT_CODE,
+ EXIT_IN_IOE_TEXT,
+ NAME,
+ ARG_THROW_IOE);
+ }
+
+ @Test
+ public void testThrowable() throws Throwable {
+ assertLaunchOutcome(EXIT_EXCEPTION_THROWN,
+ "java.lang.OutOfMemoryError",
+ NAME,
+ ARG_THROWABLE);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceInterruptHandling.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceInterruptHandling.java
new file mode 100644
index 0000000..d599a0b
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceInterruptHandling.java
@@ -0,0 +1,118 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.BreakableService;
+import org.apache.hadoop.service.launcher.testservices.FailureTestService;
+import org.apache.hadoop.util.ExitUtil;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test service launcher interrupt handling
+ */
+public class TestServiceInterruptHandling extends AbstractServiceLauncherTestBase {
+ private static final Logger LOG = LoggerFactory.getLogger(
+ TestServiceInterruptHandling.class);
+
+
+ @Test
+ public void testRegisterAndRaise() throws Throwable {
+ InterruptCatcher catcher = new InterruptCatcher();
+ String name = IrqHandler.CONTROL_C;
+ IrqHandler irqHandler = new IrqHandler(name, catcher);
+ assertEquals(0, irqHandler.getSignalCount());
+ irqHandler.raise();
+ // allow for an async event
+ Thread.sleep(500);
+ IrqHandler.InterruptData data = catcher.interruptData;
+ assertNotNull(data);
+ assertEquals(name, data.name);
+ assertEquals(1, irqHandler.getSignalCount());
+ }
+
+
+ @Test
+ public void testInterruptEscalationShutdown() throws Throwable {
+ NonExitingServiceLauncher launcher =
+ new NonExitingServiceLauncher(
+ BreakableService.class.getName());
+ BreakableService service = new BreakableService();
+ launcher.setService(service);
+
+ InterruptEscalator escalator =
+ new InterruptEscalator(launcher, 500);
+
+ // call the interrupt operation directly
+ try {
+ escalator.interrupted(new IrqHandler.InterruptData("INT", 3));
+ fail("Expected an exception to be raised");
+ } catch (ExitUtil.ExitException e) {
+ assertExceptionDetails(EXIT_INTERRUPTED, "", e);
+ }
+ //the service is now stopped
+ assertStopped(service);
+ assertTrue(escalator.isSignalAlreadyReceived());
+ assertFalse(escalator.isForcedShutdownTimedOut());
+
+ // now interrupt it a second time and expect it to escalate to a halt
+ try {
+ escalator.interrupted(new IrqHandler.InterruptData("INT", 3));
+ fail("Expected an exception to be raised");
+ } catch (ExitUtil.HaltException e) {
+ assertExceptionDetails(EXIT_INTERRUPTED, "", e);
+ }
+ }
+
+
+ @Test
+ public void testBlockingShutdownTimeouts() throws Throwable {
+ NonExitingServiceLauncher launcher =
+ new NonExitingServiceLauncher(
+ FailureTestService.class.getName());
+ FailureTestService service =
+ new FailureTestService(false, false, false, 2000);
+ launcher.setService(service);
+
+ InterruptEscalator escalator =
+ new InterruptEscalator(launcher, 500);
+ // call the interrupt operation directly
+ try {
+ escalator.interrupted(new IrqHandler.InterruptData("INT", 3));
+ fail("Expected an exception to be raised");
+ } catch (ExitUtil.ExitException e) {
+ assertExceptionDetails(EXIT_INTERRUPTED, "", e);
+ }
+
+ assertTrue(escalator.isForcedShutdownTimedOut());
+ }
+
+ private static class InterruptCatcher implements IrqHandler.Interrupted {
+
+ public IrqHandler.InterruptData interruptData;
+
+ @Override
+ public void interrupted(IrqHandler.InterruptData data) {
+ LOG.info("Interrupt caught");
+ this.interruptData = data;
+ }
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchExceptionFormatting.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchExceptionFormatting.java
new file mode 100644
index 0000000..1ca8570
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchExceptionFormatting.java
@@ -0,0 +1,69 @@
+/*
+ * 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.service.launcher;
+
+import org.junit.Test;
+
+/**
+ * As the exception is doing some formatting tricks, these
+ * tests verify that exception arguments are being correctly
+ * used as initializers
+ */
+public class TestServiceLaunchExceptionFormatting extends
+ AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testBasicExceptionFormatting() throws Throwable {
+
+ ServiceLaunchException ex =
+ new ServiceLaunchException(0, "%03x", 32);
+ assertMessageContains(ex, "020");
+ }
+
+ @Test
+ public void testNotEnoughArgsExceptionFormatting() throws Throwable {
+
+ ServiceLaunchException ex =
+ new ServiceLaunchException(0, "%03x");
+ assertMessageContains(ex, "%03x");
+ }
+
+ @Test
+ public void testInnerCause() throws Throwable {
+
+ Exception cause = new Exception("cause");
+ ServiceLaunchException ex =
+ new ServiceLaunchException(0, "%03x: %s", 32, cause);
+ assertMessageContains(ex, "020");
+ assertMessageContains(ex, "cause");
+ assertSame(cause, ex.getCause());
+ }
+
+ @Test
+ public void testInnerCauseNotInFormat() throws Throwable {
+
+ Exception cause = new Exception("cause");
+ ServiceLaunchException ex =
+ new ServiceLaunchException(0, "%03x:", 32, cause);
+ assertMessageContains(ex, "020");
+ assertFalse(ex.getMessage().contains("cause"));
+ assertSame(cause, ex.getCause());
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchInitInConstructor.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchInitInConstructor.java
new file mode 100644
index 0000000..a91455d
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchInitInConstructor.java
@@ -0,0 +1,31 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.launcher.testservices.InitInConstructorLaunchableService;
+import org.junit.Test;
+
+public class TestServiceLaunchInitInConstructor extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(InitInConstructorLaunchableService.NAME);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchNoArgsAllowed.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchNoArgsAllowed.java
new file mode 100644
index 0000000..8a89eee
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchNoArgsAllowed.java
@@ -0,0 +1,44 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.launcher.testservices.NoArgsAllowedService;
+import org.junit.Test;
+
+public class TestServiceLaunchNoArgsAllowed extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(NoArgsAllowedService.NAME);
+ }
+
+ @Test
+ public void testOneArg() throws Throwable {
+ assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR, "1",
+ NoArgsAllowedService.NAME, "one");
+ }
+
+ @Test
+ public void testConfsStripped() throws Throwable {
+ assertRuns(NoArgsAllowedService.NAME,
+ ServiceLauncher.ARG_CONF,
+ configFile(newConf()));
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchStoppingInStart.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchStoppingInStart.java
new file mode 100644
index 0000000..208b621
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchStoppingInStart.java
@@ -0,0 +1,32 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.launcher.testservices.InitInConstructorLaunchableService;
+import org.apache.hadoop.service.launcher.testservices.StoppingInStartLaunchableService;
+import org.junit.Test;
+
+public class TestServiceLaunchStoppingInStart extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(StoppingInStartLaunchableService.NAME);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchStringConstructor.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchStringConstructor.java
new file mode 100644
index 0000000..ae9c31f
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchStringConstructor.java
@@ -0,0 +1,31 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.launcher.testservices.StringConstructorOnlyService;
+import org.junit.Test;
+
+public class TestServiceLaunchStringConstructor extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(StringConstructorOnlyService.NAME);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchedRunning.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchedRunning.java
new file mode 100644
index 0000000..f89ee2c
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLaunchedRunning.java
@@ -0,0 +1,51 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.testservices.LaunchableRunningService;
+import org.junit.Test;
+
+public class TestServiceLaunchedRunning extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(LaunchableRunningService.NAME);
+ }
+
+ @Test
+ public void testConfPropagationOverInitBindings() throws Throwable {
+ Configuration conf = newConf(LaunchableRunningService.FAIL_IN_RUN, "true");
+ assertLaunchOutcome(EXIT_FAIL,
+ "failed",
+ LaunchableRunningService.NAME,
+ ServiceLauncher.ARG_CONF,
+ configFile(conf));
+ }
+
+ @Test
+ public void testArgBinding() throws Throwable {
+ Configuration conf = newConf(LaunchableRunningService.FAIL_IN_RUN, "true");
+ assertLaunchOutcome(EXIT_OTHER_FAILURE,
+ "",
+ LaunchableRunningService.NAME,
+ LaunchableRunningService.ARG_FAILING);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLauncherCreationFailures.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLauncherCreationFailures.java
new file mode 100644
index 0000000..6e5991e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLauncherCreationFailures.java
@@ -0,0 +1,81 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.launcher.testservices.FailInConstructorService;
+import org.apache.hadoop.service.launcher.testservices.FailInInitService;
+import org.apache.hadoop.service.launcher.testservices.FailInStartService;
+import org.apache.hadoop.service.launcher.testservices.FailingStopInStartService;
+import org.junit.Test;
+
+public class TestServiceLauncherCreationFailures extends
+ AbstractServiceLauncherTestBase {
+
+
+ public static final String SELF =
+ "org.apache.hadoop.service.launcher.TestServiceLauncherCreationFailures";
+
+ @Test
+ public void testNoArgs() throws Throwable {
+ try {
+ ServiceLauncher.serviceMain();
+ } catch (ServiceLaunchException e) {
+ assertExceptionDetails(EXIT_USAGE, "", e);
+ }
+ }
+
+ @Test
+ public void testUnknownClass() throws Throwable {
+ assertServiceCreationFails("no.such.classname");
+ }
+
+ @Test
+ public void testNotAService() throws Throwable {
+ assertServiceCreationFails(SELF);
+ }
+
+ @Test
+ public void testNoSimpleConstructor() throws Throwable {
+ assertServiceCreationFails(
+ "org.apache.hadoop.service.launcher.FailureTestService");
+ }
+
+ @Test
+ public void testFailInConstructor() throws Throwable {
+ assertServiceCreationFails(FailInConstructorService.NAME);
+ }
+
+ @Test
+ public void testFailInInit() throws Throwable {
+ assertLaunchOutcome(FailInInitService.EXIT_CODE, "",
+ FailInInitService.NAME);
+ }
+
+ @Test
+ public void testFailInStart() throws Throwable {
+ assertLaunchOutcome(FailInStartService.EXIT_CODE, "",
+ FailInStartService.NAME);
+ }
+
+ @Test
+ public void testFailInStopIsIgnored() throws Throwable {
+ assertRuns(FailingStopInStartService.NAME);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLauncherInnerMethods.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLauncherInnerMethods.java
new file mode 100644
index 0000000..be6cbd3
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceLauncherInnerMethods.java
@@ -0,0 +1,76 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.BreakableService;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.service.launcher.testservices.ExceptionInExecuteLaunchableService;
+import org.apache.hadoop.service.launcher.testservices.LaunchableRunningService;
+import org.apache.hadoop.service.launcher.testservices.NoArgsAllowedService;
+import org.junit.Test;
+
+/**
+ * Test the inner launcher methods.
+ */
+@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+public class TestServiceLauncherInnerMethods extends
+ AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testLaunchService() throws Throwable {
+ ServiceLauncher launcher =
+ launchService(NoArgsAllowedService.class, new Configuration());
+ }
+
+ @Test
+ public void testLaunchServiceArgs() throws Throwable {
+ launchExpectingException(NoArgsAllowedService.class,
+ new Configuration(),
+ "arguments", EXIT_COMMAND_ARGUMENT_ERROR,
+ "one", "two");
+ }
+
+ @Test
+ public void testAccessLaunchedService() throws Throwable {
+ ServiceLauncher launcher =
+ launchService(LaunchableRunningService.class, new Configuration());
+ LaunchableRunningService service = launcher.getService();
+ assertInState(service, Service.STATE.STARTED);
+ service.failInRun = true;
+ service.exitCode = EXIT_CONNECTIVITY_PROBLEM;
+ assertEquals(EXIT_CONNECTIVITY_PROBLEM, service.execute());
+ }
+
+ @Test
+ public void testLaunchThrowableRaised() throws Throwable {
+ launchExpectingException(ExceptionInExecuteLaunchableService.class,
+ new Configuration(),
+ "java.lang.OutOfMemoryError", EXIT_EXCEPTION_THROWN,
+ ExceptionInExecuteLaunchableService.ARG_THROWABLE);
+ }
+
+ @Test
+ public void testBreakableServiceLifecycle() throws Throwable {
+ ServiceLauncher launcher =
+ launchService(BreakableService.class, new Configuration());
+
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceNullBind.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceNullBind.java
new file mode 100644
index 0000000..611f356
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceNullBind.java
@@ -0,0 +1,31 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.launcher.testservices.NullBindLaunchableService;
+import org.junit.Test;
+
+public class TestServiceNullBind extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(NullBindLaunchableService.NAME);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceRunning.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceRunning.java
new file mode 100644
index 0000000..e9b2110
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceRunning.java
@@ -0,0 +1,36 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.testservices.RunningService;
+import org.junit.Test;
+
+import java.util.List;
+
+public class TestServiceRunning extends AbstractServiceLauncherTestBase {
+
+
+ @Test
+ public void testRunService() throws Throwable {
+ assertRuns(RunningService.NAME);
+ }
+
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceShutdownHook.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceShutdownHook.java
new file mode 100644
index 0000000..af478aa
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceShutdownHook.java
@@ -0,0 +1,49 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.service.BreakableService;
+import org.junit.Test;
+
+public class TestServiceShutdownHook extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testShutdownHookNullReference() throws Throwable {
+ ServiceShutdownHook hook = new ServiceShutdownHook(null) ;
+ hook.run();
+ }
+
+ @Test
+ public void testShutdownHook() throws Throwable {
+ BreakableService service = new BreakableService();
+ ServiceShutdownHook hook = new ServiceShutdownHook(service);
+ hook.run();
+ assertStopped(service);
+ }
+
+ @Test
+ public void testFailingHookCaught() throws Throwable {
+ BreakableService service = new BreakableService(false, false, true);
+ ServiceShutdownHook hook = new ServiceShutdownHook(service);
+ hook.run();
+ assertStopped(service);
+
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceStopping.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceStopping.java
new file mode 100644
index 0000000..4901861
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/TestServiceStopping.java
@@ -0,0 +1,44 @@
+/*
+ * 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.service.launcher;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.testservices.FailingStopInStartService;
+import org.junit.Test;
+
+/**
+ * Test the behaviour of service stop logic
+ */
+public class TestServiceStopping extends AbstractServiceLauncherTestBase {
+
+ @Test
+ public void testStopInStartup() throws Throwable {
+ FailingStopInStartService svc = new FailingStopInStartService();
+ svc.init(new Configuration());
+ svc.start();
+ assertStopped(svc);
+ Throwable cause = svc.getFailureCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof ServiceLaunchException);
+ assertTrue(svc.waitForServiceToStop(0));
+ ServiceLaunchException e = (ServiceLaunchException) cause;
+ assertEquals(FailingStopInStartService.EXIT_CODE, e.getExitCode());
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/ExceptionInExecuteLaunchableService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/ExceptionInExecuteLaunchableService.java
new file mode 100644
index 0000000..de6377c
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/ExceptionInExecuteLaunchableService.java
@@ -0,0 +1,97 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.AbstractLaunchableService;
+import org.apache.hadoop.service.launcher.LauncherExitCodes;
+import org.apache.hadoop.service.launcher.ServiceLaunchException;
+import org.apache.hadoop.util.ExitCodeProvider;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Raise an exception in the execute() method; the exception type can
+ * be configured from the CLI
+ */
+public class ExceptionInExecuteLaunchableService extends
+ AbstractLaunchableService {
+
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.ExceptionInExecuteLaunchableService";
+ public static final String ARG_THROW_SLE = "--SLE";
+ public static final String ARG_THROW_IOE = "--IOE";
+ public static final String ARG_THROWABLE = "--throwable";
+ public static final String SLE_TEXT = "SLE raised in execute()";
+ public static final String OTHER_EXCEPTION_TEXT = "Other exception";
+
+ public static final String EXIT_IN_IOE_TEXT = "Exit in IOE";
+ public static final int IOE_EXIT_CODE = 64;
+ ExType exceptionType = ExType.EX;
+ ;
+
+ public ExceptionInExecuteLaunchableService() {
+ super("ExceptionInExecuteLaunchedService");
+ }
+
+ @Override
+ public Configuration bindArgs(Configuration config, List args) throws
+ Exception {
+ if (args.contains(ARG_THROW_SLE)) {
+ exceptionType = ExType.SLE;
+ } else if (args.contains(ARG_THROW_IOE)) {
+ exceptionType = ExType.IOE;
+ } else if (args.contains(ARG_THROWABLE)) {
+ exceptionType = ExType.THROWABLE;
+ }
+ return super.bindArgs(config, args);
+ }
+
+ @Override
+ public int execute() throws Exception {
+ switch (exceptionType) {
+ case SLE:
+ throw new ServiceLaunchException(LauncherExitCodes.EXIT_OTHER_FAILURE,
+ SLE_TEXT);
+ case IOE:
+ throw new IOECodedException();
+ case THROWABLE:
+ throw new OutOfMemoryError("OOM");
+ case EX:
+ default:
+ throw new Exception(OTHER_EXCEPTION_TEXT);
+ }
+ }
+
+ enum ExType {EX, SLE, IOE, THROWABLE}
+
+ public static class IOECodedException extends IOException implements
+ ExitCodeProvider {
+
+ public IOECodedException() {
+ super(EXIT_IN_IOE_TEXT);
+ }
+
+ @Override
+ public int getExitCode() {
+ return IOE_EXIT_CODE;
+ }
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInConstructorService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInConstructorService.java
new file mode 100644
index 0000000..bc51855
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInConstructorService.java
@@ -0,0 +1,30 @@
+/*
+ * 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.service.launcher.testservices;
+
+public class FailInConstructorService extends FailureTestService {
+
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.FailInConstructorService";
+
+ public FailInConstructorService() {
+ super(false, false, false, 0);
+ throw new NullPointerException("oops");
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInInitService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInInitService.java
new file mode 100644
index 0000000..705f6a7
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInInitService.java
@@ -0,0 +1,35 @@
+/*
+ * 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.service.launcher.testservices;
+
+public class FailInInitService extends FailureTestService {
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.FailInInitService";
+ public static final int EXIT_CODE = -1;
+
+ public FailInInitService() {
+ super(true, false, false, 0
+ );
+ }
+
+ @Override
+ int getExitCode() {
+ return EXIT_CODE;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInStartService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInStartService.java
new file mode 100644
index 0000000..06287af
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailInStartService.java
@@ -0,0 +1,34 @@
+/*
+ * 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.service.launcher.testservices;
+
+public class FailInStartService extends FailureTestService {
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.FailInStartService";
+ public static final int EXIT_CODE = -2;
+
+ public FailInStartService() {
+ super(false, true, false, 0);
+ }
+
+ @Override
+ int getExitCode() {
+ return EXIT_CODE;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailingStopInStartService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailingStopInStartService.java
new file mode 100644
index 0000000..57a5741
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailingStopInStartService.java
@@ -0,0 +1,47 @@
+/*
+ * 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.service.launcher.testservices;
+
+/**
+ * This service also stops immediately
+ */
+public class FailingStopInStartService extends FailureTestService {
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.FailingStopInStartService";
+ public static final int EXIT_CODE = -4;
+
+ public FailingStopInStartService() {
+ super(false, false, true, 0);
+ }
+
+ @Override
+ protected void serviceStart() throws Exception {
+ super.serviceStart();
+ try {
+ stop();
+ } catch (Exception e) {
+ //this is secretly swallowed
+ }
+ }
+
+ @Override
+ int getExitCode() {
+ return EXIT_CODE;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailureTestService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailureTestService.java
new file mode 100644
index 0000000..fd70033
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/FailureTestService.java
@@ -0,0 +1,55 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.service.BreakableService;
+import org.apache.hadoop.service.launcher.ServiceLaunchException;
+
+/**
+ * Launcher test service that does not take CLI arguments
+ */
+public class FailureTestService extends BreakableService {
+
+ public final int delay;
+
+ public FailureTestService(boolean failOnInit,
+ boolean failOnStart,
+ boolean failOnStop,
+ int delay) {
+ super(failOnInit, failOnStart, failOnStop);
+ this.delay = delay;
+ }
+
+ @Override
+ protected void serviceStop() throws Exception {
+ if (delay > 0) {
+ Thread.sleep(delay);
+ }
+ super.serviceStop();
+ }
+
+ @Override
+ protected Exception createFailureException(String action) {
+ return new ServiceLaunchException(getExitCode(), toString());
+ }
+
+ int getExitCode() {
+ return -1;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/InitInConstructorLaunchableService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/InitInConstructorLaunchableService.java
new file mode 100644
index 0000000..8374a3e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/InitInConstructorLaunchableService.java
@@ -0,0 +1,63 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.AbstractLaunchableService;
+import org.junit.Assert;
+
+import java.util.List;
+
+/**
+ * Init in the constructor and make sure that it isn't inited again
+ */
+public class InitInConstructorLaunchableService extends
+ AbstractLaunchableService {
+
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.InitInConstructorLaunchableService";
+ private final Configuration originalConf = new Configuration();
+
+ public InitInConstructorLaunchableService() {
+ super("InitInConstructorLaunchableService");
+ init(originalConf);
+ }
+
+ @Override
+ public void init(Configuration conf) {
+ Assert.assertEquals(STATE.NOTINITED, getServiceState());
+ super.init(conf);
+ }
+
+ @Override
+ public Configuration bindArgs(Configuration config, List args) throws
+ Exception {
+ Assert.assertEquals(STATE.INITED, getServiceState());
+ Assert.assertTrue(isInState(STATE.INITED));
+ Assert.assertNotSame(getConfig(), config);
+ return null;
+ }
+
+ @Override
+ public int execute() throws Exception {
+ Assert.assertEquals(STATE.STARTED, getServiceState());
+ Assert.assertSame(originalConf, getConfig());
+ return super.execute();
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/LaunchableRunningService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/LaunchableRunningService.java
new file mode 100644
index 0000000..df0e50e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/LaunchableRunningService.java
@@ -0,0 +1,98 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.LaunchableService;
+import org.apache.hadoop.service.launcher.LauncherExitCodes;
+import org.junit.Assert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * A service which implements {@link LaunchableService} and
+ *
+ * - does nothing in its {@link #serviceStart()}
+ * - does its sleep+ maybe fail operation in its {@link #execute()} method
+ * - gets the failing flag from the argument {@link #ARG_FAILING} first,
+ * the config file second.
+ * - returns 0 for a succesful execute
+ * - returns a configurable exit code for a failing execute
+ * - generates a new configuration in its {@link #bindArgs(Configuration, List)}
+ * to verify that these propagate.
+ *
+ */
+public class LaunchableRunningService extends RunningService implements
+ LaunchableService {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(RunningService.class);
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.LaunchableRunningService";
+ public static final String ARG_FAILING = "--failing";
+ public static final String EXIT_CODE_PROP = "exit.code";
+ public int exitCode = 0;
+
+ public LaunchableRunningService() {
+ this("LaunchableRunningService");
+ }
+
+ public LaunchableRunningService(String name) {
+ super(name);
+ }
+
+ @Override
+ public Configuration bindArgs(Configuration config, List args) throws
+ Exception {
+ Assert.assertEquals(STATE.NOTINITED, getServiceState());
+ Configuration newConf = new Configuration(config);
+ if (args.contains(ARG_FAILING)) {
+ LOG.info("CLI contains " + ARG_FAILING);
+ failInRun = true;
+ newConf.setInt(EXIT_CODE_PROP, LauncherExitCodes.EXIT_OTHER_FAILURE);
+ }
+ return newConf;
+ }
+
+ @Override
+ protected void serviceInit(Configuration conf) throws Exception {
+ super.serviceInit(conf);
+ if (conf.getBoolean(FAIL_IN_RUN, false)) {
+ //if the conf value says fail, the exit code goes to it too
+ exitCode = LauncherExitCodes.EXIT_FAIL;
+ }
+ // the exit code can be read off the property
+ exitCode = conf.getInt(EXIT_CODE_PROP, exitCode);
+ }
+
+ @Override
+ protected void serviceStart() throws Exception {
+ // no-op
+ }
+
+ @Override
+ public int execute() throws Exception {
+ Thread.sleep(delayTime);
+ if (failInRun) {
+ return exitCode;
+ }
+ return 0;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/NoArgsAllowedService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/NoArgsAllowedService.java
new file mode 100644
index 0000000..6e86922
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/NoArgsAllowedService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.launcher.AbstractLaunchableService;
+import org.apache.hadoop.service.launcher.LauncherExitCodes;
+import org.apache.hadoop.service.launcher.ServiceLaunchException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * service that does not allow any arguments
+ */
+public class NoArgsAllowedService extends AbstractLaunchableService {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(NoArgsAllowedService.class);
+
+ public NoArgsAllowedService() {
+ super("NoArgsAllowedService");
+ }
+
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.NoArgsAllowedService";
+
+ @Override
+ public Configuration bindArgs(Configuration config, List args) throws
+ Exception {
+ Configuration configuration = super.bindArgs(config, args);
+ if (!args.isEmpty()) {
+ LOG.error("Got {} arguments, first one: {}", args.size(), args.get(0));
+ throw new ServiceLaunchException(LauncherExitCodes.EXIT_COMMAND_ARGUMENT_ERROR,
+ "Expected 0 arguments but got %d",
+ args.size());
+ }
+ return configuration;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/NullBindLaunchableService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/NullBindLaunchableService.java
new file mode 100644
index 0000000..1f1c448
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/NullBindLaunchableService.java
@@ -0,0 +1,46 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.conf.Configuration;
+
+import java.util.List;
+
+/**
+ * An extension of {@link LaunchableRunningService} which returns null from
+ * the {@link #bindArgs(Configuration, List)} method.
+ */
+public class NullBindLaunchableService extends LaunchableRunningService {
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.NullBindLaunchableService";
+
+ public NullBindLaunchableService() {
+ this("NullBindLaunchableService");
+ }
+
+ public NullBindLaunchableService(String name) {
+ super(name);
+ }
+
+ @Override
+ public Configuration bindArgs(Configuration config, List args) throws
+ Exception {
+ return null;
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/RunningService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/RunningService.java
new file mode 100644
index 0000000..93187ed
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/RunningService.java
@@ -0,0 +1,79 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.AbstractService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RunningService extends AbstractService implements Runnable {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(RunningService.class);
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.RunningService";
+ public static final int DELAY = 100;
+
+ /**
+ * Property on delay times
+ */
+ public static final String DELAY_TIME = "delay.time";
+ public static final String FAIL_IN_RUN = "fail.runnable";
+ public static final String FAILURE_MESSAGE = "FAIL_IN_RUN";
+ public boolean interrupted;
+
+ public int delayTime = DELAY;
+ public boolean failInRun;
+
+ public RunningService() {
+ super("RunningService");
+ }
+
+ public RunningService(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void serviceInit(Configuration conf) throws Exception {
+ super.serviceInit(conf);
+ delayTime = getConfig().getInt(DELAY_TIME, delayTime);
+ failInRun = getConfig().getBoolean(FAIL_IN_RUN, failInRun);
+ }
+
+ @Override
+ protected void serviceStart() throws Exception {
+ Thread thread = new Thread(this);
+ thread.setName(getName());
+ thread.start();
+ }
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(delayTime);
+ if (failInRun) {
+ noteFailure(new Exception(FAILURE_MESSAGE));
+ }
+ } catch (InterruptedException e) {
+ interrupted = true;
+ LOG.info("Interrupted");
+ }
+ stop();
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/StoppingInStartLaunchableService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/StoppingInStartLaunchableService.java
new file mode 100644
index 0000000..41e1549
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/StoppingInStartLaunchableService.java
@@ -0,0 +1,45 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.service.launcher.AbstractLaunchableService;
+import org.apache.hadoop.service.launcher.LauncherExitCodes;
+import org.apache.hadoop.service.launcher.ServiceLaunchException;
+
+public class StoppingInStartLaunchableService extends AbstractLaunchableService {
+
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.StoppingInStartLaunchableService";
+ public StoppingInStartLaunchableService(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void serviceStart() throws Exception {
+ super.serviceStart();
+ stop();
+ }
+
+ @Override
+ public int execute() throws Exception {
+ throw new ServiceLaunchException(
+ LauncherExitCodes.EXIT_SERVICE_LIFECYCLE_EXCEPTION,
+ "Should not have been executed");
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/StringConstructorOnlyService.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/StringConstructorOnlyService.java
new file mode 100644
index 0000000..4a31bbb
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/service/launcher/testservices/StringConstructorOnlyService.java
@@ -0,0 +1,39 @@
+/*
+ * 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.service.launcher.testservices;
+
+import org.apache.hadoop.service.launcher.AbstractLaunchableService;
+
+/**
+ * service that only has one constructor that takes a string.
+ * This is the standard base class of a YARN service, so handle it
+ * in the launch
+ */
+public class StringConstructorOnlyService extends AbstractLaunchableService {
+
+
+ public StringConstructorOnlyService(String name) {
+ super(name);
+ }
+
+ public static final String NAME =
+ "org.apache.hadoop.service.launcher.testservices.StringConstructorOnlyService";
+
+
+}