org.apache.sentry.tests.e2e.hdfs.TestDbHdfsBase.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sentry.tests.e2e.hdfs.TestDbHdfsBase.java

Source

package org.apache.sentry.tests.e2e.hdfs;

/*
 * 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.
 */

import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.sql.Connection;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import com.google.common.base.Strings;

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.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclStatus;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.sentry.tests.e2e.hive.fs.DFSFactory.DFSType;
import org.apache.sentry.tests.e2e.hive.fs.TestFSContants;
import org.apache.sentry.tests.e2e.hive.hiveserver.HiveServerFactory.HiveServer2Type;
import org.junit.After;
import org.junit.BeforeClass;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.sentry.tests.e2e.hive.AbstractTestWithStaticConfiguration;

/**
 * A base class for HDFS SynUp tests:
 * The way to run one test could be like the below:
 * mvn test
 -P cluster-hadoop-provider-db \
 -f pom.xml \
 -Dsentry.e2etest.admin.user=hive \
 -Dsentry.e2etest.admin.group=hive \
 -Dhive.server2.thrift.port=10000 \
 -Dhive.server2.authentication.kerberos.keytab=.. \
 -Dhive.server2.authentication.kerberos.principal=.. \
 -Dhive.server2.thrift.bind.host=${HS2_HOST} \
 -Dhive.server2.authentication=kerberos \
 -Dsentry.e2e.hive.keytabs.location=.. \
 -Dsentry.host=${SENTRY_HOST} \
 -Dsentry.service.security.mode=kerberos \
 -Dtest.hdfs.e2e.ext.path=/data
 */

