/*
* 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.accumulo.harness;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import org.apache.accumulo.cluster.ClusterUser;
import org.apache.accumulo.cluster.ClusterUsers;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.security.tokens.AuthenticationToken;
import org.apache.accumulo.core.client.security.tokens.KerberosToken;
import org.apache.accumulo.core.client.security.tokens.PasswordToken;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.security.TablePermission;
import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl;
import org.apache.accumulo.test.categories.MiniClusterOnlyTests;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
import org.apache.hadoop.security.UserGroupInformation;
import org.junit.experimental.categories.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Convenience class which starts a single MAC instance for a test to leverage.
*
* There isn't a good way to build this off of the {@link AccumuloClusterHarness} (as would be the logical place) because we need to start the
* MiniAccumuloCluster in a static BeforeClass-annotated method. Because it is static and invoked before any other BeforeClass methods in the implementation,
* the actual test classes can't expose any information to tell the base class that it is to perform the one-MAC-per-class semantics.
*
* Implementations of this class must be sure to invoke {@link #startMiniCluster()} or {@link #startMiniClusterWithConfig(MiniClusterConfigurationCallback)} in
* a method annotated with the {@link org.junit.BeforeClass} JUnit annotation and {@link #stopMiniCluster()} in a method annotated with the
* {@link org.junit.AfterClass} JUnit annotation.
*/
@Category(MiniClusterOnlyTests.class)
public abstract class SharedMiniClusterBase extends AccumuloITBase implements ClusterUsers {
private static final Logger log = LoggerFactory.getLogger(SharedMiniClusterBase.class);
public static final String TRUE = Boolean.toString(true);
private static String principal = "root";
private static String rootPassword;
private static AuthenticationToken token;
private static MiniAccumuloClusterImpl cluster;
private static TestingKdc krb;
/**
* Starts a MiniAccumuloCluster instance with the default configuration.
*/
public static void startMiniCluster() throws Exception {
startMiniClusterWithConfig(MiniClusterConfigurationCallback.NO_CALLBACK);
}
/**
* Starts a MiniAccumuloCluster instance with the default configuration but also provides the caller the opportunity to update the configuration before the
* MiniAccumuloCluster is started.
*
* @param miniClusterCallback
* A callback to configure the minicluster before it is started.
*/
public static void startMiniClusterWithConfig(MiniClusterConfigurationCallback miniClusterCallback) throws Exception {
File baseDir = new File(System.getProperty("user.dir") + "/target/mini-tests");
assertTrue(baseDir.mkdirs() || baseDir.isDirectory());
// Make a shared MAC instance instead of spinning up one per test method
MiniClusterHarness harness = new MiniClusterHarness();
if (TRUE.equals(System.getProperty(MiniClusterHarness.USE_KERBEROS_FOR_IT_OPTION))) {
krb = new TestingKdc();
krb.start();
// Enabled krb auth
Configuration conf = new Configuration(false);
conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
UserGroupInformation.setConfiguration(conf);
// Login as the client
ClusterUser rootUser = krb.getRootUser();
// Get the krb token
UserGroupInformation.loginUserFromKeytab(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
token = new KerberosToken();
} else {
rootPassword = "rootPasswordShared1";
token = new PasswordToken(rootPassword);
}
cluster = harness.create(SharedMiniClusterBase.class.getName(), System.currentTimeMillis() + "_" + new Random().nextInt(Short.MAX_VALUE), token,
miniClusterCallback, krb);
cluster.start();
if (null != krb) {
final String traceTable = Property.TRACE_TABLE.getDefaultValue();
final ClusterUser systemUser = krb.getAccumuloServerUser(), rootUser = krb.getRootUser();
// Login as the trace user
// Open a connector as the system user (ensures the user will exist for us to assign permissions to)
UserGroupInformation.loginUserFromKeytab(systemUser.getPrincipal(), systemUser.getKeytab().getAbsolutePath());
Connector conn = cluster.getConnector(systemUser.getPrincipal(), new KerberosToken());
// Then, log back in as the "root" user and do the grant
UserGroupInformation.loginUserFromKeytab(rootUser.getPrincipal(), rootUser.getKeytab().getAbsolutePath());
conn = cluster.getConnector(principal, token);
// Create the trace table
conn.tableOperations().create(traceTable);
// Trace user (which is the same kerberos principal as the system user, but using a normal KerberosToken) needs
// to have the ability to read, write and alter the trace table
conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.READ);
conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.WRITE);
conn.securityOperations().grantTablePermission(systemUser.getPrincipal(), traceTable, TablePermission.ALTER_TABLE);
}
}
/**
* Stops the MiniAccumuloCluster and related services if they are running.
*/
public static void stopMiniCluster() throws Exception {
if (null != cluster) {
try {
cluster.stop();
} catch (Exception e) {
log.error("Failed to stop minicluster", e);
}
}
if (null != krb) {
try {
krb.stop();
} catch (Exception e) {
log.error("Failed to stop KDC", e);
}
}
}
public static String getRootPassword() {
return rootPassword;
}
public static AuthenticationToken getToken() {
if (token instanceof KerberosToken) {
try {
UserGroupInformation.loginUserFromKeytab(getPrincipal(), krb.getRootUser().getKeytab().getAbsolutePath());
} catch (IOException e) {
throw new RuntimeException("Failed to login", e);
}
}
return token;
}
public static String getPrincipal() {
return principal;
}
public static MiniAccumuloClusterImpl getCluster() {
return cluster;
}
public static File getMiniClusterDir() {
return cluster.getConfig().getDir();
}
public static Connector getConnector() {
try {
return getCluster().getConnector(principal, getToken());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static TestingKdc getKdc() {
return krb;
}
@Override
public ClusterUser getAdminUser() {
if (null == krb) {
return new ClusterUser(getPrincipal(), getRootPassword());
} else {
return krb.getRootUser();
}
}
@Override
public ClusterUser getUser(int offset) {
if (null == krb) {
String user = SharedMiniClusterBase.class.getName() + "_" + testName.getMethodName() + "_" + offset;
// Password is the username
return new ClusterUser(user, user);
} else {
return krb.getClientPrincipal(offset);
}
}
}