com.twitter.heron.apiserver.Runtime.java Source code

Java tutorial

Introduction

Here is the source code for com.twitter.heron.apiserver.Runtime.java

Source

//  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.apiserver.utils.Utils;
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"), HeronCorePackagePath("heron-core-package-path");

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

        final Option downloadHeronCoreName = Option.builder()
                .desc("Path to Heron Core Package. API Server can serve Heron Core Package")
                .longOpt(Flag.HeronCorePackagePath.name).hasArg().argName(Flag.HeronCorePackagePath.name)
                .required(false).build();

        return new Options().addOption(baseTemplate).addOption(cluster).addOption(config).addOption(port)
                .addOption(release).addOption(property).addOption(verbose).addOption(downloadHostName)
                .addOption(downloadHeronCoreName);
    }

    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 getHeronCorePackagePath(CommandLine cmd) {
        if (cmd.hasOption(Flag.HeronCorePackagePath.name)) {
            return String.valueOf(cmd.getOptionValue(Flag.HeronCorePackagePath.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 String heronCorePackagePath = getHeronCorePackagePath(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,
                Utils.isNotEmpty(downloadHostName) ? String.valueOf(downloadHostName) : null);
        contextHandler.setAttribute(HeronResource.ATTRIBUTE_HERON_CORE_PACKAGE_PATH,
                Utils.isNotEmpty(heronCorePackagePath) ? String.valueOf(heronCorePackagePath) : null);

        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() {
    }
}