public abstract class TestDbHdfsBase extends AbstractTestWithStaticConfiguration {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestDbHdfsBase.class);

    protected static String metastoreDir;
    protected static String scratchLikeDir;
    protected static String authenticationType;
    protected static UserGroupInformation adminUgi;
    protected static UserGroupInformation hiveUgi;
    protected static int NUM_RETRIES_FOR_ACLS = 12;
    protected static int WAIT_SECS_FOR_ACLS = Integer
            .parseInt(System.getProperty(TestFSContants.SENTRY_E2E_TEST_HDFS_ACLS_SYNCUP_SECS, "1000")); // seconds
    protected static String testExtPathDir = System.getProperty(TestFSContants.SENTRY_E2E_TEST_HDFS_EXT_PATH);
    protected static final String KEYTAB_LOCATION = System
            .getProperty(TestFSContants.SENTRY_E2E_TEST_HIVE_KEYTAB_LOC, "/cdep/keytabs");
    protected static String DFS_TYPE = System.getProperty(TestFSContants.SENTRY_E2E_TEST_DFS_TYPE,
            DFSType.MiniDFS.name());

    protected final static String dfsAdmin = System.getProperty(TestFSContants.SENTRY_E2E_TEST_DFS_ADMIN, "hdfs");
    protected final static String storageUriStr = System.getProperty(TestFSContants.SENTRY_E2E_TEST_STORAGE_URI);

    @BeforeClass
    public static void setupTestStaticConfiguration() throws Exception {
        if (!Strings.isNullOrEmpty(storageUriStr)) {
            LOGGER.warn("Skip HDFS tests if HDFS fileSystem is not configured on hdfs");
            assumeTrue(storageUriStr.toLowerCase().startsWith("hdfs"));
        }
        useSentryService = true;
        enableHDFSAcls = true;
        AbstractTestWithStaticConfiguration.setupTestStaticConfiguration();
        AbstractTestWithStaticConfiguration.setupAdmin();
        scratchLikeDir = context.getProperty(HiveConf.ConfVars.SCRATCHDIR.varname);
        metastoreDir = context.getProperty(HiveConf.ConfVars.METASTOREWAREHOUSE.varname);
        authenticationType = System.getProperty(HiveConf.ConfVars.HIVE_SERVER2_AUTHENTICATION.varname);
        assumeNotNull(metastoreDir, scratchLikeDir);
        if (dfsType.equals(DFSType.ClusterDFS.name())) {
            LOGGER.info("Start to run hdfs e2e tests on a real cluster.");
            assumeNotNull(KEYTAB_LOCATION, authenticationType);
            assumeThat(authenticationType, equalToIgnoringCase("kerberos"));
        } else if (dfsType.equals(DFSType.MiniDFS.name())) {
            LOGGER.info("Start to run hdfs e2e tests on a mini cluster.");
            setupMiniCluster();
        } else {
            LOGGER.error("Unknown DFS cluster type: either MiniCluster or ClusterDFS");
            return;
        }
        // Since they are real e2e tests,for now they
        // work on a real cluster managed outside of the tests
        assumeThat(hiveServer2Type, equalTo(HiveServer2Type.UnmanagedHiveServer2));
        assumeThat(dfsType, equalTo(DFSType.ClusterDFS.name()));
    }

    private static void setupMiniCluster() throws Exception {
        createGgis();
    }

    @After
    public void clearAfterPerTest() throws Exception {
        super.clearAfterPerTest();
        // Clean up any extra data created during testing in external path
        LOGGER.info("TestDbHdfsBase clearAfterPerTest");
        kinitFromKeytabFile(dfsAdmin, getKeyTabFileFullPath(dfsAdmin));
        if (!Strings.isNullOrEmpty(testExtPathDir)) {
            Path path = new Path(testExtPathDir);
            FileStatus[] children = fileSystem.listStatus(path);
            for (FileStatus fs : children) {
                LOGGER.info("Deleting " + fs.toString());
                fileSystem.delete(fs.getPath(), true);
            }
        }
    }

    private FileSystem getFS(UserGroupInformation ugi) throws Exception {
        return ugi.doAs(new PrivilegedExceptionAction<FileSystem>() {
            public FileSystem run() throws Exception {
                Configuration conf = new Configuration();
                return FileSystem.get(conf);
            }
        });
    }

    private static void createGgis() throws Exception {
        if (dfsType.equals(DFSType.MiniDFS.name())) {
            adminUgi = UserGroupInformation.createUserForTesting(System.getProperty("user.name"),
                    new String[] { "supergroup" });
            hiveUgi = UserGroupInformation.createUserForTesting("hive", new String[] { "hive" });
        } else if (dfsType.equals(DFSType.ClusterDFS.name())) {
            adminUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI("hdfs",
                    KEYTAB_LOCATION + "/hdfs.keytab");
            hiveUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI("hive",
                    KEYTAB_LOCATION + "/hive.keytab");
        }
    }

    protected void verifyAclsRecursive(final List<AclEntry> expectedAcls, final String pathLoc,
            final boolean recursive) throws Exception {
        if (DFS_TYPE.equals(DFSType.MiniDFS.name())) {
            fileSystem = getFS(adminUgi);
            adminUgi.doAs(new PrivilegedExceptionAction<Void>() {
                @Override
                public Void run() throws Exception {
                    verifyAclsHelper(expectedAcls, pathLoc, recursive);
                    return null;
                }
            });
        } else if (DFS_TYPE.equals(DFSType.ClusterDFS.name())) {
            kinitFromKeytabFile(dfsAdmin, getKeyTabFileFullPath(dfsAdmin));
            verifyAclsHelper(expectedAcls, pathLoc, recursive);
        } else {
            fail("Unknown DFS cluster type: " + DFS_TYPE);
        }
    }

    protected void verifyNoAclRecursive(final List<AclEntry> noAcls, final String pathLoc, final boolean recursive)
            throws Exception {
        if (DFS_TYPE.equals(DFSType.MiniDFS.name())) {
            fileSystem = getFS(adminUgi);
            adminUgi.doAs(new PrivilegedExceptionAction<Void>() {
                @Override
                public Void run() throws Exception {
                    verifyNoAclHelper(noAcls, pathLoc, recursive);
                    return null;
                }
            });
        } else if (DFS_TYPE.equals(DFSType.ClusterDFS.name())) {
            kinitFromKeytabFile(dfsAdmin, getKeyTabFileFullPath(dfsAdmin));
            verifyNoAclHelper(noAcls, pathLoc, recursive);
        } else {
            fail("Unknown DFS cluster type: " + DFS_TYPE);
        }
    }

    /**
     * Verify extended acl entries are correctly synced up
     * @param expectedAcls
     * @param pathLoc
     * @param recursive
     * @throws Exception
     */
    private void verifyAclsHelper(List<AclEntry> expectedAcls, String pathLoc, boolean recursive) throws Exception {
        int retry = 0;
        Path path = new Path(pathLoc);
        LOGGER.info("expectedAcls of [" + pathLoc + "] = " + expectedAcls.toString());
        // Syncing up acls takes some time so make validation in a loop
        while (retry < NUM_RETRIES_FOR_ACLS) {
            AclStatus aclStatus = fileSystem.getAclStatus(path);
            List<AclEntry> actualAcls = new ArrayList<>(aclStatus.getEntries());
            LOGGER.info("[" + retry + "] actualAcls of [" + pathLoc + "] = " + actualAcls.toString());
            retry += 1;
            if (!actualAcls.isEmpty() && !actualAcls.contains(expectedAcls.get(expectedAcls.size() - 1))) {
                Thread.sleep(WAIT_SECS_FOR_ACLS);
            } else {
                for (AclEntry expected : expectedAcls) {
                    assertTrue("Fail to find aclEntry: " + expected.toString(), actualAcls.contains(expected));
                }
                break;
            }
        }
        assertThat(retry, lessThan(NUM_RETRIES_FOR_ACLS));
        if (recursive && fileSystem.getFileStatus(path).isDirectory()) {
            FileStatus[] children = fileSystem.listStatus(path);
            for (FileStatus fs : children) {
                verifyAclsRecursive(expectedAcls, fs.getPath().toString(), recursive);
            }
        }
    }

    /**
     * Verify there is no specified acls gotten synced up in the path status
     * @param noAcls
     * @param pathLoc
     * @param recursive
     * @throws Exception
     */
    private void verifyNoAclHelper(List<AclEntry> noAcls, String pathLoc, boolean recursive) throws Exception {
        int retry = 0;
        // Retry a couple of times in case the incorrect acls take time to be synced up
        while (retry < NUM_RETRIES_FOR_ACLS) {
            Path path = new Path(pathLoc);
            AclStatus aclStatus = fileSystem.getAclStatus(path);
            List<AclEntry> actualAcls = new ArrayList<>(aclStatus.getEntries());
            LOGGER.info("[" + retry + "] actualAcls of [" + pathLoc + "] = " + actualAcls.toString());
            Thread.sleep(1000); // wait for syncup
            retry += 1;
            for (AclEntry acl : actualAcls) {
                if (noAcls.contains(acl)) {
                    fail("Path [ " + pathLoc + " ] should not contain " + acl.toString());
                }
            }
        }
        Path path = new Path(pathLoc);
        if (recursive && fileSystem.getFileStatus(path).isDirectory()) {
            FileStatus[] children = fileSystem.listStatus(path);
            for (FileStatus fs : children) {
                verifyNoAclRecursive(noAcls, fs.getPath().toString(), recursive);
            }
        }
    }

    /**
     * Drop and create role, in case the previous
     * tests leave same roles uncleaned up
     * @param statement
     * @param roleName
     * @throws Exception
     */
    protected void dropRecreateRole(Statement statement, String roleName) throws Exception {
        try {
            exec(statement, "DROP ROLE " + roleName);
        } catch (Exception ex) {
            //noop
            LOGGER.info("Role " + roleName + " does not exist. But it's ok.");
        } finally {
            exec(statement, "CREATE ROLE " + roleName);
        }
    }

    /**
     * Create an internal test database and table
     * @param db
     * @param tbl
     * @throws Exception
     */
    protected void dropRecreateDbTblRl(String db, String tbl) throws Exception {
        dropRecreateDbTblRl(null, db, tbl);
    }

    /**
     * Create test database and table with location pointing to testPathLoc
     * @param testPathLoc
     * @param db
     * @param tbl
     * @throws Exception
     */
    protected void dropRecreateDbTblRl(String testPathLoc, String db, String tbl) throws Exception {
        Connection connection = context.createConnection(ADMIN1);
        Statement statement = connection.createStatement();
        exec(statement, "DROP DATABASE IF EXISTS " + db + " CASCADE");
        if (testPathLoc != null) {
            exec(statement, "CREATE DATABASE " + db + " LOCATION \'" + testPathLoc + "\'");
        } else {
            exec(statement, "CREATE DATABASE " + db);
        }
        exec(statement, "USE " + db);
        exec(statement, "CREATE TABLE " + tbl + "(number INT, value STRING) PARTITIONED BY (par INT)");
        exec(statement, "INSERT INTO TABLE " + tbl + " PARTITION(par=1) VALUES (1, 'test1')");
        exec(statement, "SELECT * FROM " + tbl);
        if (statement != null) {
            statement.close();
        }
        if (connection != null) {
            connection.close();
        }
    }

    /**
     * Create test database and table with location pointing
     * to testPathLoc without partitions
     * @param db
     * @param tbl
     * @throws Exception
     */
    protected void dropRecreateDbTblNoPar(String db, String tbl) throws Exception {
        Connection connection = context.createConnection(ADMIN1);
        Statement statement = connection.createStatement();
        exec(statement, "DROP DATABASE IF EXISTS " + db + " CASCADE");
        exec(statement, "CREATE DATABASE " + db);
        exec(statement, "USE " + db);
        exec(statement, "CREATE TABLE " + tbl + "(number INT, value STRING)");
        exec(statement, "INSERT INTO TABLE " + tbl + " VALUES (1, 'test1')");
        exec(statement, "SELECT * FROM " + tbl);
        if (statement != null) {
            statement.close();
        }
        if (connection != null) {
            connection.close();
        }
    }

    protected static void kinitFromKeytabFile(String user, String keyTabFile) throws IOException {
        Configuration conf = new Configuration();
        conf.set("hadoop.security.authentication", authenticationType);
        UserGroupInformation.setConfiguration(conf);
        UserGroupInformation.loginUserFromKeytab(user, keyTabFile);
    }

    protected static String getKeyTabFileFullPath(String user) {
        return KEYTAB_LOCATION + "/" + user + ".keytab";
    }
}