You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by in...@apache.org on 2017/05/02 22:05:35 UTC
[18/50] [abbrv] hadoop git commit: YARN-679. Add an entry point that
can start any Yarn service. Contributed by Steve Loughran.
YARN-679. Add an entry point that can start any Yarn service. Contributed by Steve Loughran.
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/373bb493
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/373bb493
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/373bb493
Branch: refs/heads/HDFS-10467
Commit: 373bb4931fb392e3ca6bfd78992887e5a405e186
Parents: cb672a4
Author: Junping Du <ju...@apache.org>
Authored: Fri Apr 28 10:45:02 2017 -0700
Committer: Junping Du <ju...@apache.org>
Committed: Fri Apr 28 10:45:02 2017 -0700
----------------------------------------------------------------------
.../hadoop/service/ServiceStateException.java | 66 +-
.../launcher/AbstractLaunchableService.java | 78 ++
.../HadoopUncaughtExceptionHandler.java | 129 +++
.../service/launcher/InterruptEscalator.java | 216 ++++
.../hadoop/service/launcher/IrqHandler.java | 178 +++
.../service/launcher/LaunchableService.java | 95 ++
.../service/launcher/LauncherArguments.java | 59 +
.../service/launcher/LauncherExitCodes.java | 183 +++
.../launcher/ServiceLaunchException.java | 81 ++
.../service/launcher/ServiceLauncher.java | 1044 ++++++++++++++++++
.../service/launcher/ServiceShutdownHook.java | 112 ++
.../hadoop/service/launcher/package-info.java | 462 ++++++++
.../apache/hadoop/util/ExitCodeProvider.java | 35 +
.../java/org/apache/hadoop/util/ExitUtil.java | 253 ++++-
.../hadoop/util/GenericOptionsParser.java | 125 ++-
.../org/apache/hadoop/util/StringUtils.java | 43 +-
.../apache/hadoop/service/BreakableService.java | 23 +-
.../AbstractServiceLauncherTestBase.java | 317 ++++++
.../launcher/ExitTrackingServiceLauncher.java | 59 +
.../service/launcher/TestServiceConf.java | 146 +++
.../launcher/TestServiceInterruptHandling.java | 118 ++
.../service/launcher/TestServiceLauncher.java | 213 ++++
.../TestServiceLauncherCreationFailures.java | 83 ++
.../TestServiceLauncherInnerMethods.java | 95 ++
.../ExceptionInExecuteLaunchableService.java | 96 ++
.../testservices/FailInConstructorService.java | 33 +
.../testservices/FailInInitService.java | 38 +
.../testservices/FailInStartService.java | 37 +
.../testservices/FailingStopInStartService.java | 47 +
.../testservices/FailureTestService.java | 55 +
.../InitInConstructorLaunchableService.java | 63 ++
.../testservices/LaunchableRunningService.java | 111 ++
.../testservices/NoArgsAllowedService.java | 64 ++
.../testservices/NullBindLaunchableService.java | 46 +
.../launcher/testservices/RunningService.java | 84 ++
.../StoppingInStartLaunchableService.java | 49 +
.../StringConstructorOnlyService.java | 39 +
.../apache/hadoop/test/GenericTestUtils.java | 34 +
38 files changed, 4861 insertions(+), 148 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/ServiceStateException.java
----------------------------------------------------------------------
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..ba4e0d2 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,37 +20,83 @@ package org.apache.hadoop.service;
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.
+ * Exception that can be raised on state change operations, whose
+ * exit code can be explicitly set, determined from that of any nested
+ * cause, or a default value of
+ * {@link LauncherExitCodes#EXIT_SERVICE_LIFECYCLE_EXCEPTION}.
*/
@Public
@Evolving
-public class ServiceStateException extends RuntimeException {
+public class ServiceStateException extends RuntimeException implements
+ ExitCodeProvider {
private static final long serialVersionUID = 1110000352259232646L;
+ /**
+ * Exit code.
+ */
+ private int exitCode ;
+
+ /**
+ * Instantiate
+ * @param message error message
+ */
public ServiceStateException(String message) {
- super(message);
+ this(message, null);
}
+ /**
+ * Instantiate with a message and cause; if the cause has an exit code
+ * then it is used, otherwise the generic
+ * {@link LauncherExitCodes#EXIT_SERVICE_LIFECYCLE_EXCEPTION} exit code
+ * is used.
+ * @param message exception message
+ * @param cause optional inner cause
+ */
public ServiceStateException(String message, Throwable cause) {
super(message, cause);
+ if(cause instanceof ExitCodeProvider) {
+ this.exitCode = ((ExitCodeProvider) cause).getExitCode();
+ } else {
+ this.exitCode = LauncherExitCodes.EXIT_SERVICE_LIFECYCLE_EXCEPTION;
+ }
+ }
+
+ /**
+ * Instantiate, using the specified exit code as the exit code
+ * of the exception, irrespetive of any exit code supplied by any inner
+ * cause.
+ *
+ * @param exitCode exit code to declare
+ * @param message exception message
+ * @param cause inner cause
+ */
+ public ServiceStateException(int exitCode,
+ String message,
+ Throwable cause) {
+ this(message, cause);
+ this.exitCode = 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
- * {@link RuntimeException} and returned.
- *
* All other exception types are wrapped in a new instance of
- * ServiceStateException
+ * {@code ServiceStateException}.
* @param fault exception or throwable
- * @return a ServiceStateException to rethrow
+ * @return a {@link RuntimeException} to rethrow
*/
public static RuntimeException convert(Throwable fault) {
if (fault instanceof RuntimeException) {
@@ -66,10 +112,10 @@ public class ServiceStateException extends RuntimeException {
* {@link RuntimeException} and returned.
*
* All other exception types are wrapped in a new instance of
- * ServiceStateException
+ * {@code ServiceStateException}.
* @param text text to use if a new exception is created
* @param fault exception or throwable
- * @return a ServiceStateException to rethrow
+ * @return a {@link RuntimeException} to rethrow
*/
public static RuntimeException convert(String text, Throwable fault) {
if (fault instanceof RuntimeException) {
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/AbstractLaunchableService.java
----------------------------------------------------------------------
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..be28c5b
--- /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 java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.AbstractService;
+
+/**
+ * Subclass of {@link AbstractService} that provides basic implementations
+ * of the {@link LaunchableService} 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.
+ */
+ protected AbstractLaunchableService(String name) {
+ super(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The base implementation logs all arguments at the debug level,
+ * then returns the passed in config unchanged.
+ */
+
+ @Override
+ public Configuration bindArgs(Configuration config, List<String> 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}
+ * <p>
+ * The action is to signal success by returning the exit code 0.
+ */
+ @Override
+ public int execute() throws Exception {
+ return LauncherExitCodes.EXIT_SUCCESS;
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java
new file mode 100644
index 0000000..bf4a863
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/HadoopUncaughtExceptionHandler.java
@@ -0,0 +1,129 @@
+/*
+ * 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 java.lang.Thread.UncaughtExceptionHandler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience.Public;
+import org.apache.hadoop.classification.InterfaceStability.Evolving;
+import org.apache.hadoop.util.ExitUtil;
+import org.apache.hadoop.util.ShutdownHookManager;
+
+/**
+ * This class is intended to be installed by calling
+ * {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)}
+ * in the main entry point.
+ *
+ * The base class will always attempt to shut down the process if an Error
+ * was raised; the behavior on a standard Exception, raised outside
+ * process shutdown, is simply to log it.
+ *
+ * (Based on the class {@code YarnUncaughtExceptionHandler})
+ */
+@SuppressWarnings("UseOfSystemOutOrSystemErr")
+@Public
+@Evolving
+public class HadoopUncaughtExceptionHandler
+ implements UncaughtExceptionHandler {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(
+ HadoopUncaughtExceptionHandler.class);
+
+ /**
+ * Delegate for simple exceptions.
+ */
+ private final UncaughtExceptionHandler delegate;
+
+ /**
+ * Create an instance delegating to the supplied handler if
+ * the exception is considered "simple".
+ * @param delegate a delegate exception handler.
+ */
+ public HadoopUncaughtExceptionHandler(UncaughtExceptionHandler delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * Basic exception handler -logs simple exceptions, then continues.
+ */
+ public HadoopUncaughtExceptionHandler() {
+ this(null);
+ }
+
+ /**
+ * Uncaught exception handler.
+ * If an error is raised: shutdown
+ * The state of the system is unknown at this point -attempting
+ * a clean shutdown is dangerous. Instead: exit
+ * @param thread thread that failed
+ * @param exception the raised exception
+ */
+ @Override
+ public void uncaughtException(Thread thread, Throwable exception) {
+ if (ShutdownHookManager.get().isShutdownInProgress()) {
+ LOG.error("Thread {} threw an error during shutdown: {}.",
+ thread.toString(),
+ exception,
+ exception);
+ } else if (exception instanceof Error) {
+ try {
+ LOG.error("Thread {} threw an error: {}. Shutting down",
+ thread.toString(),
+ exception,
+ exception);
+ } catch (Throwable err) {
+ // We don't want to not exit because of an issue with logging
+ }
+ if (exception instanceof OutOfMemoryError) {
+ // After catching an OOM java says it is undefined behavior, so don't
+ // even try to clean up or we can get stuck on shutdown.
+ try {
+ System.err.println("Halting due to Out Of Memory Error...");
+ } catch (Throwable err) {
+ // Again we don't want to exit because of logging issues.
+ }
+ ExitUtil.haltOnOutOfMemory((OutOfMemoryError) exception);
+ } else {
+ // error other than OutOfMemory
+ ExitUtil.ExitException ee =
+ ServiceLauncher.convertToExitException(exception);
+ ExitUtil.terminate(ee.status, ee);
+ }
+ } else {
+ // simple exception in a thread. There's a policy decision here:
+ // terminate the process vs. keep going after a thread has failed
+ // base implementation: do nothing but log
+ LOG.error("Thread {} threw an exception: {}",
+ thread.toString(),
+ exception,
+ exception);
+ if (delegate != null) {
+ delegate.uncaughtException(thread, exception);
+ }
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java
----------------------------------------------------------------------
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..a7e1edd
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/InterruptEscalator.java
@@ -0,0 +1,216 @@
+/*
+ * 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 java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 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.
+ * <ol>
+ * <li>The service is given a time in milliseconds to stop:
+ * if it exceeds this it the process exits anyway.</li>
+ * <li>the exit operation used is {@link ServiceLauncher#exit(int, String)}
+ * with the exit code {@link LauncherExitCodes#EXIT_INTERRUPTED}</li>
+ * <li>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.</li>
+ * </ol>
+ *
+ */
+
+@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<ServiceLauncher> ownerRef;
+
+ private final int shutdownTimeMillis;
+
+ /**
+ * Previous interrupt handlers. These are not queried.
+ */
+ private final List<IrqHandler> 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 String toString() {
+ final StringBuilder sb = new StringBuilder("InterruptEscalator{");
+ sb.append(" signalAlreadyReceived=").append(signalAlreadyReceived.get());
+ ServiceLauncher owner = ownerRef.get();
+ if (owner != null) {
+ sb.append(", owner= ").append(owner.toString());
+ }
+ sb.append(", shutdownTimeMillis=").append(shutdownTimeMillis);
+ sb.append(", forcedShutdownTimedOut=").append(forcedShutdownTimedOut);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @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) {
+ IrqHandler handler = new IrqHandler(signalName, this);
+ handler.bind();
+ interruptHandlers.add(handler);
+ }
+
+ /**
+ * 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();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java
----------------------------------------------------------------------
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..30bb91c
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/IrqHandler.java
@@ -0,0 +1,178 @@
+/*
+ * 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 java.util.concurrent.atomic.AtomicInteger;
+
+import com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+/**
+ * Handler of interrupts that 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 <code>kill</code> signal: {@value}.
+ */
+ public static final String SIGTERM = "TERM";
+
+ /**
+ * Signal name.
+ */
+ private final String name;
+
+ /**
+ * Handler to relay to.
+ */
+ private final Interrupted handler;
+
+ /** Count of how many times a signal has been raised. */
+ private final AtomicInteger signalCount = new AtomicInteger(0);
+
+ /**
+ * Stored signal.
+ */
+ private Signal signal;
+
+ /**
+ * Create an IRQ handler bound to the specific interrupt.
+ * @param name signal name
+ * @param handler handler
+ */
+ public IrqHandler(String name, Interrupted handler) {
+ Preconditions.checkArgument(name != null, "Null \"name\"");
+ Preconditions.checkArgument(handler != null, "Null \"handler\"");
+ this.handler = handler;
+ this.name = name;
+ }
+
+ /**
+ * Bind to the interrupt handler.
+ * @throws IllegalArgumentException if the exception could not be set
+ */
+ void bind() {
+ Preconditions.checkState(signal == null, "Handler already bound");
+ 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 {
+ private final String name;
+ private final int number;
+
+ public InterruptData(String name, int number) {
+ this.name = name;
+ this.number = number;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ @Override
+ public String toString() {
+ return "signal " + name + '(' + number + ')';
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java
----------------------------------------------------------------------
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..fb0a052
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LaunchableService.java
@@ -0,0 +1,95 @@
+/*
+ * 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 java.util.List;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.service.Service;
+
+/**
+ * An interface which services can implement to have their
+ * execution managed by the ServiceLauncher.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * @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<String> args)
+ throws Exception;
+
+ /**
+ * Run a service. This method is called after {@link Service#start()}.
+ * <p>
+ * The return value becomes the exit code of the launched process.
+ * <p>
+ * If an exception is raised, the policy is:
+ * <ol>
+ * <li>Any subset of {@link org.apache.hadoop.util.ExitUtil.ExitException}:
+ * the exception is passed up unmodified.
+ * </li>
+ * <li>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.</li>
+ * <li>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).</li>
+ * </ol>
+ * @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;
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java
new file mode 100644
index 0000000..08118f5
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherArguments.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * Standard launcher arguments. These are all from
+ * the {@code GenericOptionsParser}, simply extracted to constants.
+ */
+public interface LauncherArguments {
+ /**
+ * Name of the configuration argument on the CLI.
+ * Value: {@value}
+ */
+ String ARG_CONF = "conf";
+ String ARG_CONF_SHORT = "conf";
+
+ /**
+ * prefixed version of {@link #ARG_CONF}.
+ * Value: {@value}
+ */
+ String ARG_CONF_PREFIXED = "--" + ARG_CONF;
+
+ /**
+ * Name of a configuration class which is loaded before any
+ * attempt is made to load the class.
+ * <p>
+ * Value: {@value}
+ */
+ String ARG_CONFCLASS = "hadoopconf";
+ String ARG_CONFCLASS_SHORT = "hadoopconf";
+
+ /**
+ * Prefixed version of {@link #ARG_CONFCLASS}.
+ * Value: {@value}
+ */
+ String ARG_CONFCLASS_PREFIXED = "--" + ARG_CONFCLASS;
+
+ /**
+ * Error string on a parse failure.
+ * Value: {@value}
+ */
+ String E_PARSE_FAILED = "Failed to parse: ";
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java
----------------------------------------------------------------------
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..f48e38e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/LauncherExitCodes.java
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ * <p>
+ * Codes with a YARN prefix are YARN-related.
+ * <p>
+ * Many of the exit codes are designed to resemble HTTP error codes,
+ * squashed into a single byte. e.g 44 , "not found" is the equivalent
+ * of 404. The various 2XX HTTP error codes aren't followed;
+ * the Unix standard of "0" for success is used.
+ * <pre>
+ * 0-10: general command issues
+ * 30-39: equivalent to the 3XX responses, where those responses are
+ * considered errors by the application.
+ * 40-49: client-side/CLI/config problems
+ * 50-59: service-side problems.
+ * 60+ : application specific error codes
+ * </pre>
+ */
+@InterfaceAudience.Public
+@InterfaceStability.Evolving
+public interface LauncherExitCodes {
+
+ /**
+ * Success: {@value}.
+ */
+ int EXIT_SUCCESS = 0;
+
+ /**
+ * Generic "false/fail" response: {@value}.
+ * The operation worked but the result was not "true" from the viewpoint
+ * of the executed code.
+ */
+ int EXIT_FAIL = -1;
+
+ /**
+ * Exit code when a client requested service termination: {@value}.
+ */
+ int EXIT_CLIENT_INITIATED_SHUTDOWN = 1;
+
+ /**
+ * Exit code when targets could not be launched: {@value}.
+ */
+ int EXIT_TASK_LAUNCH_FAILURE = 2;
+
+ /**
+ * Exit code when a control-C, kill -3, signal was picked up: {@value}.
+ */
+ int EXIT_INTERRUPTED = 3;
+
+ /**
+ * Exit code when something happened but we can't be specific: {@value}.
+ */
+ int EXIT_OTHER_FAILURE = 5;
+
+ /**
+ * Exit code when the command line doesn't parse: {@value}, or
+ * when it is otherwise invalid.
+ * <p>
+ * Approximate HTTP equivalent: {@code 400 Bad Request}
+ */
+ int EXIT_COMMAND_ARGUMENT_ERROR = 40;
+
+ /**
+ * The request requires user authentication: {@value}.
+ * <p>
+ * approximate HTTP equivalent: Approximate HTTP equivalent: {@code 401 Unauthorized}
+ */
+ int EXIT_UNAUTHORIZED = 41;
+
+ /**
+ * Exit code when a usage message was printed: {@value}.
+ */
+ int EXIT_USAGE = 42;
+
+ /**
+ * Forbidden action: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: Approximate HTTP equivalent: {@code 403: Forbidden}
+ */
+ int EXIT_FORBIDDEN = 43;
+
+ /**
+ * Something was not found: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 404: Not Found}
+ */
+ int EXIT_NOT_FOUND = 44;
+
+ /**
+ * The operation is not allowed: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 405: Not allowed}
+ */
+ int EXIT_OPERATION_NOT_ALLOWED = 45;
+
+ /**
+ * The command is somehow not acceptable: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 406: Not Acceptable}
+ */
+ int EXIT_NOT_ACCEPTABLE = 46;
+
+ /**
+ * Exit code on connectivity problems: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 408: Request Timeout}
+ */
+ int EXIT_CONNECTIVITY_PROBLEM = 48;
+
+ /**
+ * Exit code when the configurations in valid/incomplete: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 409: Conflict}
+ */
+ int EXIT_BAD_CONFIGURATION = 49;
+
+ /**
+ * Exit code when an exception was thrown from the service: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 500 Internal Server Error}
+ */
+ int EXIT_EXCEPTION_THROWN = 50;
+
+ /**
+ * Unimplemented feature: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 501: Not Implemented}
+ */
+ int EXIT_UNIMPLEMENTED = 51;
+
+ /**
+ * Service Unavailable; it may be available later: {@value}.
+ * <p>
+ * Approximate HTTP equivalent: {@code 503 Service Unavailable}
+ */
+ int EXIT_SERVICE_UNAVAILABLE = 53;
+
+ /**
+ * The application does not support, or refuses to support this
+ * version: {@value}.
+ * <p>
+ * If raised, this is expected to be raised server-side and likely due
+ * to client/server version incompatibilities.
+ * <p>
+ * Approximate HTTP equivalent: {@code 505: Version Not Supported}
+ */
+ int EXIT_UNSUPPORTED_VERSION = 55;
+
+ /**
+ * The service instance could not be created: {@value}.
+ */
+ int EXIT_SERVICE_CREATION_FAILURE = 56;
+
+ /**
+ * The service instance could not be created: {@value}.
+ */
+ int EXIT_SERVICE_LIFECYCLE_EXCEPTION = 57;
+
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.java
----------------------------------------------------------------------
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..1243a1f
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLaunchException.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 java.util.Locale;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.util.ExitCodeProvider;
+import org.apache.hadoop.util.ExitUtil;
+
+/**
+ * A service launch exception that includes an exit code.
+ * <p>
+ * 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.
+ * <p>
+ * This uses {@link String#format(String, Object...)}
+ * to build the formatted exception in the ENGLISH locale.
+ * <p>
+ * If the last argument is a throwable, it becomes the cause of the exception.
+ * It will also be used 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]);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java
----------------------------------------------------------------------
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..6b0b4e8
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceLauncher.java
@@ -0,0 +1,1044 @@
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+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.GenericOptionsParser;
+import org.apache.hadoop.util.StringUtils;
+
+/**
+ * A class to launch any YARN service by name.
+ *
+ * It's designed to be subclassed for custom entry points.
+ *
+ * Workflow:
+ * <ol>
+ * <li>An instance of the class is created. It must be of the type
+ * {@link Service}</li>
+ * <li>If it implements
+ * {@link LaunchableService#bindArgs(Configuration, List)},
+ * it is given the binding args off the CLI after all general configuration
+ * arguments have been stripped.</li>
+ * <li>Its {@link Service#init(Configuration)} and {@link Service#start()}
+ * methods are called.</li>
+ * <li>If it implements it, {@link LaunchableService#execute()}
+ * is called and its return code used as the exit code.</li>
+ * <li>Otherwise: it waits for the service to stop, assuming that the
+ * {@link Service#start()} method spawns one or more thread
+ * to perform work</li>
+ * <li>If any exception is raised and provides an exit code,
+ * that is, it implements {@link ExitCodeProvider},
+ * the return value of {@link ExitCodeProvider#getExitCode()},
+ * becomes the exit code of the command.</li>
+ * </ol>
+ * Error and warning messages are logged to {@code stderr}.
+ *
+ * @param <S> service class to cast the generated service to.
+ */
+@SuppressWarnings("UseOfSystemOutOrSystemErr")
+public class ServiceLauncher<S extends Service>
+ implements LauncherExitCodes, LauncherArguments,
+ Thread.UncaughtExceptionHandler {
+
+ /**
+ * 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";
+
+ protected static final String USAGE_NAME = "Usage: " + NAME;
+ protected static final String USAGE_SERVICE_ARGUMENTS =
+ "service-classname <service arguments>";
+
+ /**
+ * Usage message.
+ *
+ * Text: {@value}
+ */
+ public static final String USAGE_MESSAGE =
+ USAGE_NAME
+ + " [" + ARG_CONF_PREFIXED + " <conf file>]"
+ + " [" + ARG_CONFCLASS_PREFIXED + " <configuration classname>]"
+ + " " + USAGE_SERVICE_ARGUMENTS;
+
+ /**
+ * 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;
+
+ /**
+ * Any 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;
+
+ /**
+ * Text description of service for messages.
+ */
+ private String serviceName;
+
+ /**
+ * Classname for the service to create.; empty string otherwise.
+ */
+ private String serviceClassName = "";
+
+ /**
+ * List of the standard configurations to create (and so load in properties).
+ * The values are Hadoop, HDFS and YARN configurations.
+ */
+ protected static final String[] DEFAULT_CONFIGS = {
+ "org.apache.hadoop.conf.Configuration",
+ "org.apache.hadoop.hdfs.HdfsConfiguration",
+ "org.apache.hadoop.yarn.conf.YarnConfiguration"
+ };
+
+ /**
+ * List of classnames to load to configuration before creating a
+ * {@link Configuration} instance.
+ */
+ private List<String> confClassnames = new ArrayList<>(DEFAULT_CONFIGS.length);
+
+ /**
+ * URLs of configurations to load into the configuration instance created.
+ */
+ private List<URL> confResourceUrls = new ArrayList<>(1);
+
+ /** Command options. Preserved for usage statements. */
+ private Options commandOptions;
+
+ /**
+ * Create an instance of the launcher.
+ * @param serviceClassName classname of the service
+ */
+ public ServiceLauncher(String serviceClassName) {
+ this(serviceClassName, serviceClassName);
+ }
+
+ /**
+ * Create an instance of the launcher.
+ * @param serviceName name of service for text messages
+ * @param serviceClassName classname of the service
+ */
+ public ServiceLauncher(String serviceName, String serviceClassName) {
+ this.serviceClassName = serviceClassName;
+ this.serviceName = serviceName;
+ // set up initial list of configurations
+ confClassnames.addAll(Arrays.asList(DEFAULT_CONFIGS));
+ }
+
+ /**
+ * Get the service.
+ *
+ * Null until
+ * {@link #coreServiceLaunch(Configuration, List, boolean, boolean)}
+ * has completed.
+ * @return the service
+ */
+ public final S getService() {
+ return service;
+ }
+
+ /**
+ * Setter is to give subclasses the ability to manipulate the service.
+ * @param s the new service
+ */
+ protected void setService(S s) {
+ this.service = s;
+ }
+
+ /**
+ * Get the configuration constructed from the command line arguments.
+ * @return the configuration used to create the service
+ */
+ public final Configuration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * The exit code from a successful service execution.
+ * @return the exit code.
+ */
+ public final int getServiceExitCode() {
+ return serviceExitCode;
+ }
+
+ /**
+ * Get the exit exception used to end this service.
+ * @return an exception, which will be null until the service
+ * has exited (and {@code System.exit} has not been called)
+ */
+ public final ExitUtil.ExitException getServiceException() {
+ return serviceException;
+ }
+
+ /**
+ * Probe for service classname being defined.
+ * @return true if the classname is set
+ */
+ private boolean isClassnameDefined() {
+ return serviceClassName != null && !serviceClassName.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("\"ServiceLauncher for \"");
+ sb.append(serviceName);
+ if (isClassnameDefined()) {
+ sb.append(", serviceClassName='").append(serviceClassName).append('\'');
+ }
+ if (service != null) {
+ sb.append(", service=").append(service);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Launch the service and exit.
+ *
+ * <ol>
+ * <li>Parse the command line.</li>
+ * <li>Build the service configuration from it.</li>
+ * <li>Start the service.</li>.
+ * <li>If it is a {@link LaunchableService}: execute it</li>
+ * <li>Otherwise: wait for it to finish.</li>
+ * <li>Exit passing the status code to the {@link #exit(int, String)}
+ * method.</li>
+ * </ol>
+ * @param args arguments to the service. {@code arg[0]} is
+ * assumed to be the service classname.
+ */
+ public void launchServiceAndExit(List<String> args) {
+ StringBuilder builder = new StringBuilder();
+ for (String arg : args) {
+ builder.append('"').append(arg).append("\" ");
+ }
+ String argumentString = builder.toString();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(startupShutdownMessage(serviceName, args));
+ LOG.debug(argumentString);
+ }
+ registerFailureHandling();
+ // set up the configs, using reflection to push in the -site.xml files
+ loadConfigurationClasses();
+ Configuration conf = createConfiguration();
+ for (URL resourceUrl : confResourceUrls) {
+ conf.addResource(resourceUrl);
+ }
+ bindCommandOptions();
+ ExitUtil.ExitException exitException;
+ try {
+ List<String> processedArgs = extractCommandOptions(conf, args);
+ exitException = launchService(conf, processedArgs, true, true);
+ } catch (ExitUtil.ExitException e) {
+ exitException = e;
+ noteException(exitException);
+ }
+ if (exitException.getExitCode() != 0) {
+ // something went wrong. Print the usage and commands
+ System.err.println(getUsageMessage());
+ System.err.println("Command: " + argumentString);
+ }
+ System.out.flush();
+ System.err.flush();
+ exit(exitException);
+ }
+
+ /**
+ * Set the {@link #commandOptions} field to the result of
+ * {@link #createOptions()}; protected for subclasses and test access.
+ */
+ protected void bindCommandOptions() {
+ commandOptions = createOptions();
+ }
+
+ /**
+ * Record that an Exit Exception has been raised.
+ * Save it to {@link #serviceException}, with its exit code in
+ * {@link #serviceExitCode}
+ * @param exitException exception
+ */
+ void noteException(ExitUtil.ExitException exitException) {
+ LOG.debug("Exception raised", exitException);
+ serviceExitCode = exitException.getExitCode();
+ serviceException = exitException;
+ }
+
+ /**
+ * Get the usage message, ideally dynamically.
+ * @return the usage message
+ */
+ protected String getUsageMessage() {
+ String message = USAGE_MESSAGE;
+ if (commandOptions != null) {
+ message = USAGE_NAME
+ + " " + commandOptions.toString()
+ + " " + USAGE_SERVICE_ARGUMENTS;
+ }
+ return message;
+ }
+
+ /**
+ * Override point: create an options instance to combine with the
+ * standard options set.
+ * <i>Important. Synchronize uses of {@link OptionBuilder}</i>
+ * with {@code OptionBuilder.class}
+ * @return the new options
+ */
+ @SuppressWarnings("static-access")
+ protected Options createOptions() {
+ synchronized (OptionBuilder.class) {
+ Options options = new Options();
+ Option oconf = OptionBuilder.withArgName("configuration file")
+ .hasArg()
+ .withDescription("specify an application configuration file")
+ .withLongOpt(ARG_CONF)
+ .create(ARG_CONF_SHORT);
+ Option confclass = OptionBuilder.withArgName("configuration classname")
+ .hasArg()
+ .withDescription(
+ "Classname of a Hadoop Configuration subclass to load")
+ .withLongOpt(ARG_CONFCLASS)
+ .create(ARG_CONFCLASS_SHORT);
+ Option property = OptionBuilder.withArgName("property=value")
+ .hasArg()
+ .withDescription("use value for given property")
+ .create('D');
+ options.addOption(oconf);
+ options.addOption(property);
+ options.addOption(confclass);
+ return options;
+ }
+ }
+
+ /**
+ * 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 initializer.
+ */
+ protected Configuration createConfiguration() {
+ return new Configuration();
+ }
+
+ /**
+ * Override point: Get a list of configuration classes to create.
+ * @return the array of configs to attempt to create. If any are off the
+ * classpath, that is logged
+ */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ protected List<String> getConfigurationsToCreate() {
+ return confClassnames;
+ }
+
+ /**
+ * This creates all the configurations defined by
+ * {@link #getConfigurationsToCreate()} , ensuring that
+ * the resources have been pushed in.
+ * If one cannot be loaded it is logged and the operation continues
+ * except in the case that the class does load but it isn't actually
+ * a subclass of {@link Configuration}.
+ * @throws ExitUtil.ExitException if a loaded class is of the wrong type
+ */
+ @VisibleForTesting
+ public int loadConfigurationClasses() {
+ List<String> toCreate = getConfigurationsToCreate();
+ int loaded = 0;
+ for (String classname : toCreate) {
+ try {
+ Class<?> loadClass = getClassLoader().loadClass(classname);
+ Object instance = loadClass.getConstructor().newInstance();
+ if (!(instance instanceof Configuration)) {
+ throw new ExitUtil.ExitException(EXIT_SERVICE_CREATION_FAILURE,
+ "Could not create " + classname
+ + " because it is not a Configuration class/subclass");
+ }
+ loaded++;
+ } catch (ClassNotFoundException e) {
+ // class could not be found -implies it is not on the current classpath
+ LOG.debug("Failed to load {} because it is not on the classpath",
+ classname);
+ } catch (ExitUtil.ExitException e) {
+ // rethrow
+ throw e;
+ } catch (Exception e) {
+ // any other exception
+ LOG.info("Failed to create {}", classname, e);
+ }
+ }
+ return loaded;
+ }
+
+ /**
+ * 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<String> 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 name = getServiceName();
+
+ if (exitCode == 0) {
+ exitException = new ServiceLaunchException(exitCode,
+ "%s succeeded",
+ name);
+ } else {
+ exitException = new ServiceLaunchException(exitCode,
+ "%s failed ", name);
+ }
+ // 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);
+ }
+ noteException(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<String> 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 name = getServiceName();
+ LOG.debug("Launched service {}", name);
+ LaunchableService launchableService = null;
+
+ if (service instanceof LaunchableService) {
+ // it's a LaunchableService, pass in the conf and arguments before init)
+ LOG.debug("Service {} implements LaunchableService", name);
+ launchableService = (LaunchableService) service;
+ if (launchableService.isInState(Service.STATE.INITED)) {
+ LOG.warn("LaunchableService {}"
+ + " initialized in constructor before CLI arguments passed in",
+ name);
+ }
+ 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 {}",
+ name, 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 {@code serviceClassName}.
+ *
+ * Sets the {@code configuration} field
+ * to the the value of {@code conf},
+ * and the {@code service} field to the service created.
+ *
+ * @param conf configuration to use
+ */
+ @SuppressWarnings("unchecked")
+ public Service instantiateService(Configuration conf) {
+ Preconditions.checkArgument(conf != null, "null conf");
+ Preconditions.checkArgument(serviceClassName != null,
+ "null service classname");
+ Preconditions.checkArgument(!serviceClassName.isEmpty(),
+ "undefined service classname");
+ configuration = conf;
+
+ // Instantiate the class. this requires the service to have a public
+ // zero-argument or string-argument constructor
+ Object instance;
+ 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 (Exception 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 {@code 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.
+ *
+ * <ol>
+ * <li>If is already the right type, pass it through.</li>
+ * <li>If it implements {@link ExitCodeProvider#getExitCode()},
+ * the exit code is extracted and used in the new exception.</li>
+ * <li>Otherwise, the exit code
+ * {@link LauncherExitCodes#EXIT_EXCEPTION_THROWN} is used.</li>
+ * </ol>
+ *
+ * @param thrown the exception thrown
+ * @return an {@code ExitException} with a status code
+ */
+ protected static ExitUtil.ExitException convertToExitException(
+ Throwable thrown) {
+ ExitUtil.ExitException exitException;
+ // 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();
+ if (message == null) {
+ // some exceptions do not have a message; fall back
+ // to the string value.
+ message = thrown.toString();
+ }
+ } 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);
+ }
+
+ /**
+ * Override point: register this class as the handler for the control-C
+ * and SIGINT interrupts.
+ *
+ * Subclasses can extend this with extra operations, such as
+ * an exception handler:
+ * <pre>
+ * Thread.setDefaultUncaughtExceptionHandler(
+ * new YarnUncaughtExceptionHandler());
+ * </pre>
+ */
+ 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);
+ }
+ Thread.setDefaultUncaughtExceptionHandler(
+ new HadoopUncaughtExceptionHandler(this));
+ }
+
+ /**
+ * Handler for uncaught exceptions: terminate the service.
+ * @param thread thread
+ * @param exception exception
+ */
+ @Override
+ public void uncaughtException(Thread thread, Throwable exception) {
+ LOG.error("Uncaught exception in thread {} -exiting", thread, exception);
+ exit(convertToExitException(exception));
+ }
+
+ /**
+ * 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 " + serviceName;
+ }
+ }
+
+ /**
+ * Print a warning message.
+ * <p>
+ * 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.
+ * <p>
+ * This tries to log to {@code LOG.error()}.
+ * <p>
+ * If that log level is disabled disabled the message
+ * is logged to system error along with {@code 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
+ * {@code 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,
+ * invoking {@link ExitUtil#terminate(ExitUtil.ExitException)}.
+ *
+ * This is the standard way a launched service exits.
+ * An error code of 0 means success -nothing is printed.
+ *
+ * If {@link ExitUtil#disableSystemExit()} has been called, this
+ * method will throw the exception.
+ *
+ * The method <i>may</i> be subclassed for testing
+ * @param ee exit exception
+ * @throws ExitUtil.ExitException if ExitUtil exceptions are disabled
+ */
+ 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 command options 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. {@code args[0]}is assumed to be
+ * the service classname and is skipped.
+ * @return the remaining arguments
+ * @throws ExitUtil.ExitException if JVM exiting is disabled.
+ */
+ public List<String> extractCommandOptions(Configuration conf,
+ List<String> args) {
+ int size = args.size();
+ if (size <= 1) {
+ return new ArrayList<>(0);
+ }
+ List<String> coreArgs = args.subList(1, size);
+
+ return parseCommandArgs(conf, coreArgs);
+ }
+
+ /**
+ * Parse the command arguments, extracting the service class as the last
+ * element of the list (after extracting all the rest).
+ *
+ * The field {@link #commandOptions} field must already have been set.
+ * @param conf configuration to use
+ * @param args command line argument list
+ * @return the remaining arguments
+ * @throws ServiceLaunchException if processing of arguments failed
+ */
+ protected List<String> parseCommandArgs(Configuration conf,
+ List<String> args) {
+ Preconditions.checkNotNull(commandOptions,
+ "Command options have not been created");
+ StringBuilder argString = new StringBuilder(args.size() * 32);
+ for (String arg : args) {
+ argString.append("\"").append(arg).append("\" ");
+ }
+ LOG.debug("Command line: {}", argString);
+ try {
+ String[] argArray = args.toArray(new String[args.size()]);
+ // parse this the standard way. This will
+ // update the configuration in the parser, and potentially
+ // patch the user credentials
+ GenericOptionsParser parser = createGenericOptionsParser(conf, argArray);
+ if (!parser.isParseSuccessful()) {
+ throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR,
+ E_PARSE_FAILED + " %s", argString);
+ }
+ CommandLine line = parser.getCommandLine();
+ List<String> remainingArgs = Arrays.asList(parser.getRemainingArgs());
+ LOG.debug("Remaining arguments {}", remainingArgs);
+
+ // Scan the list of configuration files
+ // and bail out if they don't exist
+ if (line.hasOption(ARG_CONF)) {
+ String[] filenames = line.getOptionValues(ARG_CONF);
+ verifyConfigurationFilesExist(filenames);
+ // Add URLs of files as list of URLs to load
+ for (String filename : filenames) {
+ File file = new File(filename);
+ LOG.debug("Configuration files {}", file);
+ confResourceUrls.add(file.toURI().toURL());
+ }
+ }
+ if (line.hasOption(ARG_CONFCLASS)) {
+ // new resources to instantiate as configurations
+ List<String> classnameList = Arrays.asList(
+ line.getOptionValues(ARG_CONFCLASS));
+ LOG.debug("Configuration classes {}", classnameList);
+ confClassnames.addAll(classnameList);
+ }
+ // return the remainder
+ return remainingArgs;
+ } catch (IOException e) {
+ // parsing problem: convert to a command argument error with
+ // the original text
+ throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR, e);
+ } catch (RuntimeException e) {
+ // lower level issue such as XML parse failure
+ throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR,
+ E_PARSE_FAILED + " %s : %s", argString, e);
+ }
+ }
+
+ /**
+ * Override point: create a generic options parser or subclass thereof.
+ * @param conf Hadoop configuration
+ * @param argArray array of arguments
+ * @return a generic options parser to parse the arguments
+ * @throws IOException on any failure
+ */
+ protected GenericOptionsParser createGenericOptionsParser(Configuration conf,
+ String[] argArray) throws IOException {
+ return new MinimalGenericOptionsParser(conf, commandOptions, argArray);
+ }
+
+ /**
+ * Verify that all the specified filenames exist.
+ * @param filenames a list of files
+ * @throws ServiceLaunchException if a file is not found
+ */
+ protected void verifyConfigurationFilesExist(String[] filenames) {
+ if (filenames == null) {
+ return;
+ }
+ for (String filename : filenames) {
+ File file = new File(filename);
+ LOG.debug("Conf file {}", file.getAbsolutePath());
+ if (!file.exists()) {
+ // no configuration file
+ throw new ServiceLaunchException(EXIT_NOT_FOUND,
+ ARG_CONF_PREFIXED + ": configuration file not found: %s",
+ file.getAbsolutePath());
+ }
+ }
+ }
+
+ /**
+ * Build a log message for starting up and shutting down.
+ * @param classname the class of the server
+ * @param args arguments
+ */
+ protected static String startupShutdownMessage(String classname,
+ List<String> 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 message to print before exiting
+ * @throws ExitUtil.ExitException if exceptions are disabled
+ */
+ protected static void exitWithMessage(int status, String message) {
+ ExitUtil.terminate(new ServiceLaunchException(status, message));
+ }
+
+ /**
+ * Exit with the usage exit code {@link #EXIT_USAGE}
+ * and message {@link #USAGE_MESSAGE}.
+ * @throws ExitUtil.ExitException if exceptions are disabled
+ */
+ protected static void exitWithUsageMessage() {
+ exitWithMessage(EXIT_USAGE, USAGE_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) {
+ serviceMain(Arrays.asList(args));
+ }
+
+ /**
+ * 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) {
+ serviceMain(Arrays.asList(args));
+ }
+
+ /* ====================================================================== */
+ /**
+ * 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<String> argsList) {
+ if (argsList.isEmpty()) {
+ // no arguments: usage message
+ exitWithUsageMessage();
+ } else {
+ ServiceLauncher<Service> serviceLauncher =
+ new ServiceLauncher<>(argsList.get(0));
+ serviceLauncher.launchServiceAndExit(argsList);
+ }
+ }
+
+ /**
+ * A generic options parser which does not parse any of the traditional
+ * Hadoop options.
+ */
+ protected static class MinimalGenericOptionsParser
+ extends GenericOptionsParser {
+ public MinimalGenericOptionsParser(Configuration conf,
+ Options options, String[] args) throws IOException {
+ super(conf, options, args);
+ }
+
+ @Override
+ protected Options buildGeneralOptions(Options opts) {
+ return opts;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/373bb493/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java
----------------------------------------------------------------------
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..9115f4e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/service/launcher/ServiceShutdownHook.java
@@ -0,0 +1,112 @@
+/*
+ * 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 java.lang.ref.WeakReference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.util.ShutdownHookManager;
+
+/**
+ * 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,
+ * and when shut down, calls {@link Service#stop()} if the reference is valid.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class ServiceShutdownHook implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(
+ ServiceShutdownHook.class);
+
+ /**
+ * A weak reference to the service.
+ */
+ private final WeakReference<Service> 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.
+ * <p>
+ * 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;
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org