/** * 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.hadoop.hbase.test; import java.io.IOException; import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Iterator; import java.util.UUID; import org.apache.commons.cli.CommandLine; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.IntegrationTestingUtility; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.chaos.factories.MonkeyFactory; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.BufferedMutator; import org.apache.hadoop.hbase.client.BufferedMutatorParams; import org.apache.hadoop.hbase.client.ClusterConnection; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.io.hfile.HFile; import org.apache.hadoop.hbase.mapreduce.Import; import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.access.AccessControlClient; import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.security.visibility.Authorizations; import org.apache.hadoop.hbase.security.visibility.CellVisibility; import org.apache.hadoop.hbase.security.visibility.VisibilityClient; import org.apache.hadoop.hbase.security.visibility.VisibilityController; import org.apache.hadoop.hbase.testclassification.IntegrationTests; import org.apache.hadoop.hbase.util.AbstractHBaseTool; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.mapreduce.Counter; import org.apache.hadoop.mapreduce.CounterGroup; import org.apache.hadoop.mapreduce.Counters; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; import org.apache.hadoop.util.Tool; import org.apache.hadoop.util.ToolRunner; import org.junit.Test; import org.junit.experimental.categories.Category; /** * IT test used to verify the deletes with visibility labels. * The test creates three tables tablename_0, tablename_1 and tablename_2 and each table * is associated with a unique pair of labels. * Another common table with the name 'commontable' is created and it has the data combined * from all these 3 tables such that there are 3 versions of every row but the visibility label * in every row corresponds to the table from which the row originated. * Then deletes are issued to the common table by selecting the visibility label * associated with each of the smaller tables. * After the delete is issued with one set of visibility labels we try to scan the common table * with each of the visibility pairs defined for the 3 tables. * So after the first delete is issued, a scan with the first set of visibility labels would * return zero result whereas the scan issued with the other two sets of visibility labels * should return all the rows corresponding to that set of visibility labels. The above * process of delete and scan is repeated until after the last set of visibility labels are * used for the deletes the common table should not return any row. * * To use this * ./hbase org.apache.hadoop.hbase.test.IntegrationTestBigLinkedListWithVisibility Loop 1 1 20000 /tmp 1 10000 * or * ./hbase org.apache.hadoop.hbase.IntegrationTestsDriver -r .*IntegrationTestBigLinkedListWithVisibility.* */ @Category(IntegrationTests.class) public class IntegrationTestBigLinkedListWithVisibility extends IntegrationTestBigLinkedList { private static final String CONFIDENTIAL = "confidential"; private static final String TOPSECRET = "topsecret"; private static final String SECRET = "secret"; private static final String PUBLIC = "public"; private static final String PRIVATE = "private"; private static final String EVERYONE = "everyone"; private static final String RESTRICTED = "restricted"; private static final String GROUP = "group"; private static final String PREVILIGED = "previliged"; private static final String OPEN = "open"; public static String labels = CONFIDENTIAL + "," + TOPSECRET + "," + SECRET + "," + RESTRICTED + "," + PRIVATE + "," + PREVILIGED + "," + GROUP + "," + OPEN + "," + PUBLIC + "," + EVERYONE; private static final String COMMA = ","; private static final String UNDER_SCORE = "_"; public static int DEFAULT_TABLES_COUNT = 3; public static String tableName = "tableName"; public static final String COMMON_TABLE_NAME = "commontable"; public static final String LABELS_KEY = "LABELS"; public static final String INDEX_KEY = "INDEX"; private static User USER; private static final String OR = "|"; private static String USER_OPT = "user"; private static String userName = "user1"; static class VisibilityGenerator extends Generator { private static final Log LOG = LogFactory.getLog(VisibilityGenerator.class); @Override protected void createSchema() throws IOException { LOG.info("Creating tables"); // Create three tables boolean acl = AccessControlClient.isAccessControllerRunning(ConnectionFactory .createConnection(getConf())); if(!acl) { LOG.info("No ACL available."); } try (Connection conn = ConnectionFactory.createConnection(getConf()); Admin admin = conn.getAdmin()) { for (int i = 0; i < DEFAULT_TABLES_COUNT; i++) { TableName tableName = IntegrationTestBigLinkedListWithVisibility.getTableName(i); createTable(admin, tableName, false, acl); } TableName tableName = TableName.valueOf(COMMON_TABLE_NAME); createTable(admin, tableName, true, acl); } } private void createTable(Admin admin, TableName tableName, boolean setVersion, boolean acl) throws IOException { if (!admin.tableExists(tableName)) { HTableDescriptor htd = new HTableDescriptor(tableName); HColumnDescriptor family = new HColumnDescriptor(FAMILY_NAME); if (setVersion) { family.setMaxVersions(DEFAULT_TABLES_COUNT); } htd.addFamily(family); admin.createTable(htd); if (acl) { LOG.info("Granting permissions for user " + USER.getShortName()); Permission.Action[] actions = { Permission.Action.READ }; try { AccessControlClient.grant(ConnectionFactory.createConnection(getConf()), tableName, USER.getShortName(), null, null, actions); } catch (Throwable e) { LOG.fatal("Error in granting permission for the user " + USER.getShortName(), e); throw new IOException(e); } } } } @Override protected void setMapperForGenerator(Job job) { job.setMapperClass(VisibilityGeneratorMapper.class); } static class VisibilityGeneratorMapper extends GeneratorMapper { BufferedMutator[] tables = new BufferedMutator[DEFAULT_TABLES_COUNT]; @Override protected void setup(org.apache.hadoop.mapreduce.Mapper.Context context) throws IOException, InterruptedException { super.setup(context); } @Override protected void instantiateHTable() throws IOException { for (int i = 0; i < DEFAULT_TABLES_COUNT; i++) { BufferedMutatorParams params = new BufferedMutatorParams(getTableName(i)); params.writeBufferSize(4 * 1024 * 1024); BufferedMutator table = connection.getBufferedMutator(params); this.tables[i] = table; } } @Override protected void cleanup(org.apache.hadoop.mapreduce.Mapper.Context context) throws IOException, InterruptedException { for (int i = 0; i < DEFAULT_TABLES_COUNT; i++) { if (tables[i] != null) { tables[i].close(); } } } @Override protected void persist(org.apache.hadoop.mapreduce.Mapper.Context output, long count, byte[][] prev, byte[][] current, byte[] id) throws IOException { String visibilityExps = ""; String[] split = labels.split(COMMA); for (int i = 0; i < current.length; i++) { for (int j = 0; j < DEFAULT_TABLES_COUNT; j++) { Put put = new Put(current[i]); byte[] value = prev == null ? NO_KEY : prev[i]; put.addColumn(FAMILY_NAME, COLUMN_PREV, value); if (count >= 0) { put.addColumn(FAMILY_NAME, COLUMN_COUNT, Bytes.toBytes(count + i)); } if (id != null) { put.addColumn(FAMILY_NAME, COLUMN_CLIENT, id); } visibilityExps = split[j * 2] + OR + split[(j * 2) + 1]; put.setCellVisibility(new CellVisibility(visibilityExps)); tables[j].mutate(put); try { Thread.sleep(1); } catch (InterruptedException e) { throw new IOException(); } } if (i % 1000 == 0) { // Tickle progress every so often else maprunner will think us hung output.progress(); } } } } } static class Copier extends Configured implements Tool { private static final Log LOG = LogFactory.getLog(Copier.class); private TableName tableName; private int labelIndex; private boolean delete; public Copier(TableName tableName, int index, boolean delete) { this.tableName = tableName; this.labelIndex = index; this.delete = delete; } public int runCopier(String outputDir) throws Exception { Job job = null; Scan scan = null; job = new Job(getConf()); job.setJobName("Data copier"); job.getConfiguration().setInt("INDEX", labelIndex); job.getConfiguration().set("LABELS", labels); job.setJarByClass(getClass()); scan = new Scan(); scan.setCacheBlocks(false); scan.setRaw(true); String[] split = labels.split(COMMA); scan.setAuthorizations(new Authorizations(split[this.labelIndex * 2], split[(this.labelIndex * 2) + 1])); if (delete) { LOG.info("Running deletes"); } else { LOG.info("Running copiers"); } if (delete) { TableMapReduceUtil.initTableMapperJob(tableName.getNameAsString(), scan, VisibilityDeleteImport.class, null, null, job); } else { TableMapReduceUtil.initTableMapperJob(tableName.getNameAsString(), scan, VisibilityImport.class, null, null, job); } job.getConfiguration().setBoolean("mapreduce.map.speculative", false); job.getConfiguration().setBoolean("mapreduce.reduce.speculative", false); TableMapReduceUtil.initTableReducerJob(COMMON_TABLE_NAME, null, job, null, null, null, null); TableMapReduceUtil.addDependencyJars(job); TableMapReduceUtil.addDependencyJars(job.getConfiguration(), AbstractHBaseTool.class); TableMapReduceUtil.initCredentials(job); job.setNumReduceTasks(0); boolean success = job.waitForCompletion(true); return success ? 0 : 1; } @Override public int run(String[] arg0) throws Exception { // TODO Auto-generated method stub return 0; } } static class VisibilityImport extends Import.Importer { private int index; private String labels; private String[] split; @Override public void setup(org.apache.hadoop.mapreduce.Mapper.Context context) { index = context.getConfiguration().getInt(INDEX_KEY, -1); labels = context.getConfiguration().get(LABELS_KEY); split = labels.split(COMMA); super.setup(context); } @Override protected void addPutToKv(Put put, Cell kv) throws IOException { String visibilityExps = split[index * 2] + OR + split[(index * 2) + 1]; put.setCellVisibility(new CellVisibility(visibilityExps)); super.addPutToKv(put, kv); } } static class VisibilityDeleteImport extends Import.Importer { private int index; private String labels; private String[] split; @Override public void setup(org.apache.hadoop.mapreduce.Mapper.Context context) { index = context.getConfiguration().getInt(INDEX_KEY, -1); labels = context.getConfiguration().get(LABELS_KEY); split = labels.split(COMMA); super.setup(context); } // Creating delete here @Override protected void processKV(ImmutableBytesWritable key, Result result, org.apache.hadoop.mapreduce.Mapper.Context context, Put put, org.apache.hadoop.hbase.client.Delete delete) throws IOException, InterruptedException { String visibilityExps = split[index * 2] + OR + split[(index * 2) + 1]; for (Cell kv : result.rawCells()) { // skip if we filter it out if (kv == null) continue; // Create deletes here if (delete == null) { delete = new Delete(key.get()); } delete.setCellVisibility(new CellVisibility(visibilityExps)); delete.addFamily(CellUtil.cloneFamily(kv)); } if (delete != null) { context.write(key, delete); } } } @Override protected void addOptions() { super.addOptions(); addOptWithArg("u", USER_OPT, "User name"); } @Override protected void processOptions(CommandLine cmd) { super.processOptions(cmd); if (cmd.hasOption(USER_OPT)) { userName = cmd.getOptionValue(USER_OPT); } } @Override public void setUpCluster() throws Exception { util = getTestingUtil(null); Configuration conf = util.getConfiguration(); conf.setInt(HFile.FORMAT_VERSION_KEY, 3); conf.set("hbase.coprocessor.master.classes", VisibilityController.class.getName()); conf.set("hbase.coprocessor.region.classes", VisibilityController.class.getName()); conf.set("hbase.superuser", User.getCurrent().getName()); conf.setBoolean("dfs.permissions", false); USER = User.createUserForTesting(conf, userName, new String[] {}); super.setUpCluster(); addLabels(); } static TableName getTableName(int i) { return TableName.valueOf(tableName + UNDER_SCORE + i); } private void addLabels() throws Exception { try { VisibilityClient.addLabels(util.getConnection(), labels.split(COMMA)); VisibilityClient.setAuths(util.getConnection(), labels.split(COMMA), USER.getName()); } catch (Throwable t) { throw new IOException(t); } } static class VisibilityVerify extends Verify { private static final Log LOG = LogFactory.getLog(VisibilityVerify.class); private TableName tableName; private int labelIndex; public VisibilityVerify(String tableName, int index) { this.tableName = TableName.valueOf(tableName); this.labelIndex = index; } @Override public int run(final Path outputDir, final int numReducers) throws Exception { LOG.info("Running Verify with outputDir=" + outputDir + ", numReducers=" + numReducers); PrivilegedExceptionAction<Integer> scanAction = new PrivilegedExceptionAction<Integer>() { @Override public Integer run() throws Exception { return doVerify(outputDir, numReducers); } }; return USER.runAs(scanAction); } private int doVerify(Path outputDir, int numReducers) throws IOException, InterruptedException, ClassNotFoundException { job = new Job(getConf()); job.setJobName("Link Verifier"); job.setNumReduceTasks(numReducers); job.setJarByClass(getClass()); setJobScannerConf(job); Scan scan = new Scan(); scan.addColumn(FAMILY_NAME, COLUMN_PREV); scan.setCaching(10000); scan.setCacheBlocks(false); String[] split = labels.split(COMMA); scan.setAuthorizations(new Authorizations(split[this.labelIndex * 2], split[(this.labelIndex * 2) + 1])); TableMapReduceUtil.initTableMapperJob(tableName.getName(), scan, VerifyMapper.class, BytesWritable.class, BytesWritable.class, job); TableMapReduceUtil.addDependencyJars(job.getConfiguration(), AbstractHBaseTool.class); job.getConfiguration().setBoolean("mapreduce.map.speculative", false); job.setReducerClass(VerifyReducer.class); job.setOutputFormatClass(TextOutputFormat.class); TextOutputFormat.setOutputPath(job, outputDir); boolean success = job.waitForCompletion(true); return success ? 0 : 1; } @Override protected void handleFailure(Counters counters) throws IOException { Configuration conf = job.getConfiguration(); ClusterConnection conn = (ClusterConnection) ConnectionFactory.createConnection(conf); TableName tableName = TableName.valueOf(COMMON_TABLE_NAME); CounterGroup g = counters.getGroup("undef"); Iterator<Counter> it = g.iterator(); while (it.hasNext()) { String keyString = it.next().getName(); byte[] key = Bytes.toBytes(keyString); HRegionLocation loc = conn.relocateRegion(tableName, key); LOG.error("undefined row " + keyString + ", " + loc); } g = counters.getGroup("unref"); it = g.iterator(); while (it.hasNext()) { String keyString = it.next().getName(); byte[] key = Bytes.toBytes(keyString); HRegionLocation loc = conn.relocateRegion(tableName, key); LOG.error("unreferred row " + keyString + ", " + loc); } } } static class VisibilityLoop extends Loop { private static final int SLEEP_IN_MS = 5000; private static final Log LOG = LogFactory.getLog(VisibilityLoop.class); IntegrationTestBigLinkedListWithVisibility it; @Override protected void runGenerator(int numMappers, long numNodes, String outputDir, Integer width, Integer wrapMultiplier, Integer numWalkers) throws Exception { Path outputPath = new Path(outputDir); UUID uuid = UUID.randomUUID(); // create a random UUID. Path generatorOutput = new Path(outputPath, uuid.toString()); Generator generator = new VisibilityGenerator(); generator.setConf(getConf()); int retCode = generator.run(numMappers, numNodes, generatorOutput, width, wrapMultiplier, numWalkers); if (retCode > 0) { throw new RuntimeException("Generator failed with return code: " + retCode); } } protected void runDelete(int numMappers, long numNodes, String outputDir, Integer width, Integer wrapMultiplier, int tableIndex) throws Exception { LOG.info("Running copier on table "+IntegrationTestBigLinkedListWithVisibility.getTableName(tableIndex)); Copier copier = new Copier( IntegrationTestBigLinkedListWithVisibility.getTableName(tableIndex), tableIndex, true); copier.setConf(getConf()); copier.runCopier(outputDir); Thread.sleep(SLEEP_IN_MS); } protected void runVerify(String outputDir, int numReducers, long expectedNumNodes, boolean allTables) throws Exception { Path outputPath = new Path(outputDir); if (allTables) { for (int i = 0; i < DEFAULT_TABLES_COUNT; i++) { LOG.info("Verifying table " + i); sleep(SLEEP_IN_MS); UUID uuid = UUID.randomUUID(); // create a random UUID. Path iterationOutput = new Path(outputPath, uuid.toString()); Verify verify = new VisibilityVerify(getTableName(i).getNameAsString(), i); verify(numReducers, expectedNumNodes, iterationOutput, verify); } } for (int i = 0; i < DEFAULT_TABLES_COUNT; i++) { runVerifyCommonTable(outputDir, numReducers, expectedNumNodes, i); } } private void runVerify(String outputDir, int numReducers, long expectedNodes, int tableIndex) throws Exception { long temp = expectedNodes; for (int i = 0; i < DEFAULT_TABLES_COUNT; i++) { if (i <= tableIndex) { expectedNodes = 0; } else { expectedNodes = temp; } LOG.info("Verifying data in the table with index "+i+ " and expected nodes is "+expectedNodes); runVerifyCommonTable(outputDir, numReducers, expectedNodes, i); } } private void sleep(long ms) throws InterruptedException { Thread.sleep(ms); } protected void runVerifyCommonTable(String outputDir, int numReducers, long expectedNumNodes, int index) throws Exception { LOG.info("Verifying common table with index " + index); sleep(SLEEP_IN_MS); Path outputPath = new Path(outputDir); UUID uuid = UUID.randomUUID(); // create a random UUID. Path iterationOutput = new Path(outputPath, uuid.toString()); Verify verify = new VisibilityVerify(TableName.valueOf(COMMON_TABLE_NAME).getNameAsString(), index); verify(numReducers, expectedNumNodes, iterationOutput, verify); } protected void runCopier(String outputDir) throws Exception { for (int i = 0; i < DEFAULT_TABLES_COUNT; i++) { LOG.info("Running copier " + IntegrationTestBigLinkedListWithVisibility.getTableName(i)); sleep(SLEEP_IN_MS); Copier copier = new Copier(IntegrationTestBigLinkedListWithVisibility.getTableName(i), i, false); copier.setConf(getConf()); copier.runCopier(outputDir); } } private void verify(int numReducers, long expectedNumNodes, Path iterationOutput, Verify verify) throws Exception { verify.setConf(getConf()); int retCode = verify.run(iterationOutput, numReducers); if (retCode > 0) { throw new RuntimeException("Verify.run failed with return code: " + retCode); } if (!verify.verify(expectedNumNodes)) { throw new RuntimeException("Verify.verify failed"); } LOG.info("Verify finished with succees. Total nodes=" + expectedNumNodes); } @Override public int run(String[] args) throws Exception { if (args.length < 5) { System.err .println("Usage: Loop <num iterations> " + "<num mappers> <num nodes per mapper> <output dir> " + "<num reducers> [<width> <wrap multiplier>]"); return 1; } LOG.info("Running Loop with args:" + Arrays.deepToString(args)); int numIterations = Integer.parseInt(args[0]); int numMappers = Integer.parseInt(args[1]); long numNodes = Long.parseLong(args[2]); String outputDir = args[3]; int numReducers = Integer.parseInt(args[4]); Integer width = (args.length < 6) ? null : Integer.parseInt(args[5]); Integer wrapMultiplier = (args.length < 7) ? null : Integer.parseInt(args[6]); long expectedNumNodes = 0; if (numIterations < 0) { numIterations = Integer.MAX_VALUE; // run indefinitely (kind of) } for (int i = 0; i < numIterations; i++) { LOG.info("Starting iteration = " + i); LOG.info("Generating data"); // By default run no concurrent walkers for test with visibility runGenerator(numMappers, numNodes, outputDir, width, wrapMultiplier, 0); expectedNumNodes += numMappers * numNodes; // Copying wont work because expressions are not returned back to the // client LOG.info("Running copier"); sleep(SLEEP_IN_MS); runCopier(outputDir); LOG.info("Verifying copied data"); sleep(SLEEP_IN_MS); runVerify(outputDir, numReducers, expectedNumNodes, true); sleep(SLEEP_IN_MS); for (int j = 0; j < DEFAULT_TABLES_COUNT; j++) { LOG.info("Deleting data on table with index: "+j); runDelete(numMappers, numNodes, outputDir, width, wrapMultiplier, j); sleep(SLEEP_IN_MS); LOG.info("Verifying common table after deleting"); runVerify(outputDir, numReducers, expectedNumNodes, j); sleep(SLEEP_IN_MS); } } return 0; } } @Override @Test public void testContinuousIngest() throws IOException, Exception { // Loop <num iterations> <num mappers> <num nodes per mapper> <output dir> // <num reducers> int ret = ToolRunner.run( getTestingUtil(getConf()).getConfiguration(), new VisibilityLoop(), new String[] { "1", "1", "20000", util.getDataTestDirOnTestFS("IntegrationTestBigLinkedListWithVisibility").toString(), "1", "10000" }); org.junit.Assert.assertEquals(0, ret); } public static void main(String[] args) throws Exception { Configuration conf = HBaseConfiguration.create(); IntegrationTestingUtility.setUseDistributedCluster(conf); int ret = ToolRunner.run(conf, new IntegrationTestBigLinkedListWithVisibility(), args); System.exit(ret); } @Override protected MonkeyFactory getDefaultMonkeyFactory() { return MonkeyFactory.getFactory(MonkeyFactory.CALM); } @Override public int runTestFromCommandLine() throws Exception { Tool tool = null; Loop loop = new VisibilityLoop(); loop.it = this; tool = loop; return ToolRunner.run(getConf(), tool, otherArgs); } }