View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations
15   * under the License.
16   */
17  package org.apache.hadoop.hbase.util;
18  
19  import java.io.IOException;
20  import java.io.InterruptedIOException;
21  import java.lang.reflect.Constructor;
22  import java.net.InetAddress;
23  import java.security.SecureRandom;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.List;
27  import java.util.Properties;
28  import java.util.Random;
29  import java.util.concurrent.atomic.AtomicReference;
30  
31  import javax.crypto.spec.SecretKeySpec;
32  
33  import org.apache.commons.cli.CommandLine;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.hbase.HBaseConfiguration;
38  import org.apache.hadoop.hbase.HBaseTestingUtility;
39  import org.apache.hadoop.hbase.HColumnDescriptor;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.HTableDescriptor;
42  import org.apache.hadoop.hbase.TableName;
43  import org.apache.hadoop.hbase.client.Durability;
44  import org.apache.hadoop.hbase.client.HBaseAdmin;
45  import org.apache.hadoop.hbase.io.compress.Compression;
46  import org.apache.hadoop.hbase.io.crypto.Cipher;
47  import org.apache.hadoop.hbase.io.crypto.Encryption;
48  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
49  import org.apache.hadoop.hbase.regionserver.BloomType;
50  import org.apache.hadoop.hbase.security.EncryptionUtil;
51  import org.apache.hadoop.hbase.security.User;
52  import org.apache.hadoop.hbase.security.access.AccessControlClient;
53  import org.apache.hadoop.hbase.security.access.Permission;
54  import org.apache.hadoop.hbase.util.test.LoadTestDataGenerator;
55  import org.apache.hadoop.hbase.util.test.LoadTestDataGeneratorWithACL;
56  import org.apache.hadoop.security.SecurityUtil;
57  import org.apache.hadoop.security.UserGroupInformation;
58  import org.apache.hadoop.util.ToolRunner;
59  
60  /**
61   * A command-line utility that reads, writes, and verifies data. Unlike
62   * {@link PerformanceEvaluation}, this tool validates the data written,
63   * and supports simultaneously writing and reading the same set of keys.
64   */
65  public class LoadTestTool extends AbstractHBaseTool {
66  
67    private static final Log LOG = LogFactory.getLog(LoadTestTool.class);
68    private static final String COLON = ":";
69  
70    /** Table name for the test */
71    private TableName tableName;
72  
73    /** Table name to use of not overridden on the command line */
74    protected static final String DEFAULT_TABLE_NAME = "cluster_test";
75  
76    /** Column family used by the test */
77    public static byte[] COLUMN_FAMILY = Bytes.toBytes("test_cf");
78  
79    /** Column families used by the test */
80    protected static final byte[][] COLUMN_FAMILIES = { COLUMN_FAMILY };
81  
82    /** The default data size if not specified */
83    protected static final int DEFAULT_DATA_SIZE = 64;
84  
85    /** The number of reader/writer threads if not specified */
86    protected static final int DEFAULT_NUM_THREADS = 20;
87  
88    /** Usage string for the load option */
89    protected static final String OPT_USAGE_LOAD =
90        "<avg_cols_per_key>:<avg_data_size>" +
91        "[:<#threads=" + DEFAULT_NUM_THREADS + ">]";
92  
93    /** Usage string for the read option */
94    protected static final String OPT_USAGE_READ =
95        "<verify_percent>[:<#threads=" + DEFAULT_NUM_THREADS + ">]";
96  
97    /** Usage string for the update option */
98    protected static final String OPT_USAGE_UPDATE =
99        "<update_percent>[:<#threads=" + DEFAULT_NUM_THREADS
100       + ">][:<#whether to ignore nonce collisions=0>]";
101 
102   protected static final String OPT_USAGE_BLOOM = "Bloom filter type, one of " +
103       Arrays.toString(BloomType.values());
104 
105   protected static final String OPT_USAGE_COMPRESSION = "Compression type, " +
106       "one of " + Arrays.toString(Compression.Algorithm.values());
107 
108   public static final String OPT_DATA_BLOCK_ENCODING_USAGE =
109     "Encoding algorithm (e.g. prefix "
110         + "compression) to use for data blocks in the test column family, "
111         + "one of " + Arrays.toString(DataBlockEncoding.values()) + ".";
112 
113   public static final String OPT_BLOOM = "bloom";
114   public static final String OPT_COMPRESSION = "compression";
115   public static final String OPT_DEFERRED_LOG_FLUSH = "deferredlogflush";
116   public static final String OPT_DEFERRED_LOG_FLUSH_USAGE = "Enable deferred log flush.";
117 
118   public static final String OPT_DATA_BLOCK_ENCODING =
119       HColumnDescriptor.DATA_BLOCK_ENCODING.toLowerCase();
120 
121   public static final String OPT_INMEMORY = "in_memory";
122   public static final String OPT_USAGE_IN_MEMORY = "Tries to keep the HFiles of the CF " +
123   		"inmemory as far as possible.  Not guaranteed that reads are always served from inmemory";
124 
125   public static final String OPT_GENERATOR = "generator";
126   public static final String OPT_GENERATOR_USAGE = "The class which generates load for the tool."
127       + " Any args for this class can be passed as colon separated after class name";
128 
129   public static final String OPT_READER = "reader";
130   public static final String OPT_READER_USAGE = "The class for executing the read requests";
131 
132   protected static final String OPT_KEY_WINDOW = "key_window";
133   protected static final String OPT_WRITE = "write";
134   protected static final String OPT_MAX_READ_ERRORS = "max_read_errors";
135   protected static final String OPT_MULTIPUT = "multiput";
136   protected static final String OPT_NUM_KEYS = "num_keys";
137   protected static final String OPT_READ = "read";
138   protected static final String OPT_START_KEY = "start_key";
139   public static final String OPT_TABLE_NAME = "tn";
140   protected static final String OPT_ZK_QUORUM = "zk";
141   protected static final String OPT_ZK_PARENT_NODE = "zk_root";
142   protected static final String OPT_SKIP_INIT = "skip_init";
143   protected static final String OPT_INIT_ONLY = "init_only";
144   protected static final String NUM_TABLES = "num_tables";
145   protected static final String OPT_REGIONS_PER_SERVER = "regions_per_server";
146   protected static final String OPT_BATCHUPDATE = "batchupdate";
147   protected static final String OPT_UPDATE = "update";
148 
149   public static final String OPT_ENCRYPTION = "encryption";
150   protected static final String OPT_ENCRYPTION_USAGE =
151     "Enables transparent encryption on the test table, one of " +
152     Arrays.toString(Encryption.getSupportedCiphers());
153 
154   public static final String OPT_NUM_REGIONS_PER_SERVER = "num_regions_per_server";
155   protected static final String OPT_NUM_REGIONS_PER_SERVER_USAGE
156     = "Desired number of regions per region server. Defaults to 5.";
157   protected static int DEFAULT_NUM_REGIONS_PER_SERVER = 5;
158 
159   protected static final long DEFAULT_START_KEY = 0;
160 
161   /** This will be removed as we factor out the dependency on command line */
162   protected CommandLine cmd;
163 
164   protected MultiThreadedWriter writerThreads = null;
165   protected MultiThreadedReader readerThreads = null;
166   protected MultiThreadedUpdater updaterThreads = null;
167 
168   protected long startKey, endKey;
169 
170   protected boolean isWrite, isRead, isUpdate;
171   protected boolean deferredLogFlush;
172 
173   // Column family options
174   protected DataBlockEncoding dataBlockEncodingAlgo;
175   protected Compression.Algorithm compressAlgo;
176   protected BloomType bloomType;
177   private boolean inMemoryCF;
178 
179   private User userOwner;
180   // Writer options
181   protected int numWriterThreads = DEFAULT_NUM_THREADS;
182   protected int minColsPerKey, maxColsPerKey;
183   protected int minColDataSize = DEFAULT_DATA_SIZE, maxColDataSize = DEFAULT_DATA_SIZE;
184   protected boolean isMultiPut;
185 
186   // Updater options
187   protected int numUpdaterThreads = DEFAULT_NUM_THREADS;
188   protected int updatePercent;
189   protected boolean ignoreConflicts = false;
190   protected boolean isBatchUpdate;
191 
192   // Reader options
193   private int numReaderThreads = DEFAULT_NUM_THREADS;
194   private int keyWindow = MultiThreadedReader.DEFAULT_KEY_WINDOW;
195   private int maxReadErrors = MultiThreadedReader.DEFAULT_MAX_ERRORS;
196   private int verifyPercent;
197 
198   private int numTables = 1;
199   private int regionsPerServer = HBaseTestingUtility.DEFAULT_REGIONS_PER_SERVER;
200 
201   private String superUser;
202 
203   private String userNames;
204   //This file is used to read authentication information in secure clusters.
205   private String authnFileName;
206 
207   // TODO: refactor LoadTestToolImpl somewhere to make the usage from tests less bad,
208   //       console tool itself should only be used from console.
209   protected boolean isSkipInit = false;
210   protected boolean isInitOnly = false;
211 
212   protected Cipher cipher = null;
213 
214   protected String[] splitColonSeparated(String option,
215       int minNumCols, int maxNumCols) {
216     String optVal = cmd.getOptionValue(option);
217     String[] cols = optVal.split(COLON);
218     if (cols.length < minNumCols || cols.length > maxNumCols) {
219       throw new IllegalArgumentException("Expected at least "
220           + minNumCols + " columns but no more than " + maxNumCols +
221           " in the colon-separated value '" + optVal + "' of the " +
222           "-" + option + " option");
223     }
224     return cols;
225   }
226 
227   protected int getNumThreads(String numThreadsStr) {
228     return parseInt(numThreadsStr, 1, Short.MAX_VALUE);
229   }
230 
231   /**
232    * Apply column family options such as Bloom filters, compression, and data
233    * block encoding.
234    */
235   protected void applyColumnFamilyOptions(TableName tableName,
236       byte[][] columnFamilies) throws IOException {
237     HBaseAdmin admin = new HBaseAdmin(conf);
238     HTableDescriptor tableDesc = admin.getTableDescriptor(tableName);
239     LOG.info("Disabling table " + tableName);
240     admin.disableTable(tableName);
241     for (byte[] cf : columnFamilies) {
242       HColumnDescriptor columnDesc = tableDesc.getFamily(cf);
243       boolean isNewCf = columnDesc == null;
244       if (isNewCf) {
245         columnDesc = new HColumnDescriptor(cf);
246       }
247       if (bloomType != null) {
248         columnDesc.setBloomFilterType(bloomType);
249       }
250       if (compressAlgo != null) {
251         columnDesc.setCompressionType(compressAlgo);
252       }
253       if (dataBlockEncodingAlgo != null) {
254         columnDesc.setDataBlockEncoding(dataBlockEncodingAlgo);
255       }
256       if (inMemoryCF) {
257         columnDesc.setInMemory(inMemoryCF);
258       }
259       if (cipher != null) {
260         byte[] keyBytes = new byte[cipher.getKeyLength()];
261         new SecureRandom().nextBytes(keyBytes);
262         columnDesc.setEncryptionType(cipher.getName());
263         columnDesc.setEncryptionKey(EncryptionUtil.wrapKey(conf,
264           User.getCurrent().getShortName(),
265           new SecretKeySpec(keyBytes, cipher.getName())));
266       }
267       if (isNewCf) {
268         admin.addColumn(tableName, columnDesc);
269       } else {
270         admin.modifyColumn(tableName, columnDesc);
271       }
272     }
273     LOG.info("Enabling table " + tableName);
274     admin.enableTable(tableName);
275     admin.close();
276   }
277 
278   @Override
279   protected void addOptions() {
280     addOptWithArg(OPT_ZK_QUORUM, "ZK quorum as comma-separated host names " +
281         "without port numbers");
282     addOptWithArg(OPT_ZK_PARENT_NODE, "name of parent znode in zookeeper");
283     addOptWithArg(OPT_TABLE_NAME, "The name of the table to read or write");
284     addOptWithArg(OPT_WRITE, OPT_USAGE_LOAD);
285     addOptWithArg(OPT_READ, OPT_USAGE_READ);
286     addOptWithArg(OPT_UPDATE, OPT_USAGE_UPDATE);
287     addOptNoArg(OPT_INIT_ONLY, "Initialize the test table only, don't do any loading");
288     addOptWithArg(OPT_BLOOM, OPT_USAGE_BLOOM);
289     addOptWithArg(OPT_COMPRESSION, OPT_USAGE_COMPRESSION);
290     addOptWithArg(OPT_DATA_BLOCK_ENCODING, OPT_DATA_BLOCK_ENCODING_USAGE);
291     addOptWithArg(OPT_MAX_READ_ERRORS, "The maximum number of read errors " +
292         "to tolerate before terminating all reader threads. The default is " +
293         MultiThreadedReader.DEFAULT_MAX_ERRORS + ".");
294     addOptWithArg(OPT_KEY_WINDOW, "The 'key window' to maintain between " +
295         "reads and writes for concurrent write/read workload. The default " +
296         "is " + MultiThreadedReader.DEFAULT_KEY_WINDOW + ".");
297 
298     addOptNoArg(OPT_MULTIPUT, "Whether to use multi-puts as opposed to " +
299         "separate puts for every column in a row");
300     addOptNoArg(OPT_BATCHUPDATE, "Whether to use batch as opposed to " +
301         "separate updates for every column in a row");
302     addOptNoArg(OPT_INMEMORY, OPT_USAGE_IN_MEMORY);
303     addOptWithArg(OPT_GENERATOR, OPT_GENERATOR_USAGE);
304     addOptWithArg(OPT_READER, OPT_READER_USAGE);
305 
306     addOptWithArg(OPT_NUM_KEYS, "The number of keys to read/write");
307     addOptWithArg(OPT_START_KEY, "The first key to read/write " +
308         "(a 0-based index). The default value is " +
309         DEFAULT_START_KEY + ".");
310     addOptNoArg(OPT_SKIP_INIT, "Skip the initialization; assume test table "
311         + "already exists");
312 
313     addOptWithArg(NUM_TABLES,
314       "A positive integer number. When a number n is speicfied, load test "
315           + "tool  will load n table parallely. -tn parameter value becomes "
316           + "table name prefix. Each table name is in format <tn>_1...<tn>_n");
317 
318     addOptWithArg(OPT_REGIONS_PER_SERVER,
319       "A positive integer number. When a number n is specified, load test "
320           + "tool will create the test table with n regions per server");
321 
322     addOptWithArg(OPT_ENCRYPTION, OPT_ENCRYPTION_USAGE);
323     addOptNoArg(OPT_DEFERRED_LOG_FLUSH, OPT_DEFERRED_LOG_FLUSH_USAGE);
324     addOptWithArg(OPT_NUM_REGIONS_PER_SERVER, OPT_NUM_REGIONS_PER_SERVER_USAGE);
325   }
326 
327   @Override
328   protected void processOptions(CommandLine cmd) {
329     this.cmd = cmd;
330 
331     tableName = TableName.valueOf(cmd.getOptionValue(OPT_TABLE_NAME,
332         DEFAULT_TABLE_NAME));
333 
334     isWrite = cmd.hasOption(OPT_WRITE);
335     isRead = cmd.hasOption(OPT_READ);
336     isUpdate = cmd.hasOption(OPT_UPDATE);
337     isInitOnly = cmd.hasOption(OPT_INIT_ONLY);
338     deferredLogFlush = cmd.hasOption(OPT_DEFERRED_LOG_FLUSH);
339 
340     if (!isWrite && !isRead && !isUpdate && !isInitOnly) {
341       throw new IllegalArgumentException("Either -" + OPT_WRITE + " or " +
342         "-" + OPT_UPDATE + "-" + OPT_READ + " has to be specified");
343     }
344 
345     if (isInitOnly && (isRead || isWrite || isUpdate)) {
346       throw new IllegalArgumentException(OPT_INIT_ONLY + " cannot be specified with"
347           + " either -" + OPT_WRITE + " or -" + OPT_UPDATE + " or -" + OPT_READ);
348     }
349 
350     if (!isInitOnly) {
351       if (!cmd.hasOption(OPT_NUM_KEYS)) {
352         throw new IllegalArgumentException(OPT_NUM_KEYS + " must be specified in "
353             + "read or write mode");
354       }
355       startKey = parseLong(cmd.getOptionValue(OPT_START_KEY,
356           String.valueOf(DEFAULT_START_KEY)), 0, Long.MAX_VALUE);
357       long numKeys = parseLong(cmd.getOptionValue(OPT_NUM_KEYS), 1,
358           Long.MAX_VALUE - startKey);
359       endKey = startKey + numKeys;
360       isSkipInit = cmd.hasOption(OPT_SKIP_INIT);
361       System.out.println("Key range: [" + startKey + ".." + (endKey - 1) + "]");
362     }
363 
364     parseColumnFamilyOptions(cmd);
365 
366     if (isWrite) {
367       String[] writeOpts = splitColonSeparated(OPT_WRITE, 2, 3);
368 
369       int colIndex = 0;
370       minColsPerKey = 1;
371       maxColsPerKey = 2 * Integer.parseInt(writeOpts[colIndex++]);
372       int avgColDataSize =
373           parseInt(writeOpts[colIndex++], 1, Integer.MAX_VALUE);
374       minColDataSize = avgColDataSize / 2;
375       maxColDataSize = avgColDataSize * 3 / 2;
376 
377       if (colIndex < writeOpts.length) {
378         numWriterThreads = getNumThreads(writeOpts[colIndex++]);
379       }
380 
381       isMultiPut = cmd.hasOption(OPT_MULTIPUT);
382 
383       System.out.println("Multi-puts: " + isMultiPut);
384       System.out.println("Columns per key: " + minColsPerKey + ".."
385           + maxColsPerKey);
386       System.out.println("Data size per column: " + minColDataSize + ".."
387           + maxColDataSize);
388     }
389 
390     if (isUpdate) {
391       String[] mutateOpts = splitColonSeparated(OPT_UPDATE, 1, 3);
392       int colIndex = 0;
393       updatePercent = parseInt(mutateOpts[colIndex++], 0, 100);
394       if (colIndex < mutateOpts.length) {
395         numUpdaterThreads = getNumThreads(mutateOpts[colIndex++]);
396       }
397       if (colIndex < mutateOpts.length) {
398         ignoreConflicts = parseInt(mutateOpts[colIndex++], 0, 1) == 1;
399       }
400 
401       isBatchUpdate = cmd.hasOption(OPT_BATCHUPDATE);
402 
403       System.out.println("Batch updates: " + isBatchUpdate);
404       System.out.println("Percent of keys to update: " + updatePercent);
405       System.out.println("Updater threads: " + numUpdaterThreads);
406       System.out.println("Ignore nonce conflicts: " + ignoreConflicts);
407     }
408 
409     if (isRead) {
410       String[] readOpts = splitColonSeparated(OPT_READ, 1, 2);
411       int colIndex = 0;
412       verifyPercent = parseInt(readOpts[colIndex++], 0, 100);
413       if (colIndex < readOpts.length) {
414         numReaderThreads = getNumThreads(readOpts[colIndex++]);
415       }
416 
417       if (cmd.hasOption(OPT_MAX_READ_ERRORS)) {
418         maxReadErrors = parseInt(cmd.getOptionValue(OPT_MAX_READ_ERRORS),
419             0, Integer.MAX_VALUE);
420       }
421 
422       if (cmd.hasOption(OPT_KEY_WINDOW)) {
423         keyWindow = parseInt(cmd.getOptionValue(OPT_KEY_WINDOW),
424             0, Integer.MAX_VALUE);
425       }
426 
427       System.out.println("Percent of keys to verify: " + verifyPercent);
428       System.out.println("Reader threads: " + numReaderThreads);
429     }
430 
431     numTables = 1;
432     if (cmd.hasOption(NUM_TABLES)) {
433       numTables = parseInt(cmd.getOptionValue(NUM_TABLES), 1, Short.MAX_VALUE);
434     }
435 
436     regionsPerServer = DEFAULT_NUM_REGIONS_PER_SERVER;
437     if (cmd.hasOption(OPT_NUM_REGIONS_PER_SERVER)) {
438       regionsPerServer = Integer.parseInt(cmd.getOptionValue(OPT_NUM_REGIONS_PER_SERVER));
439     }
440   }
441 
442   private void parseColumnFamilyOptions(CommandLine cmd) {
443     String dataBlockEncodingStr = cmd.getOptionValue(OPT_DATA_BLOCK_ENCODING);
444     dataBlockEncodingAlgo = dataBlockEncodingStr == null ? null :
445         DataBlockEncoding.valueOf(dataBlockEncodingStr);
446 
447     String compressStr = cmd.getOptionValue(OPT_COMPRESSION);
448     compressAlgo = compressStr == null ? Compression.Algorithm.NONE :
449         Compression.Algorithm.valueOf(compressStr);
450 
451     String bloomStr = cmd.getOptionValue(OPT_BLOOM);
452     bloomType = bloomStr == null ? BloomType.ROW :
453         BloomType.valueOf(bloomStr);
454 
455     inMemoryCF = cmd.hasOption(OPT_INMEMORY);
456     if (cmd.hasOption(OPT_ENCRYPTION)) {
457       cipher = Encryption.getCipher(conf, cmd.getOptionValue(OPT_ENCRYPTION));
458     }
459 
460   }
461 
462   public void initTestTable() throws IOException {
463     Durability durability = Durability.USE_DEFAULT;
464     if (deferredLogFlush) {
465       durability = Durability.ASYNC_WAL;
466     }
467 
468     HBaseTestingUtility.createPreSplitLoadTestTable(conf, tableName,
469         COLUMN_FAMILY, compressAlgo, dataBlockEncodingAlgo, regionsPerServer,
470         durability);
471     applyColumnFamilyOptions(tableName, COLUMN_FAMILIES);
472   }
473 
474   @Override
475   protected int doWork() throws IOException {
476     if (numTables > 1) {
477       return parallelLoadTables();
478     } else {
479       return loadTable();
480     }
481   }
482 
483   protected int loadTable() throws IOException {
484     if (cmd.hasOption(OPT_ZK_QUORUM)) {
485       conf.set(HConstants.ZOOKEEPER_QUORUM, cmd.getOptionValue(OPT_ZK_QUORUM));
486     }
487     if (cmd.hasOption(OPT_ZK_PARENT_NODE)) {
488       conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT, cmd.getOptionValue(OPT_ZK_PARENT_NODE));
489     }
490 
491     if (isInitOnly) {
492       LOG.info("Initializing only; no reads or writes");
493       initTestTable();
494       return 0;
495     }
496 
497     if (!isSkipInit) {
498       initTestTable();
499     }
500     LoadTestDataGenerator dataGen = null;
501     if (cmd.hasOption(OPT_GENERATOR)) {
502       String[] clazzAndArgs = cmd.getOptionValue(OPT_GENERATOR).split(COLON);
503       dataGen = getLoadGeneratorInstance(clazzAndArgs[0]);
504       String[] args;
505       if (dataGen instanceof LoadTestDataGeneratorWithACL) {
506         LOG.info("Using LoadTestDataGeneratorWithACL");
507         if (User.isHBaseSecurityEnabled(conf)) {
508           LOG.info("Security is enabled");
509           authnFileName = clazzAndArgs[1];
510           superUser = clazzAndArgs[2];
511           userNames = clazzAndArgs[3];
512           args = Arrays.copyOfRange(clazzAndArgs, 2, clazzAndArgs.length);
513           Properties authConfig = new Properties();
514           authConfig.load(this.getClass().getClassLoader().getResourceAsStream(authnFileName));
515           try {
516             addAuthInfoToConf(authConfig, conf, superUser, userNames);
517           } catch (IOException exp) {
518             LOG.error(exp);
519             return EXIT_FAILURE;
520           }
521           userOwner = User.create(loginAndReturnUGI(conf, superUser));
522         } else {
523           superUser = clazzAndArgs[1];
524           userNames = clazzAndArgs[2];
525           args = Arrays.copyOfRange(clazzAndArgs, 1, clazzAndArgs.length);
526           userOwner = User.createUserForTesting(conf, superUser, new String[0]);
527         }
528       } else {
529         args = clazzAndArgs.length == 1 ? new String[0] : Arrays.copyOfRange(clazzAndArgs, 1,
530             clazzAndArgs.length);
531       }
532       dataGen.initialize(args);
533     } else {
534       // Default DataGenerator is MultiThreadedAction.DefaultDataGenerator
535       dataGen = new MultiThreadedAction.DefaultDataGenerator(minColDataSize, maxColDataSize,
536           minColsPerKey, maxColsPerKey, COLUMN_FAMILY);
537     }
538 
539     if (userOwner != null) {
540       LOG.info("Granting permissions for user " + userOwner.getShortName());
541       Permission.Action[] actions = {
542         Permission.Action.ADMIN, Permission.Action.CREATE,
543         Permission.Action.READ, Permission.Action.WRITE };
544       try {
545         AccessControlClient.grant(conf, tableName, userOwner.getShortName(), null, null, actions);
546       } catch (Throwable e) {
547         LOG.fatal("Error in granting permission for the user " + userOwner.getShortName(), e);
548         return EXIT_FAILURE;
549       }
550     }
551     if (userNames != null) {
552       // This will be comma separated list of expressions.
553       String users[] = userNames.split(",");
554       User user = null;
555       for (String userStr : users) {
556         if (User.isHBaseSecurityEnabled(conf)) {
557           user = User.create(loginAndReturnUGI(conf, userStr));
558         } else {
559           user = User.createUserForTesting(conf, userStr, new String[0]);
560         }
561       }
562     }
563 
564     if (isWrite) {
565       if (userOwner != null) {
566         writerThreads = new MultiThreadedWriterWithACL(dataGen, conf, tableName, userOwner);
567       } else {
568         writerThreads = new MultiThreadedWriter(dataGen, conf, tableName);
569       }
570       writerThreads.setMultiPut(isMultiPut);
571     }
572 
573     if (isUpdate) {
574       if (userOwner != null) {
575         updaterThreads = new MultiThreadedUpdaterWithACL(dataGen, conf, tableName, updatePercent,
576             userOwner, userNames);
577       } else {
578         updaterThreads = new MultiThreadedUpdater(dataGen, conf, tableName, updatePercent);
579       }
580       updaterThreads.setBatchUpdate(isBatchUpdate);
581       updaterThreads.setIgnoreNonceConflicts(ignoreConflicts);
582     }
583 
584     if (isRead) {
585       if (userOwner != null) {
586         readerThreads = new MultiThreadedReaderWithACL(dataGen, conf, tableName, verifyPercent,
587             userNames);
588       } else {
589         String readerClass = null;
590         if (cmd.hasOption(OPT_READER)) {
591           readerClass = cmd.getOptionValue(OPT_READER);
592         } else {
593           readerClass = MultiThreadedReader.class.getCanonicalName();
594         }
595         readerThreads = getMultiThreadedReaderInstance(readerClass, dataGen);
596       }
597       readerThreads.setMaxErrors(maxReadErrors);
598       readerThreads.setKeyWindow(keyWindow);
599     }
600 
601     if (isUpdate && isWrite) {
602       LOG.info("Concurrent write/update workload: making updaters aware of the " +
603         "write point");
604       updaterThreads.linkToWriter(writerThreads);
605     }
606 
607     if (isRead && (isUpdate || isWrite)) {
608       LOG.info("Concurrent write/read workload: making readers aware of the " +
609         "write point");
610       readerThreads.linkToWriter(isUpdate ? updaterThreads : writerThreads);
611     }
612 
613     if (isWrite) {
614       System.out.println("Starting to write data...");
615       writerThreads.start(startKey, endKey, numWriterThreads);
616     }
617 
618     if (isUpdate) {
619       LOG.info("Starting to mutate data...");
620       System.out.println("Starting to mutate data...");
621       // TODO : currently append and increment operations not tested with tags
622       // Will update this aftet it is done
623       updaterThreads.start(startKey, endKey, numUpdaterThreads);
624     }
625 
626     if (isRead) {
627       System.out.println("Starting to read data...");
628       readerThreads.start(startKey, endKey, numReaderThreads);
629     }
630 
631     if (isWrite) {
632       writerThreads.waitForFinish();
633     }
634 
635     if (isUpdate) {
636       updaterThreads.waitForFinish();
637     }
638 
639     if (isRead) {
640       readerThreads.waitForFinish();
641     }
642 
643     boolean success = true;
644     if (isWrite) {
645       success = success && writerThreads.getNumWriteFailures() == 0;
646     }
647     if (isUpdate) {
648       success = success && updaterThreads.getNumWriteFailures() == 0;
649     }
650     if (isRead) {
651       success = success && readerThreads.getNumReadErrors() == 0
652           && readerThreads.getNumReadFailures() == 0;
653     }
654     return success ? EXIT_SUCCESS : EXIT_FAILURE;
655   }
656 
657   private LoadTestDataGenerator getLoadGeneratorInstance(String clazzName) throws IOException {
658     try {
659       Class<?> clazz = Class.forName(clazzName);
660       Constructor<?> constructor = clazz.getConstructor(int.class, int.class, int.class, int.class,
661           byte[][].class);
662       return (LoadTestDataGenerator) constructor.newInstance(minColDataSize, maxColDataSize,
663           minColsPerKey, maxColsPerKey, COLUMN_FAMILIES);
664     } catch (Exception e) {
665       throw new IOException(e);
666     }
667   }
668 
669   private MultiThreadedReader getMultiThreadedReaderInstance(String clazzName
670       , LoadTestDataGenerator dataGen) throws IOException {
671     try {
672       Class<?> clazz = Class.forName(clazzName);
673       Constructor<?> constructor = clazz.getConstructor(
674         LoadTestDataGenerator.class, Configuration.class, TableName.class, double.class);
675       return (MultiThreadedReader) constructor.newInstance(dataGen, conf, tableName, verifyPercent);
676     } catch (Exception e) {
677       throw new IOException(e);
678     }
679   }
680 
681   public static byte[] generateData(final Random r, int length) {
682     byte [] b = new byte [length];
683     int i = 0;
684 
685     for(i = 0; i < (length-8); i += 8) {
686       b[i] = (byte) (65 + r.nextInt(26));
687       b[i+1] = b[i];
688       b[i+2] = b[i];
689       b[i+3] = b[i];
690       b[i+4] = b[i];
691       b[i+5] = b[i];
692       b[i+6] = b[i];
693       b[i+7] = b[i];
694     }
695 
696     byte a = (byte) (65 + r.nextInt(26));
697     for(; i < length; i++) {
698       b[i] = a;
699     }
700     return b;
701   }
702   public static void main(String[] args) {
703     new LoadTestTool().doStaticMain(args);
704   }
705 
706   /**
707    * When NUM_TABLES is specified, the function starts multiple worker threads
708    * which individually start a LoadTestTool instance to load a table. Each
709    * table name is in format <tn>_<index>. For example, "-tn test -num_tables 2"
710    * , table names will be "test_1", "test_2"
711    *
712    * @throws IOException
713    */
714   private int parallelLoadTables()
715       throws IOException {
716     // create new command args
717     String tableName = cmd.getOptionValue(OPT_TABLE_NAME, DEFAULT_TABLE_NAME);
718     String[] newArgs = null;
719     if (!cmd.hasOption(LoadTestTool.OPT_TABLE_NAME)) {
720       newArgs = new String[cmdLineArgs.length + 2];
721       newArgs[0] = "-" + LoadTestTool.OPT_TABLE_NAME;
722       newArgs[1] = LoadTestTool.DEFAULT_TABLE_NAME;
723       System.arraycopy(cmdLineArgs, 0, newArgs, 2, cmdLineArgs.length);
724     } else {
725       newArgs = cmdLineArgs;
726     }
727 
728     int tableNameValueIndex = -1;
729     for (int j = 0; j < newArgs.length; j++) {
730       if (newArgs[j].endsWith(OPT_TABLE_NAME)) {
731         tableNameValueIndex = j + 1;
732       } else if (newArgs[j].endsWith(NUM_TABLES)) {
733         // change NUM_TABLES to 1 so that each worker loads one table
734         newArgs[j + 1] = "1";
735       }
736     }
737 
738     // starting to load multiple tables
739     List<WorkerThread> workers = new ArrayList<WorkerThread>();
740     for (int i = 0; i < numTables; i++) {
741       String[] workerArgs = newArgs.clone();
742       workerArgs[tableNameValueIndex] = tableName + "_" + (i+1);
743       WorkerThread worker = new WorkerThread(i, workerArgs);
744       workers.add(worker);
745       LOG.info(worker + " starting");
746       worker.start();
747     }
748 
749     // wait for all workers finish
750     LOG.info("Waiting for worker threads to finish");
751     for (WorkerThread t : workers) {
752       try {
753         t.join();
754       } catch (InterruptedException ie) {
755         IOException iie = new InterruptedIOException();
756         iie.initCause(ie);
757         throw iie;
758       }
759       checkForErrors();
760     }
761 
762     return EXIT_SUCCESS;
763   }
764 
765   // If an exception is thrown by one of worker threads, it will be
766   // stored here.
767   protected AtomicReference<Throwable> thrown = new AtomicReference<Throwable>();
768 
769   private void workerThreadError(Throwable t) {
770     thrown.compareAndSet(null, t);
771   }
772 
773   /**
774    * Check for errors in the writer threads. If any is found, rethrow it.
775    */
776   private void checkForErrors() throws IOException {
777     Throwable thrown = this.thrown.get();
778     if (thrown == null) return;
779     if (thrown instanceof IOException) {
780       throw (IOException) thrown;
781     } else {
782       throw new RuntimeException(thrown);
783     }
784   }
785 
786   class WorkerThread extends Thread {
787     private String[] workerArgs;
788 
789     WorkerThread(int i, String[] args) {
790       super("WorkerThread-" + i);
791       workerArgs = args;
792     }
793 
794     @Override
795     public void run() {
796       try {
797         int ret = ToolRunner.run(HBaseConfiguration.create(), new LoadTestTool(), workerArgs);
798         if (ret != 0) {
799           throw new RuntimeException("LoadTestTool exit with non-zero return code.");
800         }
801       } catch (Exception ex) {
802         LOG.error("Error in worker thread", ex);
803         workerThreadError(ex);
804       }
805     }
806   }
807 
808   private void addAuthInfoToConf(Properties authConfig, Configuration conf, String owner,
809       String userList) throws IOException {
810     List<String> users = Arrays.asList(userList.split(","));
811     users.add(owner);
812     for (String user : users) {
813       String keyTabFileConfKey = "hbase." + user + ".keytab.file";
814       String principalConfKey = "hbase." + user + ".kerberos.principal";
815       if (!authConfig.containsKey(keyTabFileConfKey) || !authConfig.containsKey(principalConfKey)) {
816         throw new IOException("Authentication configs missing for user : " + user);
817       }
818     }
819     for (String key : authConfig.stringPropertyNames()) {
820       conf.set(key, authConfig.getProperty(key));
821     }
822     LOG.debug("Added authentication properties to config successfully.");
823   }
824 
825   public static UserGroupInformation loginAndReturnUGI(Configuration conf, String username)
826       throws IOException {
827     String hostname = InetAddress.getLocalHost().getHostName();
828     String keyTabFileConfKey = "hbase." + username + ".keytab.file";
829     String keyTabFileLocation = conf.get(keyTabFileConfKey);
830     String principalConfKey = "hbase." + username + ".kerberos.principal";
831     String principal = SecurityUtil.getServerPrincipal(conf.get(principalConfKey), hostname);
832     if (keyTabFileLocation == null || principal == null) {
833       LOG.warn("Principal or key tab file null for : " + principalConfKey + ", "
834           + keyTabFileConfKey);
835     }
836     UserGroupInformation ugi =
837         UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, keyTabFileLocation);
838     return ugi;
839   }
840 }