package com.thinkbiganalytics.kerberos;

/*-
 * #%L
 * thinkbig-kerberos-test-client
 * %%
 * Copyright (C) 2017 ThinkBig Analytics
 * %%
 * 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.
 * #L%
 */

import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hive.jdbc.HiveConnection;

import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

/**
 */
public class TestKerberosKinit {

    private static final String ENVIRONMENT_HDP = "HDP";
    private static final String ENVIRONMENT_CLOUDERA = "CLOUDERA";
    private static final String DRIVER_NAME = "org.apache.hive.jdbc.HiveDriver";

    public static void main(String[] args) throws Exception {
        final TestKerberosKinit testKerberosKinit = new TestKerberosKinit();
        Scanner scanner = new Scanner(System.in);

        System.out.println(" ");
        System.out.print("Which environment are you in? Enter 1 for HDP or 2 for Cloudera: ");
        String environmentCode = scanner.nextLine();
        if (StringUtils.isEmpty(environmentCode)) {
            environmentCode = "1";
        }

        String environment;
        switch (environmentCode) {
            case "1":
                environment = ENVIRONMENT_HDP;
                break;
            case "2":
                environment = ENVIRONMENT_CLOUDERA;
                break;
            default:
                throw new Exception("Invalid environment code");
        }

        System.out.println(" ");
        System.out.println("Hit enter to default to: /etc/hadoop/conf/core-site.xml,/etc/hadoop/conf/hdfs-site.xml,/usr/hdp/current/hive-client/conf/hive-site.xml");
        System.out.print("Please enter the list of configuration resources: ");
        String configResources = scanner.nextLine();
        if (StringUtils.isEmpty(configResources)) {
            configResources = "/etc/hadoop/conf/core-site.xml,/etc/hadoop/conf/hdfs-site.xml,/usr/hdp/current/hive-client/conf/hive-site.xml";
        }

        System.out.println(" ");
        System.out.println("Hit enter to default to: /etc/security/keytabs/hive-thinkbig.headless.keytab");
        System.out.print("Please enter the keytab file location: ");
        String keytab = scanner.nextLine();
        if (StringUtils.isEmpty(keytab)) {
            keytab = "/etc/security/keytabs/hive-thinkbig.headless.keytab";
        }

        System.out.println(" ");
        System.out.println("Hit enter to default to: hive/sandbox.hortonworks.com@sandbox.hortonworks.com");
        System.out.print("Please enter the real user principal name: ");
        String realUserPrincipal = scanner.nextLine();
        if (StringUtils.isEmpty(realUserPrincipal)) {
            realUserPrincipal = "hive/sandbox.hortonworks.com@sandbox.hortonworks.com";
        }

        System.out.println(" ");
        System.out.println("Please enter Y/N (default is N)");
        System.out.print("Do you want to test with a proxy user: ");
        String proxyUser = scanner.nextLine();
        if (StringUtils.isEmpty(realUserPrincipal)) {
            proxyUser = "N";
        }

        System.out.println(" ");
        System.out.println("Hit enter to default to: hdfs://sandbox.hortonworks.com:8020");
        System.out.print("Please enter the HDFS URL: ");
        String hdfsUrl = scanner.nextLine();
        if (StringUtils.isEmpty(hdfsUrl)) {
            hdfsUrl = "hdfs://sandbox.hortonworks.com:8020";
        }

        System.out.println(" ");
        System.out.println("Hit enter to default to: jdbc:hive2://localhost:10000/default");
        System.out.print("Please enter the Hive base connection string: ");
        String hiveHost = scanner.nextLine();
        if (StringUtils.isEmpty(hiveHost)) {
            hiveHost = "jdbc:hive2://localhost:10000/default";
        }

        String proxyUserName = null;
        if ("Y".equalsIgnoreCase(proxyUser)) {
            System.out.println(" ");
            System.out.print("Please enter the proxy user: ");
            proxyUserName = scanner.next();
        }

        System.out.println(" ");
        System.out.println("Executing Kinit to generate a kerberos ticket");

        if ("Y".equalsIgnoreCase(proxyUser)) {
            System.out.println("Testing with the proxy user: " + proxyUserName);
            testKerberosKinit.testHdfsWithUserImpersonation(configResources, keytab, realUserPrincipal, proxyUserName, environment, hdfsUrl);

            //testKerberosKinit.testHiveJdbcConnectionWithUserImpersonation(configResources, keytab, realUserPrincipal, proxyUserName);
            testKerberosKinit.testHiveJdbcConnection(configResources, keytab, realUserPrincipal, proxyUserName, hiveHost);
        } else {
            System.out.println("No Proxy User");
            testKerberosKinit.testHdfsAsKerberosUser(configResources, keytab, realUserPrincipal, environment, hdfsUrl);
            testKerberosKinit.testHiveJdbcConnection(configResources, keytab, realUserPrincipal, null, hiveHost);
        }
    }

