YARN-679. Add an entry point that can start any Yarn service. Contributed by Steve Loughran.

This commit is contained in:
Junping Du 2017-04-28 10:45:02 -07:00
parent cb672a45a0
commit 373bb4931f
38 changed files with 4860 additions and 147 deletions

View File

@ -20,37 +20,83 @@
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 static RuntimeException convert(Throwable fault) {
* {@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) {

View File

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

View File

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

View File

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

View File

@ -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 + ')';
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,462 @@
/*
* 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.
<h2>Key Features</h2>
<p>
<b>General purpose YARN service launcher</b>:<p>
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.
<p>
This class is designed be invokable from the static
{@link org.apache.hadoop.service.launcher.ServiceLauncher#main(String[])}
method, or from {@code main(String[])} methods implemented by
other classes which provide their own entry points.
<p>
<b>Extended YARN Service Interface</b>:<p>
The {@link org.apache.hadoop.service.launcher.LaunchableService} interface
extends {@link org.apache.hadoop.service.Service} with methods to pass
down the CLI arguments and to execute an operation without having to
spawn a thread in the {@link org.apache.hadoop.service.Service#start()} phase.
<p>
<b>Standard Exit codes</b>:<p>
{@link org.apache.hadoop.service.launcher.LauncherExitCodes}
defines a set of exit codes that can be used by services to standardize
exit causes.
<p>
<b>Escalated shutdown</b>:<p>
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 out or a second interrupt is received.
<p><b>Tests:</b><p> test cases include interrupt handling and
lifecycle failures.
<h2>Launching a YARN Service</h2>
The Service Launcher can launch <i>any YARN service</i>.
It will instantiate the service classname provided, using the no-args
constructor, or if no such constructor is available, it will fall back
to a constructor with a single {@code String} parameter,
passing the service name as the parameter value.
<p>
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.
<p>
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
{@code STATE.STOPPED}, escalated into a non-zero return code).
<p>
To view the workflow in sequence, it is:
<ol>
<li>(prepare configuration files &mdash;covered later)</li>
<li>instantiate service via its empty or string constructor</li>
<li>call {@link org.apache.hadoop.service.Service#init(Configuration)}</li>
<li>call {@link org.apache.hadoop.service.Service#start()}</li>
<li>call
{@link org.apache.hadoop.service.Service#waitForServiceToStop(long)}</li>
<li>If an exception was raised: propagate it</li>
<li>If an exception was recorded in
{@link org.apache.hadoop.service.Service#getFailureCause()}
while the service was running: propagate it.</li>
</ol>
For a service to be fully compatible with this launch model, it must
<ul>
<li>Start worker threads, processes and executors in its
{@link org.apache.hadoop.service.Service#start()} method</li>
<li>Terminate itself via a call to
{@link org.apache.hadoop.service.Service#stop()}
in one of these asynchronous methods.</li>
</ul>
If a service does not stop itself, <i>ever</i>, 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 {@code kill}
and control-C signals &mdash;calling {@code stop()} on the service when
signaled.
This means that a daemon service <i>may</i> get a warning and time to shut
down.
<p>
To summarize: provided a service launches its long-lived threads in its Service
{@code start()} method, the service launcher can create it, configure it
and start it, triggering shutdown when signaled.
What these services can not do is get at the command line parameters or easily
propagate exit codes (there is a way covered later). These features require
some extensions to the base {@code Service} interface: <i>the Launchable
Service</i>.
<h2>Launching a Launchable YARN Service</h2>
A Launchable YARN Service is a YARN service which implements the interface
{@link org.apache.hadoop.service.launcher.LaunchableService}.
<p>
It adds two methods to the service interface &mdash;and hence two new features:
<ol>
<li>Access to the command line passed to the service launcher </li>
<li>A blocking {@code int execute()} method which can return the exit
code for the application.</li>
</ol>
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, thus integrating with YARN's
workflow and {@code YarnClient} client-side code.
<p>
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 {@code execute()} method.
It doesn't even need to {@code stop()} itself; the launcher will handle
that if necessary.
<p>
The {@link org.apache.hadoop.service.launcher.LaunchableService} interface
extends {@link org.apache.hadoop.service.Service} with two new methods.
<p>
{@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)}
provides the {@code main(String args[])} arguments as a list, after any
processing by the Service Launcher to extract configuration file references.
This method <i>is called before
{@link org.apache.hadoop.service.Service#init(Configuration)}.</i>
This is by design: it allows the arguments to be parsed before the service is
initialized, thus allowing services to tune their configuration data before
passing it to any superclass in that {@code 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 {@code 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 {@code init()} method.
<p>
After the {@code bindArgs()} processing, the service's {@code init()}
and {@code start()} methods are called, as usual.
<p>
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.
<p>
After this {@code execute()} operation completes, the
service is stopped and exit codes generated. Any exception raised
during the {@code execute()} method takes priority over any exit codes
returned by the method. This allows services to signal failures simply
by raising exceptions with exit codes.
<p>
<p>
To view the workflow in sequence, it is:
<ol>
<li>(prepare configuration files &mdash;covered later)</li>
<li>instantiate service via its empty or string constructor</li>
<li>call {@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)}</li>
<li>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)}</li>
<li>call {@link org.apache.hadoop.service.Service#start()}</li>
<li>call {@link org.apache.hadoop.service.launcher.LaunchableService#execute()}</li>
<li>call {@link org.apache.hadoop.service.Service#stop()}</li>
<li>The return code from
{@link org.apache.hadoop.service.launcher.LaunchableService#execute()}
becomes the exit code of the process, unless overridden by an exception.</li>
<li>If an exception was raised in this workflow: propagate it</li>
<li>If an exception was recorded in
{@link org.apache.hadoop.service.Service#getFailureCause()}
while the service was running: propagate it.</li>
</ol>
<h2>Exit Codes and Exceptions</h2>
<p>
For a basic service, the return code is 0 unless an exception
was raised.
<p>
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.
<p>
Exceptions are converted into exit codes -but rather than simply
have a "something went wrong" exit code, exceptions <i>may</i>
provide exit codes which will be extracted and used as the return code.
This enables Launchable Services to use exceptions as a way
of returning error codes to signal failures and for
normal Services to return any error code at all.
<p>
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}.
<p>
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 the
{@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 the like. There are also some common exit codes
for Hadoop/YARN service failures, such as
{@link org.apache.hadoop.service.launcher.LauncherExitCodes#EXIT_UNAUTHORIZED}.
Note that {@link org.apache.hadoop.util.ExitUtil.ExitException} itself
implements {@link org.apache.hadoop.util.ExitCodeProvider#getExitCode()}
<p>
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}.
<p>
To view the exit code extraction in sequence, it is:
<ol>
<li>If no exception was triggered by a basic service, a
{@link org.apache.hadoop.service.launcher.ServiceLaunchException} with an
exit code of 0 is created.</li>
<li>For a LaunchableService, the exit code is the result of {@code execute()}
Again, a {@link org.apache.hadoop.service.launcher.ServiceLaunchException}
with a return code of 0 is created.
</li>
<li>Otherwise, if the exception is an instance of {@code ExitException},
it is returned as the service terminating exception.</li>
<li>If the exception implements {@link org.apache.hadoop.util.ExitCodeProvider},
its exit code and {@code getMessage()} value become the exit exception.</li>
<li>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.</li>
<li>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.</li>
<li>The {@link org.apache.hadoop.util.ExitUtil} class then terminates the JVM
with the specified exit code, printing the {@code toString()} value
of the exception if the return code is non-zero.</li>
</ol>
This process may seem convoluted, but it is designed to allow any exception
in the Hadoop exception hierarchy to generate exit codes,
and to minimize the amount of exception wrapping which takes place.
<h2>Interrupt Handling</h2>
The Service Launcher has a helper class,
{@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.
It then triggers a JVM shutdown by way of
{@link org.apache.hadoop.util.ExitUtil#terminate(int, String)}
<p>
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}.
<p>
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.
<h2>Configuration class creation</h2>
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 {@code HdfsConfiguration} or {@code YarnConfiguration}.
<p>
What the launcher does do is use reflection to try and create instances of
these classes simply to force in the common resources. If the classes are
not on the classpath this fact will be logged.
<p>
Applications may consider it essential to either force load in the relevant
configuration, or pass it down to the service being created. In which
case further measures may be needed.
<p><b>1: Creation in an extended {@code ServiceLauncher}</b>
<p>
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
{@code main()} method.
<p><b>2: Creation in {@code bindArgs()}</b>
<p>
In
{@link org.apache.hadoop.service.launcher.LaunchableService#bindArgs(Configuration, List)},
a new configuration is created:
<pre>
public Configuration bindArgs(Configuration config, List<String> args)
throws Exception {
Configuration newConf = new YarnConfiguration(config);
return newConf;
}
</pre>
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.
<p><b>3: Creation in {@code serviceInit()}</b>
<pre>
protected void serviceInit(Configuration conf) throws Exception {
super.serviceInit(new YarnConfiguration(conf));
}
</pre>
<p>
This is a strategy used by many existing YARN services, and is ideal for
services which do 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.
<b>Summary</b>: the ServiceLauncher makes a best-effort attempt to load the
standard Configuration subclasses, but does not fail if they are not present.
Services which require a specific subclasses should follow one of the
strategies listed;
creation in {@code serviceInit()} is the recommended policy.
<h2>Configuration file loading</h2>
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: {@code --conf &lt;file&gt;}. This must refer to a file
in the local filesystem which exists.
<p>
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.
<p>
All the {@code --conf &lt;file&gt;} 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.
<h2>Utility Classes</h2>
<ul>
<li>
{@link org.apache.hadoop.service.launcher.IrqHandler}: registers interrupt
handlers using {@code sun.misc} APIs.
</li>
<li>
{@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.
</li>
</ul>
*/
package org.apache.hadoop.service.launcher;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.ExitUtil;

View File

@ -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.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();
}

View File

@ -17,41 +17,123 @@
*/
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 {
private ExitUtil() {
}
/**
* 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;
}
}
/**
@ -69,7 +151,7 @@ public static void disableSystemHalt() {
}
/**
* @return true if terminate has been called
* @return true if terminate has been called.
*/
public static boolean terminateCalled() {
// Either we set this member or we actually called System#exit
@ -77,21 +159,21 @@ public static boolean terminateCalled() {
}
/**
* @return true if halt has been called
* @return true if halt has been called.
*/
public static boolean haltCalled() {
return firstHaltException != null;
}
/**
* @return the first ExitException thrown, null if none thrown yet
* @return the first ExitException thrown, null if none thrown yet.
*/
public static ExitException getFirstExitException() {
return firstExitException;
}
/**
* @return the first {@code HaltException} thrown, null if none thrown yet
* @return the first {@code HaltException} thrown, null if none thrown yet.
*/
public static HaltException getFirstHaltException() {
return firstHaltException;
@ -110,22 +192,22 @@ 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 +217,26 @@ 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
* The exception argument is rethrown if JVM halting 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 void halt(int status, String msg) throws HaltException {
LOG.info("Halt with status " + status + " Message: " + msg);
public static synchronized void halt(HaltException ee) throws HaltException {
int status = ee.getExitCode();
String msg = ee.getMessage();
try {
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);
}
} catch (Exception ignored) {
// ignore exceptions here, as it may be due to an out of memory situation
}
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 +245,94 @@ public static void halt(int status, String msg) throws HaltException {
}
/**
* Like {@link terminate(int, String)} but uses the given throwable to
* initialize the ExitException.
*
* @param status
* @param t
* throwable used to create the ExitException
* @throws ExitException
* if System.exit is disabled for test purposes
* Like {@link #terminate(int, String)} but uses the given throwable to
* build the message to display or throw as an
* {@link ExitException}.
* <p>
* @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));
}
/**
* Handler for out of memory events -no attempt is made here
* to cleanly shutdown or support halt blocking; a robust
* printing of the event to stderr is all that can be done.
* @param oome out of memory event
*/
public static void haltOnOutOfMemory(OutOfMemoryError oome) {
//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 done want to exit because of logging issues.
}
Runtime.getRuntime().halt(-1);
}
}

View File

@ -15,9 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.util;
import java.io.File;
package org.apache.hadoop.util;import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
@ -118,6 +116,7 @@ public class GenericOptionsParser {
private static final Log LOG = LogFactory.getLog(GenericOptionsParser.class);
private Configuration conf;
private CommandLine commandLine;
private final boolean parseSuccessful;
/**
* Create an options parser with the given options to parse the args.
@ -171,7 +170,7 @@ public GenericOptionsParser(Configuration conf, String[] args)
public GenericOptionsParser(Configuration conf,
Options options, String[] args) throws IOException {
this.conf = conf;
parseGeneralOptions(options, args);
parseSuccessful = parseGeneralOptions(options, args);
}
/**
@ -208,58 +207,72 @@ public CommandLine getCommandLine() {
}
/**
* Specify properties of each generic option
* Query for the parse operation succeeding.
* @return true if parsing the CLI was successful
*/
public boolean isParseSuccessful() {
return parseSuccessful;
}
/**
* Specify properties of each generic option.
* <i>Important</i?: as {@link OptionBuilder} is not thread safe, subclasses
* must synchronize use on {@code OptionBuilder.class}
*/
@SuppressWarnings("static-access")
private static synchronized Options buildGeneralOptions(Options opts) {
Option fs = OptionBuilder.withArgName("file:///|hdfs://namenode:port")
.hasArg()
.withDescription("specify default filesystem URL to use, "
+ "overrides 'fs.defaultFS' property from configurations.")
.create("fs");
Option jt = OptionBuilder.withArgName("local|resourcemanager:port")
.hasArg()
.withDescription("specify a ResourceManager")
.create("jt");
Option oconf = OptionBuilder.withArgName("configuration file")
.hasArg()
.withDescription("specify an application configuration file")
.create("conf");
Option property = OptionBuilder.withArgName("property=value")
.hasArg()
.withDescription("use value for given property")
.create('D');
Option libjars = OptionBuilder.withArgName("paths")
.hasArg()
.withDescription("comma separated jar files to include in the classpath.")
.create("libjars");
Option files = OptionBuilder.withArgName("paths")
.hasArg()
.withDescription("comma separated files to be copied to the " +
"map reduce cluster")
.create("files");
Option archives = OptionBuilder.withArgName("paths")
.hasArg()
.withDescription("comma separated archives to be unarchived" +
" on the compute machines.")
.create("archives");
// file with security tokens
Option tokensFile = OptionBuilder.withArgName("tokensFile")
.hasArg()
.withDescription("name of the file with the tokens")
.create("tokenCacheFile");
protected Options buildGeneralOptions(Options opts) {
synchronized (OptionBuilder.class) {
Option fs = OptionBuilder.withArgName("file:///|hdfs://namenode:port")
.hasArg()
.withDescription("specify default filesystem URL to use, "
+ "overrides 'fs.defaultFS' property from configurations.")
.create("fs");
Option jt = OptionBuilder.withArgName("local|resourcemanager:port")
.hasArg()
.withDescription("specify a ResourceManager")
.create("jt");
Option oconf = OptionBuilder.withArgName("configuration file")
.hasArg()
.withDescription("specify an application configuration file")
.create("conf");
Option property = OptionBuilder.withArgName("property=value")
.hasArg()
.withDescription("use value for given property")
.create('D');
Option libjars = OptionBuilder.withArgName("paths")
.hasArg()
.withDescription(
"comma separated jar files to include in the classpath.")
.create("libjars");
Option files = OptionBuilder.withArgName("paths")
.hasArg()
.withDescription("comma separated files to be copied to the " +
"map reduce cluster")
.create("files");
Option archives = OptionBuilder.withArgName("paths")
.hasArg()
.withDescription("comma separated archives to be unarchived" +
" on the compute machines.")
.create("archives");
opts.addOption(fs);
opts.addOption(jt);
opts.addOption(oconf);
opts.addOption(property);
opts.addOption(libjars);
opts.addOption(files);
opts.addOption(archives);
opts.addOption(tokensFile);
// file with security tokens
Option tokensFile = OptionBuilder.withArgName("tokensFile")
.hasArg()
.withDescription("name of the file with the tokens")
.create("tokenCacheFile");
return opts;
opts.addOption(fs);
opts.addOption(jt);
opts.addOption(oconf);
opts.addOption(property);
opts.addOption(libjars);
opts.addOption(files);
opts.addOption(archives);
opts.addOption(tokensFile);
return opts;
}
}
/**
@ -368,7 +381,7 @@ public static URL[] getLibJars(Configuration conf) throws IOException {
}
/**
* takes input as a comma separated list of files
* Takes input as a comma separated list of files
* and verifies if they exist. It defaults for file:///
* if the files specified do not have a scheme.
* it returns the paths uri converted defaulting to file:///.
@ -543,20 +556,24 @@ private String[] preProcessForWindows(String[] args) {
*
* @param opts Options to use for parsing args.
* @param args User-specified arguments
* @return true if the parse was successful
*/
private void parseGeneralOptions(Options opts, String[] args)
private boolean parseGeneralOptions(Options opts, String[] args)
throws IOException {
opts = buildGeneralOptions(opts);
CommandLineParser parser = new GnuParser();
boolean parsed = false;
try {
commandLine = parser.parse(opts, preProcessForWindows(args), true);
processGeneralOptions(commandLine);
parsed = true;
} catch(ParseException e) {
LOG.warn("options parsing failed: "+e.getMessage());
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("general options are: ", opts);
}
return parsed;
}
/**

View File

@ -676,11 +676,11 @@ 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)
b.append("\n" + prefix + s);
b.append("\n").append(prefix).append(s);
b.append("\n************************************************************/");
return b.toString();
}
@ -711,21 +711,7 @@ static void startupShutdownMessage(Class<?> clazz, String[] args,
final LogAdapter LOG) {
final String hostname = NetUtils.getHostname();
final String classname = clazz.getSimpleName();
LOG.info(
toStartupShutdownString("STARTUP_MSG: ", new String[] {
"Starting " + classname,
" user = " + System.getProperty("user.name"),
" 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") }
)
);
LOG.info(createStartupShutdownMessage(classname, hostname, args));
if (SystemUtils.IS_OS_UNIX) {
try {
@ -745,6 +731,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.

View File

@ -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");
}
@ -107,11 +114,11 @@ public void setFailOnStop(boolean failOnStop) {
}
/**
* The exception explicitly raised on a failure
* The exception explicitly raised on a failure.
*/
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 "

View File

@ -0,0 +1,317 @@
/*
* 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.service.Service;
import org.apache.hadoop.service.ServiceOperations;
import static org.apache.hadoop.test.GenericTestUtils.*;
import org.apache.hadoop.util.ExitCodeProvider;
import org.apache.hadoop.util.ExitUtil;
import org.junit.After;
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;
public class AbstractServiceLauncherTestBase extends Assert implements
LauncherExitCodes {
private static final Logger LOG = LoggerFactory.getLogger(
AbstractServiceLauncherTestBase.class);
public static final String CONF_FILE_DIR = "target/launcher/conf";
/**
* A service which will be automatically stopped on teardown.
*/
private Service serviceToTeardown;
/**
* 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();
}
/**
* rule to name the thread JUnit.
*/
@Before
public void nameThread() {
Thread.currentThread().setName("JUnit");
}
@After
public void stopService() {
ServiceOperations.stopQuietly(serviceToTeardown);
}
public void setServiceToTeardown(Service serviceToTeardown) {
this.serviceToTeardown = serviceToTeardown;
}
/**
* 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);
failif(failed,
"Expected exception with exit code %d and text \"%s\""
+ " but got the exit code %d"
+ " in \"%s\"",
expected, text,
exitCode, e);
}
/**
* 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 {
if (LOG.isDebugEnabled()) {
LOG.debug("Launching service with expected outcome {}", expected);
for (String arg : args) {
LOG.debug(arg);
}
}
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 extends Service> 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);
try(OutputStream fos = new FileOutputStream(file)) {
conf.writeXml(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<String> 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 <S> service type
* @return the service launcher
* @throws ExitUtil.ExitException if the launch's exit code != 0
*/
protected <S extends Service> ServiceLauncher<S> launchService(
Class serviceClass,
Configuration conf,
List<String> args,
boolean execute) throws ExitUtil.ExitException {
ServiceLauncher<S> serviceLauncher =
new ServiceLauncher<>(serviceClass.getName());
ExitUtil.ExitException exitException =
serviceLauncher.launchService(conf, args, false, execute);
if (exitException.getExitCode() == 0) {
// success
return serviceLauncher;
} else {
// launch failure
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 <S> service type
* @return the service launcher
* @throws ExitUtil.ExitException if the launch's exit code != 0
*/
protected <S extends Service> ServiceLauncher<S> 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<Service> 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();
boolean condition = errorCode != actualCode ||
!StringUtils.contains(e.toString(), expectedText);
failif(condition,
"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;
}
}
}

View File

@ -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;
import org.apache.hadoop.service.Service;
import org.apache.hadoop.util.ExitUtil;
/**
* Service launcher for testing: The exit operation has been overloaded to
* record the exit exception.
*
* It relies on the test runner to have disabled exits in the
* {@link ExitUtil} class.
* @param <S> type of service to launch
*/
public class ExitTrackingServiceLauncher<S extends Service> extends
ServiceLauncher<S> {
private ExitUtil.ExitException exitException;
public ExitTrackingServiceLauncher(String serviceClassName) {
super(serviceClassName);
}
@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));
}
public void bindCommandOptions() {
super.bindCommandOptions();
}
public ExitUtil.ExitException getExitException() {
return exitException;
}
}

View File

@ -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.Service;
import org.apache.hadoop.service.launcher.testservices.LaunchableRunningService;
import org.apache.hadoop.service.launcher.testservices.RunningService;
import static org.apache.hadoop.service.launcher.LauncherArguments.*;
import org.junit.Test;
import java.io.File;
import java.io.FileWriter;
import java.util.List;
/**
* Test how configuration files are loaded off the command line.
*/
public class TestServiceConf
extends AbstractServiceLauncherTestBase {
@Test
public void testRunService() throws Throwable {
assertRuns(LaunchableRunningService.NAME);
}
@Test
public void testConfPropagationOverInitBindings() throws Throwable {
Configuration conf = newConf(RunningService.FAIL_IN_RUN, "true");
assertLaunchOutcome(EXIT_FAIL,
"failed",
LaunchableRunningService.NAME,
ARG_CONF_PREFIXED,
configFile(conf));
}
@Test
public void testUnbalancedConfArg() throws Throwable {
assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR,
E_PARSE_FAILED,
LaunchableRunningService.NAME,
ARG_CONF_PREFIXED);
}
@Test
public void testConfArgMissingFile() throws Throwable {
assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR,
E_PARSE_FAILED,
LaunchableRunningService.NAME,
ARG_CONF_PREFIXED,
"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,
ARG_CONF_PREFIXED,
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 {
ExitTrackingServiceLauncher<Service> launcher =
new ExitTrackingServiceLauncher<>(RunningService.NAME);
launcher.bindCommandOptions();
Configuration conf = newConf("propagated", "true");
assertEquals("true", conf.get("propagated", "unset"));
Configuration extracted = new Configuration(false);
List<String> argsList =
asList("Name", ARG_CONF_PREFIXED, configFile(conf));
List<String> args = launcher.extractCommandOptions(extracted,
argsList);
if (!args.isEmpty()) {
assertEquals("args beginning with " + args.get(0),
0, args.size());
}
assertEquals("true", extracted.get("propagated", "unset"));
}
@Test
public void testDualConfArgs() throws Throwable {
ExitTrackingServiceLauncher<Service> launcher =
new ExitTrackingServiceLauncher<>(RunningService.NAME);
launcher.bindCommandOptions();
String key1 = "key1";
Configuration conf1 = newConf(key1, "true");
String key2 = "file2";
Configuration conf2 = newConf(key2, "7");
Configuration extracted = new Configuration(false);
List<String> argsList =
asList("Name",
ARG_CONF_PREFIXED, configFile(conf1),
ARG_CONF_PREFIXED, configFile(conf2));
List<String> args = launcher.extractCommandOptions(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 testConfArgWrongFiletype() throws Throwable {
new File(CONF_FILE_DIR).mkdirs();
File file = new File(CONF_FILE_DIR, methodName.getMethodName());
try (FileWriter fileWriter = new FileWriter(file)) {
fileWriter.write("not-a-conf-file");
fileWriter.close();
}
assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR,
"",
RunningService.NAME,
ARG_CONF_PREFIXED,
file.getAbsolutePath());
}
}

View File

@ -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);
irqHandler.bind();
assertEquals(0, irqHandler.getSignalCount());
irqHandler.raise();
// allow for an async event
Thread.sleep(500);
IrqHandler.InterruptData data = catcher.interruptData;
assertNotNull("interrupt data", data);
assertEquals(name, data.getName());
assertEquals(1, irqHandler.getSignalCount());
}
@Test
public void testInterruptEscalationShutdown() throws Throwable {
ExitTrackingServiceLauncher<BreakableService> launcher =
new ExitTrackingServiceLauncher<>(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 in " + escalator);
} catch (ExitUtil.ExitException e) {
assertExceptionDetails(EXIT_INTERRUPTED, "", e);
}
//the service is now stopped
assertStopped(service);
assertTrue("isSignalAlreadyReceived() == false in " + escalator,
escalator.isSignalAlreadyReceived());
assertFalse("isForcedShutdownTimedOut() == true in " + escalator,
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 in " + escalator);
} catch (ExitUtil.HaltException e) {
assertExceptionDetails(EXIT_INTERRUPTED, "", e);
}
}
@Test
public void testBlockingShutdownTimeouts() throws Throwable {
ExitTrackingServiceLauncher<FailureTestService> launcher =
new ExitTrackingServiceLauncher<>(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 from " + escalator);
} catch (ExitUtil.ExitException e) {
assertExceptionDetails(EXIT_INTERRUPTED, "", e);
}
assertTrue("isForcedShutdownTimedOut() == false in " + escalator,
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;
}
}
}

