001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with this 004 * work for additional information regarding copyright ownership. The ASF 005 * licenses this file to you under the Apache License, Version 2.0 (the 006 * "License"); you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017package org.apache.hadoop.hbase.util; 018 019import java.io.IOException; 020import java.io.InterruptedIOException; 021import java.lang.reflect.Constructor; 022import java.security.SecureRandom; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.List; 026import java.util.Properties; 027import java.util.concurrent.atomic.AtomicReference; 028 029import javax.crypto.spec.SecretKeySpec; 030 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.HBaseConfiguration; 033import org.apache.hadoop.hbase.HBaseInterfaceAudience; 034import org.apache.hadoop.hbase.HBaseTestingUtility; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 038import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 039import org.apache.yetus.audience.InterfaceAudience; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042import org.apache.hadoop.hbase.client.Admin; 043import org.apache.hadoop.hbase.client.Connection; 044import org.apache.hadoop.hbase.client.ConnectionFactory; 045import org.apache.hadoop.hbase.client.Durability; 046import org.apache.hadoop.hbase.client.TableDescriptor; 047import org.apache.hadoop.hbase.io.compress.Compression; 048import org.apache.hadoop.hbase.io.crypto.Cipher; 049import org.apache.hadoop.hbase.io.crypto.Encryption; 050import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 051import org.apache.hadoop.hbase.log.HBaseMarkers; 052import org.apache.hadoop.hbase.regionserver.BloomType; 053import org.apache.hadoop.hbase.security.EncryptionUtil; 054import org.apache.hadoop.hbase.security.HBaseKerberosUtils; 055import org.apache.hadoop.hbase.security.User; 056import org.apache.hadoop.hbase.security.access.AccessControlClient; 057import org.apache.hadoop.hbase.security.access.Permission; 058import org.apache.hadoop.hbase.util.test.LoadTestDataGenerator; 059import org.apache.hadoop.hbase.util.test.LoadTestDataGeneratorWithACL; 060import org.apache.hadoop.util.ToolRunner; 061 062import org.apache.hbase.thirdparty.org.apache.commons.cli.AlreadySelectedException; 063import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 064import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLineParser; 065import org.apache.hbase.thirdparty.org.apache.commons.cli.DefaultParser; 066import org.apache.hbase.thirdparty.org.apache.commons.cli.MissingOptionException; 067import org.apache.hbase.thirdparty.org.apache.commons.cli.Options; 068import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException; 069 070/** 071 * A command-line utility that reads, writes, and verifies data. Unlike 072 * {@link org.apache.hadoop.hbase.PerformanceEvaluation}, this tool validates the data written, 073 * and supports simultaneously writing and reading the same set of keys. 074 */ 075@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS) 076public class LoadTestTool extends AbstractHBaseTool { 077 078 private static final Logger LOG = LoggerFactory.getLogger(LoadTestTool.class); 079 private static final String COLON = ":"; 080 081 /** Table name for the test */ 082 private TableName tableName; 083 084 /** Column families for the test */ 085 private byte[][] families; 086 087 /** Table name to use of not overridden on the command line */ 088 protected static final String DEFAULT_TABLE_NAME = "cluster_test"; 089 090 /** The default data size if not specified */ 091 protected static final int DEFAULT_DATA_SIZE = 64; 092 093 /** The number of reader/writer threads if not specified */ 094 protected static final int DEFAULT_NUM_THREADS = 20; 095 096 /** Usage string for the load option */ 097 protected static final String OPT_USAGE_LOAD = 098 "<avg_cols_per_key>:<avg_data_size>" + 099 "[:<#threads=" + DEFAULT_NUM_THREADS + ">]"; 100 101 /** Usage string for the read option */ 102 protected static final String OPT_USAGE_READ = 103 "<verify_percent>[:<#threads=" + DEFAULT_NUM_THREADS + ">]"; 104 105 /** Usage string for the update option */ 106 protected static final String OPT_USAGE_UPDATE = 107 "<update_percent>[:<#threads=" + DEFAULT_NUM_THREADS 108 + ">][:<#whether to ignore nonce collisions=0>]"; 109 110 protected static final String OPT_USAGE_BLOOM = "Bloom filter type, one of " + 111 Arrays.toString(BloomType.values()); 112 113 protected static final String OPT_USAGE_COMPRESSION = "Compression type, " + 114 "one of " + Arrays.toString(Compression.Algorithm.values()); 115 116 public static final String OPT_BLOOM = "bloom"; 117 public static final String OPT_COMPRESSION = "compression"; 118 public static final String OPT_DEFERRED_LOG_FLUSH = "deferredlogflush"; 119 public static final String OPT_DEFERRED_LOG_FLUSH_USAGE = "Enable deferred log flush."; 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_WRITER = "writer"; 130 public static final String OPT_WRITER_USAGE = "The class for executing the write requests"; 131 132 public static final String OPT_UPDATER = "updater"; 133 public static final String OPT_UPDATER_USAGE = "The class for executing the update requests"; 134 135 public static final String OPT_READER = "reader"; 136 public static final String OPT_READER_USAGE = "The class for executing the read requests"; 137 138 protected static final String OPT_KEY_WINDOW = "key_window"; 139 protected static final String OPT_WRITE = "write"; 140 protected static final String OPT_MAX_READ_ERRORS = "max_read_errors"; 141 public static final String OPT_MULTIPUT = "multiput"; 142 public static final String OPT_MULTIGET = "multiget_batchsize"; 143 protected static final String OPT_NUM_KEYS = "num_keys"; 144 protected static final String OPT_READ = "read"; 145 protected static final String OPT_START_KEY = "start_key"; 146 public static final String OPT_TABLE_NAME = "tn"; 147 public static final String OPT_COLUMN_FAMILIES = "families"; 148 protected static final String OPT_ZK_QUORUM = "zk"; 149 protected static final String OPT_ZK_PARENT_NODE = "zk_root"; 150 protected static final String OPT_SKIP_INIT = "skip_init"; 151 protected static final String OPT_INIT_ONLY = "init_only"; 152 protected static final String NUM_TABLES = "num_tables"; 153 protected static final String OPT_BATCHUPDATE = "batchupdate"; 154 protected static final String OPT_UPDATE = "update"; 155 156 public static final String OPT_ENCRYPTION = "encryption"; 157 protected static final String OPT_ENCRYPTION_USAGE = 158 "Enables transparent encryption on the test table, one of " + 159 Arrays.toString(Encryption.getSupportedCiphers()); 160 161 public static final String OPT_NUM_REGIONS_PER_SERVER = "num_regions_per_server"; 162 protected static final String OPT_NUM_REGIONS_PER_SERVER_USAGE 163 = "Desired number of regions per region server. Defaults to 5."; 164 public static int DEFAULT_NUM_REGIONS_PER_SERVER = 5; 165 166 public static final String OPT_REGION_REPLICATION = "region_replication"; 167 protected static final String OPT_REGION_REPLICATION_USAGE = 168 "Desired number of replicas per region"; 169 170 public static final String OPT_REGION_REPLICA_ID = "region_replica_id"; 171 protected static final String OPT_REGION_REPLICA_ID_USAGE = 172 "Region replica id to do the reads from"; 173 174 public static final String OPT_MOB_THRESHOLD = "mob_threshold"; 175 protected static final String OPT_MOB_THRESHOLD_USAGE = 176 "Desired cell size to exceed in bytes that will use the MOB write path"; 177 178 protected static final long DEFAULT_START_KEY = 0; 179 180 /** This will be removed as we factor out the dependency on command line */ 181 protected CommandLine cmd; 182 183 protected MultiThreadedWriter writerThreads = null; 184 protected MultiThreadedReader readerThreads = null; 185 protected MultiThreadedUpdater updaterThreads = null; 186 187 protected long startKey, endKey; 188 189 protected boolean isWrite, isRead, isUpdate; 190 protected boolean deferredLogFlush; 191 192 // Column family options 193 protected DataBlockEncoding dataBlockEncodingAlgo; 194 protected Compression.Algorithm compressAlgo; 195 protected BloomType bloomType; 196 private boolean inMemoryCF; 197 198 private User userOwner; 199 // Writer options 200 protected int numWriterThreads = DEFAULT_NUM_THREADS; 201 protected int minColsPerKey, maxColsPerKey; 202 protected int minColDataSize = DEFAULT_DATA_SIZE, maxColDataSize = DEFAULT_DATA_SIZE; 203 protected boolean isMultiPut; 204 205 // Updater options 206 protected int numUpdaterThreads = DEFAULT_NUM_THREADS; 207 protected int updatePercent; 208 protected boolean ignoreConflicts = false; 209 protected boolean isBatchUpdate; 210 211 // Reader options 212 private int numReaderThreads = DEFAULT_NUM_THREADS; 213 private int keyWindow = MultiThreadedReader.DEFAULT_KEY_WINDOW; 214 private int multiGetBatchSize = MultiThreadedReader.DEFAULT_BATCH_SIZE; 215 private int maxReadErrors = MultiThreadedReader.DEFAULT_MAX_ERRORS; 216 private int verifyPercent; 217 218 private int numTables = 1; 219 220 private String superUser; 221 222 private String userNames; 223 //This file is used to read authentication information in secure clusters. 224 private String authnFileName; 225 226 private int numRegionsPerServer = DEFAULT_NUM_REGIONS_PER_SERVER; 227 private int regionReplication = -1; // not set 228 private int regionReplicaId = -1; // not set 229 230 private int mobThreshold = -1; // not set 231 232 // TODO: refactor LoadTestToolImpl somewhere to make the usage from tests less bad, 233 // console tool itself should only be used from console. 234 protected boolean isSkipInit = false; 235 protected boolean isInitOnly = false; 236 237 protected Cipher cipher = null; 238 239 protected String[] splitColonSeparated(String option, 240 int minNumCols, int maxNumCols) { 241 String optVal = cmd.getOptionValue(option); 242 String[] cols = optVal.split(COLON); 243 if (cols.length < minNumCols || cols.length > maxNumCols) { 244 throw new IllegalArgumentException("Expected at least " 245 + minNumCols + " columns but no more than " + maxNumCols + 246 " in the colon-separated value '" + optVal + "' of the " + 247 "-" + option + " option"); 248 } 249 return cols; 250 } 251 252 protected int getNumThreads(String numThreadsStr) { 253 return parseInt(numThreadsStr, 1, Short.MAX_VALUE); 254 } 255 256 public byte[][] getColumnFamilies() { 257 return families; 258 } 259 260 /** 261 * Apply column family options such as Bloom filters, compression, and data 262 * block encoding. 263 */ 264 protected void applyColumnFamilyOptions(TableName tableName, 265 byte[][] columnFamilies) throws IOException { 266 try (Connection conn = ConnectionFactory.createConnection(conf); 267 Admin admin = conn.getAdmin()) { 268 TableDescriptor tableDesc = admin.getDescriptor(tableName); 269 LOG.info("Disabling table " + tableName); 270 admin.disableTable(tableName); 271 for (byte[] cf : columnFamilies) { 272 ColumnFamilyDescriptor columnDesc = tableDesc.getColumnFamily(cf); 273 boolean isNewCf = columnDesc == null; 274 ColumnFamilyDescriptorBuilder columnDescBuilder = isNewCf ? 275 ColumnFamilyDescriptorBuilder.newBuilder(cf) : 276 ColumnFamilyDescriptorBuilder.newBuilder(columnDesc); 277 if (bloomType != null) { 278 columnDescBuilder.setBloomFilterType(bloomType); 279 } 280 if (compressAlgo != null) { 281 columnDescBuilder.setCompressionType(compressAlgo); 282 } 283 if (dataBlockEncodingAlgo != null) { 284 columnDescBuilder.setDataBlockEncoding(dataBlockEncodingAlgo); 285 } 286 if (inMemoryCF) { 287 columnDescBuilder.setInMemory(inMemoryCF); 288 } 289 if (cipher != null) { 290 byte[] keyBytes = new byte[cipher.getKeyLength()]; 291 new SecureRandom().nextBytes(keyBytes); 292 columnDescBuilder.setEncryptionType(cipher.getName()); 293 columnDescBuilder.setEncryptionKey( 294 EncryptionUtil.wrapKey(conf, 295 User.getCurrent().getShortName(), 296 new SecretKeySpec(keyBytes, 297 cipher.getName()))); 298 } 299 if (mobThreshold >= 0) { 300 columnDescBuilder.setMobEnabled(true); 301 columnDescBuilder.setMobThreshold(mobThreshold); 302 } 303 304 if (isNewCf) { 305 admin.addColumnFamily(tableName, columnDescBuilder.build()); 306 } else { 307 admin.modifyColumnFamily(tableName, columnDescBuilder.build()); 308 } 309 } 310 LOG.info("Enabling table " + tableName); 311 admin.enableTable(tableName); 312 } 313 } 314 315 @Override 316 protected void addOptions() { 317 addOptWithArg(OPT_ZK_QUORUM, "ZK quorum as comma-separated host names " + 318 "without port numbers"); 319 addOptWithArg(OPT_ZK_PARENT_NODE, "name of parent znode in zookeeper"); 320 addOptWithArg(OPT_TABLE_NAME, "The name of the table to read or write"); 321 addOptWithArg(OPT_COLUMN_FAMILIES, "The name of the column families to use separated by comma"); 322 addOptWithArg(OPT_WRITE, OPT_USAGE_LOAD); 323 addOptWithArg(OPT_READ, OPT_USAGE_READ); 324 addOptWithArg(OPT_UPDATE, OPT_USAGE_UPDATE); 325 addOptNoArg(OPT_INIT_ONLY, "Initialize the test table only, don't do any loading"); 326 addOptWithArg(OPT_BLOOM, OPT_USAGE_BLOOM); 327 addOptWithArg(OPT_COMPRESSION, OPT_USAGE_COMPRESSION); 328 addOptWithArg(HFileTestUtil.OPT_DATA_BLOCK_ENCODING, HFileTestUtil.OPT_DATA_BLOCK_ENCODING_USAGE); 329 addOptWithArg(OPT_MAX_READ_ERRORS, "The maximum number of read errors " + 330 "to tolerate before terminating all reader threads. The default is " + 331 MultiThreadedReader.DEFAULT_MAX_ERRORS + "."); 332 addOptWithArg(OPT_MULTIGET, "Whether to use multi-gets as opposed to " + 333 "separate gets for every column in a row"); 334 addOptWithArg(OPT_KEY_WINDOW, "The 'key window' to maintain between " + 335 "reads and writes for concurrent write/read workload. The default " + 336 "is " + MultiThreadedReader.DEFAULT_KEY_WINDOW + "."); 337 338 addOptNoArg(OPT_MULTIPUT, "Whether to use multi-puts as opposed to " + 339 "separate puts for every column in a row"); 340 addOptNoArg(OPT_BATCHUPDATE, "Whether to use batch as opposed to " + 341 "separate updates for every column in a row"); 342 addOptNoArg(OPT_INMEMORY, OPT_USAGE_IN_MEMORY); 343 addOptWithArg(OPT_GENERATOR, OPT_GENERATOR_USAGE); 344 addOptWithArg(OPT_WRITER, OPT_WRITER_USAGE); 345 addOptWithArg(OPT_UPDATER, OPT_UPDATER_USAGE); 346 addOptWithArg(OPT_READER, OPT_READER_USAGE); 347 348 addOptWithArg(OPT_NUM_KEYS, "The number of keys to read/write"); 349 addOptWithArg(OPT_START_KEY, "The first key to read/write " + 350 "(a 0-based index). The default value is " + 351 DEFAULT_START_KEY + "."); 352 addOptNoArg(OPT_SKIP_INIT, "Skip the initialization; assume test table " 353 + "already exists"); 354 355 addOptWithArg(NUM_TABLES, 356 "A positive integer number. When a number n is specified, load test " 357 + "tool will load n table parallely. -tn parameter value becomes " 358 + "table name prefix. Each table name is in format <tn>_1...<tn>_n"); 359 360 addOptWithArg(OPT_ENCRYPTION, OPT_ENCRYPTION_USAGE); 361 addOptNoArg(OPT_DEFERRED_LOG_FLUSH, OPT_DEFERRED_LOG_FLUSH_USAGE); 362 addOptWithArg(OPT_NUM_REGIONS_PER_SERVER, OPT_NUM_REGIONS_PER_SERVER_USAGE); 363 addOptWithArg(OPT_REGION_REPLICATION, OPT_REGION_REPLICATION_USAGE); 364 addOptWithArg(OPT_REGION_REPLICA_ID, OPT_REGION_REPLICA_ID_USAGE); 365 addOptWithArg(OPT_MOB_THRESHOLD, OPT_MOB_THRESHOLD_USAGE); 366 } 367 368 @Override 369 protected CommandLineParser newParser() { 370 // Commons-CLI lacks the capability to handle combinations of options, so we do it ourselves 371 // Validate in parse() to get helpful error messages instead of exploding in processOptions() 372 return new DefaultParser() { 373 @Override 374 public CommandLine parse(Options opts, String[] args, Properties props, boolean stop) 375 throws ParseException { 376 CommandLine cl = super.parse(opts, args, props, stop); 377 378 boolean isReadWriteUpdate = cmd.hasOption(OPT_READ) 379 || cmd.hasOption(OPT_WRITE) 380 || cmd.hasOption(OPT_UPDATE); 381 boolean isInitOnly = cmd.hasOption(OPT_INIT_ONLY); 382 383 if (!isInitOnly && !isReadWriteUpdate) { 384 throw new MissingOptionException("Must specify either -" + OPT_INIT_ONLY 385 + " or at least one of -" + OPT_READ + ", -" + OPT_WRITE + ", -" + OPT_UPDATE); 386 } 387 388 if (isInitOnly && isReadWriteUpdate) { 389 throw new AlreadySelectedException(OPT_INIT_ONLY + " cannot be specified with any of -" 390 + OPT_READ + ", -" + OPT_WRITE + ", -" + OPT_UPDATE); 391 } 392 393 if (isReadWriteUpdate && !cmd.hasOption(OPT_NUM_KEYS)) { 394 throw new MissingOptionException(OPT_NUM_KEYS + " must be specified in read/write mode."); 395 } 396 397 return cl; 398 } 399 }; 400 } 401 402 @Override 403 protected void processOptions(CommandLine cmd) { 404 this.cmd = cmd; 405 406 tableName = TableName.valueOf(cmd.getOptionValue(OPT_TABLE_NAME, 407 DEFAULT_TABLE_NAME)); 408 409 if (cmd.hasOption(OPT_COLUMN_FAMILIES)) { 410 String[] list = cmd.getOptionValue(OPT_COLUMN_FAMILIES).split(","); 411 families = new byte[list.length][]; 412 for (int i = 0; i < list.length; i++) { 413 families[i] = Bytes.toBytes(list[i]); 414 } 415 } else { 416 families = HFileTestUtil.DEFAULT_COLUMN_FAMILIES; 417 } 418 419 isWrite = cmd.hasOption(OPT_WRITE); 420 isRead = cmd.hasOption(OPT_READ); 421 isUpdate = cmd.hasOption(OPT_UPDATE); 422 isInitOnly = cmd.hasOption(OPT_INIT_ONLY); 423 deferredLogFlush = cmd.hasOption(OPT_DEFERRED_LOG_FLUSH); 424 425 if (!isInitOnly) { 426 startKey = parseLong(cmd.getOptionValue(OPT_START_KEY, 427 String.valueOf(DEFAULT_START_KEY)), 0, Long.MAX_VALUE); 428 long numKeys = parseLong(cmd.getOptionValue(OPT_NUM_KEYS), 1, 429 Long.MAX_VALUE - startKey); 430 endKey = startKey + numKeys; 431 isSkipInit = cmd.hasOption(OPT_SKIP_INIT); 432 System.out.println("Key range: [" + startKey + ".." + (endKey - 1) + "]"); 433 } 434 435 parseColumnFamilyOptions(cmd); 436 437 if (isWrite) { 438 String[] writeOpts = splitColonSeparated(OPT_WRITE, 2, 3); 439 440 int colIndex = 0; 441 minColsPerKey = 1; 442 maxColsPerKey = 2 * Integer.parseInt(writeOpts[colIndex++]); 443 int avgColDataSize = 444 parseInt(writeOpts[colIndex++], 1, Integer.MAX_VALUE); 445 minColDataSize = avgColDataSize / 2; 446 maxColDataSize = avgColDataSize * 3 / 2; 447 448 if (colIndex < writeOpts.length) { 449 numWriterThreads = getNumThreads(writeOpts[colIndex++]); 450 } 451 452 isMultiPut = cmd.hasOption(OPT_MULTIPUT); 453 454 mobThreshold = -1; 455 if (cmd.hasOption(OPT_MOB_THRESHOLD)) { 456 mobThreshold = Integer.parseInt(cmd.getOptionValue(OPT_MOB_THRESHOLD)); 457 } 458 459 System.out.println("Multi-puts: " + isMultiPut); 460 System.out.println("Columns per key: " + minColsPerKey + ".." 461 + maxColsPerKey); 462 System.out.println("Data size per column: " + minColDataSize + ".." 463 + maxColDataSize); 464 } 465 466 if (isUpdate) { 467 String[] mutateOpts = splitColonSeparated(OPT_UPDATE, 1, 3); 468 int colIndex = 0; 469 updatePercent = parseInt(mutateOpts[colIndex++], 0, 100); 470 if (colIndex < mutateOpts.length) { 471 numUpdaterThreads = getNumThreads(mutateOpts[colIndex++]); 472 } 473 if (colIndex < mutateOpts.length) { 474 ignoreConflicts = parseInt(mutateOpts[colIndex++], 0, 1) == 1; 475 } 476 477 isBatchUpdate = cmd.hasOption(OPT_BATCHUPDATE); 478 479 System.out.println("Batch updates: " + isBatchUpdate); 480 System.out.println("Percent of keys to update: " + updatePercent); 481 System.out.println("Updater threads: " + numUpdaterThreads); 482 System.out.println("Ignore nonce conflicts: " + ignoreConflicts); 483 } 484 485 if (isRead) { 486 String[] readOpts = splitColonSeparated(OPT_READ, 1, 2); 487 int colIndex = 0; 488 verifyPercent = parseInt(readOpts[colIndex++], 0, 100); 489 if (colIndex < readOpts.length) { 490 numReaderThreads = getNumThreads(readOpts[colIndex++]); 491 } 492 493 if (cmd.hasOption(OPT_MAX_READ_ERRORS)) { 494 maxReadErrors = parseInt(cmd.getOptionValue(OPT_MAX_READ_ERRORS), 495 0, Integer.MAX_VALUE); 496 } 497 498 if (cmd.hasOption(OPT_KEY_WINDOW)) { 499 keyWindow = parseInt(cmd.getOptionValue(OPT_KEY_WINDOW), 500 0, Integer.MAX_VALUE); 501 } 502 503 if (cmd.hasOption(OPT_MULTIGET)) { 504 multiGetBatchSize = parseInt(cmd.getOptionValue(OPT_MULTIGET), 505 0, Integer.MAX_VALUE); 506 } 507 508 System.out.println("Multi-gets (value of 1 means no multigets): " + multiGetBatchSize); 509 System.out.println("Percent of keys to verify: " + verifyPercent); 510 System.out.println("Reader threads: " + numReaderThreads); 511 } 512 513 numTables = 1; 514 if (cmd.hasOption(NUM_TABLES)) { 515 numTables = parseInt(cmd.getOptionValue(NUM_TABLES), 1, Short.MAX_VALUE); 516 } 517 518 numRegionsPerServer = DEFAULT_NUM_REGIONS_PER_SERVER; 519 if (cmd.hasOption(OPT_NUM_REGIONS_PER_SERVER)) { 520 numRegionsPerServer = Integer.parseInt(cmd.getOptionValue(OPT_NUM_REGIONS_PER_SERVER)); 521 } 522 523 regionReplication = 1; 524 if (cmd.hasOption(OPT_REGION_REPLICATION)) { 525 regionReplication = Integer.parseInt(cmd.getOptionValue(OPT_REGION_REPLICATION)); 526 } 527 528 regionReplicaId = -1; 529 if (cmd.hasOption(OPT_REGION_REPLICA_ID)) { 530 regionReplicaId = Integer.parseInt(cmd.getOptionValue(OPT_REGION_REPLICA_ID)); 531 } 532 } 533 534 private void parseColumnFamilyOptions(CommandLine cmd) { 535 String dataBlockEncodingStr = cmd.getOptionValue(HFileTestUtil.OPT_DATA_BLOCK_ENCODING); 536 dataBlockEncodingAlgo = dataBlockEncodingStr == null ? null : 537 DataBlockEncoding.valueOf(dataBlockEncodingStr); 538 539 String compressStr = cmd.getOptionValue(OPT_COMPRESSION); 540 compressAlgo = compressStr == null ? Compression.Algorithm.NONE : 541 Compression.Algorithm.valueOf(compressStr); 542 543 String bloomStr = cmd.getOptionValue(OPT_BLOOM); 544 bloomType = bloomStr == null ? BloomType.ROW : 545 BloomType.valueOf(bloomStr); 546 547 inMemoryCF = cmd.hasOption(OPT_INMEMORY); 548 if (cmd.hasOption(OPT_ENCRYPTION)) { 549 cipher = Encryption.getCipher(conf, cmd.getOptionValue(OPT_ENCRYPTION)); 550 } 551 552 } 553 554 public void initTestTable() throws IOException { 555 Durability durability = Durability.USE_DEFAULT; 556 if (deferredLogFlush) { 557 durability = Durability.ASYNC_WAL; 558 } 559 560 HBaseTestingUtility.createPreSplitLoadTestTable(conf, tableName, 561 getColumnFamilies(), compressAlgo, dataBlockEncodingAlgo, numRegionsPerServer, 562 regionReplication, durability); 563 applyColumnFamilyOptions(tableName, getColumnFamilies()); 564 } 565 566 @Override 567 protected int doWork() throws IOException { 568 if (numTables > 1) { 569 return parallelLoadTables(); 570 } else { 571 return loadTable(); 572 } 573 } 574 575 protected int loadTable() throws IOException { 576 if (cmd.hasOption(OPT_ZK_QUORUM)) { 577 conf.set(HConstants.ZOOKEEPER_QUORUM, cmd.getOptionValue(OPT_ZK_QUORUM)); 578 } 579 if (cmd.hasOption(OPT_ZK_PARENT_NODE)) { 580 conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT, cmd.getOptionValue(OPT_ZK_PARENT_NODE)); 581 } 582 583 if (isInitOnly) { 584 LOG.info("Initializing only; no reads or writes"); 585 initTestTable(); 586 return 0; 587 } 588 589 if (!isSkipInit) { 590 initTestTable(); 591 } 592 LoadTestDataGenerator dataGen = null; 593 if (cmd.hasOption(OPT_GENERATOR)) { 594 String[] clazzAndArgs = cmd.getOptionValue(OPT_GENERATOR).split(COLON); 595 dataGen = getLoadGeneratorInstance(clazzAndArgs[0]); 596 String[] args; 597 if (dataGen instanceof LoadTestDataGeneratorWithACL) { 598 LOG.info("Using LoadTestDataGeneratorWithACL"); 599 if (User.isHBaseSecurityEnabled(conf)) { 600 LOG.info("Security is enabled"); 601 authnFileName = clazzAndArgs[1]; 602 superUser = clazzAndArgs[2]; 603 userNames = clazzAndArgs[3]; 604 args = Arrays.copyOfRange(clazzAndArgs, 2, clazzAndArgs.length); 605 Properties authConfig = new Properties(); 606 authConfig.load(this.getClass().getClassLoader().getResourceAsStream(authnFileName)); 607 try { 608 addAuthInfoToConf(authConfig, conf, superUser, userNames); 609 } catch (IOException exp) { 610 LOG.error(exp.toString(), exp); 611 return EXIT_FAILURE; 612 } 613 userOwner = User.create(HBaseKerberosUtils.loginAndReturnUGI(conf, superUser)); 614 } else { 615 superUser = clazzAndArgs[1]; 616 userNames = clazzAndArgs[2]; 617 args = Arrays.copyOfRange(clazzAndArgs, 1, clazzAndArgs.length); 618 userOwner = User.createUserForTesting(conf, superUser, new String[0]); 619 } 620 } else { 621 args = clazzAndArgs.length == 1 ? new String[0] : Arrays.copyOfRange(clazzAndArgs, 1, 622 clazzAndArgs.length); 623 } 624 dataGen.initialize(args); 625 } else { 626 // Default DataGenerator is MultiThreadedAction.DefaultDataGenerator 627 dataGen = new MultiThreadedAction.DefaultDataGenerator(minColDataSize, maxColDataSize, 628 minColsPerKey, maxColsPerKey, families); 629 } 630 631 if (userOwner != null) { 632 LOG.info("Granting permissions for user " + userOwner.getShortName()); 633 Permission.Action[] actions = { 634 Permission.Action.ADMIN, Permission.Action.CREATE, 635 Permission.Action.READ, Permission.Action.WRITE }; 636 try { 637 AccessControlClient.grant(ConnectionFactory.createConnection(conf), 638 tableName, userOwner.getShortName(), null, null, actions); 639 } catch (Throwable e) { 640 LOG.error(HBaseMarkers.FATAL, "Error in granting permission for the user " + 641 userOwner.getShortName(), e); 642 return EXIT_FAILURE; 643 } 644 } 645 646 if (userNames != null) { 647 // This will be comma separated list of expressions. 648 String users[] = userNames.split(","); 649 User user = null; 650 for (String userStr : users) { 651 if (User.isHBaseSecurityEnabled(conf)) { 652 user = User.create(HBaseKerberosUtils.loginAndReturnUGI(conf, userStr)); 653 } else { 654 user = User.createUserForTesting(conf, userStr, new String[0]); 655 } 656 } 657 } 658 659 if (isWrite) { 660 if (userOwner != null) { 661 writerThreads = new MultiThreadedWriterWithACL(dataGen, conf, tableName, userOwner); 662 } else { 663 String writerClass = null; 664 if (cmd.hasOption(OPT_WRITER)) { 665 writerClass = cmd.getOptionValue(OPT_WRITER); 666 } else { 667 writerClass = MultiThreadedWriter.class.getCanonicalName(); 668 } 669 670 writerThreads = getMultiThreadedWriterInstance(writerClass, dataGen); 671 } 672 writerThreads.setMultiPut(isMultiPut); 673 } 674 675 if (isUpdate) { 676 if (userOwner != null) { 677 updaterThreads = new MultiThreadedUpdaterWithACL(dataGen, conf, tableName, updatePercent, 678 userOwner, userNames); 679 } else { 680 String updaterClass = null; 681 if (cmd.hasOption(OPT_UPDATER)) { 682 updaterClass = cmd.getOptionValue(OPT_UPDATER); 683 } else { 684 updaterClass = MultiThreadedUpdater.class.getCanonicalName(); 685 } 686 updaterThreads = getMultiThreadedUpdaterInstance(updaterClass, dataGen); 687 } 688 updaterThreads.setBatchUpdate(isBatchUpdate); 689 updaterThreads.setIgnoreNonceConflicts(ignoreConflicts); 690 } 691 692 if (isRead) { 693 if (userOwner != null) { 694 readerThreads = new MultiThreadedReaderWithACL(dataGen, conf, tableName, verifyPercent, 695 userNames); 696 } else { 697 String readerClass = null; 698 if (cmd.hasOption(OPT_READER)) { 699 readerClass = cmd.getOptionValue(OPT_READER); 700 } else { 701 readerClass = MultiThreadedReader.class.getCanonicalName(); 702 } 703 readerThreads = getMultiThreadedReaderInstance(readerClass, dataGen); 704 } 705 readerThreads.setMaxErrors(maxReadErrors); 706 readerThreads.setKeyWindow(keyWindow); 707 readerThreads.setMultiGetBatchSize(multiGetBatchSize); 708 readerThreads.setRegionReplicaId(regionReplicaId); 709 } 710 711 if (isUpdate && isWrite) { 712 LOG.info("Concurrent write/update workload: making updaters aware of the " + 713 "write point"); 714 updaterThreads.linkToWriter(writerThreads); 715 } 716 717 if (isRead && (isUpdate || isWrite)) { 718 LOG.info("Concurrent write/read workload: making readers aware of the " + 719 "write point"); 720 readerThreads.linkToWriter(isUpdate ? updaterThreads : writerThreads); 721 } 722 723 if (isWrite) { 724 System.out.println("Starting to write data..."); 725 writerThreads.start(startKey, endKey, numWriterThreads); 726 } 727 728 if (isUpdate) { 729 LOG.info("Starting to mutate data..."); 730 System.out.println("Starting to mutate data..."); 731 // TODO : currently append and increment operations not tested with tags 732 // Will update this after it is done 733 updaterThreads.start(startKey, endKey, numUpdaterThreads); 734 } 735 736 if (isRead) { 737 System.out.println("Starting to read data..."); 738 readerThreads.start(startKey, endKey, numReaderThreads); 739 } 740 741 if (isWrite) { 742 writerThreads.waitForFinish(); 743 } 744 745 if (isUpdate) { 746 updaterThreads.waitForFinish(); 747 } 748 749 if (isRead) { 750 readerThreads.waitForFinish(); 751 } 752 753 boolean success = true; 754 if (isWrite) { 755 success = success && writerThreads.getNumWriteFailures() == 0; 756 } 757 if (isUpdate) { 758 success = success && updaterThreads.getNumWriteFailures() == 0; 759 } 760 if (isRead) { 761 success = success && readerThreads.getNumReadErrors() == 0 762 && readerThreads.getNumReadFailures() == 0; 763 } 764 return success ? EXIT_SUCCESS : EXIT_FAILURE; 765 } 766 767 private LoadTestDataGenerator getLoadGeneratorInstance(String clazzName) throws IOException { 768 try { 769 Class<?> clazz = Class.forName(clazzName); 770 Constructor<?> constructor = clazz.getConstructor(int.class, int.class, int.class, int.class, 771 byte[][].class); 772 return (LoadTestDataGenerator) constructor.newInstance(minColDataSize, maxColDataSize, 773 minColsPerKey, maxColsPerKey, families); 774 } catch (Exception e) { 775 throw new IOException(e); 776 } 777 } 778 779 private MultiThreadedWriter getMultiThreadedWriterInstance(String clazzName 780 , LoadTestDataGenerator dataGen) throws IOException { 781 try { 782 Class<?> clazz = Class.forName(clazzName); 783 Constructor<?> constructor = clazz.getConstructor( 784 LoadTestDataGenerator.class, Configuration.class, TableName.class); 785 return (MultiThreadedWriter) constructor.newInstance(dataGen, conf, tableName); 786 } catch (Exception e) { 787 throw new IOException(e); 788 } 789 } 790 791 private MultiThreadedUpdater getMultiThreadedUpdaterInstance(String clazzName 792 , LoadTestDataGenerator dataGen) throws IOException { 793 try { 794 Class<?> clazz = Class.forName(clazzName); 795 Constructor<?> constructor = clazz.getConstructor( 796 LoadTestDataGenerator.class, Configuration.class, TableName.class, double.class); 797 return (MultiThreadedUpdater) constructor.newInstance( 798 dataGen, conf, tableName, updatePercent); 799 } catch (Exception e) { 800 throw new IOException(e); 801 } 802 } 803 804 private MultiThreadedReader getMultiThreadedReaderInstance(String clazzName 805 , LoadTestDataGenerator dataGen) throws IOException { 806 try { 807 Class<?> clazz = Class.forName(clazzName); 808 Constructor<?> constructor = clazz.getConstructor( 809 LoadTestDataGenerator.class, Configuration.class, TableName.class, double.class); 810 return (MultiThreadedReader) constructor.newInstance(dataGen, conf, tableName, verifyPercent); 811 } catch (Exception e) { 812 throw new IOException(e); 813 } 814 } 815 816 public static void main(String[] args) { 817 new LoadTestTool().doStaticMain(args); 818 } 819 820 /** 821 * When NUM_TABLES is specified, the function starts multiple worker threads 822 * which individually start a LoadTestTool instance to load a table. Each 823 * table name is in format <tn>_<index>. For example, "-tn test -num_tables 2" 824 * , table names will be "test_1", "test_2" 825 * 826 * @throws IOException 827 */ 828 private int parallelLoadTables() 829 throws IOException { 830 // create new command args 831 String tableName = cmd.getOptionValue(OPT_TABLE_NAME, DEFAULT_TABLE_NAME); 832 String[] newArgs = null; 833 if (!cmd.hasOption(LoadTestTool.OPT_TABLE_NAME)) { 834 newArgs = new String[cmdLineArgs.length + 2]; 835 newArgs[0] = "-" + LoadTestTool.OPT_TABLE_NAME; 836 newArgs[1] = LoadTestTool.DEFAULT_TABLE_NAME; 837 System.arraycopy(cmdLineArgs, 0, newArgs, 2, cmdLineArgs.length); 838 } else { 839 newArgs = cmdLineArgs; 840 } 841 842 int tableNameValueIndex = -1; 843 for (int j = 0; j < newArgs.length; j++) { 844 if (newArgs[j].endsWith(OPT_TABLE_NAME)) { 845 tableNameValueIndex = j + 1; 846 } else if (newArgs[j].endsWith(NUM_TABLES)) { 847 // change NUM_TABLES to 1 so that each worker loads one table 848 newArgs[j + 1] = "1"; 849 } 850 } 851 852 // starting to load multiple tables 853 List<WorkerThread> workers = new ArrayList<>(); 854 for (int i = 0; i < numTables; i++) { 855 String[] workerArgs = newArgs.clone(); 856 workerArgs[tableNameValueIndex] = tableName + "_" + (i+1); 857 WorkerThread worker = new WorkerThread(i, workerArgs); 858 workers.add(worker); 859 LOG.info(worker + " starting"); 860 worker.start(); 861 } 862 863 // wait for all workers finish 864 LOG.info("Waiting for worker threads to finish"); 865 for (WorkerThread t : workers) { 866 try { 867 t.join(); 868 } catch (InterruptedException ie) { 869 IOException iie = new InterruptedIOException(); 870 iie.initCause(ie); 871 throw iie; 872 } 873 checkForErrors(); 874 } 875 876 return EXIT_SUCCESS; 877 } 878 879 // If an exception is thrown by one of worker threads, it will be 880 // stored here. 881 protected AtomicReference<Throwable> thrown = new AtomicReference<>(); 882 883 private void workerThreadError(Throwable t) { 884 thrown.compareAndSet(null, t); 885 } 886 887 /** 888 * Check for errors in the writer threads. If any is found, rethrow it. 889 */ 890 private void checkForErrors() throws IOException { 891 Throwable thrown = this.thrown.get(); 892 if (thrown == null) return; 893 if (thrown instanceof IOException) { 894 throw (IOException) thrown; 895 } else { 896 throw new RuntimeException(thrown); 897 } 898 } 899 900 class WorkerThread extends Thread { 901 private String[] workerArgs; 902 903 WorkerThread(int i, String[] args) { 904 super("WorkerThread-" + i); 905 workerArgs = args; 906 } 907 908 @Override 909 public void run() { 910 try { 911 int ret = ToolRunner.run(HBaseConfiguration.create(), new LoadTestTool(), workerArgs); 912 if (ret != 0) { 913 throw new RuntimeException("LoadTestTool exit with non-zero return code."); 914 } 915 } catch (Exception ex) { 916 LOG.error("Error in worker thread", ex); 917 workerThreadError(ex); 918 } 919 } 920 } 921 922 private void addAuthInfoToConf(Properties authConfig, Configuration conf, String owner, 923 String userList) throws IOException { 924 List<String> users = new ArrayList<>(Arrays.asList(userList.split(","))); 925 users.add(owner); 926 for (String user : users) { 927 String keyTabFileConfKey = "hbase." + user + ".keytab.file"; 928 String principalConfKey = "hbase." + user + ".kerberos.principal"; 929 if (!authConfig.containsKey(keyTabFileConfKey) || !authConfig.containsKey(principalConfKey)) { 930 throw new IOException("Authentication configs missing for user : " + user); 931 } 932 } 933 for (String key : authConfig.stringPropertyNames()) { 934 conf.set(key, authConfig.getProperty(key)); 935 } 936 LOG.debug("Added authentication properties to config successfully."); 937 } 938 939}