All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.ignite.internal.commandline.CommandHandler Maven / Gradle / Ivy

Go to download

Java-based middleware for in-memory processing of big data in a distributed environment.

There is a newer version: 3.0.0-beta1
Show newest version
/*
 * 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.ignite.internal.commandline;

import java.io.File;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import java.util.UUID;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;
import java.util.stream.Collectors;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.client.GridClientAuthenticationException;
import org.apache.ignite.internal.client.GridClientClosedException;
import org.apache.ignite.internal.client.GridClientConfiguration;
import org.apache.ignite.internal.client.GridClientDisconnectedException;
import org.apache.ignite.internal.client.GridClientHandshakeException;
import org.apache.ignite.internal.client.GridServerUnreachableException;
import org.apache.ignite.internal.client.impl.connection.GridClientConnectionResetException;
import org.apache.ignite.internal.client.ssl.GridSslBasicContextFactory;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.logger.java.JavaLoggerFileHandler;
import org.apache.ignite.logger.java.JavaLoggerFormatter;
import org.apache.ignite.plugin.security.SecurityCredentials;
import org.apache.ignite.plugin.security.SecurityCredentialsBasicProvider;
import org.apache.ignite.plugin.security.SecurityCredentialsProvider;
import org.apache.ignite.ssl.SslContextFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static java.lang.System.lineSeparator;
import static org.apache.ignite.internal.IgniteVersionUtils.ACK_VER_STR;
import static org.apache.ignite.internal.IgniteVersionUtils.COPYRIGHT;
import static org.apache.ignite.internal.commandline.CommandLogger.DOUBLE_INDENT;
import static org.apache.ignite.internal.commandline.CommandLogger.INDENT;
import static org.apache.ignite.internal.commandline.CommandLogger.optional;
import static org.apache.ignite.internal.commandline.CommonArgParser.CMD_AUTO_CONFIRMATION;
import static org.apache.ignite.internal.commandline.CommonArgParser.getCommonOptions;
import static org.apache.ignite.internal.commandline.TaskExecutor.DFLT_HOST;
import static org.apache.ignite.internal.commandline.TaskExecutor.DFLT_PORT;
import static org.apache.ignite.ssl.SslContextFactory.DFLT_SSL_PROTOCOL;

/**
 * Class that execute several commands passed via command line.
 */
public class CommandHandler {
    /** */
    static final String CMD_HELP = "--help";

    /** */
    public static final String CONFIRM_MSG = "y";

    /** */
    static final String DELIM = "--------------------------------------------------------------------------------";

    /** */
    public static final int EXIT_CODE_OK = 0;

    /** */
    public static final int EXIT_CODE_INVALID_ARGUMENTS = 1;

    /** */
    public static final int EXIT_CODE_CONNECTION_FAILED = 2;

    /** */
    public static final int ERR_AUTHENTICATION_FAILED = 3;

    /** */
    public static final int EXIT_CODE_UNEXPECTED_ERROR = 4;

    /** */
    private static final long DFLT_PING_INTERVAL = 5000L;

    /** */
    private static final long DFLT_PING_TIMEOUT = 30_000L;

    /** */
    private final Scanner in = new Scanner(System.in);

    /** Utility name. */
    public static final String UTILITY_NAME = "control.(sh|bat)";

    /** */
    public static final String NULL = "null";

    /** JULs logger. */
    private final Logger logger;

    /** Session. */
    protected final String ses = U.id8(UUID.randomUUID());

    /** Console instance. Public access needs for tests. */
    public GridConsole console = GridConsoleAdapter.getInstance();

    /** */
    private Object lastOperationRes;

    /**
     * @param args Arguments to parse and apply.
     */
    public static void main(String[] args) {
        CommandHandler hnd = new CommandHandler();

        System.exit(hnd.execute(Arrays.asList(args)));
    }

