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   * Issue user names as comma seperated list.
76   *./hbase IntegrationTestWithCellVisibilityLoadAndVerify -u usera,userb
77   */
78  @Category(IntegrationTests.class)
79  public class IntegrationTestWithCellVisibilityLoadAndVerify extends IntegrationTestLoadAndVerify {
80    private static final String ERROR_STR = 
81        "Two user names are to be specified seperated by a ',' like 'usera,userb'";
82    private static final char NOT = '!';
83    private static final char OR = '|';
84    private static final char AND = '&';
85    private static final String TEST_NAME = "IntegrationTestCellVisibilityLoadAndVerify";
86    private static final String CONFIDENTIAL = "confidential";
87    private static final String TOPSECRET = "topsecret";
88    private static final String SECRET = "secret";
89    private static final String PUBLIC = "public";
90    private static final String PRIVATE = "private";
91    private static final String[] LABELS = { CONFIDENTIAL, TOPSECRET, SECRET, PRIVATE, PUBLIC };
92    private static final String[] VISIBILITY_EXPS = { CONFIDENTIAL + AND + TOPSECRET + AND + PRIVATE,
93        CONFIDENTIAL + OR + TOPSECRET, PUBLIC,
94        '(' + SECRET + OR + PRIVATE + ')' + AND + NOT + CONFIDENTIAL };
95    private static final int VISIBILITY_EXPS_COUNT = VISIBILITY_EXPS.length;
96    private static final byte[] TEST_FAMILY = Bytes.toBytes("f1");
97    private static final byte[] TEST_QUALIFIER = Bytes.toBytes("q1");
98    private static final String NUM_TO_WRITE_KEY = "loadmapper.num_to_write";
99    private static final long NUM_TO_WRITE_DEFAULT = 100 * 1000;
100   private static final int SCANNER_CACHING = 500;
101   private static String USER_OPT = "users";
102   private static String userNames = "user1,user2";
103 
104   private long numRowsLoadedWithExp1, numRowsLoadedWithExp2, numRowsLoadWithExp3,
105       numRowsLoadWithExp4;
106   private long numRowsReadWithExp1, numRowsReadWithExp2, numRowsReadWithExp3, numRowsReadWithExp4;
107 
108   private static User USER1, USER2;
109 
110   private enum Counters {
111     ROWS_VIS_EXP_1, ROWS_VIS_EXP_2, ROWS_VIS_EXP_3, ROWS_VIS_EXP_4;
112   }
113 
114   @Override
115   public void setUpCluster() throws Exception {
116     util = getTestingUtil(null);
117     Configuration conf = util.getConfiguration();
118     conf.setInt(HFile.FORMAT_VERSION_KEY, 3);
119     conf.set("hbase.coprocessor.master.classes", VisibilityController.class.getName());
120     conf.set("hbase.coprocessor.region.classes", VisibilityController.class.getName());
121     conf.set("hbase.superuser", User.getCurrent().getName());
122     conf.setBoolean("dfs.permissions", false);
123     super.setUpCluster();
124     String[] users = userNames.split(",");
125     if (users.length != 2) {
126       System.err.println(ERROR_STR);
127       throw new IOException(ERROR_STR);
128     }
129     System.out.println(userNames + " "+users[0]+ " "+users[1]);
130     USER1 = User.createUserForTesting(conf, users[0], new String[] {});
131     USER2 = User.createUserForTesting(conf, users[1], new String[] {});
132     addLabelsAndAuths();
133   }
134 
135   @Override
136   protected void addOptions() {
137     super.addOptions();
138     addOptWithArg("u", USER_OPT, "User names to be passed");
139   }
140 
141   private void addLabelsAndAuths() throws Exception {
142     try {
143       VisibilityClient.addLabels(util.getConfiguration(), LABELS);
144       VisibilityClient.setAuths(util.getConfiguration(), new String[] { CONFIDENTIAL, TOPSECRET,
145           SECRET, PRIVATE }, USER1.getName());
146       VisibilityClient.setAuths(util.getConfiguration(), new String[] { PUBLIC },
147           USER2.getName());
148     } catch (Throwable t) {
149       throw new IOException(t);
150     }
151   }
152 
153   public static class LoadWithCellVisibilityMapper extends LoadMapper {
154     private Counter rowsExp1, rowsExp2, rowsExp3, rowsexp4;
155 
156     @Override
157     public void setup(Context context) throws IOException {
158       super.setup(context);
159       rowsExp1 = context.getCounter(Counters.ROWS_VIS_EXP_1);
160       rowsExp2 = context.getCounter(Counters.ROWS_VIS_EXP_2);
161       rowsExp3 = context.getCounter(Counters.ROWS_VIS_EXP_3);
162       rowsexp4 = context.getCounter(Counters.ROWS_VIS_EXP_4);
163     }
164 
165     @Override
166     protected void map(NullWritable key, NullWritable value, Context context) throws IOException,
167         InterruptedException {
168       String suffix = "/" + shortTaskId;
169       int BLOCK_SIZE = (int) (recordsToWrite / 100);
170       for (long i = 0; i < recordsToWrite;) {
171         for (long idx = 0; idx < BLOCK_SIZE && i < recordsToWrite; idx++, i++) {
172           int expIdx = rand.nextInt(BLOCK_SIZE) % VISIBILITY_EXPS_COUNT;
173           String exp = VISIBILITY_EXPS[expIdx];
174           byte[] row = Bytes.add(Bytes.toBytes(i), Bytes.toBytes(suffix), Bytes.toBytes(exp));
175           Put p = new Put(row);
176           p.add(TEST_FAMILY, TEST_QUALIFIER, HConstants.EMPTY_BYTE_ARRAY);
177           p.setCellVisibility(new CellVisibility(exp));
178           getCounter(expIdx).increment(1);
179           table.put(p);
180 
181           if (i % 100 == 0) {
182             context.setStatus("Written " + i + "/" + recordsToWrite + " records");
183             context.progress();
184           }
185         }
186         // End of block, flush all of them before we start writing anything
187         // pointing to these!
188         table.flushCommits();
189       }
190     }
191 
192     private Counter getCounter(int idx) {
193       switch (idx) {
194       case 0:
195         return rowsExp1;
196       case 1:
197         return rowsExp2;
198       case 2:
199         return rowsExp3;
200       case 3:
201         return rowsexp4;
202       default:
203         return null;
204       }
205     }
206   }
207 
208   public static class VerifyMapper extends TableMapper<BytesWritable, BytesWritable> {
209     private Counter rowsExp1, rowsExp2, rowsExp3, rowsExp4;
210 
211     @Override
212     public void setup(Context context) throws IOException {
213       rowsExp1 = context.getCounter(Counters.ROWS_VIS_EXP_1);
214       rowsExp2 = context.getCounter(Counters.ROWS_VIS_EXP_2);
215       rowsExp3 = context.getCounter(Counters.ROWS_VIS_EXP_3);
216       rowsExp4 = context.getCounter(Counters.ROWS_VIS_EXP_4);
217     }
218 
219     @Override
220     protected void map(ImmutableBytesWritable key, Result value, Context context)
221         throws IOException, InterruptedException {
222       byte[] row = value.getRow();
223       Counter c = getCounter(row);
224       c.increment(1);
225     }
226 
227     private Counter getCounter(byte[] row) {
228       Counter c = null;
229       if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[0])) != -1) {
230         c = rowsExp1;
231       } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[1])) != -1) {
232         c = rowsExp2;
233       } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[2])) != -1) {
234         c = rowsExp3;
235       } else if (Bytes.indexOf(row, Bytes.toBytes(VISIBILITY_EXPS[3])) != -1) {
236         c = rowsExp4;
237       }
238       return c;
239     }
240   }
241 
242   @Override
243   protected Job doLoad(Configuration conf, HTableDescriptor htd) throws Exception {
244     Job job = super.doLoad(conf, htd);
245     this.numRowsLoadedWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
246     this.numRowsLoadedWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
247     this.numRowsLoadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
248     this.numRowsLoadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
249     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[0] + " : "
250         + this.numRowsLoadedWithExp1);
251     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[1] + " : "
252         + this.numRowsLoadedWithExp2);
253     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[2] + " : "
254         + this.numRowsLoadWithExp3);
255     System.out.println("Rows loaded with cell visibility " + VISIBILITY_EXPS[3] + " : "
256         + this.numRowsLoadWithExp4);
257     return job;
258   }
259 
260   protected void setMapperClass(Job job) {
261     job.setMapperClass(LoadWithCellVisibilityMapper.class);
262   }
263 
264   protected void doVerify(final Configuration conf, final HTableDescriptor htd) throws Exception {
265     System.out.println(String.format("Verifying for auths %s, %s, %s, %s", CONFIDENTIAL, TOPSECRET,
266         SECRET, PRIVATE));
267     PrivilegedExceptionAction<Job> scanAction = new PrivilegedExceptionAction<Job>() {
268       @Override
269       public Job run() throws Exception {
270         return doVerify(conf, htd, CONFIDENTIAL, TOPSECRET, SECRET, PRIVATE);
271       }
272     };
273     Job job = USER1.runAs(scanAction);
274     this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
275     this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
276     this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
277     this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
278     assertEquals(this.numRowsLoadedWithExp1, this.numRowsReadWithExp1);
279     assertEquals(this.numRowsLoadedWithExp2, this.numRowsReadWithExp2);
280     assertEquals(0, this.numRowsReadWithExp3);
281     assertEquals(0, this.numRowsReadWithExp4);
282 
283     // PUBLIC label auth is not provided for user1 user.
284     System.out.println(String.format("Verifying for auths %s, %s", PRIVATE, PUBLIC));
285     scanAction = new PrivilegedExceptionAction<Job>() {
286       @Override
287       public Job run() throws Exception {
288         return doVerify(conf, htd, PRIVATE, PUBLIC);
289       }
290     };
291     job = USER1.runAs(scanAction);
292     this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
293     this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
294     this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
295     this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
296     assertEquals(0, this.numRowsReadWithExp1);
297     assertEquals(0, this.numRowsReadWithExp2);
298     assertEquals(0, this.numRowsReadWithExp3);
299     assertEquals(this.numRowsLoadWithExp4, this.numRowsReadWithExp4);
300 
301     // Normal user only having PUBLIC label auth and can view only those cells.
302     System.out.println(String.format("Verifying for auths %s, %s", PRIVATE, PUBLIC));
303     scanAction = new PrivilegedExceptionAction<Job>() {
304       @Override
305       public Job run() throws Exception {
306         return doVerify(conf, htd, PRIVATE, PUBLIC);
307       }
308     };
309     job = USER2.runAs(scanAction);
310     this.numRowsReadWithExp1 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_1).getValue();
311     this.numRowsReadWithExp2 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_2).getValue();
312     this.numRowsReadWithExp3 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_3).getValue();
313     this.numRowsReadWithExp4 = job.getCounters().findCounter(Counters.ROWS_VIS_EXP_4).getValue();
314     assertEquals(0, this.numRowsReadWithExp1);
315     assertEquals(0, this.numRowsReadWithExp2);
316     assertEquals(this.numRowsLoadWithExp3, this.numRowsReadWithExp3);
317     assertEquals(0, this.numRowsReadWithExp4);
318   }
319 
320   private Job doVerify(Configuration conf, HTableDescriptor htd, String... auths)
321       throws IOException, InterruptedException, ClassNotFoundException {
322     Path outputDir = getTestDir(TEST_NAME, "verify-output");
323     Job job = new Job(conf);
324     job.setJarByClass(this.getClass());
325     job.setJobName(TEST_NAME + " Verification for " + htd.getTableName());
326     setJobScannerConf(job);
327     Scan scan = new Scan();
328     scan.setAuthorizations(new Authorizations(auths));
329     TableMapReduceUtil.initTableMapperJob(htd.getTableName().getNameAsString(), scan,
330         VerifyMapper.class, NullWritable.class, NullWritable.class, job);
331     TableMapReduceUtil.addDependencyJars(job.getConfiguration(), AbstractHBaseTool.class);
332     int scannerCaching = conf.getInt("verify.scannercaching", SCANNER_CACHING);
333     TableMapReduceUtil.setScannerCaching(job, scannerCaching);
334     job.setNumReduceTasks(0);
335     FileOutputFormat.setOutputPath(job, outputDir);
336     assertTrue(job.waitForCompletion(true));
337     return job;
338   }
339 
340   private static void setJobScannerConf(Job job) {
341     job.getConfiguration().setBoolean(ScannerCallable.LOG_SCANNER_ACTIVITY, true);
342     long lpr = job.getConfiguration().getLong(NUM_TO_WRITE_KEY, NUM_TO_WRITE_DEFAULT) / 100;
343     job.getConfiguration().setInt(TableRecordReaderImpl.LOG_PER_ROW_COUNT, (int) lpr);
344   }
345 
346   public void usage() {
347     System.err.println(this.getClass().getSimpleName() + " -u usera,userb [-Doptions]");
348     System.err.println("  Loads a table with cell visibilities and verifies with Authorizations");
349     System.err.println("Options");
350     System.err
351         .println("  -Dloadmapper.table=<name>        Table to write/verify (default autogen)");
352     System.err.println("  -Dloadmapper.num_to_write=<n>    "
353         + "Number of rows per mapper (default 100,000 per mapper)");
354     System.err.println("  -Dloadmapper.numPresplits=<n>    "
355         + "Number of presplit regions to start with (default 40)");
356     System.err
357         .println("  -Dloadmapper.map.tasks=<n>       Number of map tasks for load (default 200)");
358     System.err.println("  -Dverify.scannercaching=<n>      "
359         + "Number hbase scanner caching rows to read (default 50)");
360   }
361 
362   public int runTestFromCommandLine() throws Exception {
363     IntegrationTestingUtility.setUseDistributedCluster(getConf());
364     int numPresplits = getConf().getInt("loadmapper.numPresplits", 5);
365     // create HTableDescriptor for specified table
366     String table = getTablename();
367     HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(table));
368     htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
369 
370     HBaseAdmin admin = new HBaseAdmin(getConf());
371     try {
372       admin.createTable(htd, Bytes.toBytes(0L), Bytes.toBytes(-1L), numPresplits);
373     } finally {
374       admin.close();
375     }
376     doLoad(getConf(), htd);
377     doVerify(getConf(), htd);
378     getTestingUtil(getConf()).deleteTable(htd.getName());
379     return 0;
380   }
381 
382   @Override
383   protected void processOptions(CommandLine cmd) {
384     List args = cmd.getArgList();
385     if (args.size() > 0) {
386       usage();
387       throw new RuntimeException("No args expected.");
388     }
389     // We always want loadAndVerify action
390     args.add("loadAndVerify");
391     if (cmd.hasOption(USER_OPT)) {
392       userNames = cmd.getOptionValue(USER_OPT);
393     }
394     super.processOptions(cmd);
395   }
396 
397   public static void main(String argv[]) throws Exception {
398     Configuration conf = HBaseConfiguration.create();
399     IntegrationTestingUtility.setUseDistributedCluster(conf);
400     int ret = ToolRunner.run(conf, new IntegrationTestWithCellVisibilityLoadAndVerify(), argv);
401     System.exit(ret);
402   }
403 }