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