View File

@ -0,0 +1,213 @@
/*
* 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.launcher.testservices.FailingStopInStartService;
import org.apache.hadoop.service.launcher.testservices.InitInConstructorLaunchableService;
import org.apache.hadoop.service.launcher.testservices.LaunchableRunningService;
import org.apache.hadoop.service.launcher.testservices.NoArgsAllowedService;
import org.apache.hadoop.service.launcher.testservices.NullBindLaunchableService;
import org.apache.hadoop.service.launcher.testservices.RunningService;
import org.apache.hadoop.service.launcher.testservices.StoppingInStartLaunchableService;
import org.apache.hadoop.service.launcher.testservices.StringConstructorOnlyService;
import static org.apache.hadoop.service.launcher.LauncherArguments.*;
import static org.apache.hadoop.test.GenericTestUtils.*;
import static org.apache.hadoop.service.launcher.testservices.ExceptionInExecuteLaunchableService.*;
import org.junit.Test;
public class TestServiceLauncher extends AbstractServiceLauncherTestBase {
@Test
public void testRunService() throws Throwable {
assertRuns(RunningService.NAME);
}
@Test
public void testNullBindService() throws Throwable {
assertRuns(NullBindLaunchableService.NAME);
}
@Test
public void testServiceLaunchStringConstructor() throws Throwable {
assertRuns(StringConstructorOnlyService.NAME);
}
/**
* Test the behaviour of service stop logic.
*/
@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());
}
@Test
public void testEx() throws Throwable {
assertLaunchOutcome(EXIT_EXCEPTION_THROWN,
OTHER_EXCEPTION_TEXT,
NAME);
}
/**
* This test verifies that exceptions in the
* {@link LaunchableService#execute()} method are relayed if an instance of
* an exit exceptions, and forwarded if not.
*/
@Test
public void testServiceLaunchException() 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);
}
/**
* As the exception is doing some formatting tricks, these
* tests verify that exception arguments are being correctly
* used as initializers.
*/
@Test
public void testBasicExceptionFormatting() throws Throwable {
ServiceLaunchException ex = new ServiceLaunchException(0, "%03x", 32);
assertExceptionContains("020", ex);
}
@Test
public void testNotEnoughArgsExceptionFormatting() throws Throwable {
ServiceLaunchException ex = new ServiceLaunchException(0, "%03x");
assertExceptionContains("%03x", ex);
}
@Test
public void testInnerCause() throws Throwable {
Exception cause = new Exception("cause");
ServiceLaunchException ex =
new ServiceLaunchException(0, "%03x: %s", 32, cause);
assertExceptionContains("020", ex);
assertExceptionContains("cause", ex);
assertSame(cause, ex.getCause());
}
@Test
public void testInnerCauseNotInFormat() throws Throwable {
Exception cause = new Exception("cause");
ServiceLaunchException ex =
new ServiceLaunchException(0, "%03x:", 32, cause);
assertExceptionContains("020", ex);
assertFalse(ex.getMessage().contains("cause"));
assertSame(cause, ex.getCause());
}
@Test
public void testServiceInitInConstructor() throws Throwable {
assertRuns(InitInConstructorLaunchableService.NAME);
}
@Test
public void testRunNoArgsAllowedService() throws Throwable {
assertRuns(NoArgsAllowedService.NAME);
}
@Test
public void testNoArgsOneArg() throws Throwable {
assertLaunchOutcome(EXIT_COMMAND_ARGUMENT_ERROR, "1",
NoArgsAllowedService.NAME, "one");
}
@Test
public void testNoArgsHasConfsStripped() throws Throwable {
assertRuns(
NoArgsAllowedService.NAME,
LauncherArguments.ARG_CONF_PREFIXED,
configFile(newConf()));
}
@Test
public void testRunLaunchableService() throws Throwable {
assertRuns(LaunchableRunningService.NAME);
}
@Test
public void testArgBinding() throws Throwable {
assertLaunchOutcome(EXIT_OTHER_FAILURE,
"",
LaunchableRunningService.NAME,
LaunchableRunningService.ARG_FAILING);
}
@Test
public void testStoppingInStartLaunchableService() throws Throwable {
assertRuns(StoppingInStartLaunchableService.NAME);
}
@Test
public void testShutdownHookNullReference() throws Throwable {
new ServiceShutdownHook(null).run();
}
@Test
public void testShutdownHook() throws Throwable {
BreakableService service = new BreakableService();
setServiceToTeardown(service);
ServiceShutdownHook hook = new ServiceShutdownHook(service);
hook.run();
assertStopped(service);
}
@Test
public void testFailingHookCaught() throws Throwable {
BreakableService service = new BreakableService(false, false, true);
setServiceToTeardown(service);
ServiceShutdownHook hook = new ServiceShutdownHook(service);
hook.run();
assertStopped(service);
}
}

