blob: 6b0b4e8628091fc2d67348672964455e25c2d135 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.service.launcher;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.service.Service;
import org.apache.hadoop.util.ExitCodeProvider;
import org.apache.hadoop.util.ExitUtil;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.util.StringUtils;
/**
* A class to launch any YARN service by name.
*
* It's designed to be subclassed for custom entry points.
*
* Workflow:
* <ol>
* <li>An instance of the class is created. It must be of the type
* {@link Service}</li>
* <li>If it implements
* {@link LaunchableService#bindArgs(Configuration, List)},
* it is given the binding args off the CLI after all general configuration
* arguments have been stripped.</li>
* <li>Its {@link Service#init(Configuration)} and {@link Service#start()}
* methods are called.</li>
* <li>If it implements it, {@link LaunchableService#execute()}
* is called and its return code used as the exit code.</li>
* <li>Otherwise: it waits for the service to stop, assuming that the
* {@link Service#start()} method spawns one or more thread
* to perform work</li>
* <li>If any exception is raised and provides an exit code,
* that is, it implements {@link ExitCodeProvider},
* the return value of {@link ExitCodeProvider#getExitCode()},
* becomes the exit code of the command.</li>
* </ol>
* Error and warning messages are logged to {@code stderr}.
*
* @param <S> service class to cast the generated service to.
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class ServiceLauncher<S extends Service>
implements LauncherExitCodes, LauncherArguments,
Thread.UncaughtExceptionHandler {
/**
* Logger.
*/
private static final Logger LOG =
LoggerFactory.getLogger(ServiceLauncher.class);
/**
* Priority for the shutdown hook: {@value}.
*/
protected static final int SHUTDOWN_PRIORITY = 30;
/**
* The name of this class.
*/
public static final String NAME = "ServiceLauncher";
protected static final String USAGE_NAME = "Usage: " + NAME;
protected static final String USAGE_SERVICE_ARGUMENTS =
"service-classname <service arguments>";
/**
* Usage message.
*
* Text: {@value}
*/
public static final String USAGE_MESSAGE =
USAGE_NAME
+ " [" + ARG_CONF_PREFIXED + " <conf file>]"
+ " [" + ARG_CONFCLASS_PREFIXED + " <configuration classname>]"
+ " " + USAGE_SERVICE_ARGUMENTS;
/**
* The shutdown time on an interrupt: {@value}.
*/
private static final int SHUTDOWN_TIME_ON_INTERRUPT = 30 * 1000;
/**
* The launched service.
*
* Invalid until the service has been created.
*/
private volatile S service;
/**
* Exit code of the service.
*
* Invalid until a service has
* executed or stopped, depending on the service type.
*/
private int serviceExitCode;
/**
* Any exception raised during execution.
*/
private ExitUtil.ExitException serviceException;
/**
* The interrupt escalator for the service.
*/
private InterruptEscalator interruptEscalator;
/**
* Configuration used for the service.
*/
private Configuration configuration;
/**
* Text description of service for messages.
*/
private String serviceName;
/**
* Classname for the service to create.; empty string otherwise.
*/
private String serviceClassName = "";
/**
* List of the standard configurations to create (and so load in properties).
* The values are Hadoop, HDFS and YARN configurations.
*/
protected static final String[] DEFAULT_CONFIGS = {
"org.apache.hadoop.conf.Configuration",
"org.apache.hadoop.hdfs.HdfsConfiguration",
"org.apache.hadoop.yarn.conf.YarnConfiguration"
};
/**
* List of classnames to load to configuration before creating a
* {@link Configuration} instance.
*/
private List<String> confClassnames = new ArrayList<>(DEFAULT_CONFIGS.length);
/**
* URLs of configurations to load into the configuration instance created.
*/
private List<URL> confResourceUrls = new ArrayList<>(1);
/** Command options. Preserved for usage statements. */
private Options commandOptions;
/**
* Create an instance of the launcher.
* @param serviceClassName classname of the service
*/
public ServiceLauncher(String serviceClassName) {
this(serviceClassName, serviceClassName);
}
/**
* Create an instance of the launcher.
* @param serviceName name of service for text messages
* @param serviceClassName classname of the service
*/
public ServiceLauncher(String serviceName, String serviceClassName) {
this.serviceClassName = serviceClassName;
this.serviceName = serviceName;
// set up initial list of configurations
confClassnames.addAll(Arrays.asList(DEFAULT_CONFIGS));
}
/**
* Get the service.
*
* Null until
* {@link #coreServiceLaunch(Configuration, List, boolean, boolean)}
* has completed.
* @return the service
*/
public final S getService() {
return service;
}
/**
* Setter is to give subclasses the ability to manipulate the service.
* @param s the new service
*/
protected void setService(S s) {
this.service = s;
}
/**
* Get the configuration constructed from the command line arguments.
* @return the configuration used to create the service
*/
public final Configuration getConfiguration() {
return configuration;
}
/**
* The exit code from a successful service execution.
* @return the exit code.
*/
public final int getServiceExitCode() {
return serviceExitCode;
}
/**
* Get the exit exception used to end this service.
* @return an exception, which will be null until the service
* has exited (and {@code System.exit} has not been called)
*/
public final ExitUtil.ExitException getServiceException() {
return serviceException;
}
/**
* Probe for service classname being defined.
* @return true if the classname is set
*/
private boolean isClassnameDefined() {
return serviceClassName != null && !serviceClassName.isEmpty();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("\"ServiceLauncher for \"");
sb.append(serviceName);
if (isClassnameDefined()) {
sb.append(", serviceClassName='").append(serviceClassName).append('\'');
}
if (service != null) {
sb.append(", service=").append(service);
}
return sb.toString();
}
/**
* Launch the service and exit.
*
* <ol>
* <li>Parse the command line.</li>
* <li>Build the service configuration from it.</li>
* <li>Start the service.</li>.
* <li>If it is a {@link LaunchableService}: execute it</li>
* <li>Otherwise: wait for it to finish.</li>
* <li>Exit passing the status code to the {@link #exit(int, String)}
* method.</li>
* </ol>
* @param args arguments to the service. {@code arg[0]} is
* assumed to be the service classname.
*/
public void launchServiceAndExit(List<String> args) {
StringBuilder builder = new StringBuilder();
for (String arg : args) {
builder.append('"').append(arg).append("\" ");
}
String argumentString = builder.toString();
if (LOG.isDebugEnabled()) {
LOG.debug(startupShutdownMessage(serviceName, args));
LOG.debug(argumentString);
}
registerFailureHandling();
// set up the configs, using reflection to push in the -site.xml files
loadConfigurationClasses();
Configuration conf = createConfiguration();
for (URL resourceUrl : confResourceUrls) {
conf.addResource(resourceUrl);
}
bindCommandOptions();
ExitUtil.ExitException exitException;
try {
List<String> processedArgs = extractCommandOptions(conf, args);
exitException = launchService(conf, processedArgs, true, true);
} catch (ExitUtil.ExitException e) {
exitException = e;
noteException(exitException);
}
if (exitException.getExitCode() != 0) {
// something went wrong. Print the usage and commands
System.err.println(getUsageMessage());
System.err.println("Command: " + argumentString);
}
System.out.flush();
System.err.flush();
exit(exitException);
}
/**
* Set the {@link #commandOptions} field to the result of
* {@link #createOptions()}; protected for subclasses and test access.
*/
protected void bindCommandOptions() {
commandOptions = createOptions();
}
/**
* Record that an Exit Exception has been raised.
* Save it to {@link #serviceException}, with its exit code in
* {@link #serviceExitCode}
* @param exitException exception
*/
void noteException(ExitUtil.ExitException exitException) {
LOG.debug("Exception raised", exitException);
serviceExitCode = exitException.getExitCode();
serviceException = exitException;
}
/**
* Get the usage message, ideally dynamically.
* @return the usage message
*/
protected String getUsageMessage() {
String message = USAGE_MESSAGE;
if (commandOptions != null) {
message = USAGE_NAME
+ " " + commandOptions.toString()
+ " " + USAGE_SERVICE_ARGUMENTS;
}
return message;
}
/**
* Override point: create an options instance to combine with the
* standard options set.
* <i>Important. Synchronize uses of {@link OptionBuilder}</i>
* with {@code OptionBuilder.class}
* @return the new options
*/
@SuppressWarnings("static-access")
protected Options createOptions() {
synchronized (OptionBuilder.class) {
Options options = new Options();
Option oconf = OptionBuilder.withArgName("configuration file")
.hasArg()
.withDescription("specify an application configuration file")
.withLongOpt(ARG_CONF)
.create(ARG_CONF_SHORT);
Option confclass = OptionBuilder.withArgName("configuration classname")
.hasArg()
.withDescription(
"Classname of a Hadoop Configuration subclass to load")
.withLongOpt(ARG_CONFCLASS)
.create(ARG_CONFCLASS_SHORT);
Option property = OptionBuilder.withArgName("property=value")
.hasArg()
.withDescription("use value for given property")
.create('D');
options.addOption(oconf);
options.addOption(property);
options.addOption(confclass);
return options;
}
}
/**
* Override point: create the base configuration for the service.
*
* Subclasses can override to create HDFS/YARN configurations etc.
* @return the configuration to use as the service initializer.
*/
protected Configuration createConfiguration() {
return new Configuration();
}
/**
* Override point: Get a list of configuration classes to create.
* @return the array of configs to attempt to create. If any are off the
* classpath, that is logged
*/
@SuppressWarnings("ReturnOfCollectionOrArrayField")
protected List<String> getConfigurationsToCreate() {
return confClassnames;
}
/**
* This creates all the configurations defined by
* {@link #getConfigurationsToCreate()} , ensuring that
* the resources have been pushed in.
* If one cannot be loaded it is logged and the operation continues
* except in the case that the class does load but it isn't actually
* a subclass of {@link Configuration}.
* @throws ExitUtil.ExitException if a loaded class is of the wrong type
*/
@VisibleForTesting
public int loadConfigurationClasses() {
List<String> toCreate = getConfigurationsToCreate();
int loaded = 0;
for (String classname : toCreate) {
try {
Class<?> loadClass = getClassLoader().loadClass(classname);
Object instance = loadClass.getConstructor().newInstance();
if (!(instance instanceof Configuration)) {
throw new ExitUtil.ExitException(EXIT_SERVICE_CREATION_FAILURE,
"Could not create " + classname
+ " because it is not a Configuration class/subclass");
}
loaded++;
} catch (ClassNotFoundException e) {
// class could not be found -implies it is not on the current classpath
LOG.debug("Failed to load {} because it is not on the classpath",
classname);
} catch (ExitUtil.ExitException e) {
// rethrow
throw e;
} catch (Exception e) {
// any other exception
LOG.info("Failed to create {}", classname, e);
}
}
return loaded;
}
/**
* Launch a service catching all exceptions and downgrading them to exit codes
* after logging.
*
* Sets {@link #serviceException} to this value.
* @param conf configuration to use
* @param processedArgs command line after the launcher-specific arguments
* have been stripped out.
* @param addShutdownHook should a shutdown hook be added to terminate
* this service on shutdown. Tests should set this to false.
* @param execute execute/wait for the service to stop.
* @return an exit exception, which will have a status code of 0 if it worked
*/
@VisibleForTesting
public ExitUtil.ExitException launchService(Configuration conf,
List<String> processedArgs,
boolean addShutdownHook,
boolean execute) {
ExitUtil.ExitException exitException;
try {
int exitCode = coreServiceLaunch(conf, processedArgs, addShutdownHook,
execute);
if (service != null) {
// check to see if the service failed
Throwable failure = service.getFailureCause();
if (failure != null) {
// the service exited with a failure.
// check what state it is in
Service.STATE failureState = service.getFailureState();
if (failureState == Service.STATE.STOPPED) {
// the failure occurred during shutdown, not important enough
// to bother the user as it may just scare them
LOG.debug("Failure during shutdown: {} ", failure, failure);
} else {
//throw it for the catch handlers to deal with
throw failure;
}
}
}
String name = getServiceName();
if (exitCode == 0) {
exitException = new ServiceLaunchException(exitCode,
"%s succeeded",
name);
} else {
exitException = new ServiceLaunchException(exitCode,
"%s failed ", name);
}
// either the service succeeded, or an error raised during shutdown,
// which we don't worry that much about
} catch (ExitUtil.ExitException ee) {
// exit exceptions are passed through unchanged
exitException = ee;
} catch (Throwable thrown) {
exitException = convertToExitException(thrown);
}
noteException(exitException);
return exitException;
}
/**
* Launch the service.
*
* All exceptions that occur are propagated upwards.
*
* If the method returns a status code, it means that it got as far starting
* the service, and if it implements {@link LaunchableService}, that the
* method {@link LaunchableService#execute()} has completed.
*
* After this method returns, the service can be retrieved returned by
* {@link #getService()}.
*
* @param conf configuration
* @param processedArgs arguments after the configuration parameters
* have been stripped out.
* @param addShutdownHook should a shutdown hook be added to terminate
* this service on shutdown. Tests should set this to false.
* @param execute execute/wait for the service to stop
* @throws ClassNotFoundException classname not on the classpath
* @throws IllegalAccessException not allowed at the class
* @throws InstantiationException not allowed to instantiate it
* @throws InterruptedException thread interrupted
* @throws ExitUtil.ExitException any exception defining the status code.
* @throws Exception any other failure -if it implements
* {@link ExitCodeProvider} then it defines the exit code for any
* containing exception
*/
protected int coreServiceLaunch(Configuration conf,
List<String> processedArgs,
boolean addShutdownHook,
boolean execute) throws Exception {
// create the service instance
instantiateService(conf);
ServiceShutdownHook shutdownHook = null;
// and the shutdown hook if requested
if (addShutdownHook) {
shutdownHook = new ServiceShutdownHook(service);
shutdownHook.register(SHUTDOWN_PRIORITY);
}
String name = getServiceName();
LOG.debug("Launched service {}", name);
LaunchableService launchableService = null;
if (service instanceof LaunchableService) {
// it's a LaunchableService, pass in the conf and arguments before init)
LOG.debug("Service {} implements LaunchableService", name);
launchableService = (LaunchableService) service;
if (launchableService.isInState(Service.STATE.INITED)) {
LOG.warn("LaunchableService {}"
+ " initialized in constructor before CLI arguments passed in",
name);
}
Configuration newconf = launchableService.bindArgs(configuration,
processedArgs);
if (newconf != null) {
configuration = newconf;
}
}
//some class constructors init; here this is picked up on.
if (!service.isInState(Service.STATE.INITED)) {
service.init(configuration);
}
int exitCode;
try {
// start the service
service.start();
exitCode = EXIT_SUCCESS;
if (execute && service.isInState(Service.STATE.STARTED)) {
if (launchableService != null) {
// assume that runnable services are meant to run from here
try {
exitCode = launchableService.execute();
LOG.debug("Service {} execution returned exit code {}",
name, exitCode);
} finally {
// then stop the service
service.stop();
}
} else {
//run the service until it stops or an interrupt happens
// on a different thread.
LOG.debug("waiting for service threads to terminate");
service.waitForServiceToStop(0);
}
}
} finally {
if (shutdownHook != null) {
shutdownHook.unregister();
}
}
return exitCode;
}
/**
* Instantiate the service defined in {@code serviceClassName}.
*
* Sets the {@code configuration} field
* to the the value of {@code conf},
* and the {@code service} field to the service created.
*
* @param conf configuration to use
*/
@SuppressWarnings("unchecked")
public Service instantiateService(Configuration conf) {
Preconditions.checkArgument(conf != null, "null conf");
Preconditions.checkArgument(serviceClassName != null,
"null service classname");
Preconditions.checkArgument(!serviceClassName.isEmpty(),
"undefined service classname");
configuration = conf;
// Instantiate the class. this requires the service to have a public
// zero-argument or string-argument constructor
Object instance;
try {
Class<?> serviceClass = getClassLoader().loadClass(serviceClassName);
try {
instance = serviceClass.getConstructor().newInstance();
} catch (NoSuchMethodException noEmptyConstructor) {
// no simple constructor, fall back to a string
LOG.debug("No empty constructor {}", noEmptyConstructor,
noEmptyConstructor);
instance = serviceClass.getConstructor(String.class)
.newInstance(serviceClassName);
}
} catch (Exception e) {
throw serviceCreationFailure(e);
}
if (!(instance instanceof Service)) {
//not a service
throw new ServiceLaunchException(
LauncherExitCodes.EXIT_SERVICE_CREATION_FAILURE,
"Not a service class: \"%s\"", serviceClassName);
}
// cast to the specific instance type of this ServiceLauncher
service = (S) instance;
return service;
}
/**
* Convert an exception to an {@code ExitException}.
*
* This process may just be a simple pass through, otherwise a new
* exception is created with an exit code, the text of the supplied
* exception, and the supplied exception as an inner cause.
*
* <ol>
* <li>If is already the right type, pass it through.</li>
* <li>If it implements {@link ExitCodeProvider#getExitCode()},
* the exit code is extracted and used in the new exception.</li>
* <li>Otherwise, the exit code
* {@link LauncherExitCodes#EXIT_EXCEPTION_THROWN} is used.</li>
* </ol>
*
* @param thrown the exception thrown
* @return an {@code ExitException} with a status code
*/
protected static ExitUtil.ExitException convertToExitException(
Throwable thrown) {
ExitUtil.ExitException exitException;
// get the exception message
String message = thrown.toString();
int exitCode;
if (thrown instanceof ExitCodeProvider) {
// the exception provides a status code -extract it
exitCode = ((ExitCodeProvider) thrown).getExitCode();
message = thrown.getMessage();
if (message == null) {
// some exceptions do not have a message; fall back
// to the string value.
message = thrown.toString();
}
} else {
// no exception code: use the default
exitCode = EXIT_EXCEPTION_THROWN;
}
// construct the new exception with the original message and
// an exit code
exitException = new ServiceLaunchException(exitCode, message);
exitException.initCause(thrown);
return exitException;
}
/**
* Generate an exception announcing a failure to create the service.
* @param exception inner exception.
* @return a new exception, with the exit code
* {@link LauncherExitCodes#EXIT_SERVICE_CREATION_FAILURE}
*/
protected ServiceLaunchException serviceCreationFailure(Exception exception) {
return new ServiceLaunchException(EXIT_SERVICE_CREATION_FAILURE, exception);
}
/**
* Override point: register this class as the handler for the control-C
* and SIGINT interrupts.
*
* Subclasses can extend this with extra operations, such as
* an exception handler:
* <pre>
* Thread.setDefaultUncaughtExceptionHandler(
* new YarnUncaughtExceptionHandler());
* </pre>
*/
protected void registerFailureHandling() {
try {
interruptEscalator = new InterruptEscalator(this,
SHUTDOWN_TIME_ON_INTERRUPT);
interruptEscalator.register(IrqHandler.CONTROL_C);
interruptEscalator.register(IrqHandler.SIGTERM);
} catch (IllegalArgumentException e) {
// downgrade interrupt registration to warnings
LOG.warn("{}", e, e);
}
Thread.setDefaultUncaughtExceptionHandler(
new HadoopUncaughtExceptionHandler(this));
}
/**
* Handler for uncaught exceptions: terminate the service.
* @param thread thread
* @param exception exception
*/
@Override
public void uncaughtException(Thread thread, Throwable exception) {
LOG.error("Uncaught exception in thread {} -exiting", thread, exception);
exit(convertToExitException(exception));
}
/**
* Get the service name via {@link Service#getName()}.
*
* If the service is not instantiated, the classname is returned instead.
* @return the service name
*/
public String getServiceName() {
Service s = service;
String name = null;
if (s != null) {
try {
name = s.getName();
} catch (Exception ignored) {
// ignored
}
}
if (name != null) {
return "service " + name;
} else {
return "service " + serviceName;
}
}
/**
* Print a warning message.
* <p>
* This tries to log to the log's warn() operation.
* If the log at that level is disabled it logs to system error
* @param text warning text
*/
protected void warn(String text) {
if (LOG.isWarnEnabled()) {
LOG.warn(text);
} else {
System.err.println(text);
}
}
/**
* Report an error.
* <p>
* This tries to log to {@code LOG.error()}.
* <p>
* If that log level is disabled disabled the message
* is logged to system error along with {@code thrown.toString()}
* @param message message for the user
* @param thrown the exception thrown
*/
protected void error(String message, Throwable thrown) {
String text = "Exception: " + message;
if (LOG.isErrorEnabled()) {
LOG.error(text, thrown);
} else {
System.err.println(text);
if (thrown != null) {
System.err.println(thrown.toString());
}
}
}
/**
* Exit the JVM.
*
* This is method can be overridden for testing, throwing an
* exception instead. Any subclassed method MUST raise an
* {@code ExitException} instance/subclass.
* The service launcher code assumes that after this method is invoked,
* no other code in the same method is called.
* @param exitCode code to exit
*/
protected void exit(int exitCode, String message) {
ExitUtil.terminate(exitCode, message);
}
/**
* Exit the JVM using an exception for the exit code and message,
* invoking {@link ExitUtil#terminate(ExitUtil.ExitException)}.
*
* This is the standard way a launched service exits.
* An error code of 0 means success -nothing is printed.
*
* If {@link ExitUtil#disableSystemExit()} has been called, this
* method will throw the exception.
*
* The method <i>may</i> be subclassed for testing
* @param ee exit exception
* @throws ExitUtil.ExitException if ExitUtil exceptions are disabled
*/
protected void exit(ExitUtil.ExitException ee) {
ExitUtil.terminate(ee);
}
/**
* Override point: get the classloader to use.
* @return the classloader for loading a service class.
*/
protected ClassLoader getClassLoader() {
return this.getClass().getClassLoader();
}
/**
* Extract the command options and apply them to the configuration,
* building an array of processed arguments to hand down to the service.
*
* @param conf configuration to update.
* @param args main arguments. {@code args[0]}is assumed to be
* the service classname and is skipped.
* @return the remaining arguments
* @throws ExitUtil.ExitException if JVM exiting is disabled.
*/
public List<String> extractCommandOptions(Configuration conf,
List<String> args) {
int size = args.size();
if (size <= 1) {
return new ArrayList<>(0);
}
List<String> coreArgs = args.subList(1, size);
return parseCommandArgs(conf, coreArgs);
}
/**
* Parse the command arguments, extracting the service class as the last
* element of the list (after extracting all the rest).
*
* The field {@link #commandOptions} field must already have been set.
* @param conf configuration to use
* @param args command line argument list
* @return the remaining arguments
* @throws ServiceLaunchException if processing of arguments failed
*/
protected List<String> parseCommandArgs(Configuration conf,
List<String> args) {
Preconditions.checkNotNull(commandOptions,
"Command options have not been created");
StringBuilder argString = new StringBuilder(args.size() * 32);
for (String arg : args) {
argString.append("\"").append(arg).append("\" ");
}
LOG.debug("Command line: {}", argString);
try {
String[] argArray = args.toArray(new String[args.size()]);
// parse this the standard way. This will
// update the configuration in the parser, and potentially
// patch the user credentials
GenericOptionsParser parser = createGenericOptionsParser(conf, argArray);
if (!parser.isParseSuccessful()) {
throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR,
E_PARSE_FAILED + " %s", argString);
}
CommandLine line = parser.getCommandLine();
List<String> remainingArgs = Arrays.asList(parser.getRemainingArgs());
LOG.debug("Remaining arguments {}", remainingArgs);
// Scan the list of configuration files
// and bail out if they don't exist
if (line.hasOption(ARG_CONF)) {
String[] filenames = line.getOptionValues(ARG_CONF);
verifyConfigurationFilesExist(filenames);
// Add URLs of files as list of URLs to load
for (String filename : filenames) {
File file = new File(filename);
LOG.debug("Configuration files {}", file);
confResourceUrls.add(file.toURI().toURL());
}
}
if (line.hasOption(ARG_CONFCLASS)) {
// new resources to instantiate as configurations
List<String> classnameList = Arrays.asList(
line.getOptionValues(ARG_CONFCLASS));
LOG.debug("Configuration classes {}", classnameList);
confClassnames.addAll(classnameList);
}
// return the remainder
return remainingArgs;
} catch (IOException e) {
// parsing problem: convert to a command argument error with
// the original text
throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR, e);
} catch (RuntimeException e) {
// lower level issue such as XML parse failure
throw new ServiceLaunchException(EXIT_COMMAND_ARGUMENT_ERROR,
E_PARSE_FAILED + " %s : %s", argString, e);
}
}
/**
* Override point: create a generic options parser or subclass thereof.
* @param conf Hadoop configuration
* @param argArray array of arguments
* @return a generic options parser to parse the arguments
* @throws IOException on any failure
*/
protected GenericOptionsParser createGenericOptionsParser(Configuration conf,
String[] argArray) throws IOException {
return new MinimalGenericOptionsParser(conf, commandOptions, argArray);
}
/**
* Verify that all the specified filenames exist.
* @param filenames a list of files
* @throws ServiceLaunchException if a file is not found
*/
protected void verifyConfigurationFilesExist(String[] filenames) {
if (filenames == null) {
return;
}
for (String filename : filenames) {
File file = new File(filename);
LOG.debug("Conf file {}", file.getAbsolutePath());
if (!file.exists()) {
// no configuration file
throw new ServiceLaunchException(EXIT_NOT_FOUND,
ARG_CONF_PREFIXED + ": configuration file not found: %s",
file.getAbsolutePath());
}
}
}
/**
* Build a log message for starting up and shutting down.
* @param classname the class of the server
* @param args arguments
*/
protected static String startupShutdownMessage(String classname,
List<String> args) {
final String hostname = NetUtils.getHostname();
return StringUtils.createStartupShutdownMessage(classname, hostname,
args.toArray(new String[args.size()]));
}
/**
* Exit with a printed message.
* @param status status code
* @param message message message to print before exiting
* @throws ExitUtil.ExitException if exceptions are disabled
*/
protected static void exitWithMessage(int status, String message) {
ExitUtil.terminate(new ServiceLaunchException(status, message));
}
/**
* Exit with the usage exit code {@link #EXIT_USAGE}
* and message {@link #USAGE_MESSAGE}.
* @throws ExitUtil.ExitException if exceptions are disabled
*/
protected static void exitWithUsageMessage() {
exitWithMessage(EXIT_USAGE, USAGE_MESSAGE);
}
/**
* This is the JVM entry point for the service launcher.
*
* Converts the arguments to a list, then invokes {@link #serviceMain(List)}
* @param args command line arguments.
*/
public static void main(String[] args) {
serviceMain(Arrays.asList(args));
}
/**
* Varargs version of the entry point for testing and other in-JVM use.
* Hands off to {@link #serviceMain(List)}
* @param args command line arguments.
*/
public static void serviceMain(String... args) {
serviceMain(Arrays.asList(args));
}
/* ====================================================================== */
/**
* The real main function, which takes the arguments as a list.
* Argument 0 MUST be the service classname
* @param argsList the list of arguments
*/
/* ====================================================================== */
public static void serviceMain(List<String> argsList) {
if (argsList.isEmpty()) {
// no arguments: usage message
exitWithUsageMessage();
} else {
ServiceLauncher<Service> serviceLauncher =
new ServiceLauncher<>(argsList.get(0));
serviceLauncher.launchServiceAndExit(argsList);
}
}
/**
* A generic options parser which does not parse any of the traditional
* Hadoop options.
*/
protected static class MinimalGenericOptionsParser
extends GenericOptionsParser {
public MinimalGenericOptionsParser(Configuration conf,
Options options, String[] args) throws IOException {
super(conf, options, args);
}
@Override
protected Options buildGeneralOptions(Options opts) {
return opts;
}
}
}