blob: 14de304f3239fe9a030f0ac85994a0d2ee78e024 [file] [log] [blame]
// Copyright 2017 Twitter. All rights reserved.
//
// Licensed 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 com.twitter.heron.apiserver;
import java.io.IOException;
import java.net.BindException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.twitter.heron.apiserver.resources.HeronResource;
import com.twitter.heron.apiserver.utils.ConfigUtils;
import com.twitter.heron.apiserver.utils.Logging;
import com.twitter.heron.spi.common.Config;
import com.twitter.heron.spi.common.Key;
public final class Runtime {
private static final Logger LOG = LoggerFactory.getLogger(Runtime.class);
private static final String API_BASE_PATH = "/api/v1/*";
private enum Flag {
Help("h"),
BaseTemplate("base-template"),
Cluster("cluster"),
ConfigPath("config-path"),
Port("port"),
Property("D"),
ReleaseFile("release-file"),
Verbose("verbose"),
DownloadHostName("download-hostname");
final String name;
Flag(String name) {
this.name = name;
}
}
private static Options createOptions() {
final Option cluster = Option.builder()
.desc("Cluster in which to deploy topologies")
.longOpt(Flag.Cluster.name)
.hasArg()
.argName(Flag.Cluster.name)
.required()
.build();
final Option baseTemplate = Option.builder()
.desc("Base configuration to use for deploying topologies")
.longOpt(Flag.BaseTemplate.name)
.hasArg()
.argName(Flag.BaseTemplate.name)
.required(false)
.build();
final Option config = Option.builder()
.desc("Path to the base configuration for deploying topologies")
.longOpt(Flag.ConfigPath.name)
.hasArg()
.argName(Flag.ConfigPath.name)
.required(false)
.build();
final Option port = Option.builder()
.desc("Port to bind to")
.longOpt(Flag.Port.name)
.hasArg()
.argName(Flag.Port.name)
.required(false)
.build();
final Option property = Option.builder(Flag.Property.name)
.argName("property=value")
.numberOfArgs(2)
.valueSeparator()
.desc("use value for given property")
.build();
final Option release = Option.builder()
.desc("Path to the release file")
.longOpt(Flag.ReleaseFile.name)
.hasArg()
.argName(Flag.ReleaseFile.name)
.required(false)
.build();
final Option verbose = Option.builder()
.desc("Verbose mode. Increases logging level to show debug messages.")
.longOpt(Flag.Verbose.name)
.hasArg(false)
.argName(Flag.Verbose.name)
.required(false)
.build();
final Option downloadHostName = Option.builder()
.desc("Download Hostname Override")
.longOpt(Flag.DownloadHostName.name)
.hasArg()
.argName(Flag.DownloadHostName.name)
.required(false)
.build();
return new Options()
.addOption(baseTemplate)
.addOption(cluster)
.addOption(config)
.addOption(port)
.addOption(release)
.addOption(property)
.addOption(verbose)
.addOption(downloadHostName);
}
private static Options constructHelpOptions() {
Option help = Option.builder(Flag.Help.name)
.desc("List all options and their description")
.longOpt("help")
.hasArg(false)
.required(false)
.build();
return new Options()
.addOption(help);
}
// Print usage options
private static void usage(Options options) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("heron-apiserver", options);
}
private static String getConfigurationDirectory(String toolsHome, CommandLine cmd) {
if (cmd.hasOption(Flag.ConfigPath.name)) {
return cmd.getOptionValue(Flag.ConfigPath.name);
} else if (cmd.hasOption(Flag.BaseTemplate.name)) {
return Paths.get(toolsHome, Constants.DEFAULT_HERON_CONFIG_DIRECTORY,
cmd.getOptionValue(Flag.BaseTemplate.name)).toFile().getAbsolutePath();
}
return Paths.get(toolsHome, Constants.DEFAULT_HERON_CONFIG_DIRECTORY,
cmd.getOptionValue(Flag.Cluster.name)).toFile().getAbsolutePath();
}
// Get the heron home directory
// In local mode this is ~/.heron
// If running in a cluster this is ./heron-core and assumes the
// heron has been installed in the container's working directory
// working-dir/heron-core
private static String getHeronDirectory(CommandLine cmd) {
final String cluster = cmd.getOptionValue(Flag.Cluster.name);
if ("local".equalsIgnoreCase(cluster) || "standalone".equalsIgnoreCase(cluster)) {
return Constants.DEFAULT_HERON_LOCAL;
}
return Key.HERON_CLUSTER_HOME.getDefaultString();
}
private static String getReleaseFile(String toolsHome, CommandLine cmd) {
if (cmd.hasOption(Flag.ReleaseFile.name)) {
return cmd.getOptionValue(Flag.ReleaseFile.name);
}
return Paths.get(toolsHome, Constants.DEFAULT_HERON_RELEASE_FILE)
.toFile().getAbsolutePath();
}
private static int getPort(CommandLine cmd) {
if (cmd.hasOption(Flag.Port.name)) {
return Integer.valueOf(cmd.getOptionValue(Flag.Port.name));
}
return Constants.DEFAULT_PORT;
}
private static String getDownloadHostName(CommandLine cmd) {
if (cmd.hasOption(Flag.DownloadHostName.name)) {
return String.valueOf(cmd.getOptionValue(Flag.DownloadHostName.name));
}
return null;
}
private static String loadOverrides(CommandLine cmd) throws IOException {
return ConfigUtils.createOverrideConfiguration(
cmd.getOptionProperties(Flag.Property.name));
}
private static String getToolsHome() throws URISyntaxException {
final String jarLocation =
Runtime.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
return Paths.get(jarLocation).getParent().getParent().getParent().toFile().getAbsolutePath();
}
private static Boolean isVerbose(CommandLine cmd) {
return cmd.hasOption(Flag.Verbose.name) ? Boolean.TRUE : Boolean.FALSE;
}
@SuppressWarnings({"IllegalCatch", "RegexpSinglelineJava"})
public static void main(String[] args) throws Exception {
final Options options = createOptions();
final Options helpOptions = constructHelpOptions();
CommandLineParser parser = new DefaultParser();
// parse the help options first.
CommandLine cmd = parser.parse(helpOptions, args, true);
if (cmd.hasOption(Flag.Help.name)) {
usage(options);
return;
}
try {
cmd = parser.parse(options, args);
} catch (ParseException pe) {
System.err.println(pe.getMessage());
usage(options);
return;
}
final boolean verbose = isVerbose(cmd);
// set and configure logging level
Logging.setVerbose(verbose);
Logging.configure(verbose);
LOG.debug("apiserver overrides:\n {}", cmd.getOptionProperties(Flag.Property.name));
final String toolsHome = getToolsHome();
// read command line flags
final String cluster = cmd.getOptionValue(Flag.Cluster.name);
final String heronConfigurationDirectory = getConfigurationDirectory(toolsHome, cmd);
final String heronDirectory = getHeronDirectory(cmd);
final String releaseFile = getReleaseFile(toolsHome, cmd);
final String configurationOverrides = loadOverrides(cmd);
final int port = getPort(cmd);
final String downloadHostName = getDownloadHostName(cmd);
final Config baseConfiguration =
ConfigUtils.getBaseConfiguration(heronDirectory,
heronConfigurationDirectory,
releaseFile,
configurationOverrides);
final ResourceConfig config = new ResourceConfig(Resources.get());
final Server server = new Server(port);
final ServletContextHandler contextHandler =
new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
LOG.info("using configuration path: {}", heronConfigurationDirectory);
contextHandler.setAttribute(HeronResource.ATTRIBUTE_CLUSTER, cluster);
contextHandler.setAttribute(HeronResource.ATTRIBUTE_CONFIGURATION, baseConfiguration);
contextHandler.setAttribute(HeronResource.ATTRIBUTE_CONFIGURATION_DIRECTORY,
heronConfigurationDirectory);
contextHandler.setAttribute(HeronResource.ATTRIBUTE_CONFIGURATION_OVERRIDE_PATH,
configurationOverrides);
contextHandler.setAttribute(HeronResource.ATTRIBUTE_PORT,
String.valueOf(port));
contextHandler.setAttribute(HeronResource.ATTRIBUTE_DOWNLOAD_HOSTNAME,
String.valueOf(downloadHostName));
server.setHandler(contextHandler);
final ServletHolder apiServlet =
new ServletHolder(new ServletContainer(config));
contextHandler.addServlet(apiServlet, API_BASE_PATH);
try {
server.start();
LOG.info("Heron apiserver started at {}", server.getURI());
server.join();
} catch (Exception ex) {
final String message = getErrorMessage(server, port, ex);
LOG.error(message);
System.err.println(message);
System.exit(1);
} finally {
server.destroy();
}
}
private static String getErrorMessage(Server server, int port, Exception ex) {
if (ex instanceof BindException) {
final URI uri = server.getURI();
return String.format("%s http://%s:%d", ex.getMessage(), uri.getHost(), port);
}
return ex.getMessage();
}
private Runtime() {
}
}