View File

@ -0,0 +1,83 @@
/*
* 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;
/**
* Explore the ways in which the launcher is expected to (safely) fail.
*/
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);
}
}

View File

@ -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 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;
import java.util.List;
/**
* Test the inner launcher methods.
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class TestServiceLauncherInnerMethods extends
AbstractServiceLauncherTestBase {
@Test
public void testLaunchService() throws Throwable {
ServiceLauncher<NoArgsAllowedService> launcher =
launchService(NoArgsAllowedService.class, new Configuration());
NoArgsAllowedService service = launcher.getService();
assertNotNull("null service from " + launcher, service);
service.stop();
}
@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<LaunchableRunningService> launcher =
launchService(LaunchableRunningService.class, new Configuration());
LaunchableRunningService service = launcher.getService();
assertInState(service, Service.STATE.STARTED);
service.failInRun = true;
service.setExitCode(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<BreakableService> launcher =
launchService(BreakableService.class, new Configuration());
BreakableService service = launcher.getService();
assertNotNull("null service from " + launcher, service);
service.stop();
}
@Test
public void testConfigLoading() throws Throwable {
ServiceLauncher<BreakableService> launcher =
new ServiceLauncher<>("BreakableService");
List<String> configurationsToCreate = launcher.getConfigurationsToCreate();
assertTrue(configurationsToCreate.size() > 1);
int created = launcher.loadConfigurationClasses();
assertEquals(1, created);
}
}

View File

@ -0,0 +1,96 @@
/*
* 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;
private ExType exceptionType = ExType.EX;
public ExceptionInExecuteLaunchableService() {
super("ExceptionInExecuteLaunchedService");
}
@Override
public Configuration bindArgs(Configuration config, List<String> 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;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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;
/**
* Service which fails in its constructor.
*/
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");
}
}