    /**
     * @return prepared JULs logger.
     */
    private Logger setupJavaLogger() {
        Logger result = initLogger(CommandHandler.class.getName() + "Log");

        // Adding logging to file.
        try {
            String absPathPattern = new File(JavaLoggerFileHandler.logDirectory(U.defaultWorkDirectory()), "control-utility-%g.log").getAbsolutePath();

            FileHandler fileHandler = new FileHandler(absPathPattern, 5 * 1024 * 1024, 5);

            fileHandler.setFormatter(new JavaLoggerFormatter());

            result.addHandler(fileHandler);
        }
        catch (Exception e) {
            System.out.println("Failed to configure logging to file");
        }

        // Adding logging to console.
        result.addHandler(setupStreamHandler());

        return result;
    }

    /**
     * @return StreamHandler with empty formatting
     */
    public static StreamHandler setupStreamHandler() {
        return new StreamHandler(System.out, new Formatter() {
            @Override public String format(LogRecord record) {
                return record.getMessage() + "\n";
            }
        });
    }

    /**
     * Initialises JULs logger with basic settings
     * @param loggerName logger name. If {@code null} anonymous logger is returned.
     * @return logger
     */
    public static Logger initLogger(@Nullable String loggerName) {
        Logger result;

        if (loggerName == null)
            result = Logger.getAnonymousLogger();
        else
            result = Logger.getLogger(loggerName);

        result.setLevel(Level.INFO);
        result.setUseParentHandlers(false);

        return result;
    }

    /**
     *
     */
    public CommandHandler() {
        logger = setupJavaLogger();
    }

    /**
     * @param logger Logger to use.
     */
    public CommandHandler(Logger logger) {
        this.logger = logger;
    }

    /**
     * Parse and execute command.
     *
     * @param rawArgs Arguments to parse and execute.
     * @return Exit code.
     */
    public int execute(List rawArgs) {
        LocalDateTime startTime = LocalDateTime.now();

        Thread.currentThread().setName("session=" + ses);

        logger.info("Control utility [ver. " + ACK_VER_STR + "]");
        logger.info(COPYRIGHT);
        logger.info("User: " + System.getProperty("user.name"));
        logger.info("Time: " + startTime);

        String commandName = "";

        try {
            if (F.isEmpty(rawArgs) || (rawArgs.size() == 1 && CMD_HELP.equalsIgnoreCase(rawArgs.get(0)))) {
                printHelp();

                return EXIT_CODE_OK;
            }

            ConnectionAndSslParameters args = new CommonArgParser(logger).parseAndValidate(rawArgs.iterator());

            Command command = args.command();
            commandName = command.name();

            GridClientConfiguration clientCfg = getClientConfiguration(args);

            int tryConnectMaxCount = 3;

            boolean suppliedAuth = !F.isEmpty(args.userName()) && !F.isEmpty(args.password());

            boolean credentialsRequested = false;

            while (true) {
                try {
                    if (!args.autoConfirmation()) {
                        command.prepareConfirmation(clientCfg);

                        if (!confirm(command.confirmationPrompt())) {
                            logger.info("Operation cancelled.");

                            return EXIT_CODE_OK;
                        }
                    }

                    logger.info("Command [" + commandName + "] started");
                    logger.info("Arguments: " + String.join(" ", rawArgs));
                    logger.info(DELIM);

                    lastOperationRes = command.execute(clientCfg, logger);

                    break;
                }
                catch (Throwable e) {
                    if (!isAuthError(e))
                        throw e;

                    if (suppliedAuth)
                        throw new GridClientAuthenticationException("Wrong credentials.");

                    if (tryConnectMaxCount == 0) {
                        throw new GridClientAuthenticationException("Maximum number of " +
                            "retries exceeded");
                    }

                    logger.info(credentialsRequested ?
                        "Authentication error, please try again." :
                        "This cluster requires authentication.");

                    if (credentialsRequested)
                        tryConnectMaxCount--;

                    String user = retrieveUserName(args, clientCfg);

                    String pwd = new String(requestPasswordFromConsole("password: "));

                    clientCfg = getClientConfiguration(user, pwd,  args);

                    credentialsRequested = true;
                }
            }

            logger.info("Command [" + commandName + "] finished with code: " + EXIT_CODE_OK);

            return EXIT_CODE_OK;
        }
        catch (IllegalArgumentException e) {
            logger.severe("Check arguments. " + CommandLogger.errorMessage(e));

            logger.info("Command [" + commandName + "] finished with code: " + EXIT_CODE_INVALID_ARGUMENTS);

            return EXIT_CODE_INVALID_ARGUMENTS;
        }
        catch (Throwable e) {
            if (isAuthError(e)) {
                logger.severe("Authentication error. " + CommandLogger.errorMessage(e));

                logger.info("Command [" + commandName + "] finished with code: " + ERR_AUTHENTICATION_FAILED);

                return ERR_AUTHENTICATION_FAILED;
            }

            if (isConnectionError(e)) {
                IgniteCheckedException cause = X.cause(e, IgniteCheckedException.class);

                if (isConnectionClosedSilentlyException(e))
                    logger.severe("Connection to cluster failed. Please check firewall settings and " +
                        "client and server are using the same SSL configuration.");
                else {
                    if (isSSLMisconfigurationError(cause))
                        e = cause;

                    logger.severe("Connection to cluster failed. " + CommandLogger.errorMessage(e));

                }

                logger.info("Command [" + commandName + "] finished with code: " + EXIT_CODE_CONNECTION_FAILED);

                return EXIT_CODE_CONNECTION_FAILED;
            }

            if (X.hasCause(e, IllegalArgumentException.class)) {
                IllegalArgumentException iae = X.cause(e, IllegalArgumentException.class);

                logger.severe("Check arguments. " + CommandLogger.errorMessage(iae));
                logger.info("Command [" + commandName + "] finished with code: " + EXIT_CODE_INVALID_ARGUMENTS);

                return EXIT_CODE_INVALID_ARGUMENTS;
            }

            logger.severe(CommandLogger.errorMessage(e));
            logger.info("Command [" + commandName + "] finished with code: " + EXIT_CODE_UNEXPECTED_ERROR);

            return EXIT_CODE_UNEXPECTED_ERROR;
        }
        finally {
            LocalDateTime endTime = LocalDateTime.now();

            Duration diff = Duration.between(startTime, endTime);

            logger.info("Control utility has completed execution at: " + endTime);
            logger.info("Execution time: " + diff.toMillis() + " ms");

            Arrays.stream(logger.getHandlers())
                  .filter(handler -> handler instanceof FileHandler)
                  .forEach(Handler::close);
        }
    }

