blob: be75a3e7fc34fdcb408b851e39611fb81503895e [file] [log] [blame]
package brooklyn.cli;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyShell;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import org.iq80.cli.Cli;
import org.iq80.cli.Cli.CliBuilder;
import org.iq80.cli.Command;
import org.iq80.cli.Help;
import org.iq80.cli.Option;
import org.iq80.cli.OptionType;
import org.iq80.cli.ParseException;
import brooklyn.entity.basic.AbstractApplication;
import brooklyn.entity.basic.Entities;
import brooklyn.launcher.BrooklynLauncher;
import brooklyn.location.Location;
import brooklyn.location.basic.CommandLineLocations;
import brooklyn.location.basic.LocationRegistry;
import brooklyn.util.ResourceUtils;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
// Error codes
public static final int PARSE_ERROR = 1;
public static final int EXECUTION_ERROR = 2;
public static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String...args) {
Cli<BrooklynCommand> parser = buildCli();
try {
log.debug("Parsing command line arguments: {}",args);
BrooklynCommand command = parser.parse(args);
log.debug("Executing command: {}", command);
command.call();
} catch (ParseException pe) { // looks like the user typed it wrong
System.err.println("Parse error: " + pe.getMessage()); // display error
System.err.println(getUsageInfo(parser)); // display cli help
System.exit(PARSE_ERROR);
} catch (Exception e) { // unexpected error during command execution
log.error("Execution error: {}\n{}" + e.getMessage(),e.getStackTrace());
System.err.println("Execution error: " + e.getMessage());
e.printStackTrace();
System.exit(EXECUTION_ERROR);
}
}
public static abstract class BrooklynCommand implements Callable<Void> {
@Inject
public Help help;
@Option(type = OptionType.GLOBAL, name = { "-v", "--verbose" }, description = "Verbose mode")
public boolean verbose = false;
@Option(type = OptionType.GLOBAL, name = { "-q", "--quiet" }, description = "Quiet mode")
public boolean quiet = false;
public ToStringHelper string() {
return Objects.toStringHelper(getClass())
.add("verbose", verbose)
.add("quiet", quiet);
}
@Override
public String toString() {
return string().toString();
}
}
@Command(name = "help", description = "Display help information about brooklyn")
public static class HelpCommand extends BrooklynCommand {
@Override
public Void call() throws Exception {
log.debug("Invoked help command");
return help.call();
}
}
@Command(name = "launch", description = "Starts a brooklyn application. Note that a BROOKLYN_CLASSPATH environment variable needs to be set up beforehand to point to the user application classpath.")
public static class LaunchCommand extends BrooklynCommand {
@Option(name = { "-a", "--app" }, required = true, title = "application class or file",
description = "The Application to start. For example my.AppName or file://my/AppName.groovy or classpath://my/AppName.groovy")
public String app;
@Beta
@Option(name = { "-s", "--script" }, title = "script URI",
description = "EXPERIMENTAL. URI for a Groovy script to parse and load. This script will run before starting the app.")
public String script = null;
@Option(name = { "-l", "--location", "--locations" }, title = "location list",
description = "Specifies the locations where the application will be launched.")
public Collection<String> locations;
@Option(name = { "-p", "--port" }, title = "port number",
description = "Specifies the port to be used by the Brooklyn Management Console.")
public int port = 8081;
@Option(name = { "-nc", "--noConsole" },
description = "Whether to start the web console")
public boolean noConsole = false;
@Option(name = { "-ns", "--noShutdownOnExit" },
description = "Whether to stop the application when the JVM exits")
public boolean noShutdownOnExit = false;
/**
* Note that this is a temporrary workaround to allow for runnig the
* brooklyn-whirr example.
*
* This will be replaced by more powerful CLI control for running processes,
* to send shutdown and other commands to brooklyn.
*
* Without using this flag you get a java.util.concurrent.RejectedExecutionException
* because the brooklyn and whirr shutdown hooks get executed in parallel.
* This is how it looks like: {@linktourl https://gist.github.com/47066f72d6f6f79b953e}
*/
@Beta
@Option(name = { "-sk", "--stopOnKeyPress" },
description = "After the application gets started, brooklyn will wait for a key press to stop it.")
public boolean stopOnKeyPress = false;
@Override
public Void call() throws Exception {
log.debug("Invoked launch command");
if (verbose) {
System.out.println("Launching brooklyn app: "+app+" in "+Iterables.toString(locations));
}
ResourceUtils utils = new ResourceUtils(this);
ClassLoader parent = utils.getLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
// Get an instance of the brooklyn app
log.debug("Load the user's application: {}", app);
AbstractApplication application = loadApplicationFromClasspathOrParse(utils, loader, app);
//First, run a setup script if the user has provided one
if (script != null) {
log.debug("Running the user povided script: {}", script);
String content = utils.getResourceAsString(script);
GroovyShell shell = new GroovyShell(loader);
shell.evaluate(content);
}
// Figure out the brooklyn location(s) where to launch the application
List<Location> brooklynLocations = new LocationRegistry().getLocationsById(
(locations==null || Iterables.isEmpty(locations)) ? ImmutableSet.of(CommandLineLocations.LOCALHOST) : locations);
// Start the application
log.info("Adding application under brooklyn management");
BrooklynLauncher.manage(application, port, !noShutdownOnExit, !noConsole);
log.info("Starting brooklyn application: {}", app);
application.start(brooklynLocations);
if (verbose) {
Entities.dumpInfo(application);
}
if(stopOnKeyPress){
// Wait for the user to type a key
log.info("Application started. Press return to stop.");
System.in.read();
application.stop();
} else {
// Block forever so that Brooklyn doesn't exit (until someone does cntrl-c or kill)
log.info("Launched application; now blocking to wait for cntrl-c or kill");
waitUntilInterrupted();
}
return null;
}
private synchronized void waitUntilInterrupted() {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return; // exit gracefully
}
}
/**
* Helper method that gets an instance of a brooklyn application
*/
@VisibleForTesting
AbstractApplication loadApplicationFromClasspathOrParse(ResourceUtils utils, GroovyClassLoader loader, String app)
throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException {
Class<?> appClass;
try {
log.debug("Trying to load application as class on classpath: {}", app);
appClass = loader.loadClass(app, true, false);
} catch (ClassNotFoundException cnfe) { // Not a class on the classpath
log.debug("Loading \"{}\" as class on classpath failed, now trying as .groovy source file",app);
String content = utils.getResourceAsString(app);
appClass = loader.parseClass(content);
}
Constructor<?> constructor = appClass.getConstructor();
return (AbstractApplication) constructor.newInstance();
}
@Override
public ToStringHelper string() {
return super.string()
.add("app", app)
.add("script", script)
.add("location", locations)
.add("port", port)
.add("noConsole",noConsole)
.add("noShutdwonOnExit",noShutdownOnExit);
}
}
@VisibleForTesting
static Cli<BrooklynCommand> buildCli() {
@SuppressWarnings({ "unchecked" })
CliBuilder<BrooklynCommand> builder = Cli.buildCli("brooklyn", BrooklynCommand.class)
.withDescription("Brooklyn Management Service")
.withDefaultCommand(HelpCommand.class)
.withCommands(
HelpCommand.class,
LaunchCommand.class
);
return builder.build();
}
static String getUsageInfo(Cli<BrooklynCommand> parser) {
StringBuilder help = new StringBuilder();
help.append("\n");
Help.help(parser.getMetadata(), ImmutableList.of("brooklyn"),help);
help.append("See 'brooklyn help <command>' for more information on a specific command.");
return help.toString();
}
}