View File

@ -0,0 +1,38 @@
/*
* 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;
/**
* Service which fails in its init() operation.
*/
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;
}
}

View File

@ -0,0 +1,37 @@
/*
* 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;
/**
* Service which fails in its start() operation.
*/
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;
}
}

View File

@ -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 stops during its start operation.
*/
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;
}
}

View File

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

View File

@ -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<String> 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();
}
}

View File

@ -0,0 +1,111 @@
/*
* 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}.
* It
* <ol>
* <li>does nothing in its {@link #serviceStart()}</li>
* <li>does its sleep+ maybe fail operation in its {@link #execute()}
* method</li>
* <li>gets the failing flag from the argument {@link #ARG_FAILING} first,
* the config file second.</li>
* <li>returns 0 for a successful execute</li>
* <li>returns a configurable exit code for a failing execute</li>
* <li>generates a new configuration in {@link #bindArgs(Configuration, List)}
* to verify that these propagate.</li>
* </ol>
*/
public class LaunchableRunningService extends RunningService implements
LaunchableService {
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";
private static final Logger LOG =
LoggerFactory.getLogger(LaunchableRunningService.class);
private int exitCode = 0;
public LaunchableRunningService() {
this("LaunchableRunningService");
}
public LaunchableRunningService(String name) {
super(name);
}
@Override
public Configuration bindArgs(Configuration config, List<String> args) throws
Exception {
Assert.assertEquals(STATE.NOTINITED, getServiceState());
for (String arg : args) {
LOG.info(arg);
}
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;
}
public int getExitCode() {
return exitCode;
}
public void setExitCode(int exitCode) {
this.exitCode = exitCode;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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;
import java.util.Map;
/**
* 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<String> args)
throws Exception {
Configuration configuration = super.bindArgs(config, args);
if (!args.isEmpty()) {
StringBuilder argsList = new StringBuilder();
for (String arg : args) {
argsList.append('"').append(arg).append("\" ");
}
LOG.error("Got {} arguments: {}", args.size(), argsList);
throw new ServiceLaunchException(
LauncherExitCodes.EXIT_COMMAND_ARGUMENT_ERROR,
"Expected 0 arguments but got %d: %s",
args.size(),
argsList);
}
return configuration;
}
}

View File

@ -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<String> args)
throws Exception {
return null;
}
}

View File

@ -0,0 +1,84 @@
/*
* 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";
private 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();
}
public boolean isInterrupted() {
return interrupted;
}
}

View File

@ -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.testservices;
import org.apache.hadoop.service.launcher.AbstractLaunchableService;
import org.apache.hadoop.service.launcher.LauncherExitCodes;
import org.apache.hadoop.service.launcher.ServiceLaunchException;
/**
* Try to stop() in service start; in execute() raise an exception.
*/
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");
}
}

View File

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

View File

@ -31,6 +31,7 @@
import java.lang.management.ThreadMXBean;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@ -692,4 +693,37 @@ private static void addPlusses(StringBuilder bld, BufferedReader r)
bld.append(" + ").append(l).append("\n");
}
}
/**
* Formatted fail, via {@link String#format(String, Object...)}.
* @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
*/
public 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, via {@link String#format(String, Object...)}.
* @param condition condition: if true the method fails
* @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
*/
public static void failif(boolean condition,
String format,
Object... args) {
if (condition) {
failf(format, args);
}
}
}