    /**
     * Analyses passed exception to find out whether it is related to SSL misconfiguration issues.
     *
     * (!) Implementation depends heavily on structure of exception stack trace
     * thus is very fragile to any changes in that structure.
     *
     * @param e Exception to analyze.
     *
     * @return {@code True} if exception may be related to SSL misconfiguration issues.
     */
    private boolean isSSLMisconfigurationError(Throwable e) {
        return e != null && e.getMessage() != null && e.getMessage().contains("SSL");
    }

    /**
     * Analyses passed exception to find out whether it is caused by server closing connection silently.
     * This happens when client tries to establish unprotected connection
     * to the cluster supporting only secured communications (e.g. when server is configured to use SSL certificates
     * and client is not).
     *
     * (!) Implementation depends heavily on structure of exception stack trace
     * thus is very fragile to any changes in that structure.
     *
     * @param e Exception to analyse.
     * @return {@code True} if exception may be related to the attempt to establish unprotected connection
     * to secured cluster.
     */
    private boolean isConnectionClosedSilentlyException(Throwable e) {
        if (!(e instanceof GridClientDisconnectedException))
            return false;

        Throwable cause = e.getCause();

        if (cause == null)
            return false;

        cause = cause.getCause();

        if (cause instanceof GridClientConnectionResetException &&
            cause.getMessage() != null &&
            cause.getMessage().contains("Failed to perform handshake")
        )
            return true;

        return false;
    }