    private static Configuration createConfigurationFromList(String configurationFiles) {
        Configuration config = new Configuration();
        String[] resources = configurationFiles.split(",");
        for (String resource : resources) {
            config.addResource(new Path(resource));
        }
        return config;
    }

    private static UserGroupInformation generateKerberosTicket(Configuration configuration, String keytabLocation, String principal) throws IOException {
        System.setProperty("sun.security.krb5.debug", "false");
        configuration.set("hadoop.security.authentication", "Kerberos");
        UserGroupInformation.setConfiguration(configuration);

        System.out.println("Generating Kerberos ticket for principal: " + principal + " at key tab location: " + keytabLocation);
        return UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, keytabLocation);
    }

    private void testHdfsWithUserImpersonation(final String configResources, final String keytab, final String principal, String proxyUser, final String environment, final String hdfsUrl) {
        final String path = "/user";
        try {
            final Configuration configuration = TestKerberosKinit.createConfigurationFromList(configResources);
            UserGroupInformation realugi = TestKerberosKinit.generateKerberosTicket(configuration, keytab, principal);
            System.out.println(" ");
            System.out.println("Sucessfully got a kerberos ticket in the JVM");
            System.out.println("current user is: " + realugi.getUserName());

            UserGroupInformation ugiProxy = UserGroupInformation.createProxyUser(proxyUser, realugi);
            System.out.println("proxy user is: " + ugiProxy.getUserName());
            ugiProxy.doAs(new PrivilegedExceptionAction<Object>() {
                public Object run() {
                    try {
                        searchHDFS(configuration, environment, path, hdfsUrl);
                    } catch (Exception e) {
                        throw new RuntimeException("Error testing HDFS with Kerberos Hive Impersonation", e);
                    }
                    return null;
                }
            });


        } catch (Exception e) {
            System.out.println("Error testing HDFS\n\n");
            e.printStackTrace();
        }
    }

    private void testHdfsAsKerberosUser(final String configResources, final String keytab, final String principal, final String environment, final String hdfsUrl) {
        final String path = "/user";
        try {
            final Configuration configuration = TestKerberosKinit.createConfigurationFromList(configResources);
            UserGroupInformation realugi = TestKerberosKinit.generateKerberosTicket(configuration, keytab, principal);
            System.out.println(" ");
            System.out.println("Sucessfully got a kerberos ticket in the JVM");
            System.out.println("current user is: " + realugi.getUserName());

            realugi.doAs(new PrivilegedExceptionAction<Object>() {
                public Object run() {
                    try {
                        searchHDFS(configuration, environment, path, hdfsUrl);
                    } catch (Exception e) {
                        throw new RuntimeException("Error testing HDFS with Kerberos", e);
                    }
                    return null;
                }
            });


        } catch (Exception e) {
            System.out.println("Error testing HDFS\n\n");
            e.printStackTrace();
        }
    }

    private void searchHDFS(Configuration configuration, final String environment, String hdfsPath, String hdfsUrl) throws Exception {
        configuration.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName());
        configuration.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName());
        FileSystem fs = FileSystem.get(configuration);

        if (environment.equalsIgnoreCase(ENVIRONMENT_CLOUDERA)) {
            FileStatus[] status = fs.listStatus(new Path(hdfsUrl + hdfsPath));
            System.out.println("File Count: " + status.length);
        } else {
            if (environment.equalsIgnoreCase(ENVIRONMENT_HDP)) {
                FileStatus[] status = fs.listStatus(new Path(hdfsUrl + hdfsPath));
                System.out.println("File Count: " + status.length);
            }
        }
    }

    private void testHiveJdbcConnection(final String configResources, final String keytab, final String realUserPrincipal, final String proxyUser, final String hiveHostName) throws Exception {

        final Configuration configuration = TestKerberosKinit.createConfigurationFromList(configResources);
        UserGroupInformation realugi = TestKerberosKinit.generateKerberosTicket(configuration, keytab, realUserPrincipal);

        System.out.println(" ");
        System.out.println("Sucessfully got a kerberos ticket in the JVM");

        HiveConnection realUserConnection = (HiveConnection) realugi.doAs(new PrivilegedExceptionAction<Connection>() {
            public Connection run() {
                Connection connection;
                try {
                    Class.forName(DRIVER_NAME);
                    String url = hiveHostName;
                    if (proxyUser != null) {
                        url = url + ";hive.server2.proxy.user=" + proxyUser;
                    }
                    System.out.println("Hive URL: " + url);
                    connection = DriverManager.getConnection(url);

                    Class.forName(DRIVER_NAME);

                    System.out.println("creating statement");
                    Statement stmt = connection.createStatement();

                    String sql = "show databases";
                    ResultSet res = stmt.executeQuery(sql);
                    System.out.println(" \n");
                    System.out.println("Executing the Hive Query:");
                    System.out.println(" ");

                    System.out.println("List of Databases");
                    while (res.next()) {
                        System.out.println(res.getString(1));
                    }

                } catch (Exception e) {
                    throw new RuntimeException("Error creating connection with proxy user", e);
                }
                return connection;
            }
        });


    }
}
