com.netflix.exhibitor.application.ExhibitorMain.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.exhibitor.application.ExhibitorMain.java

Source

/*
 *
 *  Copyright 2011 Netflix, Inc.
 *
 *     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.netflix.exhibitor.application;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.io.Closeables;
import com.netflix.exhibitor.core.Exhibitor;
import com.netflix.exhibitor.core.ExhibitorArguments;
import com.netflix.exhibitor.core.backup.BackupProvider;
import com.netflix.exhibitor.core.backup.filesystem.FileSystemBackupProvider;
import com.netflix.exhibitor.core.backup.s3.S3BackupProvider;
import com.netflix.exhibitor.core.config.AutoManageLockArguments;
import com.netflix.exhibitor.core.config.ConfigProvider;
import com.netflix.exhibitor.core.config.DefaultProperties;
import com.netflix.exhibitor.core.config.JQueryStyle;
import com.netflix.exhibitor.core.config.filesystem.FileSystemConfigProvider;
import com.netflix.exhibitor.core.config.s3.S3ConfigArguments;
import com.netflix.exhibitor.core.config.s3.S3ConfigAutoManageLockArguments;
import com.netflix.exhibitor.core.config.s3.S3ConfigProvider;
import com.netflix.exhibitor.core.rest.UIContext;
import com.netflix.exhibitor.core.rest.jersey.JerseySupport;
import com.netflix.exhibitor.core.s3.PropertyBasedS3Credential;
import com.netflix.exhibitor.core.s3.S3ClientFactoryImpl;
import com.sun.jersey.api.core.DefaultResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.security.BasicAuthenticator;
import org.mortbay.jetty.security.Constraint;
import org.mortbay.jetty.security.ConstraintMapping;
import org.mortbay.jetty.security.Credential;
import org.mortbay.jetty.security.HashUserRealm;
import org.mortbay.jetty.security.SecurityHandler;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;

public class ExhibitorMain implements Closeable {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private final Server server;
    private final AtomicBoolean isClosed = new AtomicBoolean(false);
    private final Exhibitor exhibitor;
    private final AtomicBoolean shutdownSignaled = new AtomicBoolean(false);

    private static final String FILESYSTEMCONFIG_DIRECTORY = "fsconfigdir";
    private static final String FILESYSTEMCONFIG_NAME = "fsconfigname";
    private static final String FILESYSTEMCONFIG_PREFIX = "fsconfigprefix";
    private static final String FILESYSTEMCONFIG_LOCK_PREFIX = "fsconfiglockprefix";
    private static final String S3_CREDENTIALS = "s3credentials";
    private static final String S3_BACKUP = "s3backup";
    private static final String S3_CONFIG = "s3config";
    private static final String S3_CONFIG_PREFIX = "s3configprefix";
    private static final String FILESYSTEMBACKUP = "filesystembackup";
    private static final String TIMEOUT = "timeout";
    private static final String LOGLINES = "loglines";
    private static final String HOSTNAME = "hostname";
    private static final String CONFIGCHECKMS = "configcheckms";
    private static final String HELP = "help";
    private static final String ALT_HELP = "?";
    private static final String HTTP_PORT = "port";
    private static final String EXTRA_HEADING_TEXT = "headingtext";
    private static final String NODE_MUTATIONS = "nodemodification";
    private static final String JQUERY_STYLE = "jquerystyle";

    private static final String DEFAULT_FILESYSTEMCONFIG_NAME = "exhibitor.properties";
    private static final String DEFAULT_FILESYSTEMCONFIG_PREFIX = "exhibitor-";
    private static final String DEFAULT_FILESYSTEMCONFIG_LOCK_PREFIX = "exhibitor-lock-";

    private static final String BASIC_AUTH_REALM = "basicauthrealm";
    private static final String CONSOLE_USER = "consoleuser";
    private static final String CURATOR_USER = "curatoruser";
    private static final String CONSOLE_PASSWORD = "consolepassword";
    private static final String CURATOR_PASSWORD = "curatorpassword";

    public static void main(String[] args) throws Exception {
        String hostname = Exhibitor.getHostname();

        Options options = new Options();
        options.addOption(null, FILESYSTEMCONFIG_DIRECTORY, true,
                "Directory to store Exhibitor properties (cannot be used with s3config). Exhibitor uses file system locks so you can specify a shared location so as to enable complete ensemble management. Default location is "
                        + System.getProperty("user.dir"));
        options.addOption(null, FILESYSTEMCONFIG_NAME, true,
                "The name of the file to store config in. Used in conjunction with " + FILESYSTEMCONFIG_DIRECTORY
                        + ". Default is " + DEFAULT_FILESYSTEMCONFIG_NAME);
        options.addOption(null, FILESYSTEMCONFIG_PREFIX, true,
                "A prefix for various config values such as heartbeats. Used in conjunction with "
                        + FILESYSTEMCONFIG_DIRECTORY + ". Default is " + DEFAULT_FILESYSTEMCONFIG_PREFIX);
        options.addOption(null, FILESYSTEMCONFIG_LOCK_PREFIX, true,
                "A prefix for a locking mechanism. Used in conjunction with " + FILESYSTEMCONFIG_DIRECTORY
                        + ". Default is " + DEFAULT_FILESYSTEMCONFIG_LOCK_PREFIX);
        options.addOption(null, S3_CREDENTIALS, true,
                "Optional credentials to use for s3backup or s3config. Argument is the path to an AWS credential properties file with two properties: "
                        + PropertyBasedS3Credential.PROPERTY_S3_KEY_ID + " and "
                        + PropertyBasedS3Credential.PROPERTY_S3_SECRET_KEY);
        options.addOption(null, S3_BACKUP, true,
                "If true, enables AWS S3 backup of ZooKeeper log files (s3credentials may be provided as well).");
        options.addOption(null, S3_CONFIG, true,
                "Enables AWS S3 shared config files as opposed to file system config files (s3credentials may be provided as well). Argument is [bucket name]:[key].");
        options.addOption(null, S3_CONFIG_PREFIX, true,
                "When using AWS S3 shared config files, the prefix to use for values such as heartbeats. Default is "
                        + DEFAULT_FILESYSTEMCONFIG_PREFIX);
        options.addOption(null, FILESYSTEMBACKUP, true,
                "If true, enables file system backup of ZooKeeper log files.");
        options.addOption(null, TIMEOUT, true, "Connection timeout (ms) for ZK connections. Default is 30000.");
        options.addOption(null, LOGLINES, true,
                "Max lines of logging to keep in memory for display. Default is 1000.");
        options.addOption(null, HOSTNAME, true, "Hostname to use for this JVM. Default is: " + hostname);
        options.addOption(null, CONFIGCHECKMS, true, "Period (ms) to check config file. Default is: 30000");
        options.addOption(null, HTTP_PORT, true, "Port for the HTTP Server. Default is: 8080");
        options.addOption(null, EXTRA_HEADING_TEXT, true, "Extra text to display in UI header");
        options.addOption(null, NODE_MUTATIONS, true,
                "If true, the Explorer UI will allow nodes to be modified (use with caution).");
        options.addOption(null, JQUERY_STYLE, true,
                "Styling used for the JQuery-based UI. Currently available options: " + getStyleOptions());
        options.addOption(null, BASIC_AUTH_REALM, true, "Basic Auth Realm to Protect the Exhibitor UI");
        options.addOption(null, CONSOLE_USER, true, "Basic Auth Username to Protect the Exhibitor UI");
        options.addOption(null, CONSOLE_PASSWORD, true, "Basic Auth Password to Protect the Exhibitor UI");
        options.addOption(null, CURATOR_USER, true, "Basic Auth Password to Protect the cluster list api");
        options.addOption(null, CURATOR_PASSWORD, true, "Basic Auth Password to Protect cluster list api");
        options.addOption(ALT_HELP, HELP, false, "Print this help");

        CommandLine commandLine;
        try {
            CommandLineParser parser = new PosixParser();
            commandLine = parser.parse(options, args);
            if (commandLine.hasOption('?') || commandLine.hasOption(HELP)
                    || (commandLine.getArgList().size() > 0)) {
                printHelp(options);
                return;
            }
        } catch (ParseException e) {
            printHelp(options);
            return;
        }

        if (!checkMutuallyExclusive(options, commandLine, S3_BACKUP, FILESYSTEMBACKUP)) {
            return;
        }
        if (!checkMutuallyExclusive(options, commandLine, S3_CONFIG, FILESYSTEMCONFIG_DIRECTORY)) {
            return;
        }

        PropertyBasedS3Credential awsCredentials = null;
        if (commandLine.hasOption(S3_CREDENTIALS)) {
            awsCredentials = new PropertyBasedS3Credential(new File(commandLine.getOptionValue(S3_CREDENTIALS)));
        }

        BackupProvider backupProvider = null;
        if ("true".equalsIgnoreCase(commandLine.getOptionValue(S3_BACKUP))) {
            backupProvider = new S3BackupProvider(new S3ClientFactoryImpl(), awsCredentials);
        } else if ("true".equalsIgnoreCase(commandLine.getOptionValue(FILESYSTEMBACKUP))) {
            backupProvider = new FileSystemBackupProvider();
        }

        int timeoutMs = Integer.parseInt(commandLine.getOptionValue(TIMEOUT, "30000"));
        int logWindowSizeLines = Integer.parseInt(commandLine.getOptionValue(LOGLINES, "1000"));
        int configCheckMs = Integer.parseInt(commandLine.getOptionValue(CONFIGCHECKMS, "30000"));
        String useHostname = commandLine.getOptionValue(HOSTNAME, hostname);
        int httpPort = Integer.parseInt(commandLine.getOptionValue(HTTP_PORT, "8080"));
        String extraHeadingText = commandLine.getOptionValue(EXTRA_HEADING_TEXT, null);
        boolean allowNodeMutations = "true".equalsIgnoreCase(commandLine.getOptionValue(NODE_MUTATIONS));

        ConfigProvider configProvider;
        if (commandLine.hasOption(S3_CONFIG)) {
            configProvider = getS3Provider(options, commandLine, awsCredentials, useHostname);
        } else {
            configProvider = getFileSystemProvider(commandLine, backupProvider);
        }
        if (configProvider == null) {
            printHelp(options);
            return;
        }

        JQueryStyle jQueryStyle;
        try {
            jQueryStyle = JQueryStyle.valueOf(commandLine.getOptionValue(JQUERY_STYLE, "red").toUpperCase());
        } catch (IllegalArgumentException e) {
            printHelp(options);
            return;
        }

        String realm = commandLine.getOptionValue(BASIC_AUTH_REALM);
        String user = commandLine.getOptionValue(CONSOLE_USER);
        String password = commandLine.getOptionValue(CONSOLE_PASSWORD);
        String curatorUser = commandLine.getOptionValue(CURATOR_USER);
        String curatorPassword = commandLine.getOptionValue(CURATOR_PASSWORD);

        SecurityHandler handler = null;
        if (notNullOrEmpty(realm) && notNullOrEmpty(user) && notNullOrEmpty(password) && notNullOrEmpty(curatorUser)
                && notNullOrEmpty(curatorPassword)) {
            handler = getSecurityHandler(realm, user, password, curatorUser, curatorPassword);
        }

        ExhibitorArguments.Builder builder = ExhibitorArguments.builder().connectionTimeOutMs(timeoutMs)
                .logWindowSizeLines(logWindowSizeLines).thisJVMHostname(useHostname).configCheckMs(configCheckMs)
                .extraHeadingText(extraHeadingText).allowNodeMutations(allowNodeMutations).jQueryStyle(jQueryStyle)
                .restPort(httpPort);

        ExhibitorMain exhibitorMain = new ExhibitorMain(backupProvider, configProvider, builder, httpPort, handler);
        setShutdown(exhibitorMain);

        exhibitorMain.start();
        try {
            exhibitorMain.join();
        } finally {
            exhibitorMain.close();
        }
    }

    private static String getStyleOptions() {
        Iterable<String> transformed = Iterables.transform(Arrays.asList(JQueryStyle.values()),
                new Function<JQueryStyle, String>() {
                    @Override
                    public String apply(JQueryStyle style) {
                        return style.name().toLowerCase();
                    }
                });
        return Joiner.on(", ").join(transformed);
    }

    private static ConfigProvider getFileSystemProvider(CommandLine commandLine, BackupProvider backupProvider)
            throws IOException {
        File directory = commandLine.hasOption(FILESYSTEMCONFIG_DIRECTORY)
                ? new File(commandLine.getOptionValue(FILESYSTEMCONFIG_DIRECTORY))
                : new File(System.getProperty("user.dir"));
        String name = commandLine.hasOption(FILESYSTEMCONFIG_NAME)
                ? commandLine.getOptionValue(FILESYSTEMCONFIG_NAME)
                : DEFAULT_FILESYSTEMCONFIG_NAME;
        String prefix = commandLine.hasOption(FILESYSTEMCONFIG_PREFIX)
                ? commandLine.getOptionValue(FILESYSTEMCONFIG_PREFIX)
                : DEFAULT_FILESYSTEMCONFIG_PREFIX;
        String lockPrefix = commandLine.hasOption(FILESYSTEMCONFIG_LOCK_PREFIX)
                ? commandLine.getOptionValue(FILESYSTEMCONFIG_LOCK_PREFIX)
                : DEFAULT_FILESYSTEMCONFIG_LOCK_PREFIX;
        return new FileSystemConfigProvider(directory, name, prefix, DefaultProperties.get(backupProvider),
                new AutoManageLockArguments(lockPrefix));
    }

    private static ConfigProvider getS3Provider(Options options, CommandLine commandLine,
            PropertyBasedS3Credential awsCredentials, String hostname) throws Exception {
        ConfigProvider provider;
        String prefix = options.hasOption(S3_CONFIG_PREFIX) ? commandLine.getOptionValue(S3_CONFIG_PREFIX)
                : DEFAULT_FILESYSTEMCONFIG_PREFIX;
        provider = new S3ConfigProvider(new S3ClientFactoryImpl(), awsCredentials,
                getS3Arguments(commandLine.getOptionValue(S3_CONFIG), options, prefix), hostname);
        return provider;
    }

    private static boolean checkMutuallyExclusive(Options options, CommandLine commandLine, String option1,
            String option2) {
        if (commandLine.hasOption(option1) && commandLine.hasOption(option2)) {
            System.err.println(option1 + " and " + option2 + " cannot be used at the same time");
            printHelp(options);
            return false;
        }
        return true;
    }

    private static S3ConfigArguments getS3Arguments(String value, Options options, String prefix) {
        String[] parts = value.split(":");
        if (parts.length != 2) {
            System.err.println("Bad s3config argument: " + value);
            printHelp(options);
            return null;
        }
        return new S3ConfigArguments(parts[0].trim(), parts[1].trim(), prefix,
                new S3ConfigAutoManageLockArguments(prefix + "-lock-"));
    }

    public ExhibitorMain(BackupProvider backupProvider, ConfigProvider configProvider,
            ExhibitorArguments.Builder builder, int httpPort) throws Exception {
        this(backupProvider, configProvider, builder, httpPort, null);
    }

    public ExhibitorMain(BackupProvider backupProvider, ConfigProvider configProvider,
            ExhibitorArguments.Builder builder, int httpPort, SecurityHandler security) throws Exception {
        builder.shutdownProc(makeShutdownProc(this));
        exhibitor = new Exhibitor(configProvider, null, backupProvider, builder.build());
        exhibitor.start();

        DefaultResourceConfig application = JerseySupport.newApplicationConfig(new UIContext(exhibitor));
        ServletContainer container = new ServletContainer(application);
        server = new Server(httpPort);
        Context root = new Context(server, "/", Context.SESSIONS);
        root.addServlet(new ServletHolder(container), "/*");
        if (security != null) {
            root.setSecurityHandler(security);
        }
    }

    public void start() throws Exception {
        server.start();
    }

    public void join() {
        try {
            while (!shutdownSignaled.get() && !Thread.currentThread().isInterrupted()) {
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void close() throws IOException {
        if (isClosed.compareAndSet(false, true)) {
            log.info("Shutting down");

            Closeables.closeQuietly(exhibitor);
            try {
                server.stop();
            } catch (Exception e) {
                log.error("Error shutting down Jetty", e);
            }
            server.destroy();
        }
    }

    private static void setShutdown(final ExhibitorMain exhibitorMain) {
        Runtime.getRuntime().addShutdownHook(new Thread(makeShutdownProc(exhibitorMain)));
    }

    private static Runnable makeShutdownProc(final ExhibitorMain exhibitorMain) {
        return new Runnable() {
            @Override
            public void run() {
                exhibitorMain.shutdownSignaled.set(true);
            }
        };
    }

    private static void printHelp(Options options) {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("ExhibitorMain", options);
        System.exit(0);
    }

    private static boolean notNullOrEmpty(String arg) {
        return arg != null && (!"".equals(arg));
    }

    private static SecurityHandler getSecurityHandler(String realm, String consoleUser, String consolePassword,
            String curatorUser, String curatorPassword) {

        HashUserRealm userRealm = new HashUserRealm(realm);
        userRealm.put(consoleUser, Credential.getCredential(consolePassword));
        userRealm.addUserToRole(consoleUser, "console");
        userRealm.put(curatorUser, Credential.getCredential(curatorPassword));
        userRealm.addUserToRole(curatorUser, "curator");

        Constraint console = new Constraint();
        console.setName("consoleauth");
        console.setRoles(new String[] { "console" });
        console.setAuthenticate(true);

        Constraint curator = new Constraint();
        curator.setName("curatorauth");
        curator.setRoles(new String[] { "curator", "console" });
        curator.setAuthenticate(true);

        ConstraintMapping consoleMapping = new ConstraintMapping();
        consoleMapping.setConstraint(console);
        consoleMapping.setPathSpec("/*");

        ConstraintMapping curatorMapping = new ConstraintMapping();
        curatorMapping.setConstraint(curator);
        curatorMapping.setPathSpec("/exhibitor/v1/cluster/list");

        SecurityHandler handler = new SecurityHandler();
        handler.setUserRealm(userRealm);
        handler.setConstraintMappings(new ConstraintMapping[] { consoleMapping, curatorMapping });
        handler.setAuthenticator(new BasicAuthenticator());

        return handler;
    }
}