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