    /**
     * Does one of three things:
     * 
    *
  • returns user name from connection parameters if it is there;
  • *
  • returns user name from client configuration if it is there;
  • *
  • requests user input and returns entered name.
  • *
* * @param args Connection parameters. * @param clientCfg Client configuration. * @throws IgniteCheckedException If security credetials cannot be provided from client configuration. */ private String retrieveUserName( ConnectionAndSslParameters args, GridClientConfiguration clientCfg ) throws IgniteCheckedException { if (!F.isEmpty(args.userName())) return args.userName(); else if (clientCfg.getSecurityCredentialsProvider() == null) return requestDataFromConsole("user: "); else return (String)clientCfg.getSecurityCredentialsProvider().credentials().getLogin(); } /** * @param args Common arguments. * @return Thin client configuration to connect to cluster. * @throws IgniteCheckedException If error occur. */ @NotNull private GridClientConfiguration getClientConfiguration( ConnectionAndSslParameters args ) throws IgniteCheckedException { return getClientConfiguration(args.userName(), args.password(), args); } /** * @param userName User name for authorization. * @param password Password for authorization. * @param args Common arguments. * @return Thin client configuration to connect to cluster. * @throws IgniteCheckedException If error occur. */ @NotNull private GridClientConfiguration getClientConfiguration( String userName, String password, ConnectionAndSslParameters args ) throws IgniteCheckedException { GridClientConfiguration clientCfg = new GridClientConfiguration(); clientCfg.setPingInterval(args.pingInterval()); clientCfg.setPingTimeout(args.pingTimeout()); clientCfg.setServers(Collections.singletonList(args.host() + ":" + args.port())); if (!F.isEmpty(userName)) clientCfg.setSecurityCredentialsProvider(getSecurityCredentialsProvider(userName, password, clientCfg)); if (!F.isEmpty(args.sslKeyStorePath())) clientCfg.setSslContextFactory(createSslSupportFactory(args)); return clientCfg; } /** * @param userName User name for authorization. * @param password Password for authorization. * @param clientCfg Thin client configuration to connect to cluster. * @return Security credentials provider with usage of given user name and password. * @throws IgniteCheckedException If error occur. */ @NotNull private SecurityCredentialsProvider getSecurityCredentialsProvider( String userName, String password, GridClientConfiguration clientCfg ) throws IgniteCheckedException { SecurityCredentialsProvider securityCredential = clientCfg.getSecurityCredentialsProvider(); if (securityCredential == null) return new SecurityCredentialsBasicProvider(new SecurityCredentials(userName, password)); final SecurityCredentials credential = securityCredential.credentials(); credential.setLogin(userName); credential.setPassword(password); return securityCredential; } /** * @param args Commond args. * @return Ssl support factory. */ @NotNull private GridSslBasicContextFactory createSslSupportFactory(ConnectionAndSslParameters args) { GridSslBasicContextFactory factory = new GridSslBasicContextFactory(); List sslProtocols = split(args.sslProtocol(), ","); String sslProtocol = F.isEmpty(sslProtocols) ? DFLT_SSL_PROTOCOL : sslProtocols.get(0); factory.setProtocol(sslProtocol); factory.setKeyAlgorithm(args.sslKeyAlgorithm()); if (sslProtocols.size() > 1) factory.setProtocols(sslProtocols); factory.setCipherSuites(split(args.getSslCipherSuites(), ",")); factory.setKeyStoreFilePath(args.sslKeyStorePath()); if (args.sslKeyStorePassword() != null) factory.setKeyStorePassword(args.sslKeyStorePassword()); else factory.setKeyStorePassword(requestPasswordFromConsole("SSL keystore password: ")); factory.setKeyStoreType(args.sslKeyStoreType()); if (F.isEmpty(args.sslTrustStorePath())) factory.setTrustManagers(GridSslBasicContextFactory.getDisabledTrustManager()); else { factory.setTrustStoreFilePath(args.sslTrustStorePath()); if (args.sslTrustStorePassword() != null) factory.setTrustStorePassword(args.sslTrustStorePassword()); else factory.setTrustStorePassword(requestPasswordFromConsole("SSL truststore password: ")); factory.setTrustStoreType(args.sslTrustStoreType()); } return factory; } /** * Used for tests. * * @return Last operation result; */ public T getLastOperationResult() { return (T)lastOperationRes; } /** * Provides a prompt, then reads a single line of text from the console. * * @param prompt text * @return A string containing the line read from the console */ private String readLine(String prompt) { System.out.print(prompt); return in.nextLine(); } /** * Requests interactive user confirmation if forthcoming operation is dangerous. * * @return {@code true} if operation confirmed (or not needed), {@code false} otherwise. */ private boolean confirm(String str) { if (str == null) return true; String prompt = str + lineSeparator() + "Press '" + CONFIRM_MSG + "' to continue . . . "; return CONFIRM_MSG.equalsIgnoreCase(readLine(prompt)); } /** * @param e Exception to check. * @return {@code true} if specified exception is {@link GridClientAuthenticationException}. */ public static boolean isAuthError(Throwable e) { return X.hasCause(e, GridClientAuthenticationException.class); } /** * @param e Exception to check. * @return {@code true} if specified exception is a connection error. */ private static boolean isConnectionError(Throwable e) { return e instanceof GridClientClosedException || e instanceof GridClientConnectionResetException || e instanceof GridClientDisconnectedException || e instanceof GridClientHandshakeException || e instanceof GridServerUnreachableException; } /** * Requests password from console with message. * * @param msg Message. * @return Password. */ private char[] requestPasswordFromConsole(String msg) { if (console == null) throw new UnsupportedOperationException("Failed to securely read password (console is unavailable): " + msg); else return console.readPassword(msg); } /** * Requests user data from console with message. * * @param msg Message. * @return Input user data. */ private String requestDataFromConsole(String msg) { if (console != null) return console.readLine(msg); else { Scanner scanner = new Scanner(System.in); logger.info(msg); return scanner.nextLine(); } } /** * Split string into items. * * @param s String to process. * @param delim Delimiter. * @return List with items. */ private static List split(String s, String delim) { if (F.isEmpty(s)) return Collections.emptyList(); return Arrays.stream(s.split(delim)) .map(String::trim) .filter(item -> !item.isEmpty()) .collect(Collectors.toList()); } /** */ private void printHelp() { logger.info("Control utility script is used to execute admin commands on cluster or get common cluster info. " + "The command has the following syntax:"); logger.info(""); logger.info(INDENT + CommandLogger.join(" ", CommandLogger.join(" ", UTILITY_NAME, CommandLogger.join(" ", getCommonOptions())), optional("command"), "")); logger.info(""); logger.info(""); logger.info("This utility can do the following commands:"); Arrays.stream(CommandList.values()).forEach(c -> c.command().printUsage(logger)); logger.info("By default commands affecting the cluster require interactive confirmation."); logger.info("Use " + CMD_AUTO_CONFIRMATION + " option to disable it."); logger.info(""); logger.info("Default values:"); logger.info(DOUBLE_INDENT + "HOST_OR_IP=" + DFLT_HOST); logger.info(DOUBLE_INDENT + "PORT=" + DFLT_PORT); logger.info(DOUBLE_INDENT + "PING_INTERVAL=" + DFLT_PING_INTERVAL); logger.info(DOUBLE_INDENT + "PING_TIMEOUT=" + DFLT_PING_TIMEOUT); logger.info(DOUBLE_INDENT + "SSL_PROTOCOL=" + SslContextFactory.DFLT_SSL_PROTOCOL); logger.info(DOUBLE_INDENT + "SSL_KEY_ALGORITHM=" + SslContextFactory.DFLT_KEY_ALGORITHM); logger.info(DOUBLE_INDENT + "KEYSTORE_TYPE=" + SslContextFactory.DFLT_STORE_TYPE); logger.info(DOUBLE_INDENT + "TRUSTSTORE_TYPE=" + SslContextFactory.DFLT_STORE_TYPE); logger.info(""); logger.info("Exit codes:"); logger.info(DOUBLE_INDENT + EXIT_CODE_OK + " - successful execution."); logger.info(DOUBLE_INDENT + EXIT_CODE_INVALID_ARGUMENTS + " - invalid arguments."); logger.info(DOUBLE_INDENT + EXIT_CODE_CONNECTION_FAILED + " - connection failed."); logger.info(DOUBLE_INDENT + ERR_AUTHENTICATION_FAILED + " - authentication failed."); logger.info(DOUBLE_INDENT + EXIT_CODE_UNEXPECTED_ERROR + " - unexpected error."); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy