org.apache.hive.hcatalog.templeton.Main.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hive.hcatalog.templeton.Main.java

Source

/**
 * 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.hive.hcatalog.templeton;

import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.spi.container.servlet.ServletContainer;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.hadoop.hive.common.classification.InterfaceAudience;
import org.apache.hadoop.hive.common.classification.InterfaceStability;
import org.apache.hadoop.hdfs.web.AuthFilter;
import org.apache.hadoop.hive.shims.Utils;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.client.PseudoAuthenticator;
import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler;
import org.apache.hadoop.util.GenericOptionsParser;
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.slf4j.bridge.SLF4JBridgeHandler;

import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;

/**
 * The main executable that starts up and runs the Server.
 */
@InterfaceAudience.LimitedPrivate("Integration Tests")
@InterfaceStability.Unstable
public class Main {
    public static final String SERVLET_PATH = "templeton";
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);

    public static final int DEFAULT_PORT = 8080;
    private Server server;

    private static volatile AppConfig conf;

    /**
     * Retrieve the config singleton.
     */
    public static synchronized AppConfig getAppConfigInstance() {
        if (conf == null)
            LOG.error("Bug: configuration not yet loaded");
        return conf;
    }

    Main(String[] args) {
        init(args);
    }

    public void init(String[] args) {
        initLogger();
        conf = loadConfig(args);
        conf.startCleanup();
        LOG.debug("Loaded conf " + conf);
    }

    // Jersey uses java.util.logging - bridge to slf4
    private void initLogger() {
        java.util.logging.Logger rootLogger = java.util.logging.LogManager.getLogManager().getLogger("");
        for (java.util.logging.Handler h : rootLogger.getHandlers())
            rootLogger.removeHandler(h);

        SLF4JBridgeHandler.install();
    }

    public AppConfig loadConfig(String[] args) {
        AppConfig cf = new AppConfig();
        try {
            GenericOptionsParser parser = new GenericOptionsParser(cf, args);
            if (parser.getRemainingArgs().length > 0)
                usage();
        } catch (IOException e) {
            LOG.error("Unable to parse options: " + e);
            usage();
        }

        return cf;
    }

    public void usage() {
        System.err.println("usage: templeton [-Dtempleton.port=N] [-D...]");
        System.exit(1);
    }

    public void run() {
        int port = conf.getInt(AppConfig.PORT, DEFAULT_PORT);
        try {
            checkEnv();
            runServer(port);
            // Currently only print the first port to be consistent with old behavior
            port = ArrayUtils.isEmpty(server.getConnectors()) ? -1
                    : ((ServerConnector) (server.getConnectors()[0])).getLocalPort();

            System.out.println("templeton: listening on port " + port);
            LOG.info("Templeton listening on port " + port);
        } catch (Exception e) {
            System.err.println("templeton: Server failed to start: " + e.getMessage());
            LOG.error("Server failed to start: ", e);
            System.exit(1);
        }
    }

    void stop() {
        if (server != null) {
            try {
                server.stop();
            } catch (Exception ex) {
                LOG.warn("Failed to stop jetty.Server", ex);
            }
        }
    }

    private void checkEnv() {
        checkCurrentDirPermissions();

    }

    private void checkCurrentDirPermissions() {
        //org.apache.commons.exec.DefaultExecutor requires
        // that current directory exists
        File pwd = new File(".");
        if (!pwd.exists()) {
            String msg = "Server failed to start: templeton: Current working directory '.' does not exist!";
            System.err.println(msg);
            LOG.error(msg);
            System.exit(1);
        }
    }

    public Server runServer(int port) throws Exception {

        //Authenticate using keytab
        if (UserGroupInformation.isSecurityEnabled()) {
            UserGroupInformation.loginUserFromKeytab(conf.kerberosPrincipal(), conf.kerberosKeytab());
        }

        // Create the Jetty server. If jetty conf file exists, use that to create server
        // to have more control.
        Server server = null;
        if (StringUtils.isEmpty(conf.jettyConfiguration())) {
            server = new Server(port);
        } else {
            FileInputStream jettyConf = new FileInputStream(conf.jettyConfiguration());
            XmlConfiguration configuration = new XmlConfiguration(jettyConf);
            server = (Server) configuration.configure();
        }

        ServletContextHandler root = new ServletContextHandler(server, "/");

        // Add the Auth filter
        FilterHolder fHolder = makeAuthFilter();
        EnumSet<DispatcherType> dispatches = EnumSet.of(DispatcherType.REQUEST);

        /* 
         * We add filters for each of the URIs supported by templeton.
         * If we added the entire sub-structure using '/*', the mapreduce 
         * notification cannot give the callback to templeton in secure mode.
         * This is because mapreduce does not use secure credentials for 
         * callbacks. So jetty would fail the request as unauthorized.
         */
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/ddl/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/pig/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/hive/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/sqoop/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/queue/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/jobs/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/mapreduce/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/status/*", dispatches);
        root.addFilter(fHolder, "/" + SERVLET_PATH + "/v1/version/*", dispatches);

        if (conf.getBoolean(AppConfig.XSRF_FILTER_ENABLED, false)) {
            root.addFilter(makeXSRFFilter(), "/" + SERVLET_PATH + "/*", dispatches);
            LOG.debug("XSRF filter enabled");
        } else {
            LOG.warn("XSRF filter disabled");
        }

        // Connect Jersey
        ServletHolder h = new ServletHolder(new ServletContainer(makeJerseyConfig()));
        root.addServlet(h, "/" + SERVLET_PATH + "/*");
        // Add any redirects
        addRedirects(server);

        // Start the server
        server.start();
        this.server = server;
        return server;
    }

    public FilterHolder makeXSRFFilter() {
        String customHeader = null; // The header to look for. We use "X-XSRF-HEADER" if this is null.
        String methodsToIgnore = null; // Methods to not filter. By default: "GET,OPTIONS,HEAD,TRACE" if null.
        FilterHolder fHolder = new FilterHolder(Utils.getXSRFFilter());
        if (customHeader != null) {
            fHolder.setInitParameter(Utils.XSRF_CUSTOM_HEADER_PARAM, customHeader);
        }
        if (methodsToIgnore != null) {
            fHolder.setInitParameter(Utils.XSRF_CUSTOM_METHODS_TO_IGNORE_PARAM, methodsToIgnore);
        }
        FilterHolder xsrfFilter = fHolder;

        return xsrfFilter;
    }

    // Configure the AuthFilter with the Kerberos params iff security
    // is enabled.
    public FilterHolder makeAuthFilter() {
        FilterHolder authFilter = new FilterHolder(AuthFilter.class);
        UserNameHandler.allowAnonymous(authFilter);
        if (UserGroupInformation.isSecurityEnabled()) {
            //http://hadoop.apache.org/docs/r1.1.1/api/org/apache/hadoop/security/authentication/server/AuthenticationFilter.html
            authFilter.setInitParameter("dfs.web.authentication.signature.secret", conf.kerberosSecret());
            //https://svn.apache.org/repos/asf/hadoop/common/branches/branch-1.2/src/packages/templates/conf/hdfs-site.xml
            authFilter.setInitParameter("dfs.web.authentication.kerberos.principal", conf.kerberosPrincipal());
            //http://https://svn.apache.org/repos/asf/hadoop/common/branches/branch-1.2/src/packages/templates/conf/hdfs-site.xml
            authFilter.setInitParameter("dfs.web.authentication.kerberos.keytab", conf.kerberosKeytab());
        }
        return authFilter;
    }

    public PackagesResourceConfig makeJerseyConfig() {
        PackagesResourceConfig rc = new PackagesResourceConfig("org.apache.hive.hcatalog.templeton");
        HashMap<String, Object> props = new HashMap<String, Object>();
        props.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
        props.put("com.sun.jersey.config.property.WadlGeneratorConfig",
                "org.apache.hive.hcatalog.templeton.WadlConfig");
        rc.setPropertiesAndFeatures(props);

        return rc;
    }

    public void addRedirects(Server server) {
        RewriteHandler rewrite = new RewriteHandler();

        RedirectPatternRule redirect = new RedirectPatternRule();
        redirect.setPattern("/templeton/v1/application.wadl");
        redirect.setLocation("/templeton/application.wadl");
        rewrite.addRule(redirect);

        HandlerList handlerlist = new HandlerList();
        ArrayList<Handler> handlers = new ArrayList<Handler>();

        // Any redirect handlers need to be added first
        handlers.add(rewrite);

        // Now add all the default handlers
        for (Handler handler : server.getHandlers()) {
            handlers.add(handler);
        }
        Handler[] newlist = new Handler[handlers.size()];
        handlerlist.setHandlers(handlers.toArray(newlist));
        server.setHandler(handlerlist);
    }

    public static void main(String[] args) {
        Main templeton = new Main(args);
        templeton.run();
    }

    /**
     * as of 3/6/2014 all WebHCat gives examples of POST requests that send user.name as a form 
     * parameter (in simple security mode).  That is no longer supported by PseudoAuthenticationHandler.
     * This class compensates for it.  
     * Alternatively, WebHCat could have implemented it's own version of PseudoAuthenticationHandler
     * and make sure that it's picked up by AuthenticationFilter.init(); (HADOOP-10193 has some context)
     * @deprecated since 0.13; callers should submit user.name as a query parameter.  user.name as a 
     * form param will be de-supported in 0.15
     */
    static final class UserNameHandler {
        static void allowAnonymous(FilterHolder authFilter) {
            /*note that will throw if Anonymous mode is not allowed & user.name is not in query string of the request;
            * this ensures that in the context of WebHCat, PseudoAuthenticationHandler allows Anonymous even though
            * WebHCat itself will throw if it can't figure out user.name*/
            authFilter.setInitParameter("dfs.web.authentication." + PseudoAuthenticationHandler.ANONYMOUS_ALLOWED,
                    "true");
        }

        static String getUserName(HttpServletRequest request) {
            if (!UserGroupInformation.isSecurityEnabled() && "POST".equalsIgnoreCase(request.getMethod())) {
                /*as of hadoop 2.3.0, PseudoAuthenticationHandler only expects user.name as a query param
                * (not as a form param in a POST request.  For backwards compatibility, we this logic
                * to get user.name when it's sent as a form parameter.
                * This is added in Hive 0.13 and should be de-supported in 0.15*/
                String userName = request.getParameter(PseudoAuthenticator.USER_NAME);
                if (userName != null) {
                    LOG.warn(PseudoAuthenticator.USER_NAME
                            + " is sent as form parameter which is deprecated as of Hive 0.13.  Should send it in the query string.");
                }
                return userName;
            }
            return null;
        }
    }
}