View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    * <p/>
10   * http://www.apache.org/licenses/LICENSE-2.0
11   * <p/>
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.test;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertTrue;
22  
23  import java.io.IOException;
24  import java.security.PrivilegedExceptionAction;
25  import java.util.List;
26  
27  import org.apache.commons.cli.CommandLine;
28  import org.apache.hadoop.conf.Configuration;
29  import org.apache.hadoop.fs.Path;
30  import org.apache.hadoop.hbase.HBaseConfiguration;
31  import org.apache.hadoop.hbase.HColumnDescriptor;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.HTableDescriptor;
34  import org.apache.hadoop.hbase.IntegrationTestingUtility;
35  import org.apache.hadoop.hbase.IntegrationTests;
36  import org.apache.hadoop.hbase.TableName;
37  import org.apache.hadoop.hbase.client.HBaseAdmin;
38  import org.apache.hadoop.hbase.client.Put;
39  import org.apache.hadoop.hbase.client.Result;
40  import org.apache.hadoop.hbase.client.Scan;
41  import org.apache.hadoop.hbase.client.ScannerCallable;
42  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
43  import org.apache.hadoop.hbase.io.hfile.HFile;
44  import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
45  import org.apache.hadoop.hbase.mapreduce.TableMapper;
46  import org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl;
47  import org.apache.hadoop.hbase.security.User;
48  import org.apache.hadoop.hbase.security.visibility.Authorizations;
49  import org.apache.hadoop.hbase.security.visibility.CellVisibility;
50  import org.apache.hadoop.hbase.security.visibility.VisibilityClient;
51  import org.apache.hadoop.hbase.security.visibility.VisibilityController;
52  import org.apache.hadoop.hbase.util.AbstractHBaseTool;
53  import org.apache.hadoop.hbase.util.Bytes;
54  import org.apache.hadoop.io.BytesWritable;
55  import org.apache.hadoop.io.NullWritable;
56  import org.apache.hadoop.mapreduce.Counter;
57  import org.apache.hadoop.mapreduce.Job;
58  import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
59  import org.apache.hadoop.util.ToolRunner;
60  import org.junit.experimental.categories.Category;
61  
62  /**
63   * A large test which loads a lot of data with cell visibility, and verifies the data. Test adds 2
64   * users with different sets of visibility labels authenticated for them. Every row (so cells in
65   * that) added with visibility expressions. In load step, 200 map tasks are launched, which in turn
66   * write loadmapper.num_to_write (default 100K) rows to an hbase table. Rows are written in blocks,
67   * for a total of 100 blocks.
68   * 
69   * Verify step scans the table as both users with Authorizations. This step asserts that user can
70   * see only those rows (and so cells) with visibility for which they have label auth.
71   * 
72   * This class can be run as a unit test, as an integration test, or from the command line.
73   * 
74   * Originally taken from Apache Bigtop.
75   */
76  @Category(IntegrationTests.class)
77  public class IntegrationTestWithCellVisibilityLoadAndVerify extends IntegrationTestLoadAndVerify {
78    private static final char NOT = '!';
79    private static final char OR = '|';
80    private static final char AND = '&';
81    private static final String TEST_NAME = "IntegrationTestCellVisibilityLoadAndVerify";
82    private static final String CONFIDENTIAL = "confidential";
83    private static final String TOPSECRET = "topsecret";
84    private static final String SECRET = "secret";
85    private static final String PUBLIC = "public";
86    private static final String PRIVATE = "private";
87    private static final String[] LABELS = { CONFIDENTIAL, TOPSECRET, SECRET, PRIVATE, PUBLIC };
88    private static final String[] VISIBILITY_EXPS = { CONFIDENTIAL + AND + TOPSECRET + AND + PRIVATE,
89        CONFIDENTIAL + OR + TOPSECRET, PUBLIC,
90        '(' + SECRET + OR + PRIVATE + ')' + AND + NOT + CONFIDENTIAL };
91    private static final int VISIBILITY_EXPS_COUNT = VISIBILITY_EXPS.length;
92    private static final byte[] TEST_FAMILY = Bytes.toBytes("f1");
93    private static final byte[] TEST_QUALIFIER = Bytes.toBytes("q1");
94    private static final String NUM_TO_WRITE_KEY = "loadmapper.num_to_write";
95    private static final long NUM_TO_WRITE_DEFAULT = 100 * 1000;
96    private static final int SCANNER_CACHING = 500;
97  
98    private long numRowsLoadedWithExp1, numRowsLoadedWithExp2, numRowsLoadWithExp3,
99        numRowsLoadWithExp4;
100   private long numRowsReadWithExp1, numRowsReadWithExp2, numRowsReadWithExp3, numRowsReadWithExp4;
101 
102   private static User USER1, USER2;
103 
104   private enum Counters {
105     ROWS_VIS_EXP_1, ROWS_VIS_EXP_2, ROWS_VIS_EXP_3, ROWS_VIS_EXP_4;
106   }
107 
108   @Override
109   public void setUpCluster() throws Exception {
110     util = getTestingUtil(null);
111     Configuration conf = util.getConfiguration();
112     conf.setInt(HFile.FORMAT_VERSION_KEY, 3);
113     conf.set("hbase.coprocessor.master.classes", VisibilityController.class.getName());
114     conf.set("hbase.coprocessor.region.classes", VisibilityController.class.getName());
115     conf.set("hbase.superuser", User.getCurrent().getName());
116     super.setUpCluster();
117     USER1 = User.createUserForTesting(conf, "user1", new String[] {});
118     USER2 = User.createUserForTesting(conf, "user2", new String[] {});
119     addLabelsAndAuths();
120   }
121 
122   private void addLabelsAndAuths() throws Exception {
123     try {
124       VisibilityClient.addLabels(util.getConfiguration(), LABELS);
125       VisibilityClient.setAuths(util.getConfiguration(), new String[] { CONFIDENTIAL, TOPSECRET,
126           SECRET, PRIVATE }, USER1.getName());
127       VisibilityClient.setAuths(util.getConfiguration(), new String[] { PUBLIC },
128           USER2.getName());
129     } catch (Throwable t) {
130       throw new IOException(t);
131     }
132   }
133 
134   public static class LoadWithCellVisibilityMapper extends LoadMapper {
135     private Counter rowsExp1, rowsExp2, rowsExp3, rowsexp4;
136 
137     @Override
138     public void setup(Context context) throws IOException {
139       super.setup(context);
140       rowsExp1 = context.getCounter(Counters.ROWS_VIS_EXP_1);
141       rowsExp2 = context.getCounter(Counters.ROWS_VIS_EXP_2);
142       rowsExp3 = context.getCounter(Counters.ROWS_VIS_EXP_3);
143       rowsexp4 = context.getCounter(Counters.ROWS_VIS_EXP_4);
144     }
145 
146     @Override
147     protected void map(NullWritable key, NullWritable value, Context context) throws IOException,
148         InterruptedException {
149       String suffix = "/" + shortTaskId;
150       int BLOCK_SIZE = (int) (recordsToWrite / 100);
151       for (long i = 0; i < recordsToWrite;) {
152         for (long idx = 0; idx < BLOCK_SIZE && i < recordsToWrite; idx++, i++) {
153           int expIdx = rand.nextInt(BLOCK_SIZE) % VISIBILITY_EXPS_COUNT;
154           String exp = VISIBILITY_EXPS[expIdx];
155           byte[] row = Bytes.add(Bytes.toBytes(i), Bytes.toBytes(suffix), Bytes.toBytes(exp));
156           Put p = new Put(row);
157           p.add(TEST_FAMILY, TEST_QUALIFIER, HConstants.EMPTY_BYTE_ARRAY);
158           p.setCellVisibility(new CellVisibility(exp));
159           getCounter(expIdx).increment(1);
160           table.put(p);
161 
162           if (i % 100 == 0) {
163             context.setStatus("Written " + i + "/" + recordsToWrite + " records");
164             context.progress();
165           }
166         }
167         // End of block, flush all of them before we start writing anything
168         // pointing to these!
169         table.flushCommits();
170       }
171     }
172 
173     private Counter getCounter(int idx) {
174       switch (idx) {
175       case 0:
176         return rowsExp1;
177       case 1:
178         return rowsExp2;
179       case 2:
180         return rowsExp3;
181       case 3:
182         return rowsexp4;
183       default:
184         return null;
185       }
186     }
187   }
188 
189   public static class VerifyMapper extends TableMapper<BytesWritable, BytesWritable> {
190     private Counter rowsExp1, rowsExp2, rowsExp3, rowsExp4;
191 
192     @Override
193     public void setup(Context context) throws IOException {
194       rowsExp1 = context.getCounter(Counters.ROWS_VIS_EXP_1);
195       rowsExp2 = context.getCounter(Counters.ROWS_VIS_EXP_2);
196       rowsExp3 = context.getCounter(Counters.ROWS_VIS_EXP_3);
197       rowsExp4 = context.getCounter(Counters.ROWS_VIS_EXP_4);
198     }
199 
200     @Override
201     protected void map(ImmutableBytesWritable key, Result value, Context context)
202         throws IOException, InterruptedException {
203       byte[] row = value.getRow();
204       Counter c = getCounter(row);
205       c.increment(1);
206     }
207 
208     private Counter getCounter(byte[] row) {
209       Counter c = null;
210       if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[0])) != -1) {
211         c = rowsExp1;
212       } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[1])) != -1) {
213         c = rowsExp2;
214       } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[2])) != -1) {
215         c = rowsExp3;
216       } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[3])) != -1) {
217         c = rowsExp4;
218       }
219       return c;
220     }
221   }
222 
223   @Override
224   protected Job doLoad(Configuration conf, HTableDescriptor htd) throws Exception {
225     Job job = super.doLoad(conf, htd);
226     this.numRowsLoadedWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
227     this.numRowsLoadedWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
228     this.numRowsLoadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
229     this.numRowsLoadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
230     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[0] + " : "
231         + this.numRowsLoadedWithExp1);
232     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[1] + " : "
233         + this.numRowsLoadedWithExp2);
234     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[2] + " : "
235         + this.numRowsLoadWithExp3);
236     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[3] + " : "
237         + this.numRowsLoadWithExp4);
238     return job;
239   }
240 
241   protected void setMapperClass(Job job) {
242     job.setMapperClass(LoadWithCellVisibilityMapper.class);
243   }
244 
245   protected void doVerify(final Configuration conf, final HTableDescriptor htd) throws Exception {
246     System.out.println(String.format("Verifying for auths %s, %s, %s, %s", CONFIDENTIAL, TOPSECRET,
247         SECRET, PRIVATE));
248     PrivilegedExceptionAction<Job> scanAction = new PrivilegedExceptionAction<Job>() {
249       @Override
250       public Job run() throws Exception {
251         return doVerify(conf, htd, CONFIDENTIAL, TOPSECRET, SECRET, PRIVATE);
252       }
253     };
254     Job job = USER1.runAs(scanAction);
255     this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
256     this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
257     this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
258     this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
259     assertEquals(this.numRowsLoadedWithExp1, this.numRowsReadWithExp1);
260     assertEquals(this.numRowsLoadedWithExp2, this.numRowsReadWithExp2);
261     assertEquals(0, this.numRowsReadWithExp3);
262     assertEquals(0, this.numRowsReadWithExp4);
263 
264     // PUBLIC label auth is not provided for user1 user.
265     System.out.println(String.format("Verifying for auths %s, %s", PRIVATE, PUBLIC));
266     scanAction = new PrivilegedExceptionAction<Job>() {
267       @Override
268       public Job run() throws Exception {
269         return doVerify(conf, htd, PRIVATE, PUBLIC);
270       }
271     };
272     job = USER1.runAs(scanAction);
273     this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
274     this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
275     this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
276     this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
277     assertEquals(0, this.numRowsReadWithExp1);
278     assertEquals(0, this.numRowsReadWithExp2);
279     assertEquals(0, this.numRowsReadWithExp3);
280     assertEquals(this.numRowsLoadWithExp4, this.numRowsReadWithExp4);
281 
282     // Normal user only having PUBLIC label auth and can view only those cells.
283     System.out.println(String.format("Verifying for auths %s, %s", PRIVATE, PUBLIC));
284     scanAction = new PrivilegedExceptionAction<Job>() {
285       @Override
286       public Job run() throws Exception {
287         return doVerify(conf, htd, PRIVATE, PUBLIC);
288       }
289     };
290     job = USER2.runAs(scanAction);
291     this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
292     this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
293     this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
294     this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
295     assertEquals(0, this.numRowsReadWithExp1);
296     assertEquals(0, this.numRowsReadWithExp2);
297     assertEquals(this.numRowsLoadWithExp3, this.numRowsReadWithExp3);
298     assertEquals(0, this.numRowsReadWithExp4);
299   }
300 
301   private Job doVerify(Configuration conf, HTableDescriptor htd, String... auths)
302       throws IOException, InterruptedException, ClassNotFoundException {
303     Path outputDir = getTestDir(TEST_NAME, "verify-output");
304     Job job = new Job(conf);
305     job.setJarByClass(this.getClass());
306     job.setJobName(TEST_NAME + " Verification for " + htd.getTableName());
307     setJobScannerConf(job);
308     Scan scan = new Scan();
309     scan.setAuthorizations(new Authorizations(auths));
310     TableMapReduceUtil.initTableMapperJob(htd.getTableName().getNameAsString(), scan,
311         VerifyMapper.class, NullWritable.class, NullWritable.class, job);
312     TableMapReduceUtil.addDependencyJars(job.getConfiguration(), AbstractHBaseTool.class);
313     int scannerCaching = conf.getInt("verify.scannercaching", SCANNER_CACHING);
314     TableMapReduceUtil.setScannerCaching(job, scannerCaching);
315     job.setNumReduceTasks(0);
316     FileOutputFormat.setOutputPath(job, outputDir);
317     assertTrue(job.waitForCompletion(true));
318     return job;
319   }
320 
321   private static void setJobScannerConf(Job job) {
322     job.getConfiguration().setBoolean(ScannerCallable.LOG_SCANNER_ACTIVITY, true);
323     long lpr = job.getConfiguration().getLong(NUM_TO_WRITE_KEY, NUM_TO_WRITE_DEFAULT) / 100;
324     job.getConfiguration().setInt(TableRecordReaderImpl.LOG_PER_ROW_COUNT, (int) lpr);
325   }
326 
327   public void usage() {
328     System.err.println(this.getClass().getSimpleName() + " [-Doptions]");
329     System.err.println("  Loads a table with cell visibilities and verifies with Authorizations");
330     System.err.println("Options");
331     System.err
332         .println("  -Dloadmapper.table=<name>        Table to write/verify (default autogen)");
333     System.err.println("  -Dloadmapper.num_to_write=<n>    "
334         + "Number of rows per mapper (default 100,000 per mapper)");
335     System.err.println("  -Dloadmapper.numPresplits=<n>    "
336         + "Number of presplit regions to start with (default 40)");
337     System.err
338         .println("  -Dloadmapper.map.tasks=<n>       Number of map tasks for load (default 200)");
339     System.err.println("  -Dverify.scannercaching=<n>      "
340         + "Number hbase scanner caching rows to read (default 50)");
341   }
342 
343   public int runTestFromCommandLine() throws Exception {
344     IntegrationTestingUtility.setUseDistributedCluster(getConf());
345     int numPresplits = getConf().getInt("loadmapper.numPresplits", 5);
346     // create HTableDescriptor for specified table
347     String table = getTablename();
348     HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(table));
349     htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
350 
351     HBaseAdmin admin = new HBaseAdmin(getConf());
352     try {
353       admin.createTable(htd, Bytes.toBytes(0L), Bytes.toBytes(-1L), numPresplits);
354     } finally {
355       admin.close();
356     }
357     doLoad(getConf(), htd);
358     doVerify(getConf(), htd);
359     getTestingUtil(getConf()).deleteTable(htd.getName());
360     return 0;
361   }
362 
363   @Override
364   protected void processOptions(CommandLine cmd) {
365     List args = cmd.getArgList();
366     if (args.size() > 0) {
367       usage();
368       throw new RuntimeException("No args expected.");
369     }
370     // We always want loadAndVerify action
371     args.add("loadAndVerify");
372     super.processOptions(cmd);
373   }
374 
375   public static void main(String argv[]) throws Exception {
376     Configuration conf = HBaseConfiguration.create();
377     IntegrationTestingUtility.setUseDistributedCluster(conf);
378     int ret = ToolRunner.run(conf, new IntegrationTestWithCellVisibilityLoadAndVerify(), argv);
379     System.exit(ret);
380   }
381 }